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:
commit
8c4d69b2f6
6 changed files with 169 additions and 75 deletions
|
@ -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),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
let result = set_selinux_security_context(path, None);
|
if !is_selinux_enabled() {
|
||||||
|
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,10 +256,18 @@ 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]
|
||||||
|
@ -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());
|
||||||
assert!(
|
if let Err(err) = result {
|
||||||
matches!(result.unwrap_err(), Error::FileOpenFailure),
|
match err {
|
||||||
"Expected file open error for nonexistent file"
|
SeLinuxError::FileOpenFailure(e) => {
|
||||||
);
|
assert!(
|
||||||
|
e.contains("No such file"),
|
||||||
|
"Error should mention file not found"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => panic!("Expected FileOpenFailure error but got: {}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue