Commit 21fda8c
authored
fix(install): follow symlink components in destination path with -D
## 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.1 parent 29fdaa6 commit 21fda8c
3 files changed
Lines changed: 130 additions & 67 deletions
File tree
- src
- uucore/src/lib/features
- uu/install/src
- tests/by-util
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
631 | 631 | | |
632 | 632 | | |
633 | 633 | | |
634 | | - | |
635 | | - | |
| 634 | + | |
636 | 635 | | |
637 | 636 | | |
638 | 637 | | |
| |||
643 | 642 | | |
644 | 643 | | |
645 | 644 | | |
646 | | - | |
| 645 | + | |
647 | 646 | | |
648 | 647 | | |
649 | 648 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
475 | 475 | | |
476 | 476 | | |
477 | 477 | | |
478 | | - | |
479 | | - | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
480 | 481 | | |
481 | | - | |
482 | | - | |
| 482 | + | |
| 483 | + | |
483 | 484 | | |
484 | 485 | | |
485 | 486 | | |
486 | | - | |
487 | | - | |
| 487 | + | |
488 | 488 | | |
489 | 489 | | |
490 | 490 | | |
| |||
554 | 554 | | |
555 | 555 | | |
556 | 556 | | |
557 | | - | |
558 | | - | |
559 | | - | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
560 | 561 | | |
561 | 562 | | |
562 | 563 | | |
| |||
1251 | 1252 | | |
1252 | 1253 | | |
1253 | 1254 | | |
1254 | | - | |
| 1255 | + | |
1255 | 1256 | | |
1256 | 1257 | | |
1257 | 1258 | | |
1258 | 1259 | | |
1259 | | - | |
1260 | | - | |
| 1260 | + | |
| 1261 | + | |
1261 | 1262 | | |
1262 | 1263 | | |
1263 | 1264 | | |
1264 | | - | |
| 1265 | + | |
1265 | 1266 | | |
1266 | 1267 | | |
1267 | 1268 | | |
1268 | | - | |
1269 | | - | |
1270 | | - | |
| 1269 | + | |
| 1270 | + | |
| 1271 | + | |
1271 | 1272 | | |
1272 | 1273 | | |
1273 | 1274 | | |
| |||
1284 | 1285 | | |
1285 | 1286 | | |
1286 | 1287 | | |
1287 | | - | |
1288 | | - | |
| 1288 | + | |
| 1289 | + | |
1289 | 1290 | | |
1290 | 1291 | | |
1291 | 1292 | | |
| |||
1294 | 1295 | | |
1295 | 1296 | | |
1296 | 1297 | | |
1297 | | - | |
| 1298 | + | |
1298 | 1299 | | |
1299 | 1300 | | |
1300 | 1301 | | |
1301 | 1302 | | |
1302 | | - | |
1303 | | - | |
1304 | | - | |
1305 | | - | |
| 1303 | + | |
| 1304 | + | |
| 1305 | + | |
1306 | 1306 | | |
1307 | | - | |
1308 | | - | |
| 1307 | + | |
| 1308 | + | |
1309 | 1309 | | |
1310 | 1310 | | |
1311 | 1311 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2583 | 2583 | | |
2584 | 2584 | | |
2585 | 2585 | | |
2586 | | - | |
2587 | | - | |
| 2586 | + | |
| 2587 | + | |
2588 | 2588 | | |
2589 | 2589 | | |
2590 | 2590 | | |
2591 | 2591 | | |
2592 | 2592 | | |
2593 | | - | |
2594 | 2593 | | |
2595 | | - | |
2596 | | - | |
2597 | 2594 | | |
2598 | 2595 | | |
2599 | | - | |
2600 | 2596 | | |
2601 | | - | |
2602 | | - | |
| 2597 | + | |
2603 | 2598 | | |
2604 | | - | |
2605 | | - | |
| 2599 | + | |
| 2600 | + | |
2606 | 2601 | | |
2607 | 2602 | | |
2608 | 2603 | | |
2609 | 2604 | | |
2610 | | - | |
2611 | | - | |
2612 | | - | |
| 2605 | + | |
2613 | 2606 | | |
2614 | | - | |
| 2607 | + | |
2615 | 2608 | | |
2616 | | - | |
2617 | | - | |
| 2609 | + | |
| 2610 | + | |
| 2611 | + | |
| 2612 | + | |
| 2613 | + | |
| 2614 | + | |
2618 | 2615 | | |
2619 | 2616 | | |
2620 | | - | |
2621 | | - | |
2622 | | - | |
2623 | | - | |
2624 | | - | |
2625 | | - | |
2626 | | - | |
2627 | | - | |
2628 | | - | |
2629 | | - | |
| 2617 | + | |
| 2618 | + | |
| 2619 | + | |
| 2620 | + | |
| 2621 | + | |
2630 | 2622 | | |
2631 | 2623 | | |
2632 | 2624 | | |
2633 | 2625 | | |
2634 | 2626 | | |
2635 | | - | |
| 2627 | + | |
2636 | 2628 | | |
2637 | 2629 | | |
2638 | 2630 | | |
2639 | 2631 | | |
2640 | 2632 | | |
2641 | | - | |
2642 | 2633 | | |
2643 | 2634 | | |
2644 | 2635 | | |
2645 | | - | |
2646 | 2636 | | |
2647 | 2637 | | |
2648 | 2638 | | |
2649 | | - | |
2650 | 2639 | | |
2651 | 2640 | | |
2652 | 2641 | | |
2653 | 2642 | | |
2654 | 2643 | | |
2655 | 2644 | | |
2656 | 2645 | | |
2657 | | - | |
2658 | | - | |
2659 | | - | |
| 2646 | + | |
| 2647 | + | |
| 2648 | + | |
| 2649 | + | |
| 2650 | + | |
| 2651 | + | |
| 2652 | + | |
| 2653 | + | |
| 2654 | + | |
2660 | 2655 | | |
2661 | | - | |
| 2656 | + | |
2662 | 2657 | | |
2663 | | - | |
2664 | | - | |
| 2658 | + | |
| 2659 | + | |
2665 | 2660 | | |
| 2661 | + | |
| 2662 | + | |
| 2663 | + | |
| 2664 | + | |
| 2665 | + | |
| 2666 | + | |
| 2667 | + | |
| 2668 | + | |
| 2669 | + | |
| 2670 | + | |
| 2671 | + | |
| 2672 | + | |
| 2673 | + | |
| 2674 | + | |
| 2675 | + | |
| 2676 | + | |
| 2677 | + | |
| 2678 | + | |
2666 | 2679 | | |
2667 | | - | |
| 2680 | + | |
| 2681 | + | |
| 2682 | + | |
| 2683 | + | |
| 2684 | + | |
| 2685 | + | |
| 2686 | + | |
| 2687 | + | |
| 2688 | + | |
| 2689 | + | |
| 2690 | + | |
| 2691 | + | |
| 2692 | + | |
| 2693 | + | |
| 2694 | + | |
| 2695 | + | |
| 2696 | + | |
| 2697 | + | |
| 2698 | + | |
| 2699 | + | |
| 2700 | + | |
| 2701 | + | |
| 2702 | + | |
| 2703 | + | |
| 2704 | + | |
| 2705 | + | |
| 2706 | + | |
| 2707 | + | |
| 2708 | + | |
| 2709 | + | |
| 2710 | + | |
| 2711 | + | |
| 2712 | + | |
| 2713 | + | |
| 2714 | + | |
| 2715 | + | |
| 2716 | + | |
| 2717 | + | |
| 2718 | + | |
| 2719 | + | |
| 2720 | + | |
| 2721 | + | |
| 2722 | + | |
| 2723 | + | |
| 2724 | + | |
| 2725 | + | |
| 2726 | + | |
| 2727 | + | |
| 2728 | + | |
| 2729 | + | |
| 2730 | + | |
| 2731 | + | |
2668 | 2732 | | |
2669 | | - | |
2670 | | - | |
| 2733 | + | |
| 2734 | + | |
2671 | 2735 | | |
2672 | 2736 | | |
0 commit comments