Canonical’s April 2026 audit of uutils (Rust GNU coreutils) found 44 CVEs, none caught by the borrow checker, clippy, or cargo audit.
Key Takeaways
TOCTOU races are the largest bug cluster: fs::metadata, File::create, and fs::remove_file re-resolve paths each call, enabling symlink-swap attacks between syscalls.
Fix: anchor on a file descriptor – use OpenOptions::create_new(true) for new files, open the parent dir once and operate relative to that handle.
Permissions set after creation leave a race window; use OpenOptions::mode() and DirBuilderExt::mode() so files are born with correct permissions.
Path string equality is not filesystem identity: chmod -R 000 /../ bypassed preserve_root because /../ != /; fix with fs::canonicalize or compare (dev, inode) pairs.
UTF-8 assumptions corrupt Unix byte streams: from_utf8_lossy silently rewrites invalid bytes; stay in OsStr/&[u8] at Unix boundaries. Every unwrap or expect on untrusted input is a potential DoS via panic!.
Hacker News Comment Review
Core debate: the bugs are not Rust failures – they are Unix API experience failures. Long-time coreutils maintainers say these TOCTOU issues were identified and fixed in GNU code decades ago; the uutils team knew Rust but not Unix systems programming.
A recurring practical concern is why differential fuzzing against GNU coreutils did not surface these before the audit, given that uutils already has a fuzzing setup.
Commenters noted that bug-for-bug compatibility is itself a security property: kill -1 interpreted as “signal every visible process” instead of “signal 1 to a PID” is a concrete example of behavioral divergence becoming a system-wide footgun.
Notable Comments
@collinfunk: GNU Coreutils maintainer agrees std::fs makes TOCTOU easy; prefers fstat + (st_dev, st_ino) over canonicalize for path identity.
@hombre_fatal: Production code accumulates decades of implicit lessons; rewrites must excavate that hidden work before claiming parity, not after shipping.
@misja111: get_user_by_name inside a chroot loads shared libraries from the new root, giving an attacker code execution as uid 0 – a booby-trap API regardless of language.