Contributing to uutils/coreutils: Fixing TOCTOU Race Conditions in Rust
uutils/coreutils is one of the most ambitious Rust projects out there: a complete, cross-platform rewrite of the GNU coreutils. These are the fundamental command-line utilities — cp, mv, rm, chmod, chown — that every Unix system depends on. Rewriting them in Rust means bringing memory safety to some of the most security-critical code in the entire Linux ecosystem.
I've been contributing as a core contributor and systems engineer since January 2026. Here's what I've been working on and what I've learned.
The Problem: TOCTOU Race Conditions
Time-of-Check to Time-of-Use (TOCTOU) is a class of race condition that occurs when the state of a resource changes between the time you check it and the time you use it. In file system operations, this is particularly dangerous.
Consider a simplified version of what rm -rf does:
// Dangerous: TOCTOU vulnerability
fn remove_path(path: &Path) -> Result<()> {
let metadata = fs::symlink_metadata(path)?; // CHECK: is this a directory?
// <-- attacker swaps path with a symlink here -->
if metadata.is_dir() {
fs::remove_dir_all(path)?; // USE: remove the "directory"
} else {
fs::remove_file(path)?;
}
Ok(())
}
Between the symlink_metadata call (the check) and the remove_dir_all call (the use), an attacker can replace the target with a symlink pointing to /etc or /home. The program thinks it's removing a regular directory, but it follows the symlink and destroys something else entirely.
This isn't theoretical. Symlink attacks against file utilities are a well-known class of privilege escalation vulnerabilities, especially when tools run as root.
The Fix: openat and O_NOFOLLOW
The solution is to eliminate the gap between check and use by operating on file descriptors rather than paths. POSIX provides the openat family of system calls for exactly this purpose.
Instead of:
- Stat the path (check)
- Operate on the path (use)
You do:
- Open a file descriptor to the parent directory
- Use
openatwithO_NOFOLLOWto open the target relative to the parent - Operate on the file descriptor directly
use std::os::unix::io::AsRawFd;
// Safe: no TOCTOU gap
fn safe_remove(dir_fd: &OwnedFd, name: &OsStr) -> Result<()> {
// O_NOFOLLOW prevents symlink traversal
// openat operates relative to dir_fd, not the global namespace
let fd = openat(dir_fd.as_raw_fd(), name, OFlag::O_NOFOLLOW | OFlag::O_RDONLY, Mode::empty())?;
let metadata = fstat(fd)?;
// Now metadata and fd refer to the SAME inode
// No race condition possible
if metadata.st_mode & libc::S_IFDIR != 0 {
// It's a directory — remove contents via the fd
remove_dir_contents(fd)?;
unlinkat(dir_fd.as_raw_fd(), name, AtFlags::AT_REMOVEDIR)?;
} else {
unlinkat(dir_fd.as_raw_fd(), name, AtFlags::empty())?;
}
Ok(())
}
The key insight: O_NOFOLLOW causes open to fail if the target is a symlink, and openat resolves names relative to a directory file descriptor rather than doing a full path traversal. Together, they eliminate the TOCTOU window.
Cross-Platform Challenges
The Rust rewrite of coreutils targets not just Linux, but macOS, FreeBSD, and other Unix variants. Each platform has subtly different POSIX implementations:
- Linux: Full
openat2support withRESOLVE_NO_SYMLINKSfor even stricter resolution - macOS:
openatis available but some flags behave differently - FreeBSD: Generally good POSIX compliance but different
fstatatbehavior
Making the TOCTOU fixes work correctly across all platforms required careful use of conditional compilation and platform-specific code paths. Rust's cfg attributes made this manageable:
#[cfg(target_os = "linux")]
fn open_nofollow(dir_fd: RawFd, name: &CStr) -> Result<OwnedFd> {
// Linux-specific: use openat2 with RESOLVE_NO_SYMLINKS
// for maximum safety
openat2(dir_fd, name, &OpenHow {
flags: O_RDONLY | O_NOFOLLOW,
resolve: RESOLVE_NO_SYMLINKS,
..Default::default()
})
}
#[cfg(not(target_os = "linux"))]
fn open_nofollow(dir_fd: RawFd, name: &CStr) -> Result<OwnedFd> {
// POSIX fallback: openat with O_NOFOLLOW
openat(dir_fd, name, OFlag::O_RDONLY | OFlag::O_NOFOLLOW, Mode::empty())
}
Navigating Code Review
Working on a project of this scale taught me a lot about production open-source development. My PRs (#9792 and #10140) went through rigorous review by project lead Sylvestre Ledru.
The review process was thorough:
- Security correctness: Every edge case in the TOCTOU fix had to be justified
- Cross-platform testing: CI/CD pipelines run tests across Linux, macOS, and FreeBSD
- Performance: File traversal is hot-path code — the fix couldn't introduce regression
- Compatibility: The behavior had to match GNU coreutils exactly
This was a different level of rigor from personal projects. Every line had to be defensible, every design decision documented. It made me a significantly better engineer.
What I Learned
Contributing to uutils/coreutils has been the most educational systems programming experience I've had:
- POSIX deeply matters: Understanding file descriptors,
openat,fstatat, and the Unix security model isn't academic — it's the foundation of secure systems programming - Race conditions are subtle: TOCTOU bugs are easy to miss in code review because both the check and use look correct in isolation
- Rust helps but doesn't solve everything: Rust's memory safety guarantees are powerful, but they don't prevent logic bugs like TOCTOU. You still need to understand the underlying OS semantics
- Open source review is invaluable: Having experienced maintainers scrutinize your code accelerates learning faster than anything else
If you're interested in systems programming in Rust, I'd highly recommend contributing to uutils/coreutils. The codebase is well-organized, the maintainers are welcoming, and the problems are real.