diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index b9aebbbe7..820c99f9b 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -920,6 +920,7 @@ impl Stater { ret } + #[allow(clippy::too_many_arguments)] fn process_token_files( &self, t: &Token, @@ -928,6 +929,7 @@ impl Stater { file: &OsString, file_type: &FileType, from_user: bool, + _follow_symbolic_links: bool, ) -> Result<(), i32> { match *t { Token::Byte(byte) => write_raw_byte(byte), @@ -956,8 +958,10 @@ impl Stater { #[cfg(feature = "selinux")] { if uucore::selinux::is_selinux_enabled() { - match uucore::selinux::get_selinux_security_context(Path::new(file)) - { + match uucore::selinux::get_selinux_security_context( + Path::new(file), + _follow_symbolic_links, + ) { Ok(ctx) => OutputType::Str(ctx), Err(_) => OutputType::Str(get_message( "stat-selinux-failed-get-context", @@ -1113,7 +1117,8 @@ impl Stater { } } } else { - let result = if self.follow || stdin_is_fifo && display_name == "-" { + let follow_symbolic_links = self.follow || stdin_is_fifo && display_name == "-"; + let result = if follow_symbolic_links { fs::metadata(&file) } else { fs::symlink_metadata(&file) @@ -1137,6 +1142,7 @@ impl Stater { &file, &file_type, self.from_user, + follow_symbolic_links, ) { return code; } diff --git a/src/uucore/src/lib/features/selinux.rs b/src/uucore/src/lib/features/selinux.rs index ce71fd39f..0b260dd75 100644 --- a/src/uucore/src/lib/features/selinux.rs +++ b/src/uucore/src/lib/features/selinux.rs @@ -182,7 +182,7 @@ pub fn set_selinux_security_context( /// use uucore::selinux::{get_selinux_security_context, SeLinuxError}; /// /// // Get the SELinux context for a file -/// match get_selinux_security_context(Path::new("/path/to/file")) { +/// match get_selinux_security_context(Path::new("/path/to/file"), false) { /// Ok(context) => { /// if context.is_empty() { /// println!("No SELinux context found for the file"); @@ -197,16 +197,16 @@ pub fn set_selinux_security_context( /// Err(SeLinuxError::ContextSetFailure(ctx, e)) => println!("Failed to set context '{ctx}': {e}"), /// } /// ``` -pub fn get_selinux_security_context(path: &Path) -> Result { +pub fn get_selinux_security_context( + path: &Path, + follow_symbolic_links: bool, +) -> Result { if !is_selinux_enabled() { return Err(SeLinuxError::SELinuxNotEnabled); } - let f = std::fs::File::open(path) - .map_err(|e| SeLinuxError::FileOpenFailure(selinux_error_description(&e)))?; - // Get the security context of the file - let context = match SecurityContext::of_file(&f, false) { + let context = match SecurityContext::of_path(path, follow_symbolic_links, false) { Ok(Some(ctx)) => ctx, Ok(None) => return Ok(String::new()), // No context found, return empty string Err(e) => { @@ -317,7 +317,7 @@ pub fn preserve_security_context(from_path: &Path, to_path: &Path) -> Result<(), } // Get context from the source path - let context = get_selinux_security_context(from_path)?; + let context = get_selinux_security_context(from_path, false)?; // If no context was found, just return success (nothing to preserve) if context.is_empty() { @@ -357,7 +357,7 @@ mod tests { default_result.err() ); - let context = get_selinux_security_context(path).expect("Failed to get context"); + let context = get_selinux_security_context(path, false).expect("Failed to get context"); assert!( !context.is_empty(), "Expected non-empty context after setting default context" @@ -367,7 +367,7 @@ mod tests { let explicit_result = set_selinux_security_context(path, Some(&test_context)); if explicit_result.is_ok() { - let new_context = get_selinux_security_context(path) + let new_context = get_selinux_security_context(path, false) .expect("Failed to get context after setting explicit context"); assert!( @@ -420,7 +420,7 @@ mod tests { } std::fs::write(path, b"test content").expect("Failed to write to tempfile"); - let result = get_selinux_security_context(path); + let result = get_selinux_security_context(path, false); if result.is_ok() { let context = result.unwrap(); @@ -484,11 +484,70 @@ mod tests { println!("test skipped: Kernel has no support for SElinux context"); return; } - let result = get_selinux_security_context(path); + let result = get_selinux_security_context(path, false); assert!(result.is_err()); } + #[test] + fn test_get_selinux_context_symlink() { + use std::os::unix::fs::symlink; + use tempfile::tempdir; + + if !is_selinux_enabled() { + println!("test skipped: Kernel has no support for SElinux context"); + return; + } + + let tmp_dir = tempdir().expect("Failed to create temporary directory"); + let dir_path = tmp_dir.path(); + + // Create a normal file + let file_path = dir_path.join("file"); + std::fs::File::create(&file_path).expect("Failed to create file"); + + // Create a symlink to the file + let symlink_path = dir_path.join("symlink"); + symlink(&file_path, &symlink_path).expect("Failed to create symlink"); + + // Set a different context for the file (but not the symlink) + let file_context = String::from("system_u:object_r:user_tmp_t:s0"); + set_selinux_security_context(&file_path, Some(&file_context)) + .expect("Failed to set security context."); + + // Context must be different if we don't follow the link + let file_context = get_selinux_security_context(&file_path, false) + .expect("Failed to get security context."); + let symlink_context = get_selinux_security_context(&symlink_path, false) + .expect("Failed to get security context."); + assert_ne!(file_context.to_string(), symlink_context.to_string()); + + // Context must be the same if we follow the link + let symlink_follow_context = get_selinux_security_context(&symlink_path, true) + .expect("Failed to get security context."); + assert_eq!(file_context.to_string(), symlink_follow_context.to_string()); + } + + #[test] + fn test_get_selinux_context_fifo() { + use tempfile::tempdir; + + if !is_selinux_enabled() { + println!("test skipped: Kernel has no support for SElinux context"); + return; + } + + let tmp_dir = tempdir().expect("Failed to create temporary directory"); + let dir_path = tmp_dir.path(); + + // Create a FIFO (pipe) + let fifo_path = dir_path.join("my_fifo"); + crate::fs::make_fifo(&fifo_path).expect("Failed to create FIFO"); + + // Just getting a context is good enough + get_selinux_security_context(&fifo_path, false).expect("Cannot get fifo context"); + } + #[test] fn test_contexts_differ() { let file1 = NamedTempFile::new().expect("Failed to create first tempfile");