mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
selinux/uucore: add two functions: contexts_differ & preserve_security_context
This commit is contained in:
parent
eff2cd997e
commit
71af6d2089
1 changed files with 230 additions and 18 deletions
|
@ -228,6 +228,106 @@ pub fn get_selinux_security_context(path: &Path) -> Result<String, SeLinuxError>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compares SELinux security contexts of two filesystem paths.
|
||||||
|
///
|
||||||
|
/// This function retrieves and compares the SELinux security contexts of two paths.
|
||||||
|
/// If the contexts differ or an error occurs during retrieval, it returns true.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `from_path` - Source filesystem path.
|
||||||
|
/// * `to_path` - Destination filesystem path.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `true` - If contexts differ, cannot be retrieved, or if SELinux is not enabled.
|
||||||
|
/// * `false` - If contexts are the same.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use std::path::Path;
|
||||||
|
/// use uucore::selinux::contexts_differ;
|
||||||
|
///
|
||||||
|
/// // Check if contexts differ between two files
|
||||||
|
/// let differ = contexts_differ(Path::new("/path/to/source"), Path::new("/path/to/destination"));
|
||||||
|
/// if differ {
|
||||||
|
/// println!("Files have different SELinux contexts");
|
||||||
|
/// } else {
|
||||||
|
/// println!("Files have the same SELinux context");
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn contexts_differ(from_path: &Path, to_path: &Path) -> bool {
|
||||||
|
if !is_selinux_enabled() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if SELinux contexts differ
|
||||||
|
match (
|
||||||
|
selinux::SecurityContext::of_path(from_path, false, false),
|
||||||
|
selinux::SecurityContext::of_path(to_path, false, false),
|
||||||
|
) {
|
||||||
|
(Ok(Some(from_ctx)), Ok(Some(to_ctx))) => {
|
||||||
|
// Convert contexts to CString and compare
|
||||||
|
match (from_ctx.to_c_string(), to_ctx.to_c_string()) {
|
||||||
|
(Ok(Some(from_c_str)), Ok(Some(to_c_str))) => {
|
||||||
|
from_c_str.to_string_lossy() != to_c_str.to_string_lossy()
|
||||||
|
}
|
||||||
|
// If contexts couldn't be converted to CString or were None, consider them different
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If either context is None or an error occurred, assume contexts differ
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Preserves the SELinux security context from one filesystem path to another.
|
||||||
|
///
|
||||||
|
/// This function copies the security context from the source path to the destination path.
|
||||||
|
/// If SELinux is not enabled, or if the source has no context, the function returns success
|
||||||
|
/// without making any changes.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `from_path` - Source filesystem path from which to copy the SELinux context.
|
||||||
|
/// * `to_path` - Destination filesystem path to which the context should be applied.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Ok(())` - If the context was successfully preserved or if SELinux is not enabled.
|
||||||
|
/// * `Err(SeLinuxError)` - If an error occurred during context retrieval or application.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use std::path::Path;
|
||||||
|
/// use uucore::selinux::preserve_security_context;
|
||||||
|
///
|
||||||
|
/// // Preserve the SELinux context from source to destination
|
||||||
|
/// match preserve_security_context(Path::new("/path/to/source"), Path::new("/path/to/destination")) {
|
||||||
|
/// Ok(_) => println!("Context preserved successfully (or SELinux is not enabled)"),
|
||||||
|
/// Err(err) => eprintln!("Failed to preserve context: {}", err),
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn preserve_security_context(from_path: &Path, to_path: &Path) -> Result<(), SeLinuxError> {
|
||||||
|
// If SELinux is not enabled, return success without doing anything
|
||||||
|
if !is_selinux_enabled() {
|
||||||
|
return Err(SeLinuxError::SELinuxNotEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get context from the source path
|
||||||
|
let context = get_selinux_security_context(from_path)?;
|
||||||
|
|
||||||
|
// If no context was found, just return success (nothing to preserve)
|
||||||
|
if context.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the context to the destination path
|
||||||
|
set_selinux_security_context(to_path, Some(&context))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -295,18 +395,6 @@ mod tests {
|
||||||
let result = set_selinux_security_context(path, Some(&invalid_context));
|
let result = set_selinux_security_context(path, Some(&invalid_context));
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
if let Err(err) = result {
|
|
||||||
match err {
|
|
||||||
SeLinuxError::ContextConversionFailure(ctx, msg) => {
|
|
||||||
assert_eq!(ctx, "invalid\0context");
|
|
||||||
assert!(
|
|
||||||
msg.contains("nul byte"),
|
|
||||||
"Error message should mention nul byte"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_ => panic!("Expected ContextConversionFailure error but got: {}", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -402,15 +490,139 @@ mod tests {
|
||||||
let result = get_selinux_security_context(path);
|
let result = get_selinux_security_context(path);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_contexts_differ() {
|
||||||
|
let file1 = NamedTempFile::new().expect("Failed to create first tempfile");
|
||||||
|
let file2 = NamedTempFile::new().expect("Failed to create second tempfile");
|
||||||
|
let path1 = file1.path();
|
||||||
|
let path2 = file2.path();
|
||||||
|
|
||||||
|
std::fs::write(path1, b"content for file 1").expect("Failed to write to first tempfile");
|
||||||
|
std::fs::write(path2, b"content for file 2").expect("Failed to write to second tempfile");
|
||||||
|
|
||||||
|
if !is_selinux_enabled() {
|
||||||
|
assert!(
|
||||||
|
contexts_differ(path1, path2),
|
||||||
|
"contexts_differ should return true when SELinux is not enabled"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let test_context = String::from("system_u:object_r:tmp_t:s0");
|
||||||
|
let result1 = set_selinux_security_context(path1, Some(&test_context));
|
||||||
|
let result2 = set_selinux_security_context(path2, Some(&test_context));
|
||||||
|
|
||||||
|
if result1.is_ok() && result2.is_ok() {
|
||||||
|
assert!(
|
||||||
|
!contexts_differ(path1, path2),
|
||||||
|
"Contexts should not differ when the same context is set on both files"
|
||||||
|
);
|
||||||
|
|
||||||
|
let different_context = String::from("system_u:object_r:user_tmp_t:s0");
|
||||||
|
if set_selinux_security_context(path2, Some(&different_context)).is_ok() {
|
||||||
|
assert!(
|
||||||
|
contexts_differ(path1, path2),
|
||||||
|
"Contexts should differ when different contexts are set"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"Note: Couldn't set SELinux contexts to test differences. This is expected if the test doesn't have sufficient permissions."
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
contexts_differ(path1, path2),
|
||||||
|
"Contexts should differ when different contexts are set"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let nonexistent_path = Path::new("/nonexistent/file/path");
|
||||||
|
assert!(
|
||||||
|
contexts_differ(path1, nonexistent_path),
|
||||||
|
"contexts_differ should return true when one path doesn't exist"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_preserve_security_context() {
|
||||||
|
let source_file = NamedTempFile::new().expect("Failed to create source tempfile");
|
||||||
|
let dest_file = NamedTempFile::new().expect("Failed to create destination tempfile");
|
||||||
|
let source_path = source_file.path();
|
||||||
|
let dest_path = dest_file.path();
|
||||||
|
|
||||||
|
std::fs::write(source_path, b"source content").expect("Failed to write to source tempfile");
|
||||||
|
std::fs::write(dest_path, b"destination content")
|
||||||
|
.expect("Failed to write to destination tempfile");
|
||||||
|
|
||||||
|
if !is_selinux_enabled() {
|
||||||
|
let result = preserve_security_context(source_path, dest_path);
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"preserve_security_context should fail when SELinux is not enabled"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let source_context = String::from("system_u:object_r:tmp_t:s0");
|
||||||
|
let result = set_selinux_security_context(source_path, Some(&source_context));
|
||||||
|
|
||||||
|
if result.is_ok() {
|
||||||
|
let preserve_result = preserve_security_context(source_path, dest_path);
|
||||||
|
assert!(
|
||||||
|
preserve_result.is_ok(),
|
||||||
|
"Failed to preserve context: {:?}",
|
||||||
|
preserve_result.err()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!contexts_differ(source_path, dest_path),
|
||||||
|
"Contexts should be the same after preserving"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"Note: Couldn't set SELinux context on source file to test preservation. This is expected if the test doesn't have sufficient permissions."
|
||||||
|
);
|
||||||
|
|
||||||
|
let preserve_result = preserve_security_context(source_path, dest_path);
|
||||||
|
assert!(preserve_result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
let nonexistent_path = Path::new("/nonexistent/file/path");
|
||||||
|
let result = preserve_security_context(nonexistent_path, dest_path);
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"preserve_security_context should fail when source file doesn't exist"
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = preserve_security_context(source_path, nonexistent_path);
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"preserve_security_context should fail when destination file doesn't exist"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_preserve_security_context_empty_context() {
|
||||||
|
let source_file = NamedTempFile::new().expect("Failed to create source tempfile");
|
||||||
|
let dest_file = NamedTempFile::new().expect("Failed to create destination tempfile");
|
||||||
|
let source_path = source_file.path();
|
||||||
|
let dest_path = dest_file.path();
|
||||||
|
|
||||||
|
if !is_selinux_enabled() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = preserve_security_context(source_path, dest_path);
|
||||||
|
|
||||||
if let Err(err) = result {
|
if let Err(err) = result {
|
||||||
match err {
|
match err {
|
||||||
SeLinuxError::FileOpenFailure(e) => {
|
SeLinuxError::ContextSetFailure(_, _) => {
|
||||||
assert!(
|
println!("Note: Could not set context due to permissions: {}", err);
|
||||||
e.contains("No such file"),
|
}
|
||||||
"Error should mention file not found"
|
unexpected => {
|
||||||
);
|
panic!("Unexpected error: {}", unexpected);
|
||||||
}
|
}
|
||||||
_ => panic!("Expected FileOpenFailure error but got: {}", err),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue