VYPR
Low severity3.6NVD Advisory· Published Apr 22, 2026· Updated Apr 27, 2026

CVE-2026-35362

CVE-2026-35362

Description

The safe_traversal module in uutils coreutils, which provides protection against Time-of-Check to Time-of-Use (TOCTOU) symlink races using file-descriptor-relative syscalls, is incorrectly limited to Linux targets. On other Unix-like systems such as macOS and FreeBSD, the utility fails to utilize these protections, leaving directory traversal operations vulnerable to symlink race conditions.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
coreutilscrates.io
< 0.6.00.6.0

Affected products

1

Patches

1
30239e69a328

feat: Expand safe directory traversal to all Unix platforms and fix related type conversions. (#9792)

https://github.com/uutils/coreutilsJake AbendrothJan 9, 2026via ghsa
17 files changed · +145 130
  • src/uu/chmod/Cargo.toml+4 7 modified
    @@ -20,15 +20,12 @@ path = "src/chmod.rs"
     [dependencies]
     clap = { workspace = true }
     thiserror = { workspace = true }
    -uucore = { workspace = true, features = [
    -  "entries",
    -  "fs",
    -  "mode",
    -  "perms",
    -  "safe-traversal",
    -] }
    +uucore = { workspace = true, features = ["entries", "fs", "mode", "perms"] }
     fluent = { workspace = true }
     
    +[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies]
    +uucore = { workspace = true, features = ["safe-traversal"] }
    +
     [[bin]]
     name = "chmod"
     path = "src/main.rs"
    
  • src/uu/chmod/src/chmod.rs+20 13 modified
    @@ -18,7 +18,7 @@ use uucore::libc::mode_t;
     use uucore::mode;
     use uucore::perms::{TraverseSymlinks, configure_symlink_and_recursion};
     
    -#[cfg(target_os = "linux")]
    +#[cfg(all(unix, not(target_os = "redox")))]
     use uucore::safe_traversal::DirFd;
     use uucore::{format_usage, show, show_error};
     
    @@ -338,7 +338,7 @@ impl Chmoder {
         }
     
         /// Handle symlinks during directory traversal based on traversal mode
    -    #[cfg(not(target_os = "linux"))]
    +    #[cfg(not(unix))]
         fn handle_symlink_during_traversal(
             &self,
             path: &Path,
    @@ -423,7 +423,8 @@ impl Chmoder {
             matches!(fs::canonicalize(&file), Ok(p) if p == Path::new("/"))
         }
     
    -    #[cfg(not(target_os = "linux"))]
    +    // Non-safe traversal implementation for platforms without safe_traversal support
    +    #[cfg(any(not(unix), target_os = "redox"))]
         fn walk_dir_with_context(&self, file_path: &Path, is_command_line_arg: bool) -> UResult<()> {
             let mut r = self.chmod_file(file_path);
     
    @@ -436,8 +437,7 @@ impl Chmoder {
     
             // If the path is a directory (or we should follow symlinks), recurse into it
             if (!file_path.is_symlink() || should_follow_symlink) && file_path.is_dir() {
    -            // We buffer all paths in this dir to not keep to be able to close the fd so not
    -            // too many fd's are open during the recursion
    +            // We buffer all paths in this dir to not keep too many fd's open during recursion
                 let mut paths_in_this_dir = Vec::new();
     
                 for dir_entry in file_path.read_dir()? {
    @@ -450,17 +450,24 @@ impl Chmoder {
                     }
                 }
                 for path in paths_in_this_dir {
    -                if path.is_symlink() {
    -                    r = self.handle_symlink_during_recursion(&path).and(r);
    -                } else {
    +                #[cfg(not(unix))]
    +                {
    +                    if path.is_symlink() {
    +                        r = self.handle_symlink_during_recursion(&path).and(r);
    +                    } else {
    +                        r = self.walk_dir_with_context(path.as_path(), false).and(r);
    +                    }
    +                }
    +                #[cfg(target_os = "redox")]
    +                {
                         r = self.walk_dir_with_context(path.as_path(), false).and(r);
                     }
                 }
             }
             r
         }
     
    -    #[cfg(target_os = "linux")]
    +    #[cfg(all(unix, not(target_os = "redox")))]
         fn walk_dir_with_context(&self, file_path: &Path, is_command_line_arg: bool) -> UResult<()> {
             let mut r = self.chmod_file(file_path);
     
    @@ -490,7 +497,7 @@ impl Chmoder {
             r
         }
     
    -    #[cfg(target_os = "linux")]
    +    #[cfg(all(unix, not(target_os = "redox")))]
         fn safe_traverse_dir(&self, dir_fd: &DirFd, dir_path: &Path) -> UResult<()> {
             let mut r = Ok(());
     
    @@ -546,7 +553,7 @@ impl Chmoder {
             r
         }
     
    -    #[cfg(target_os = "linux")]
    +    #[cfg(all(unix, not(target_os = "redox")))]
         fn handle_symlink_during_safe_recursion(
             &self,
             path: &Path,
    @@ -578,7 +585,7 @@ impl Chmoder {
             }
         }
     
    -    #[cfg(target_os = "linux")]
    +    #[cfg(all(unix, not(target_os = "redox")))]
         fn safe_chmod_file(
             &self,
             file_path: &Path,
    @@ -608,7 +615,7 @@ impl Chmoder {
             Ok(())
         }
     
    -    #[cfg(not(target_os = "linux"))]
    +    #[cfg(not(unix))]
         fn handle_symlink_during_recursion(&self, path: &Path) -> UResult<()> {
             // Use the common symlink handling logic
             self.handle_symlink_during_traversal(path, false)
    
  • src/uucore/src/lib/features.rs+1 1 modified
    @@ -72,7 +72,7 @@ pub mod pipes;
     pub mod proc_info;
     #[cfg(all(unix, feature = "process"))]
     pub mod process;
    -#[cfg(target_os = "linux")]
    +#[cfg(all(unix, not(target_os = "redox")))]
     pub mod safe_traversal;
     #[cfg(all(target_os = "linux", feature = "tty"))]
     pub mod tty;
    
  • src/uucore/src/lib/features/safe_traversal.rs+33 36 modified
    @@ -6,7 +6,7 @@
     // Safe directory traversal using openat() and related syscalls
     // This module provides TOCTOU-safe filesystem operations for recursive traversal
     //
    -// Only available on Linux
    +// Available on Unix
     //
     // spell-checker:ignore CLOEXEC RDONLY TOCTOU closedir dirp fdopendir fstatat openat REMOVEDIR unlinkat smallfile
     // spell-checker:ignore RAII dirfd fchownat fchown FchmodatFlags fchmodat fchmod
    @@ -85,15 +85,11 @@ fn read_dir_entries(fd: &OwnedFd) -> io::Result<Vec<OsString>> {
     
         // Duplicate the fd for Dir (it takes ownership)
         let dup_fd = nix::unistd::dup(fd).map_err(|e| io::Error::from_raw_os_error(e as i32))?;
    -
         let mut dir = Dir::from_fd(dup_fd).map_err(|e| io::Error::from_raw_os_error(e as i32))?;
    -
         for entry_result in dir.iter() {
             let entry = entry_result.map_err(|e| io::Error::from_raw_os_error(e as i32))?;
    -
             let name = entry.file_name();
             let name_os = OsStr::from_bytes(name.to_bytes());
    -
             if name_os != "." && name_os != ".." {
                 entries.push(name_os.to_os_string());
             }
    @@ -117,23 +113,20 @@ impl DirFd {
                     source: io::Error::from_raw_os_error(e as i32),
                 }
             })?;
    -
             Ok(Self { fd })
         }
     
         /// Open a subdirectory relative to this directory
         pub fn open_subdir(&self, name: &OsStr) -> io::Result<Self> {
             let name_cstr =
                 CString::new(name.as_bytes()).map_err(|_| SafeTraversalError::PathContainsNull)?;
    -
             let flags = OFlag::O_RDONLY | OFlag::O_DIRECTORY | OFlag::O_CLOEXEC;
             let fd = openat(&self.fd, name_cstr.as_c_str(), flags, Mode::empty()).map_err(|e| {
                 SafeTraversalError::OpenFailed {
                     path: name.into(),
                     source: io::Error::from_raw_os_error(e as i32),
                 }
             })?;
    -
             Ok(Self { fd })
         }
     
    @@ -174,7 +167,6 @@ impl DirFd {
                 path: translate!("safe-traversal-current-directory").into(),
                 source: io::Error::from_raw_os_error(e as i32),
             })?;
    -
             Ok(stat)
         }
     
    @@ -254,7 +246,7 @@ impl DirFd {
                 FchmodatFlags::NoFollowSymlink
             };
     
    -        let mode = Mode::from_bits_truncate(mode);
    +        let mode = Mode::from_bits_truncate(mode as libc::mode_t);
     
             let name_cstr =
                 CString::new(name.as_bytes()).map_err(|_| SafeTraversalError::PathContainsNull)?;
    @@ -267,7 +259,7 @@ impl DirFd {
     
         /// Change mode of this directory
         pub fn fchmod(&self, mode: u32) -> io::Result<()> {
    -        let mode = Mode::from_bits_truncate(mode);
    +        let mode = Mode::from_bits_truncate(mode as libc::mode_t);
     
             nix::sys::stat::fchmod(&self.fd, mode)
                 .map_err(|e| io::Error::from_raw_os_error(e as i32))?;
    @@ -378,30 +370,30 @@ impl Metadata {
         }
     
         pub fn file_type(&self) -> FileType {
    -        FileType::from_mode(self.stat.st_mode)
    +        FileType::from_mode(self.stat.st_mode as libc::mode_t)
         }
     
         pub fn file_info(&self) -> FileInfo {
             FileInfo::from_stat(&self.stat)
         }
     
    +    // st_size type varies by platform (i64 vs u64)
    +    #[allow(clippy::unnecessary_cast)]
         pub fn size(&self) -> u64 {
             self.stat.st_size as u64
         }
     
    +    // st_mode type varies by platform (u16 on macOS, u32 on Linux)
    +    #[allow(clippy::unnecessary_cast)]
         pub fn mode(&self) -> u32 {
    -        self.stat.st_mode
    +        self.stat.st_mode as u32
         }
     
         pub fn nlink(&self) -> u64 {
    -        // st_nlink is u32 on most platforms except x86_64
    -        #[cfg(target_arch = "x86_64")]
    -        {
    -            self.stat.st_nlink
    -        }
    -        #[cfg(not(target_arch = "x86_64"))]
    +        // st_nlink type varies by platform (u16 on FreeBSD, u32/u64 on others)
    +        #[allow(clippy::unnecessary_cast)]
             {
    -            self.stat.st_nlink.into()
    +            self.stat.st_nlink as u64
             }
         }
     
    @@ -421,34 +413,31 @@ impl Metadata {
     
     // Add MetadataExt trait implementation for compatibility
     impl std::os::unix::fs::MetadataExt for Metadata {
    +    // st_dev type varies by platform (i32 on macOS, u64 on Linux)
    +    #[allow(clippy::unnecessary_cast)]
         fn dev(&self) -> u64 {
    -        self.stat.st_dev
    +        self.stat.st_dev as u64
         }
     
         fn ino(&self) -> u64 {
    -        #[cfg(target_pointer_width = "32")]
    -        {
    -            self.stat.st_ino.into()
    -        }
    -        #[cfg(not(target_pointer_width = "32"))]
    +        // st_ino type varies by platform (u32 on FreeBSD, u64 on Linux)
    +        #[allow(clippy::unnecessary_cast)]
             {
    -            self.stat.st_ino
    +            self.stat.st_ino as u64
             }
         }
     
    +    // st_mode type varies by platform (u16 on macOS, u32 on Linux)
    +    #[allow(clippy::unnecessary_cast)]
         fn mode(&self) -> u32 {
    -        self.stat.st_mode
    +        self.stat.st_mode as u32
         }
     
         fn nlink(&self) -> u64 {
    -        // st_nlink is u32 on most platforms except x86_64
    -        #[cfg(target_arch = "x86_64")]
    -        {
    -            self.stat.st_nlink
    -        }
    -        #[cfg(not(target_arch = "x86_64"))]
    +        // st_nlink type varies by platform (u16 on FreeBSD, u32/u64 on others)
    +        #[allow(clippy::unnecessary_cast)]
             {
    -            self.stat.st_nlink.into()
    +            self.stat.st_nlink as u64
             }
         }
     
    @@ -460,10 +449,14 @@ impl std::os::unix::fs::MetadataExt for Metadata {
             self.stat.st_gid
         }
     
    +    // st_rdev type varies by platform (i32 on macOS, u64 on Linux)
    +    #[allow(clippy::unnecessary_cast)]
         fn rdev(&self) -> u64 {
    -        self.stat.st_rdev
    +        self.stat.st_rdev as u64
         }
     
    +    // st_size type varies by platform (i64 on some platforms, u64 on others)
    +    #[allow(clippy::unnecessary_cast)]
         fn size(&self) -> u64 {
             self.stat.st_size as u64
         }
    @@ -534,10 +527,14 @@ impl std::os::unix::fs::MetadataExt for Metadata {
             }
         }
     
    +    // st_blksize type varies by platform (i32/i64/u32/u64 depending on platform)
    +    #[allow(clippy::unnecessary_cast)]
         fn blksize(&self) -> u64 {
             self.stat.st_blksize as u64
         }
     
    +    // st_blocks type varies by platform (i64 on some platforms, u64 on others)
    +    #[allow(clippy::unnecessary_cast)]
         fn blocks(&self) -> u64 {
             self.stat.st_blocks as u64
         }
    
  • src/uucore/src/lib/lib.rs+1 1 modified
    @@ -99,7 +99,7 @@ pub use crate::features::perms;
     pub use crate::features::pipes;
     #[cfg(all(unix, feature = "process"))]
     pub use crate::features::process;
    -#[cfg(target_os = "linux")]
    +#[cfg(all(unix, not(target_os = "redox")))]
     pub use crate::features::safe_traversal;
     #[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))]
     pub use crate::features::signals;
    
  • src/uu/cp/src/cp.rs+5 2 modified
    @@ -1566,6 +1566,7 @@ fn file_mode_for_interactive_overwrite(
                 match path.metadata() {
                     Ok(me) => {
                         // Cast is necessary on some platforms
    +                    #[allow(clippy::unnecessary_cast)]
                         let mode: mode_t = me.mode() as mode_t;
     
                         // It looks like this extra information is added to the prompt iff the file's user write bit is 0
    @@ -1758,7 +1759,7 @@ pub(crate) fn copy_attributes(
             Ok(())
         })?;
     
    -    #[cfg(feature = "selinux")]
    +    #[cfg(all(feature = "selinux", target_os = "linux"))]
         handle_preserve(&attributes.context, || -> CopyResult<()> {
             // Get the source context and apply it to the destination
             if let Ok(context) = selinux::SecurityContext::of_path(source, false, false) {
    @@ -2552,7 +2553,7 @@ fn copy_file(
             copy_attributes(source, dest, &options.attributes)?;
         }
     
    -    #[cfg(feature = "selinux")]
    +    #[cfg(all(feature = "selinux", target_os = "linux"))]
         if options.set_selinux_context && uucore::selinux::is_selinux_enabled() {
             // Set the given selinux permissions on the copied file.
             if let Err(e) =
    @@ -2620,8 +2621,10 @@ fn handle_no_preserve_mode(options: &Options, org_mode: u32) -> u32 {
                 target_os = "redox",
             ))]
             {
    +            #[allow(clippy::unnecessary_cast)]
                 const MODE_RW_UGO: u32 =
                     (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32;
    +            #[allow(clippy::unnecessary_cast)]
                 const S_IRWXUGO: u32 = (S_IRWXU | S_IRWXG | S_IRWXO) as u32;
                 return if is_explicit_no_preserve_mode {
                     MODE_RW_UGO
    
  • src/uu/du/Cargo.toml+3 1 modified
    @@ -27,11 +27,13 @@ uucore = { workspace = true, features = [
       "parser-size",
       "parser-glob",
       "time",
    -  "safe-traversal",
     ] }
     thiserror = { workspace = true }
     fluent = { workspace = true }
     
    +[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies]
    +uucore = { workspace = true, features = ["safe-traversal"] }
    +
     [target.'cfg(target_os = "windows")'.dependencies]
     windows-sys = { workspace = true, features = [
       "Win32_Storage_FileSystem",
    
  • src/uu/du/src/du.rs+20 14 modified
    @@ -25,7 +25,7 @@ use uucore::display::{Quotable, print_verbatim};
     use uucore::error::{FromIo, UError, UResult, USimpleError, set_exit_code};
     use uucore::fsext::{MetadataTimeField, metadata_get_time};
     use uucore::line_ending::LineEnding;
    -#[cfg(target_os = "linux")]
    +#[cfg(all(unix, not(target_os = "redox")))]
     use uucore::safe_traversal::DirFd;
     use uucore::translate;
     
    @@ -164,7 +164,7 @@ impl Stat {
         }
     
         /// Create a Stat using safe traversal methods with `DirFd` for the root directory
    -    #[cfg(target_os = "linux")]
    +    #[cfg(all(unix, not(target_os = "redox")))]
         fn new_from_dirfd(dir_fd: &DirFd, full_path: &Path) -> std::io::Result<Self> {
             // Get metadata for the directory itself using fstat
             let safe_metadata = dir_fd.metadata()?;
    @@ -293,9 +293,9 @@ fn read_block_size(s: Option<&str>) -> UResult<u64> {
         }
     }
     
    -#[cfg(target_os = "linux")]
    -// For now, implement safe_du only on Linux
    -// This is done for Ubuntu but should be extended to other platforms that support openat
    +#[cfg(all(unix, not(target_os = "redox")))]
    +// Implement safe_du on Unix (except Redox which lacks full stat support)
    +// This is done for TOCTOU safety
     fn safe_du(
         path: &Path,
         options: &TraversalOptions,
    @@ -439,7 +439,8 @@ fn safe_du(
             const S_IFMT: u32 = 0o170_000;
             const S_IFDIR: u32 = 0o040_000;
             const S_IFLNK: u32 = 0o120_000;
    -        let is_symlink = (lstat.st_mode & S_IFMT) == S_IFLNK;
    +        #[allow(clippy::unnecessary_cast)]
    +        let is_symlink = (lstat.st_mode as u32 & S_IFMT) == S_IFLNK;
     
             // Handle symlinks with -L option
             // For safe traversal with -L, we skip symlinks to directories entirely
    @@ -450,12 +451,14 @@ fn safe_du(
                 continue;
             }
     
    -        let is_dir = (lstat.st_mode & S_IFMT) == S_IFDIR;
    +        #[allow(clippy::unnecessary_cast)]
    +        let is_dir = (lstat.st_mode as u32 & S_IFMT) == S_IFDIR;
             let entry_stat = lstat;
     
    +        #[allow(clippy::unnecessary_cast)]
             let file_info = (entry_stat.st_ino != 0).then_some(FileInfo {
                 file_id: entry_stat.st_ino as u128,
    -            dev_id: entry_stat.st_dev,
    +            dev_id: entry_stat.st_dev as u64,
             });
     
             // For safe traversal, we need to handle stats differently
    @@ -465,6 +468,7 @@ fn safe_du(
                 Stat {
                     path: entry_path.clone(),
                     size: 0,
    +                #[allow(clippy::unnecessary_cast)]
                     blocks: entry_stat.st_blocks as u64,
                     inodes: 1,
                     inode: file_info,
    @@ -476,7 +480,9 @@ fn safe_du(
                 // For files
                 Stat {
                     path: entry_path.clone(),
    +                #[allow(clippy::unnecessary_cast)]
                     size: entry_stat.st_size as u64,
    +                #[allow(clippy::unnecessary_cast)]
                     blocks: entry_stat.st_blocks as u64,
                     inodes: 1,
                     inode: file_info,
    @@ -1096,14 +1102,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
             let mut seen_inodes: HashSet<FileInfo> = HashSet::new();
     
             // Determine which traversal method to use
    -        #[cfg(target_os = "linux")]
    +        #[cfg(all(unix, not(target_os = "redox")))]
             let use_safe_traversal = traversal_options.dereference != Deref::All;
    -        #[cfg(not(target_os = "linux"))]
    +        #[cfg(not(all(unix, not(target_os = "redox"))))]
             let use_safe_traversal = false;
     
             if use_safe_traversal {
    -            // Use safe traversal (Linux only, when not using -L)
    -            #[cfg(target_os = "linux")]
    +            // Use safe traversal (Unix except Redox, when not using -L)
    +            #[cfg(all(unix, not(target_os = "redox")))]
                 {
                     // Pre-populate seen_inodes with the starting directory to detect cycles
                     if let Ok(stat) = Stat::new(&path, None, &traversal_options) {
    @@ -1158,9 +1164,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
                         .send(Ok(StatPrintInfo { stat, depth: 0 }))
                         .map_err(|e| USimpleError::new(1, e.to_string()))?;
                 } else {
    -                #[cfg(target_os = "linux")]
    +                #[cfg(unix)]
                     let error_msg = translate!("du-error-cannot-access", "path" => path.quote());
    -                #[cfg(not(target_os = "linux"))]
    +                #[cfg(not(unix))]
                     let error_msg =
                         translate!("du-error-cannot-access-no-such-file", "path" => path.quote());
     
    
  • src/uu/install/src/install.rs+12 12 modified
    @@ -10,7 +10,7 @@ mod mode;
     use clap::{Arg, ArgAction, ArgMatches, Command};
     use file_diff::diff;
     use filetime::{FileTime, set_file_times};
    -#[cfg(feature = "selinux")]
    +#[cfg(all(feature = "selinux", target_os = "linux"))]
     use selinux::SecurityContext;
     use std::ffi::OsString;
     use std::fmt::Debug;
    @@ -27,7 +27,7 @@ use uucore::error::{FromIo, UError, UResult, UUsageError};
     use uucore::fs::dir_strip_dot_for_creation;
     use uucore::perms::{Verbosity, VerbosityLevel, wrap_chown};
     use uucore::process::{getegid, geteuid};
    -#[cfg(feature = "selinux")]
    +#[cfg(all(feature = "selinux", target_os = "linux"))]
     use uucore::selinux::{
         SeLinuxError, contexts_differ, get_selinux_security_context, is_selinux_enabled,
         selinux_error_description, set_selinux_security_context,
    @@ -118,7 +118,7 @@ enum InstallError {
         #[error("{}", translate!("install-error-extra-operand", "operand" => .0.quote(), "usage" => .1.clone()))]
         ExtraOperand(OsString, String),
     
    -    #[cfg(feature = "selinux")]
    +    #[cfg(all(feature = "selinux", target_os = "linux"))]
         #[error("{}", .0)]
         SelinuxContextFailed(String),
     }
    @@ -1030,7 +1030,7 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> {
         Ok(())
     }
     
    -#[cfg(feature = "selinux")]
    +#[cfg(all(feature = "selinux", target_os = "linux"))]
     fn get_context_for_selinux(b: &Behavior) -> Option<&String> {
         if b.default_context {
             None
    @@ -1165,7 +1165,7 @@ fn need_copy(from: &Path, to: &Path, b: &Behavior) -> bool {
         false
     }
     
    -#[cfg(feature = "selinux")]
    +#[cfg(all(feature = "selinux", target_os = "linux"))]
     /// Sets the `SELinux` security context for install's -Z flag behavior.
     ///
     /// This function implements the specific behavior needed for install's -Z flag,
    @@ -1199,7 +1199,7 @@ pub fn set_selinux_default_context(path: &Path) -> Result<(), SeLinuxError> {
         }
     }
     
    -#[cfg(feature = "selinux")]
    +#[cfg(all(feature = "selinux", target_os = "linux"))]
     /// Gets the default `SELinux` context for a path based on the system's security policy.
     ///
     /// This function attempts to determine what the "correct" `SELinux` context should be
    @@ -1255,7 +1255,7 @@ fn get_default_context_for_path(path: &Path) -> Result<Option<String>, SeLinuxEr
         Ok(None)
     }
     
    -#[cfg(feature = "selinux")]
    +#[cfg(all(feature = "selinux", target_os = "linux"))]
     /// Derives an appropriate `SELinux` context based on a parent directory context.
     ///
     /// This is a heuristic function that attempts to generate an appropriate
    @@ -1293,7 +1293,7 @@ fn derive_context_from_parent(parent_context: &str) -> String {
         }
     }
     
    -#[cfg(feature = "selinux")]
    +#[cfg(all(feature = "selinux", target_os = "linux"))]
     /// Helper function to collect paths that need `SELinux` context setting.
     ///
     /// Traverses from the given starting path up to existing parent directories.
    @@ -1307,7 +1307,7 @@ fn collect_paths_for_context_setting(starting_path: &Path) -> Vec<&Path> {
         paths
     }
     
    -#[cfg(feature = "selinux")]
    +#[cfg(all(feature = "selinux", target_os = "linux"))]
     /// Sets the `SELinux` security context for a directory hierarchy.
     ///
     /// This function traverses from the given starting path up to existing parent directories
    @@ -1347,7 +1347,7 @@ fn set_selinux_context_for_directories(target_path: &Path, context: Option<&Stri
         }
     }
     
    -#[cfg(feature = "selinux")]
    +#[cfg(all(feature = "selinux", target_os = "linux"))]
     /// Sets `SELinux` context for created directories using install's -Z default behavior.
     ///
     /// Similar to `set_selinux_context_for_directories` but uses install's
    @@ -1371,10 +1371,10 @@ pub fn set_selinux_context_for_directories_install(target_path: &Path, context:
     
     #[cfg(test)]
     mod tests {
    -    #[cfg(feature = "selinux")]
    +    #[cfg(all(feature = "selinux", target_os = "linux"))]
         use super::derive_context_from_parent;
     
    -    #[cfg(feature = "selinux")]
    +    #[cfg(all(feature = "selinux", target_os = "linux"))]
         #[test]
         fn test_derive_context_from_parent() {
             // Test cases: (input_context, file_type, expected_output, description)
    
  • src/uu/mkfifo/src/mkfifo.rs+1 1 modified
    @@ -59,7 +59,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
             }
     
             // Apply SELinux context if requested
    -        #[cfg(feature = "selinux")]
    +        #[cfg(all(feature = "selinux", target_os = "linux"))]
             {
                 // Extract the SELinux related flags and options
                 let set_security_context = matches.get_flag(options::SECURITY_CONTEXT);
    
  • src/uu/rm/Cargo.toml+4 1 modified
    @@ -20,10 +20,13 @@ path = "src/rm.rs"
     [dependencies]
     thiserror = { workspace = true }
     clap = { workspace = true }
    -uucore = { workspace = true, features = ["fs", "parser", "safe-traversal"] }
    +uucore = { workspace = true, features = ["fs", "parser"] }
     fluent = { workspace = true }
     indicatif = { workspace = true }
     
    +[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies]
    +uucore = { workspace = true, features = ["safe-traversal"] }
    +
     [target.'cfg(unix)'.dependencies]
     libc = { workspace = true }
     
    
  • src/uu/rm/src/platform/mod.rs+4 4 modified
    @@ -5,8 +5,8 @@
     
     // Platform-specific implementations for the rm utility
     
    -#[cfg(target_os = "linux")]
    -pub mod linux;
    +#[cfg(all(unix, not(target_os = "redox")))]
    +pub mod unix;
     
    -#[cfg(target_os = "linux")]
    -pub use linux::*;
    +#[cfg(all(unix, not(target_os = "redox")))]
    +pub use unix::*;
    
  • src/uu/rm/src/platform/unix.rs+16 8 renamed
    @@ -3,7 +3,7 @@
     // For the full copyright and license information, please view the LICENSE
     // file that was distributed with this source code.
     
    -// Linux-specific implementations for the rm utility
    +// Unix-specific implementations for the rm utility
     
     // spell-checker:ignore fstatat unlinkat statx behaviour
     
    @@ -42,8 +42,8 @@ fn prompt_file_with_stat(path: &Path, stat: &libc::stat, options: &Options) -> b
             return true;
         }
     
    -    let is_symlink = (stat.st_mode & libc::S_IFMT) == libc::S_IFLNK;
    -    let writable = mode_writable(stat.st_mode);
    +    let is_symlink = ((stat.st_mode as libc::mode_t) & libc::S_IFMT) == libc::S_IFLNK;
    +    let writable = mode_writable(stat.st_mode as libc::mode_t);
         let len = stat.st_size as u64;
         let stdin_ok = options.__presume_input_tty.unwrap_or(false) || stdin().is_terminal();
     
    @@ -82,8 +82,8 @@ fn prompt_dir_with_mode(path: &Path, mode: libc::mode_t, options: &Options) -> b
             return true;
         }
     
    -    let readable = mode_readable(mode);
    -    let writable = mode_writable(mode);
    +    let readable = mode_readable(mode as libc::mode_t);
    +    let writable = mode_writable(mode as libc::mode_t);
         let stdin_ok = options.__presume_input_tty.unwrap_or(false) || stdin().is_terminal();
     
         match (stdin_ok, readable, writable, options.interactive) {
    @@ -317,7 +317,7 @@ pub fn safe_remove_dir_recursive(
         } else {
             // Ask user permission if needed
             if options.interactive == InteractiveMode::Always
    -            && !prompt_dir_with_mode(path, initial_mode, options)
    +            && !prompt_dir_with_mode(path, initial_mode as libc::mode_t, options)
             {
                 return false;
             }
    @@ -345,6 +345,7 @@ pub fn safe_remove_dir_recursive(
         }
     }
     
    +#[cfg(not(target_os = "redox"))]
     pub fn safe_remove_dir_recursive_impl(path: &Path, dir_fd: &DirFd, options: &Options) -> bool {
         // Read directory entries using safe traversal
         let entries = match dir_fd.read_dir() {
    @@ -376,7 +377,7 @@ pub fn safe_remove_dir_recursive_impl(path: &Path, dir_fd: &DirFd, options: &Opt
             };
     
             // Check if it's a directory
    -        let is_dir = (entry_stat.st_mode & libc::S_IFMT) == libc::S_IFDIR;
    +        let is_dir = ((entry_stat.st_mode as libc::mode_t) & libc::S_IFMT) == libc::S_IFDIR;
     
             if is_dir {
                 // Ask user if they want to descend into this directory
    @@ -413,7 +414,7 @@ pub fn safe_remove_dir_recursive_impl(path: &Path, dir_fd: &DirFd, options: &Opt
                 // Ask user permission if needed for this subdirectory
                 if !child_error
                     && options.interactive == InteractiveMode::Always
    -                && !prompt_dir_with_mode(&entry_path, entry_stat.st_mode, options)
    +                && !prompt_dir_with_mode(&entry_path, entry_stat.st_mode as libc::mode_t, options)
                 {
                     continue;
                 }
    @@ -432,3 +433,10 @@ pub fn safe_remove_dir_recursive_impl(path: &Path, dir_fd: &DirFd, options: &Opt
     
         error
     }
    +
    +#[cfg(target_os = "redox")]
    +pub fn safe_remove_dir_recursive_impl(_path: &Path, _dir_fd: &DirFd, _options: &Options) -> bool {
    +    // safe_traversal stat_at is not supported on Redox
    +    // This shouldn't be called on Redox, but provide a stub for compilation
    +    true // Return error
    +}
    
  • src/uu/rm/src/rm.rs+11 21 modified
    @@ -26,7 +26,7 @@ use uucore::translate;
     use uucore::{format_usage, os_str_as_bytes, prompt_yes, show_error};
     
     mod platform;
    -#[cfg(target_os = "linux")]
    +#[cfg(all(unix, not(target_os = "redox")))]
     use platform::{safe_remove_dir_recursive, safe_remove_empty_dir, safe_remove_file};
     
     #[derive(Debug, Error)]
    @@ -538,17 +538,7 @@ fn is_readable_metadata(metadata: &Metadata) -> bool {
     }
     
     /// Whether the given file or directory is readable.
    -#[cfg(unix)]
    -#[cfg(not(target_os = "linux"))]
    -fn is_readable(path: &Path) -> bool {
    -    match fs::metadata(path) {
    -        Err(_) => false,
    -        Ok(metadata) => is_readable_metadata(&metadata),
    -    }
    -}
    -
    -/// Whether the given file or directory is readable.
    -#[cfg(not(unix))]
    +#[cfg(any(not(unix), target_os = "redox"))]
     fn is_readable(_path: &Path) -> bool {
         true
     }
    @@ -605,14 +595,14 @@ fn remove_dir_recursive(
             return false;
         }
     
    -    // Use secure traversal on Linux for all recursive directory removals
    -    #[cfg(target_os = "linux")]
    +    // Use secure traversal on Unix (except Redox) for all recursive directory removals
    +    #[cfg(all(unix, not(target_os = "redox")))]
         {
             safe_remove_dir_recursive(path, options, progress_bar)
         }
     
    -    // Fallback for non-Linux or use fs::remove_dir_all for very long paths
    -    #[cfg(not(target_os = "linux"))]
    +    // Fallback for non-Unix, Redox, or use fs::remove_dir_all for very long paths
    +    #[cfg(any(not(unix), target_os = "redox"))]
         {
             if let Some(s) = path.to_str() {
                 if s.len() > 1000 {
    @@ -734,8 +724,8 @@ fn remove_dir(path: &Path, options: &Options, progress_bar: Option<&ProgressBar>
             return true;
         }
     
    -    // Use safe traversal on Linux for empty directory removal
    -    #[cfg(target_os = "linux")]
    +    // Use safe traversal on Unix (except Redox) for empty directory removal
    +    #[cfg(all(unix, not(target_os = "redox")))]
         {
             if let Some(result) = safe_remove_empty_dir(path, options, progress_bar) {
                 return result;
    @@ -758,15 +748,15 @@ fn remove_file(path: &Path, options: &Options, progress_bar: Option<&ProgressBar
                 pb.inc(1);
             }
     
    -        // Use safe traversal on Linux for individual file removal
    -        #[cfg(target_os = "linux")]
    +        // Use safe traversal on Unix (except Redox) for individual file removal
    +        #[cfg(all(unix, not(target_os = "redox")))]
             {
                 if let Some(result) = safe_remove_file(path, options, progress_bar) {
                     return result;
                 }
             }
     
    -        // Fallback method for non-Linux or when safe traversal is unavailable
    +        // Fallback method for non-Unix, Redox, or when safe traversal is unavailable
             match fs::remove_file(path) {
                 Ok(_) => {
                     verbose_removed_file(path, options);
    
  • src/uu/stat/src/stat.rs+2 2 modified
    @@ -1044,7 +1044,7 @@ impl Stater {
                         'B' => OutputType::Unsigned(512),
                         // SELinux security context string
                         'C' => {
    -                        #[cfg(feature = "selinux")]
    +                        #[cfg(all(feature = "selinux", target_os = "linux"))]
                             {
                                 if uucore::selinux::is_selinux_enabled() {
                                     match uucore::selinux::get_selinux_security_context(
    @@ -1060,7 +1060,7 @@ impl Stater {
                                     OutputType::Str(translate!("stat-selinux-unsupported-system"))
                                 }
                             }
    -                        #[cfg(not(feature = "selinux"))]
    +                        #[cfg(not(all(feature = "selinux", target_os = "linux")))]
                             {
                                 OutputType::Str(translate!("stat-selinux-unsupported-os"))
                             }
    
  • tests/by-util/test_chmod.rs+7 6 modified
    @@ -390,10 +390,12 @@ fn test_chmod_recursive_correct_exit_code() {
         perms.set_mode(0o000);
         set_permissions(at.plus_as_string("a"), perms).unwrap();
     
    -    #[cfg(not(target_os = "linux"))]
    -    let err_msg = "chmod: Permission denied\n";
    -    #[cfg(target_os = "linux")]
    +    // With safe_traversal enabled on all Unix platforms (except Redox),
    +    // we get detailed error messages that include the file path
    +    #[cfg(all(unix, not(target_os = "redox")))]
         let err_msg = "chmod: cannot access 'a': Permission denied\n";
    +    #[cfg(not(all(unix, not(target_os = "redox"))))]
    +    let err_msg = "chmod: Permission denied\n";
     
         // order of command is a, a/b then c
         // command is expected to fail and not just take the last exit code
    @@ -434,9 +436,8 @@ fn test_chmod_recursive() {
         make_file(&at.plus_as_string("a/b/b"), 0o100444);
         make_file(&at.plus_as_string("a/b/c/c"), 0o100444);
         make_file(&at.plus_as_string("z/y"), 0o100444);
    -    #[cfg(not(target_os = "linux"))]
    -    let err_msg = "chmod: Permission denied\n";
    -    #[cfg(target_os = "linux")]
    +    // With safe_traversal enabled on all Unix platforms, the error message
    +    // now includes the file path consistently across platforms
         let err_msg = "chmod: cannot access 'z': Permission denied\n";
     
         // only the permissions of folder `a` and `z` are changed
    
  • .vscode/cspell.dictionaries/acronyms+names.wordlist.txt+1 0 modified
    @@ -34,6 +34,7 @@ RISCV
     RNG # random number generator
     RNGs
     Solaris
    +TOCTOU # time-of-check time-of-use
     UID # user ID
     UIDs
     UUID # universally unique identifier
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.