uutils/coreutils · error · io::Error
safe-traversal-error-invalid-fd
Error message
safe-traversal-error-invalid-fd
What it means
`DirFd::from_raw_fd` validates that the caller-supplied raw file descriptor is non-negative before taking ownership. A negative fd is invalid (and would make `OwnedFd::from_raw_fd` UB), so it returns `InvalidInput` with the localized "safe-traversal-error-invalid-fd" message.
Source
Thrown at src/uucore/src/lib/features/safe_traversal.rs:481
// target truncated. Callers reach here right after unlinking `name`,
// which is precisely the window an attacker races.
let flags = OFlag::O_CREAT
| OFlag::O_WRONLY
| OFlag::O_TRUNC
| OFlag::O_CLOEXEC
| OFlag::O_NOFOLLOW;
let mode = Mode::from_bits_truncate(0o666); // Default file permissions
let fd: OwnedFd = openat(self.fd.as_fd(), name_cstr.as_c_str(), flags, mode)
.map_err(|e| io::Error::from_raw_os_error(e as i32))?;
Ok(fs::File::from(fd))
}
/// Create a DirFd from an existing file descriptor (takes ownership)
pub fn from_raw_fd(fd: RawFd) -> io::Result<Self> {
if fd < 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
translate!("safe-traversal-error-invalid-fd"),
));
}
// SAFETY: We've verified fd >= 0, and the caller is transferring ownership
let owned_fd = unsafe { OwnedFd::from_raw_fd(fd) };
Ok(Self { fd: owned_fd })
}
}
/// Find the deepest existing directory ancestor for a path.
///
/// Returns the existing ancestor path and a list of components that need to be created.
/// Uses `metadata` (follows symlinks) so that symlinks to directories are treated as
/// existing ancestors rather than components to create.
fn find_existing_ancestor(path: &Path) -> io::Result<(PathBuf, Vec<OsString>)> {
let mut current = path.to_path_buf();
let mut components: Vec<OsString> = Vec::new();View on GitHub (pinned to 85295bbf78)
Solutions
- Check the raw fd >= 0 (i.e. the underlying open succeeded) before calling from_raw_fd
- Return/propagate the original open error (errno) instead of the fd
- Prefer APIs returning Result<OwnedFd> so the invalid case can't reach from_raw_fd
Example fix
// before
let fd = unsafe { libc::open(path.as_ptr(), flags) };
DirFd::from_raw_fd(fd)?;
// after
let fd = unsafe { libc::open(path.as_ptr(), flags) };
if fd < 0 { return Err(io::Error::last_os_error()); }
DirFd::from_raw_fd(fd)?; Defensive patterns
Strategy: validation
Validate before calling
fn safe_from_raw_fd(fd: std::os::fd::RawFd) -> io::Result<DirFd> {
if fd < 0 { return Err(io::Error::last_os_error()); }
DirFd::from_raw_fd(fd)
} Type guard
fn is_valid_fd(fd: i32) -> bool { fd >= 0 } Try / catch
match DirFd::from_raw_fd(fd) {
Err(e) if e.kind() == io::ErrorKind::InvalidInput => {
return Err(io::Error::last_os_error());
}
other => other?,
} Prevention
- Always check libc open/openat return values before wrapping
- Prefer Result-returning wrappers over raw fd plumbing
- Run code under a debug allocator that flags invalid fds
When it happens
Trigger: Calling `DirFd::from_raw_fd(-1)` — typically after an `open`/`openat` call returned -1 on error and the raw fd was forwarded without checking.
Common situations: FFI wrappers that forget to check the -1 error return of libc open/openat, scripts binding native traversal APIs, refactored code losing an error check.
Related errors
AI-assisted analysis of uutils/coreutils@85295bbf78 (2026-08-31).
Data as JSON: /api/errors/fb108d5810849d23.
Report an issue: GitHub.