From 6e930966fb97ddc0ffce600847f7b9c9338cd250 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 11 Jul 2025 23:11:24 +0900 Subject: [PATCH] selinux: introduce get_getfattr_output to verify the output of getfatt --- src/uucore/src/lib/features/selinux.rs | 52 ++++++++++++++++++++++++ tests/by-util/test_cp.rs | 26 +----------- tests/by-util/test_install.rs | 28 +++++-------- tests/by-util/test_mkdir.rs | 23 +++-------- tests/by-util/test_mkfifo.rs | 22 +++------- tests/by-util/test_mknod.rs | 24 +++-------- tests/by-util/test_mv.rs | 56 +++++++------------------- 7 files changed, 93 insertions(+), 138 deletions(-) diff --git a/src/uucore/src/lib/features/selinux.rs b/src/uucore/src/lib/features/selinux.rs index 0b260dd75..939210ae8 100644 --- a/src/uucore/src/lib/features/selinux.rs +++ b/src/uucore/src/lib/features/selinux.rs @@ -328,6 +328,58 @@ pub fn preserve_security_context(from_path: &Path, to_path: &Path) -> Result<(), set_selinux_security_context(to_path, Some(&context)) } +/// Gets the SELinux security context for a file using getfattr. +/// +/// This function is primarily used for testing purposes to verify that SELinux +/// contexts have been properly set on files. It uses the `getfattr` command +/// to retrieve the security.selinux extended attribute. +/// +/// # Arguments +/// +/// * `f` - The file path as a string. +/// +/// # Returns +/// +/// Returns the SELinux context string extracted from the getfattr output. +/// If the context cannot be retrieved, the function will panic. +/// +/// # Panics +/// +/// This function will panic if: +/// - The `getfattr` command fails to execute +/// - The `getfattr` command returns a non-zero exit status +/// +/// # Examples +/// +/// ```no_run +/// use uucore::selinux::get_getfattr_output; +/// +/// let context = get_getfattr_output("/path/to/file"); +/// println!("SELinux context: {}", context); +/// ``` +pub fn get_getfattr_output(f: &str) -> String { + use std::process::Command; + + let getfattr_output = Command::new("getfattr") + .arg(f) + .arg("-n") + .arg("security.selinux") + .output() + .expect("Failed to run `getfattr` on the destination file"); + println!("{getfattr_output:?}"); + assert!( + getfattr_output.status.success(), + "getfattr did not run successfully: {}", + String::from_utf8_lossy(&getfattr_output.stderr) + ); + + String::from_utf8_lossy(&getfattr_output.stdout) + .split('"') + .nth(1) + .unwrap_or("") + .to_string() +} + #[cfg(test)] mod tests { use super::*; diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 8575d1959..7c6c14f48 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -6,6 +6,8 @@ // spell-checker:ignore (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs neve ROOTDIR USERDIR outfile uufs xattrs // spell-checker:ignore bdfl hlsl IRWXO IRWXG nconfined matchpathcon libselinux-devel use uucore::display::Quotable; +#[cfg(feature = "feat_selinux")] +use uucore::selinux::get_getfattr_output; use uutests::util::TestScenario; use uutests::{at_and_ucmd, new_ucmd, path_concat, util_name}; @@ -6296,30 +6298,6 @@ fn test_cp_from_stream_permission() { assert_eq!(at.metadata(target).permissions().mode(), 0o100_777); } -#[cfg(feature = "feat_selinux")] -fn get_getfattr_output(f: &str) -> String { - use std::process::Command; - - let getfattr_output = Command::new("getfattr") - .arg(f) - .arg("-n") - .arg("security.selinux") - .output() - .expect("Failed to run `getfattr` on the destination file"); - println!("{getfattr_output:?}"); - assert!( - getfattr_output.status.success(), - "getfattr did not run successfully: {}", - String::from_utf8_lossy(&getfattr_output.stderr) - ); - - String::from_utf8_lossy(&getfattr_output.stdout) - .split('"') - .nth(1) - .unwrap_or("") - .to_string() -} - #[test] #[cfg(feature = "feat_selinux")] fn test_cp_selinux() { diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index 436e5a424..900e88a32 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -13,6 +13,8 @@ use std::process::Command; #[cfg(any(target_os = "linux", target_os = "android"))] use std::thread::sleep; use uucore::process::{getegid, geteuid}; +#[cfg(feature = "feat_selinux")] +use uucore::selinux::get_getfattr_output; use uutests::at_and_ucmd; use uutests::new_ucmd; use uutests::util::{TestScenario, is_ci, run_ucmd_as_root}; @@ -2235,8 +2237,6 @@ fn test_install_no_target_basic() { #[test] #[cfg(feature = "feat_selinux")] fn test_selinux() { - use std::process::Command; - let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; let src = "orig"; @@ -2265,29 +2265,19 @@ fn test_selinux() { result.success().stdout_contains("orig' -> '"); - let getfattr_output = Command::new("getfattr") - .arg(at.plus_as_string(dest)) - .arg("-n") - .arg("security.selinux") - .output(); + // Try to get SELinux context, skip test if getfattr is not available + let context_value = + std::panic::catch_unwind(|| get_getfattr_output(&at.plus_as_string(dest))); - // Skip test if getfattr is not available - let Ok(getfattr_output) = getfattr_output else { - println!("Skipping SELinux test: getfattr not available"); + let Ok(context_value) = context_value else { + println!("Skipping SELinux test: getfattr not available or failed"); at.remove(&at.plus_as_string(dest)); continue; }; - println!("{getfattr_output:?}"); - assert!( - getfattr_output.status.success(), - "getfattr did not run successfully: {}", - String::from_utf8_lossy(&getfattr_output.stderr) - ); - let stdout = String::from_utf8_lossy(&getfattr_output.stdout); assert!( - stdout.contains("unconfined_u"), - "Expected 'foo' not found in getfattr output:\n{stdout}" + context_value.contains("unconfined_u"), + "Expected 'unconfined_u' not found in getfattr output:\n{context_value}" ); at.remove(&at.plus_as_string(dest)); } diff --git a/tests/by-util/test_mkdir.rs b/tests/by-util/test_mkdir.rs index e6bd6b2c1..1c734449f 100644 --- a/tests/by-util/test_mkdir.rs +++ b/tests/by-util/test_mkdir.rs @@ -11,6 +11,8 @@ use libc::mode_t; #[cfg(not(windows))] use std::os::unix::fs::PermissionsExt; +#[cfg(feature = "feat_selinux")] +use uucore::selinux::get_getfattr_output; #[cfg(not(windows))] use uutests::at_and_ucmd; use uutests::new_ucmd; @@ -390,8 +392,6 @@ fn test_empty_argument() { #[test] #[cfg(feature = "feat_selinux")] fn test_selinux() { - use std::process::Command; - let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; let dest = "test_dir_a"; @@ -404,25 +404,12 @@ fn test_selinux() { .succeeds() .stdout_contains("created directory"); - let getfattr_output = Command::new("getfattr") - .arg(at.plus_as_string(dest)) - .arg("-n") - .arg("security.selinux") - .output() - .expect("Failed to run `getfattr` on the destination file"); - + let context_value = get_getfattr_output(&at.plus_as_string(dest)); assert!( - getfattr_output.status.success(), - "getfattr did not run successfully: {}", - String::from_utf8_lossy(&getfattr_output.stderr) - ); - - let stdout = String::from_utf8_lossy(&getfattr_output.stdout); - assert!( - stdout.contains("unconfined_u"), + context_value.contains("unconfined_u"), "Expected '{}' not found in getfattr output:\n{}", "unconfined_u", - stdout + context_value ); at.rmdir(dest); } diff --git a/tests/by-util/test_mkfifo.rs b/tests/by-util/test_mkfifo.rs index 6bc8f3dd4..7ed97b3dd 100644 --- a/tests/by-util/test_mkfifo.rs +++ b/tests/by-util/test_mkfifo.rs @@ -5,6 +5,8 @@ // spell-checker:ignore nconfined +#[cfg(feature = "feat_selinux")] +use uucore::selinux::get_getfattr_output; use uutests::new_ucmd; use uutests::util::TestScenario; use uutests::util_name; @@ -108,7 +110,6 @@ fn test_create_fifo_with_umask() { #[test] #[cfg(feature = "feat_selinux")] fn test_mkfifo_selinux() { - use std::process::Command; let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; let dest = "test_file"; @@ -121,23 +122,10 @@ fn test_mkfifo_selinux() { ts.ucmd().arg(arg).arg(dest).succeeds(); assert!(at.is_fifo("test_file")); - let getfattr_output = Command::new("getfattr") - .arg(at.plus_as_string(dest)) - .arg("-n") - .arg("security.selinux") - .output() - .expect("Failed to run `getfattr` on the destination file"); - println!("{getfattr_output:?}"); + let context_value = get_getfattr_output(&at.plus_as_string(dest)); assert!( - getfattr_output.status.success(), - "getfattr did not run successfully: {}", - String::from_utf8_lossy(&getfattr_output.stderr) - ); - - let stdout = String::from_utf8_lossy(&getfattr_output.stdout); - assert!( - stdout.contains("unconfined_u"), - "Expected 'foo' not found in getfattr output:\n{stdout}" + context_value.contains("unconfined_u"), + "Expected 'unconfined_u' not found in getfattr output:\n{context_value}" ); at.remove(&at.plus_as_string(dest)); } diff --git a/tests/by-util/test_mknod.rs b/tests/by-util/test_mknod.rs index 1c558dfd3..34136b828 100644 --- a/tests/by-util/test_mknod.rs +++ b/tests/by-util/test_mknod.rs @@ -7,6 +7,8 @@ use std::os::unix::fs::PermissionsExt; +#[cfg(feature = "feat_selinux")] +use uucore::selinux::get_getfattr_output; use uutests::new_ucmd; use uutests::util::TestScenario; use uutests::util::run_ucmd_as_root; @@ -155,7 +157,6 @@ fn test_mknod_mode_permissions() { #[test] #[cfg(feature = "feat_selinux")] fn test_mknod_selinux() { - use std::process::Command; let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; let dest = "test_file"; @@ -175,25 +176,10 @@ fn test_mknod_selinux() { assert!(ts.fixtures.is_fifo("test_file")); assert!(ts.fixtures.metadata("test_file").permissions().readonly()); - let getfattr_output = Command::new("getfattr") - .arg(at.plus_as_string(dest)) - .arg("-n") - .arg("security.selinux") - .output() - .expect("Failed to run `getfattr` on the destination file"); - println!("{getfattr_output:?}"); + let context_value = get_getfattr_output(&at.plus_as_string(dest)); assert!( - getfattr_output.status.success(), - "getfattr did not run successfully: {}", - String::from_utf8_lossy(&getfattr_output.stderr) - ); - - let stdout = String::from_utf8_lossy(&getfattr_output.stdout); - assert!( - stdout.contains("unconfined_u"), - "Expected '{}' not found in getfattr output:\n{}", - "foo", - stdout + context_value.contains("unconfined_u"), + "Expected 'unconfined_u' not found in getfattr output:\n{context_value}" ); at.remove(&at.plus_as_string(dest)); } diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 00cba71b4..8da94e864 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -10,11 +10,8 @@ use rstest::rstest; use std::io::Write; #[cfg(not(windows))] use std::path::Path; -<<<<<<< HEAD -======= -#[cfg(feature = "selinux")] +#[cfg(feature = "feat_selinux")] use uucore::selinux::get_getfattr_output; ->>>>>>> 947680a6d (fix build) use uutests::new_ucmd; use uutests::util::TestScenario; use uutests::{at_and_ucmd, util_name}; @@ -2497,8 +2494,6 @@ fn test_mv_cross_device_permission_denied() { #[test] #[cfg(feature = "selinux")] fn test_mv_selinux_context() { - use std::process::Command; - let test_cases = [ ("-Z", None), ( @@ -2513,16 +2508,14 @@ fn test_mv_selinux_context() { let src = "source.txt"; let dest = "dest.txt"; - at.touch(&src); - - for (arg, context_value, expected_context) in test_cases { + at.touch(src); let mut cmd = scene.ucmd(); cmd.arg(arg); let result = cmd - .arg(at.plus_as_string(&src)) - .arg(at.plus_as_string(&dest)) + .arg(at.plus_as_string(src)) + .arg(at.plus_as_string(dest)) .run(); // Skip test if SELinux is not enabled @@ -2535,41 +2528,22 @@ fn test_mv_selinux_context() { } result.success(); - assert!(at.file_exists(&dest)); - assert!(!at.file_exists(&src)); + assert!(at.file_exists(dest)); + assert!(!at.file_exists(src)); // Verify SELinux context was set using getfattr - let getfattr_output = Command::new("getfattr") - .arg(at.plus_as_string(&dest)) - .arg("-n") - .arg("security.selinux") - .output(); - - if let Ok(output) = getfattr_output { - let selinux_context = String::from_utf8_lossy(&output.stdout); - if !selinux_context.is_empty() { - match expected_context { - Some(expected) => { - let context_value = - selinux_context.split('"').nth(1).unwrap_or("").to_string(); - assert!( - context_value.contains(expected), - "Expected context to contain '{}', got: {}", - expected, - context_value - ); - } - None => { - if selinux_context.contains("security.selinux") { - println!("SELinux context successfully set with {} flag", arg); - } - } - } + let context_value = get_getfattr_output(&at.plus_as_string(dest)); + if !context_value.is_empty() { + if let Some(expected) = expected_context { + assert!( + context_value.contains(expected), + "Expected context to contain '{expected}', got: {context_value}" + ); } } // Clean up files - let _ = std::fs::remove_file(at.plus_as_string(&dest)); - let _ = std::fs::remove_file(at.plus_as_string(&src)); + let _ = std::fs::remove_file(at.plus_as_string(dest)); + let _ = std::fs::remove_file(at.plus_as_string(src)); } }