In `-P` / no-dereference mode, cp now opens the source file with
`O_NOFOLLOW`, matching GNU cp. This closes a TOCTOU window where an
attacker who can swap the source path between cp's `lstat` check and
the subsequent open could redirect the read through a symlink to a
sensitive file (e.g. /etc/shadow). With `O_NOFOLLOW` the open fails
with `ELOOP` instead.
The same flag is propagated to `safe_copy::create_dest_restrictive`,
so the destination open also refuses to follow a symlink in
no-dereference mode. Without that, an attacker who plants the dest
path as a symlink between the caller's check and the open could
redirect the truncate (and the subsequent write) to any file the
caller has permission to write — the symmetric attack to the source
side. With `nofollow=true` the dest open returns `ELOOP` and the
victim file is left untouched.
`copy_on_write` gains a `nofollow` parameter threaded from
`copy_helper`, set to `!options.dereference(source_in_command_line)`.
In deref mode the flag is false and behavior is unchanged — cp still
follows symlinks, matching GNU.
Extends `util/check-safe-traversal.sh` with a cp -P strace check so
the invariant is locked in: future changes that drop `O_NOFOLLOW`
here will fail the smoke test.
cp previously created the destination with mode 0o666 masked by umask
(typically 0o644), then later applied the final permissions via
set_permissions. In a shared directory like /tmp this opened an
observable window where another user could open the destination with
the intermediate broad mode before cp narrowed it, leaking file
contents that were intended to stay private.
Create dest with 0o600 initially in every non-symlink code path —
clone, sparse_copy, sparse_copy_without_hole, fs_copy, the stream
path, and the non-Linux fs::copy fallback. The existing
set_permissions call in copy_file applies the real final mode after
the content is written, so user-visible end state is unchanged; only
the intermediate mode is tightened. Matches GNU cp.
Extend `util/check-safe-traversal.sh` with a cp strace check that
asserts the destination openat carries mode 0600 so a future change
that reintroduces 0666 fails the smoke test.
* chmod: fix TOCTOU race in recursive traversal
Use fchmodat2 (Linux 6.6+) with AT_SYMLINK_NOFOLLOW to prevent an
attacker from replacing a directory entry with a symlink between the
stat and chmod calls. Falls back to fchmodat on older kernels.
- Restrict fchmodat2 (syscall 452) to asm-generic architectures only
(x86_64, x86, arm, aarch64, riscv)
- Add SAFETY comment on unsafe syscall block per project convention
- Add O_PATH + /proc/self/fd fallback for musl on kernel < 6.6
- Cache ENOSYS result with AtomicBool to skip fchmodat2 on old kernels
- Remove unnecessary nix::Mode round-trip on the fchmodat2 path
- safe_chmod_file() takes explicit SymlinkBehavior parameter
- Always pass NoFollow for regular entries during recursion
- Document residual TOCTOU in symlink branch as intentional for -L
- Add test verifying NoFollow chmod doesn't modify symlink target
- Update check-safe-traversal.sh to recognize fchmodat2
* factor: emit GNU's 'X is not a valid positive integer' wording
GNU's factor.c routes both stdin and command-line input through the same
print_factors() and reports invalid input as
factor: 'X' is not a valid positive integer
Match that wording exactly so the new GNU 9.11 'nul4' test passes and
the 'cont' test no longer needs the warning/invalid-digit hunk in
tests_factor_factor.pl.patch.
* Add 'cmdline' to spell-checker ignore list
GNU coreutils 9.11 changed fail-perm.sh to use the EACCES helper
in the expected diagnostics. Match the full diagnostic line so the
existing uutils message adaptation still applies.
Mirrors test_compare_test_results.py: covers human_kb formatting,
load_sizes (date-keyed and flat), compare() including threshold
boundaries on both growth and shrinkage, format_report, and an
end-to-end main() check that the comment file is only written when
something significant is reported.
Wired into code-quality.yml alongside the existing compare_test_results
unit tests.
Per-binary comparison script that reads the existing
individual-size-result.json format (date-keyed with sha + sizes-in-KB)
and reports binaries whose size changed by both >=5% and >=4 KB.
This is the size-tracking counterpart of compare_test_results.py: it
emits GitHub workflow annotations and writes a PR comment body to its
--output path only when there is something significant to report.
Buffer formatted output so that --invalid=fail does not duplicate lines
on error, and --invalid=abort streams directly for partial output.
Fixes GNU numfmt.pl test: field-3. Re-enables the full numfmt.pl test
suite by removing the skip list added earlier in the stack.
* refactor: simplify DisplayItemName struct and improve dired position handling
This commit refactors the `display_item_name` function to return a `DisplayItemName` struct containing both the displayed name and its dired length, rather than just the OsString. This change simplifies the code by:
1. Eliminating the need to call `dired_name_len()` separately after getting the displayed name
2. Reducing redundant length calculations in the dired position handling
3. Making the code more maintainable by encapsulating related data together
The change also fixes an issue where dired positions were being calculated incorrectly for symlink names that required quoting, ensuring proper highlighting in dired mode.
Closes: #10248