cp -r gives every directory it creates the source's mode bits unmasked, where GNU applies the umask. Regular files in the same copy are masked correctly, so files and directories disagree and the directory branch wins.
$ mkdir -p src/dir && echo x > src/dir/f && chmod 777 src/dir && chmod 666 src/dir/f
$ (umask 077; cp -r src d)
$ stat -c '%a %n' d/dir d/dir/f
|
d/dir |
d/dir/f |
| uutils 0.11.0 / main |
777 |
600 |
| GNU 9.11 |
700 |
600 |
Under umask 022 uutils gives 777 where GNU gives 755. --no-preserve=mode gives 700, matching GNU, and -a gives 777 on both, which is correct because -a preserves.
Cause
copy_dir creates each directory through build_dir, which does apply the umask, and records it in dirs_needing_permissions (copydir.rs:520) for a final pass (copydir.rs:574). That pass calls copy_attributes, which promotes any freshly created directory to Preserve::Yes even when the attribute set is Attributes::NONE:
// cp.rs:1868
let mode = if !mode_explicitly_disabled && dest_is_freshly_created_dir {
and the preserving path then writes the source's unmasked mode at cp.rs:1943, overwriting what build_dir chose. Regular files take the other path and apply the umask explicitly.
Suggested fix
In that branch, when attributes.mode is Preserve::No, apply source_mode & !umask - the computation the file path already uses - instead of promoting to Preserve::Yes. The promotion presumably exists so a created directory ends up with the source's mode rather than build_dir's temporary one; masking keeps that and drops the widening.
Affected: 0.11.0 and main. Reported privately by Hongkai Chen (SEFCOM Lab, Arizona State University); handled as a normal bug rather than an advisory.
cp -rgives every directory it creates the source's mode bits unmasked, where GNU applies the umask. Regular files in the same copy are masked correctly, so files and directories disagree and the directory branch wins.d/dird/dir/fUnder umask 022 uutils gives
777where GNU gives755.--no-preserve=modegives700, matching GNU, and-agives777on both, which is correct because-apreserves.Cause
copy_dircreates each directory throughbuild_dir, which does apply the umask, and records it indirs_needing_permissions(copydir.rs:520) for a final pass (copydir.rs:574). That pass callscopy_attributes, which promotes any freshly created directory toPreserve::Yeseven when the attribute set isAttributes::NONE:and the preserving path then writes the source's unmasked mode at
cp.rs:1943, overwriting whatbuild_dirchose. Regular files take the other path and apply the umask explicitly.Suggested fix
In that branch, when
attributes.modeisPreserve::No, applysource_mode & !umask- the computation the file path already uses - instead of promoting toPreserve::Yes. The promotion presumably exists so a created directory ends up with the source's mode rather thanbuild_dir's temporary one; masking keeps that and drops the widening.Affected: 0.11.0 and
main. Reported privately by Hongkai Chen (SEFCOM Lab, Arizona State University); handled as a normal bug rather than an advisory.