1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 03:27:44 +00:00

Merge pull request #7845 from sylvestre/selinux-error

set_selinux_security_context should return an Error, not String
This commit is contained in:
Daniel Hofstetter 2025-05-04 12:45:53 +02:00 committed by GitHub
commit 8c4d69b2f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 169 additions and 75 deletions

View file

@ -298,10 +298,7 @@ fn create_dir(path: &Path, is_parent: bool, config: &Config) -> UResult<()> {
if let Err(e) = uucore::selinux::set_selinux_security_context(path, config.context) if let Err(e) = uucore::selinux::set_selinux_security_context(path, config.context)
{ {
let _ = std::fs::remove_dir(path); let _ = std::fs::remove_dir(path);
return Err(USimpleError::new( return Err(USimpleError::new(1, e.to_string()));
1,
format!("failed to set SELinux security context: {}", e),
));
} }
} }

View file

@ -114,7 +114,7 @@ proc-info = ["tty", "walkdir"]
quoting-style = [] quoting-style = []
ranges = [] ranges = []
ringbuffer = [] ringbuffer = []
selinux = ["dep:selinux"] selinux = ["dep:selinux", "thiserror"]
signals = [] signals = []
sum = [ sum = [
"digest", "digest",

View file

@ -6,22 +6,34 @@
use std::path::Path; use std::path::Path;
use selinux::SecurityContext; use selinux::SecurityContext;
use thiserror::Error;
#[derive(Debug)] #[derive(Debug, Error)]
pub enum Error { pub enum SeLinuxError {
#[error("SELinux is not enabled on this system")]
SELinuxNotEnabled, SELinuxNotEnabled,
FileOpenFailure,
ContextRetrievalFailure, #[error("Failed to open the file: {0}")]
ContextConversionFailure, FileOpenFailure(String),
#[error("Failed to retrieve the security context: {0}")]
ContextRetrievalFailure(String),
#[error("Failed to set default file creation context to '{0}': {1}")]
ContextSetFailure(String, String),
#[error("Failed to set default file creation context to '{0}': {1}")]
ContextConversionFailure(String, String),
} }
impl From<Error> for i32 { impl From<SeLinuxError> for i32 {
fn from(error: Error) -> i32 { fn from(error: SeLinuxError) -> i32 {
match error { match error {
Error::SELinuxNotEnabled => 1, SeLinuxError::SELinuxNotEnabled => 1,
Error::FileOpenFailure => 2, SeLinuxError::FileOpenFailure(_) => 2,
Error::ContextRetrievalFailure => 3, SeLinuxError::ContextRetrievalFailure(_) => 3,
Error::ContextConversionFailure => 4, SeLinuxError::ContextSetFailure(_, _) => 4,
SeLinuxError::ContextConversionFailure(_, _) => 5,
} }
} }
} }
@ -76,31 +88,39 @@ pub fn is_selinux_enabled() -> bool {
/// eprintln!("Failed to set context: {}", err); /// eprintln!("Failed to set context: {}", err);
/// } /// }
/// ``` /// ```
pub fn set_selinux_security_context(path: &Path, context: Option<&String>) -> Result<(), String> { pub fn set_selinux_security_context(
path: &Path,
context: Option<&String>,
) -> Result<(), SeLinuxError> {
if !is_selinux_enabled() { if !is_selinux_enabled() {
return Err("SELinux is not enabled on this system".to_string()); return Err(SeLinuxError::SELinuxNotEnabled);
} }
if let Some(ctx_str) = context { if let Some(ctx_str) = context {
// Create a CString from the provided context string // Create a CString from the provided context string
let c_context = std::ffi::CString::new(ctx_str.as_str()) let c_context = std::ffi::CString::new(ctx_str.as_str()).map_err(|e| {
.map_err(|_| "Invalid context string (contains null bytes)".to_string())?; SeLinuxError::ContextConversionFailure(ctx_str.to_string(), e.to_string())
})?;
// Convert the CString into an SELinux security context // Convert the CString into an SELinux security context
let security_context = selinux::OpaqueSecurityContext::from_c_str(&c_context) let security_context =
.map_err(|e| format!("Failed to create security context: {}", e))?; selinux::OpaqueSecurityContext::from_c_str(&c_context).map_err(|e| {
SeLinuxError::ContextConversionFailure(ctx_str.to_string(), e.to_string())
})?;
// Set the provided security context on the specified path // Set the provided security context on the specified path
SecurityContext::from_c_str( SecurityContext::from_c_str(
&security_context.to_c_string().map_err(|e| e.to_string())?, &security_context.to_c_string().map_err(|e| {
SeLinuxError::ContextConversionFailure(ctx_str.to_string(), e.to_string())
})?,
false, false,
) )
.set_for_path(path, false, false) .set_for_path(path, false, false)
.map_err(|e| format!("Failed to set context: {}", e)) .map_err(|e| SeLinuxError::ContextSetFailure(ctx_str.to_string(), e.to_string()))
} else { } else {
// If no context provided, set the default SELinux context for the path // If no context provided, set the default SELinux context for the path
SecurityContext::set_default_for_path(path) SecurityContext::set_default_for_path(path)
.map_err(|e| format!("Failed to set default context: {}", e)) .map_err(|e| SeLinuxError::ContextSetFailure(String::new(), e.to_string()))
} }
} }
@ -117,17 +137,18 @@ pub fn set_selinux_security_context(path: &Path, context: Option<&String>) -> Re
/// ///
/// * `Ok(String)` - The SELinux context string if successfully retrieved. Returns an empty /// * `Ok(String)` - The SELinux context string if successfully retrieved. Returns an empty
/// string if no context was found. /// string if no context was found.
/// * `Err(Error)` - An error variant indicating the type of failure: /// * `Err(SeLinuxError)` - An error variant indicating the type of failure:
/// - `Error::SELinuxNotEnabled` - SELinux is not enabled on the system. /// - `SeLinuxError::SELinuxNotEnabled` - SELinux is not enabled on the system.
/// - `Error::FileOpenFailure` - Failed to open the specified file. /// - `SeLinuxError::FileOpenFailure` - Failed to open the specified file.
/// - `Error::ContextRetrievalFailure` - Failed to retrieve the security context. /// - `SeLinuxError::ContextRetrievalFailure` - Failed to retrieve the security context.
/// - `Error::ContextConversionFailure` - Failed to convert the security context to a string. /// - `SeLinuxError::ContextConversionFailure` - Failed to convert the security context to a string.
/// - `SeLinuxError::ContextSetFailure` - Failed to set the security context.
/// ///
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use std::path::Path; /// use std::path::Path;
/// use uucore::selinux::{get_selinux_security_context, Error}; /// use uucore::selinux::{get_selinux_security_context, SeLinuxError};
/// ///
/// // Get the SELinux context for a file /// // 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")) {
@ -138,29 +159,30 @@ pub fn set_selinux_security_context(path: &Path, context: Option<&String>) -> Re
/// println!("SELinux context: {}", context); /// println!("SELinux context: {}", context);
/// } /// }
/// }, /// },
/// Err(Error::SELinuxNotEnabled) => println!("SELinux is not enabled on this system"), /// Err(SeLinuxError::SELinuxNotEnabled) => println!("SELinux is not enabled on this system"),
/// Err(Error::FileOpenFailure) => println!("Failed to open the file"), /// Err(SeLinuxError::FileOpenFailure(e)) => println!("Failed to open the file: {}", e),
/// Err(Error::ContextRetrievalFailure) => println!("Failed to retrieve the security context"), /// Err(SeLinuxError::ContextRetrievalFailure(e)) => println!("Failed to retrieve the security context: {}", e),
/// Err(Error::ContextConversionFailure) => println!("Failed to convert the security context to a string"), /// Err(SeLinuxError::ContextConversionFailure(ctx, e)) => println!("Failed to convert context '{}': {}", ctx, e),
/// Err(SeLinuxError::ContextSetFailure(ctx, e)) => println!("Failed to set context '{}': {}", ctx, e),
/// } /// }
/// ``` /// ```
pub fn get_selinux_security_context(path: &Path) -> Result<String, Error> { pub fn get_selinux_security_context(path: &Path) -> Result<String, SeLinuxError> {
if !is_selinux_enabled() { if !is_selinux_enabled() {
return Err(Error::SELinuxNotEnabled); return Err(SeLinuxError::SELinuxNotEnabled);
} }
let f = std::fs::File::open(path).map_err(|_| Error::FileOpenFailure)?; let f = std::fs::File::open(path).map_err(|e| SeLinuxError::FileOpenFailure(e.to_string()))?;
// Get the security context of the file // Get the security context of the file
let context = match SecurityContext::of_file(&f, false) { let context = match SecurityContext::of_file(&f, false) {
Ok(Some(ctx)) => ctx, Ok(Some(ctx)) => ctx,
Ok(None) => return Ok(String::new()), // No context found, return empty string Ok(None) => return Ok(String::new()), // No context found, return empty string
Err(_) => return Err(Error::ContextRetrievalFailure), Err(e) => return Err(SeLinuxError::ContextRetrievalFailure(e.to_string())),
}; };
let context_c_string = context let context_c_string = context
.to_c_string() .to_c_string()
.map_err(|_| Error::ContextConversionFailure)?; .map_err(|e| SeLinuxError::ContextConversionFailure(String::new(), e.to_string()))?;
if let Some(c_str) = context_c_string { if let Some(c_str) = context_c_string {
// Convert the C string to a Rust String // Convert the C string to a Rust String
@ -180,29 +202,50 @@ mod tests {
let tmpfile = NamedTempFile::new().expect("Failed to create tempfile"); let tmpfile = NamedTempFile::new().expect("Failed to create tempfile");
let path = tmpfile.path(); let path = tmpfile.path();
if !is_selinux_enabled() {
let result = set_selinux_security_context(path, None); let result = set_selinux_security_context(path, None);
assert!(result.is_err(), "Expected error when SELinux is disabled");
match result.unwrap_err() {
SeLinuxError::SELinuxNotEnabled => {
// This is the expected error when SELinux is not enabled
}
err => panic!("Expected SELinuxNotEnabled error but got: {}", err),
}
return;
}
if result.is_ok() { let default_result = set_selinux_security_context(path, None);
// SELinux enabled and successfully set default context assert!(
assert!(true, "Successfully set SELinux context"); default_result.is_ok(),
} else { "Failed to set default context: {:?}",
let err = result.unwrap_err(); default_result.err()
let valid_errors = [ );
"SELinux is not enabled on this system",
&format!( let context = get_selinux_security_context(path).expect("Failed to get context");
"Failed to set default context: selinux_lsetfilecon_default() failed on path '{}'", assert!(
path.display() !context.is_empty(),
), "Expected non-empty context after setting default context"
]; );
let test_context = String::from("system_u:object_r:tmp_t:s0");
let explicit_result = set_selinux_security_context(path, Some(&test_context));
if explicit_result.is_ok() {
let new_context = get_selinux_security_context(path)
.expect("Failed to get context after setting explicit context");
assert!( assert!(
valid_errors.contains(&err.as_str()), new_context.contains("tmp_t"),
"Unexpected error message: {}", "Expected context to contain 'tmp_t', but got: {}",
err new_context
);
} else {
println!(
"Note: Could not set explicit context {:?}",
explicit_result.err()
); );
} }
} }
#[test] #[test]
fn test_invalid_context_string_error() { fn test_invalid_context_string_error() {
let tmpfile = NamedTempFile::new().expect("Failed to create tempfile"); let tmpfile = NamedTempFile::new().expect("Failed to create tempfile");
@ -213,11 +256,19 @@ 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());
assert_eq!( if let Err(err) = result {
result.unwrap_err(), match err {
"Invalid context string (contains null bytes)" 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]
fn test_is_selinux_enabled_runtime_behavior() { fn test_is_selinux_enabled_runtime_behavior() {
@ -243,17 +294,56 @@ mod tests {
let result = get_selinux_security_context(path); let result = get_selinux_security_context(path);
if result.is_ok() { if result.is_ok() {
println!("Retrieved SELinux context: {}", result.unwrap()); let context = result.unwrap();
println!("Retrieved SELinux context: {}", context);
assert!(
is_selinux_enabled(),
"Got a successful context result but SELinux is not enabled"
);
if !context.is_empty() {
assert!(
context.contains(':'),
"SELinux context '{}' doesn't match expected format",
context
);
}
} else { } else {
let err = result.unwrap_err(); let err = result.unwrap_err();
// Valid error types
match err { match err {
Error::SELinuxNotEnabled => assert!(true, "SELinux not supported"), SeLinuxError::SELinuxNotEnabled => {
Error::ContextRetrievalFailure => assert!(true, "Context retrieval failure"), assert!(
Error::ContextConversionFailure => assert!(true, "Context conversion failure"), !is_selinux_enabled(),
Error::FileOpenFailure => { "Got SELinuxNotEnabled error, but is_selinux_enabled() returned true"
panic!("File open failure occurred despite file being created") );
}
SeLinuxError::ContextRetrievalFailure(e) => {
assert!(
is_selinux_enabled(),
"Got ContextRetrievalFailure when SELinux is not enabled"
);
assert!(!e.is_empty(), "Error message should not be empty");
println!("Context retrieval failure: {}", e);
}
SeLinuxError::ContextConversionFailure(ctx, e) => {
assert!(
is_selinux_enabled(),
"Got ContextConversionFailure when SELinux is not enabled"
);
assert!(!e.is_empty(), "Error message should not be empty");
println!("Context conversion failure for '{}': {}", ctx, e);
}
SeLinuxError::ContextSetFailure(ctx, e) => {
assert!(false);
}
SeLinuxError::FileOpenFailure(e) => {
assert!(
Path::new(path).exists(),
"File open failure occurred despite file being created: {}",
e
);
} }
} }
} }
@ -266,9 +356,16 @@ mod tests {
let result = get_selinux_security_context(path); let result = get_selinux_security_context(path);
assert!(result.is_err()); assert!(result.is_err());
if let Err(err) = result {
match err {
SeLinuxError::FileOpenFailure(e) => {
assert!( assert!(
matches!(result.unwrap_err(), Error::FileOpenFailure), e.contains("No such file"),
"Expected file open error for nonexistent file" "Error should mention file not found"
); );
} }
_ => panic!("Expected FileOpenFailure error but got: {}", err),
}
}
}
} }

View file

@ -411,7 +411,7 @@ fn test_selinux_invalid() {
.arg(at.plus_as_string(dest)) .arg(at.plus_as_string(dest))
.fails() .fails()
.no_stdout() .no_stdout()
.stderr_contains("failed to set SELinux security context:"); .stderr_contains("Failed to set default file creation context to 'testtest':");
// invalid context, so, no directory // invalid context, so, no directory
assert!(!at.dir_exists(dest)); assert!(!at.dir_exists(dest));
} }

View file

@ -160,7 +160,7 @@ fn test_mkfifo_selinux_invalid() {
.arg(arg) .arg(arg)
.arg(dest) .arg(dest)
.fails() .fails()
.stderr_contains("Failed to"); .stderr_contains("failed to");
if at.file_exists(dest) { if at.file_exists(dest) {
at.remove(dest); at.remove(dest);
} }

View file

@ -187,7 +187,7 @@ fn test_mknod_selinux_invalid() {
.arg(dest) .arg(dest)
.arg("p") .arg("p")
.fails() .fails()
.stderr_contains("Failed to"); .stderr_contains("failed to");
if at.file_exists(dest) { if at.file_exists(dest) {
at.remove(dest); at.remove(dest);
} }