--preserve-root is only checked against the top-level operands (chmod.rs:402); the recursive walker walk_dir_with_context performs no is_root re-check. Under chmod -R -L --preserve-root, a descendant symlink pivot -> / is followed during recursion and the operation proceeds onto /. (chown's walker does re-check root at perms.rs:498; chmod's does not.)
Verified (chroot model): uutils chmodded the modeled root / itself and an off-tree file to 0777 and exited 0, despite --preserve-root; GNU refused with "it is dangerous to operate recursively on '...' (same as '/')" (exit 1).
Impact: a privileged chmod -R -L --preserve-root MODE <tree> over attacker-influenced content can be redirected onto the whole root filesystem -- exactly what --preserve-root is supposed to prevent. Fix: re-check is_root inside the recursive walker (and treat a descendant symlink resolving to / as the guard does at top level).
Source: Cantina audit (2026-05-03). Independently verified 2026-05-30 against uutils/coreutils e0ad63471 with a sandboxed PoC; GNU coreutils 9.7 used as control. See cantina-report/VERIFICATION-2026-05-30.md.
Fix
Fixed in 0.10.0 — commit a203c654c (#13621, merged 2026-07-31; 0.10.0 released 2026-08-05).
--preserve-root was only enforced for the paths named on the command line (the operand loop in Chmoder::chmod). The recursive walker never re-checked, so with -R -L a symlink met inside the tree that resolves to / was followed and the recursion walked into the real root.
Re-confirmed on 0.9.0-575-g0d02663c6 in a bubblewrap sandbox with a throwaway /: a tree containing link -> / under chmod -R -L --preserve-root 777 tree took an unrelated 0700 directory to 0777. GNU coreutils 9.10 refused the identical case in the same sandbox.
The fix re-checks the guard at every descent, in both walk_dir_with_context variants so unix, non-unix and redox are covered. Only symlinks are canonicalized, so ordinary recursive trees are unaffected. Behaviour now matches GNU: diagnose with the (same as '/') wording, skip the subtree, continue, and exit 1.
Note: PR #10033 ("Chmod preserve root", merged 2026-01-07) is easily mistaken for a complete fix but only covered the operand-level resolves-to-/ case.
Re-verified on the released 0.10.0 tag (2026-08-07) behaviourally, not by re-reading the diff, under bwrap with a throwaway minimal / holding tree/link -> / and an off-tree victim directory at 0700:
$ chmod -R -L --preserve-root 777 /tree
0.9.0 : exit 1, victim 0777 <- escaped the tree
0.10.0: exit 1, victim 0700, "chmod: it is dangerous to operate recursively on '/tree/link' (same as '/')"
GNU : exit 1, victim 0700, byte-identical message
--preserve-rootis only checked against the top-level operands (chmod.rs:402); the recursive walkerwalk_dir_with_contextperforms nois_rootre-check. Underchmod -R -L --preserve-root, a descendant symlinkpivot -> /is followed during recursion and the operation proceeds onto/. (chown's walker does re-check root atperms.rs:498; chmod's does not.)Verified (chroot model): uutils chmodded the modeled root
/itself and an off-tree file to0777and exited 0, despite--preserve-root; GNU refused with "it is dangerous to operate recursively on '...' (same as '/')" (exit 1).Impact: a privileged
chmod -R -L --preserve-root MODE <tree>over attacker-influenced content can be redirected onto the whole root filesystem -- exactly what--preserve-rootis supposed to prevent. Fix: re-checkis_rootinside the recursive walker (and treat a descendant symlink resolving to/as the guard does at top level).Source: Cantina audit (2026-05-03). Independently verified 2026-05-30 against uutils/coreutils
e0ad63471with a sandboxed PoC; GNU coreutils 9.7 used as control. See cantina-report/VERIFICATION-2026-05-30.md.Fix
Fixed in 0.10.0 — commit
a203c654c(#13621, merged 2026-07-31; 0.10.0 released 2026-08-05).--preserve-rootwas only enforced for the paths named on the command line (the operand loop inChmoder::chmod). The recursive walker never re-checked, so with-R -La symlink met inside the tree that resolves to/was followed and the recursion walked into the real root.Re-confirmed on
0.9.0-575-g0d02663c6in a bubblewrap sandbox with a throwaway/: a tree containinglink -> /underchmod -R -L --preserve-root 777 treetook an unrelated0700directory to0777. GNU coreutils 9.10 refused the identical case in the same sandbox.The fix re-checks the guard at every descent, in both
walk_dir_with_contextvariants so unix, non-unix and redox are covered. Only symlinks are canonicalized, so ordinary recursive trees are unaffected. Behaviour now matches GNU: diagnose with the(same as '/')wording, skip the subtree, continue, and exit 1.Note: PR #10033 ("Chmod preserve root", merged 2026-01-07) is easily mistaken for a complete fix but only covered the operand-level resolves-to-
/case.Re-verified on the released 0.10.0 tag (2026-08-07) behaviourally, not by re-reading the diff, under
bwrapwith a throwaway minimal/holdingtree/link -> /and an off-treevictimdirectory at0700: