mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
selinux: introduce get_getfattr_output to verify the output of getfatt
This commit is contained in:
@@ -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::*;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
+15
-41
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user