mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
21fda8cd9f
## Summary Fixes #11469 `install -D` was replacing pre-existing symlinks in the destination path with real directories instead of following them. This broke any workflow where part of the install prefix is a symlink; including BOSH deployments, Homebrew, Nix, stow, and any `make install` targeting a symlinked prefix. **Reproduction (from the issue):** ```sh mkdir -p /tmp/target ln -s /tmp/target /tmp/link echo hello > /tmp/file.txt install -D -m 644 /tmp/file.txt /tmp/link/subdir/file.txt # GNU coreutils 8.32: /tmp/link stays a symlink, file lands in /tmp/target/subdir/file.txt # uutils 0.7.0: /tmp/link is replaced with a real directory — wrong ``` ## Root cause PR #10140 introduced `create_dir_all_safe()` in `safe_traversal.rs` to prevent TOCTOU symlink race conditions. The fix was correct in intent but too aggressive: `open_or_create_subdir()` unconditionally unlinked and recreated any symlink it encountered, including pre-existing legitimate ones. ## Changes **`src/uucore/src/lib/features/safe_traversal.rs`** - `open_or_create_subdir`: when `stat_at` returns `S_IFLNK`, call `open_subdir(Follow)` instead of `unlink_at + mkdir_at`. The `O_DIRECTORY` flag already in `open_subdir` means dangling or non-directory symlinks still return an error cleanly. - `find_existing_ancestor`: switch from `fs::symlink_metadata` to `fs::metadata` so that a symlink-to-directory is recognised as an existing ancestor rather than a component to recreate (this was already the stated intent in the function's doc comment). **`src/uu/install/src/install.rs`** - Align the `dir_exists` check and the `DirFd::open` call to also follow symlinks, consistent with the above. **`tests/by-util/test_install.rs`** - Update the two tests added by #10140 — they were asserting the buggy behavior (symlink replaced). Flip the assertions to document the correct GNU behavior. - Add `test_install_d_follows_symlink_prefix` as a direct regression test for the issue's reproduction case. ## TOCTOU / security note The true TOCTOU race (a symlink *injected during the operation* into a not-yet-existing path component) is still blocked: `mkdirat` fails with `EEXIST` if an attacker creates a symlink between `stat_at` returning `ENOENT` and our `mkdir_at`. Newly-created directories are still opened with `O_NOFOLLOW`. What changes is that *pre-existing* symlinks are now followed — which is exactly what GNU coreutils 8.32 does. The previous behavior was stricter than GNU in this regard.