From c9d6f816438639eb2fe664895d392aba16c63fe7 Mon Sep 17 00:00:00 2001 From: Jake Abendroth Date: Tue, 23 Dec 2025 03:11:07 -0800 Subject: [PATCH] feat: Expand safe directory traversal to all Unix platforms and fix related type conversions. --- src/uu/chmod/src/chmod.rs | 16 +++++------ src/uu/du/src/du.rs | 28 +++++++++---------- src/uu/rm/src/platform/mod.rs | 8 +++--- src/uu/rm/src/platform/{linux.rs => unix.rs} | 2 +- src/uu/rm/src/rm.rs | 20 ++++++------- src/uucore/src/lib/features.rs | 2 +- src/uucore/src/lib/features/safe_traversal.rs | 14 +++++----- src/uucore/src/lib/lib.rs | 2 +- 8 files changed, 46 insertions(+), 46 deletions(-) rename src/uu/rm/src/platform/{linux.rs => unix.rs} (99%) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 15b608af6b2..7026118e265 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -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(unix)] 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,7 @@ impl Chmoder { r } - #[cfg(not(target_os = "linux"))] + #[cfg(not(unix))] fn walk_dir_with_context(&self, file_path: &Path, is_command_line_arg: bool) -> UResult<()> { let mut r = self.chmod_file(file_path); @@ -454,7 +454,7 @@ impl Chmoder { r } - #[cfg(target_os = "linux")] + #[cfg(unix)] fn walk_dir_with_context(&self, file_path: &Path, is_command_line_arg: bool) -> UResult<()> { let mut r = self.chmod_file(file_path); @@ -487,7 +487,7 @@ impl Chmoder { r } - #[cfg(target_os = "linux")] + #[cfg(unix)] fn safe_traverse_dir(&self, dir_fd: &DirFd, dir_path: &Path) -> UResult<()> { let mut r = Ok(()); @@ -546,7 +546,7 @@ impl Chmoder { r } - #[cfg(target_os = "linux")] + #[cfg(unix)] fn handle_symlink_during_safe_recursion( &self, path: &Path, @@ -578,7 +578,7 @@ impl Chmoder { } } - #[cfg(target_os = "linux")] + #[cfg(unix)] fn safe_chmod_file( &self, file_path: &Path, @@ -610,7 +610,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) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index f57228d7625..ddbaca28e60 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -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(unix)] 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(unix)] fn new_from_dirfd(dir_fd: &DirFd, full_path: &Path) -> std::io::Result { // 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 { } } -#[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(unix)] +// Implement safe_du on Unix +// This is done for TOCTOU safety fn safe_du( path: &Path, options: &TraversalOptions, @@ -439,7 +439,7 @@ 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; + 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 +450,12 @@ fn safe_du( continue; } - let is_dir = (lstat.st_mode & S_IFMT) == S_IFDIR; + let is_dir = (lstat.st_mode as u32 & S_IFMT) == S_IFDIR; let entry_stat = lstat; 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 @@ -1106,14 +1106,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let mut seen_inodes: HashSet = HashSet::new(); // Determine which traversal method to use - #[cfg(target_os = "linux")] + #[cfg(unix)] let use_safe_traversal = traversal_options.dereference != Deref::All; - #[cfg(not(target_os = "linux"))] + #[cfg(not(unix))] 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 only, when not using -L) + #[cfg(unix)] { // Pre-populate seen_inodes with the starting directory to detect cycles if let Ok(stat) = Stat::new(&path, None, &traversal_options) { @@ -1168,9 +1168,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.to_string_lossy().quote()); print_tx diff --git a/src/uu/rm/src/platform/mod.rs b/src/uu/rm/src/platform/mod.rs index 1f2911acbc9..f9d2a0356d6 100644 --- a/src/uu/rm/src/platform/mod.rs +++ b/src/uu/rm/src/platform/mod.rs @@ -5,8 +5,8 @@ // Platform-specific implementations for the rm utility -#[cfg(target_os = "linux")] -pub mod linux; +#[cfg(unix)] +pub mod unix; -#[cfg(target_os = "linux")] -pub use linux::*; +#[cfg(unix)] +pub use unix::*; diff --git a/src/uu/rm/src/platform/linux.rs b/src/uu/rm/src/platform/unix.rs similarity index 99% rename from src/uu/rm/src/platform/linux.rs rename to src/uu/rm/src/platform/unix.rs index 6c7d3239572..6d0c73853df 100644 --- a/src/uu/rm/src/platform/linux.rs +++ b/src/uu/rm/src/platform/unix.rs @@ -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 diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index a20a57d7f36..7d3e135b7b7 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -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(unix)] use platform::{safe_remove_dir_recursive, safe_remove_empty_dir, safe_remove_file}; #[derive(Debug, Error)] @@ -539,7 +539,7 @@ fn is_readable_metadata(metadata: &Metadata) -> bool { /// Whether the given file or directory is readable. #[cfg(unix)] -#[cfg(not(target_os = "linux"))] +#[cfg(not(unix))] fn is_readable(path: &Path) -> bool { match fs::metadata(path) { Err(_) => false, @@ -605,14 +605,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 (all supported platforms) for all recursive directory removals + #[cfg(unix)] { 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 or use fs::remove_dir_all for very long paths + #[cfg(not(unix))] { if let Some(s) = path.to_str() { if s.len() > 1000 { @@ -734,8 +734,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 (all supported platforms) for empty directory removal + #[cfg(unix)] { if let Some(result) = safe_remove_empty_dir(path, options, progress_bar) { return result; @@ -758,8 +758,8 @@ 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 for individual file removal + #[cfg(unix)] { if let Some(result) = safe_remove_file(path, options, progress_bar) { return result; diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index 548f7f2bc95..5b98d56a2a4 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -72,7 +72,7 @@ pub mod pipes; pub mod proc_info; #[cfg(all(unix, feature = "process"))] pub mod process; -#[cfg(target_os = "linux")] +#[cfg(unix)] pub mod safe_traversal; #[cfg(all(target_os = "linux", feature = "tty"))] pub mod tty; diff --git a/src/uucore/src/lib/features/safe_traversal.rs b/src/uucore/src/lib/features/safe_traversal.rs index a405ea5d91c..73e274a92e7 100644 --- a/src/uucore/src/lib/features/safe_traversal.rs +++ b/src/uucore/src/lib/features/safe_traversal.rs @@ -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 @@ -253,7 +253,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)?; @@ -266,7 +266,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))?; @@ -389,7 +389,7 @@ impl Metadata { } pub fn mode(&self) -> u32 { - self.stat.st_mode + self.stat.st_mode as u32 } pub fn nlink(&self) -> u64 { @@ -421,7 +421,7 @@ impl Metadata { // Add MetadataExt trait implementation for compatibility impl std::os::unix::fs::MetadataExt for Metadata { fn dev(&self) -> u64 { - self.stat.st_dev + self.stat.st_dev as u64 } fn ino(&self) -> u64 { @@ -436,7 +436,7 @@ impl std::os::unix::fs::MetadataExt for Metadata { } fn mode(&self) -> u32 { - self.stat.st_mode + self.stat.st_mode as u32 } fn nlink(&self) -> u64 { @@ -460,7 +460,7 @@ impl std::os::unix::fs::MetadataExt for Metadata { } fn rdev(&self) -> u64 { - self.stat.st_rdev + self.stat.st_rdev as u64 } fn size(&self) -> u64 { diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 29686ccdea5..cf7eae775b7 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -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(unix)] pub use crate::features::safe_traversal; #[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))] pub use crate::features::signals;