diff --git a/Cargo.lock b/Cargo.lock index 1004312ab..0000001cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3585,6 +3585,7 @@ dependencies = [ "number_prefix", "os_display", "regex", + "selinux", "sha1", "sha2", "sha3", diff --git a/Cargo.toml b/Cargo.toml index 5ae51301f..224e127a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ feat_selinux = [ "cp/selinux", "id/selinux", "ls/selinux", + "mkdir/selinux", "selinux", "feat_require_selinux", ] diff --git a/src/uu/mkdir/Cargo.toml b/src/uu/mkdir/Cargo.toml index 7dc27cd16..77b664738 100644 --- a/src/uu/mkdir/Cargo.toml +++ b/src/uu/mkdir/Cargo.toml @@ -19,9 +19,10 @@ path = "src/mkdir.rs" [dependencies] clap = { workspace = true } -selinux = { workspace = true } uucore = { workspace = true, features = ["fs", "mode", "fsxattr"] } +[features] +selinux = ["uucore/selinux"] [[bin]] name = "mkdir" diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index 0bc703d85..5147ec5e0 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -8,7 +8,6 @@ use clap::builder::ValueParser; use clap::parser::ValuesRef; use clap::{Arg, ArgAction, ArgMatches, Command}; -use selinux::SecurityContext; use std::ffi::OsString; use std::path::{Path, PathBuf}; #[cfg(not(windows))] @@ -75,58 +74,6 @@ fn strip_minus_from_mode(args: &mut Vec) -> bool { mode::strip_minus_from_mode(args) } -// Add a new function to handle setting the SELinux security context -#[cfg(target_os = "linux")] -fn set_selinux_security_context(path: &Path, context: Option<&String>) -> Result<(), String> { - // Get SELinux kernel support - let support = selinux::kernel_support(); - - // If SELinux is not enabled, return early - if support == selinux::KernelSupport::Unsupported { - return Err("SELinux is not enabled on this system".to_string()); - } - - // If a specific context was provided, use it - if let Some(ctx_str) = context { - // Use the provided context - match SecurityContext::of_path(path, false, false) { - Ok(_) => { - // Create a CString from the context string - let c_context = std::ffi::CString::new(ctx_str.as_str()) - .map_err(|_| "Invalid context string (contains null bytes)".to_string())?; - - // Create a security context from the string - let security_context = match selinux::OpaqueSecurityContext::from_c_str(&c_context) - { - Ok(ctx) => ctx, - Err(e) => return Err(format!("Failed to create security context: {}", e)), - }; - - // Convert back to string for the API - let context_str = match security_context.to_c_string() { - Ok(ctx) => ctx, - Err(e) => return Err(format!("Failed to convert context to string: {}", e)), - }; - - // Set the context on the file - let sc = SecurityContext::from_c_str(&context_str, false); - - match sc.set_for_path(path, false, false) { - Ok(_) => Ok(()), - Err(e) => Err(format!("Failed to set context: {}", e)), - } - } - Err(e) => Err(format!("Failed to get current context: {}", e)), - } - } else { - // If no context was specified, use the default context for the path - match SecurityContext::set_default_for_path(path) { - Ok(_) => Ok(()), - Err(e) => Err(format!("Failed to set default context: {}", e)), - } - } -} - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let mut args = args.collect_lossy(); @@ -373,7 +320,9 @@ fn create_dir( // Apply SELinux context if requested #[cfg(target_os = "linux")] if set_selinux_context { - if let Err(e) = set_selinux_security_context(path, context) { + if let Err(e) = uucore::selinux_support::set_selinux_security_context(path, context) + { + let _ = std::fs::remove_dir(path); return Err(USimpleError::new( 1, format!("failed to set SELinux security context: {}", e), diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 2da81bc1c..6f70843db 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -58,6 +58,7 @@ crc32fast = { workspace = true, optional = true } regex = { workspace = true, optional = true } bigdecimal = { workspace = true, optional = true } num-traits = { workspace = true, optional = true } +selinux = { workspace = true, optional = true } [target.'cfg(unix)'.dependencies] walkdir = { workspace = true, optional = true } @@ -105,13 +106,14 @@ format = [ mode = ["libc"] perms = ["entries", "libc", "walkdir"] buf-copy = [] +parser = ["extendedbigdecimal", "glob", "num-traits"] pipes = [] process = ["libc"] proc-info = ["tty", "walkdir"] quoting-style = [] ranges = [] ringbuffer = [] -parser = ["extendedbigdecimal", "glob", "num-traits"] +selinux = ["dep:selinux"] signals = [] sum = [ "digest", diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index d3a8ebb44..b19387c69 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -66,6 +66,8 @@ pub mod tty; #[cfg(all(unix, feature = "fsxattr"))] pub mod fsxattr; +#[cfg(all(target_os = "linux", feature = "selinux"))] +pub mod selinux; #[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))] pub mod signals; #[cfg(all( diff --git a/src/uucore/src/lib/features/selinux.rs b/src/uucore/src/lib/features/selinux.rs new file mode 100644 index 000000000..09c71078b --- /dev/null +++ b/src/uucore/src/lib/features/selinux.rs @@ -0,0 +1,130 @@ +// This file is part of the uutils uucore package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use std::path::Path; + +use selinux::SecurityContext; + +/// Sets the SELinux security context for the given filesystem path. +/// +/// If a specific context is provided, it attempts to set this context explicitly. +/// Otherwise, it applies the default SELinux context for the provided path. +/// +/// # Arguments +/// +/// * `path` - Filesystem path on which to set the SELinux context. +/// * `context` - Optional SELinux context string to explicitly set. +/// +/// # Errors +/// +/// Returns an error if: +/// - SELinux is not enabled on the system. +/// - The provided context is invalid or cannot be applied. +/// - The default SELinux context cannot be set. +/// +/// # Examples +/// +/// Setting default context: +/// ``` +/// use std::path::Path; +/// use uucore::selinux::set_selinux_security_context; +/// +/// // Set the default SELinux context for a file +/// let result = set_selinux_security_context(Path::new("/path/to/file"), None); +/// if let Err(err) = result { +/// eprintln!("Failed to set default context: {}", err); +/// } +/// ``` +/// +/// Setting specific context: +/// ``` +/// use std::path::Path; +/// use uucore::selinux::set_selinux_security_context; +/// +/// // Set a specific SELinux context for a file +/// let context = String::from("unconfined_u:object_r:user_home_t:s0"); +/// let result = set_selinux_security_context(Path::new("/path/to/file"), Some(&context)); +/// if let Err(err) = result { +/// eprintln!("Failed to set context: {}", err); +/// } +/// ``` +pub fn set_selinux_security_context(path: &Path, context: Option<&String>) -> Result<(), String> { + // Check if SELinux is enabled on the system + if selinux::kernel_support() == selinux::KernelSupport::Unsupported { + return Err("SELinux is not enabled on this system".into()); + } + + if let Some(ctx_str) = context { + // Create a CString from the provided context string + let c_context = std::ffi::CString::new(ctx_str.as_str()) + .map_err(|_| "Invalid context string (contains null bytes)".to_string())?; + + // Convert the CString into an SELinux security context + let security_context = selinux::OpaqueSecurityContext::from_c_str(&c_context) + .map_err(|e| format!("Failed to create security context: {}", e))?; + + // Set the provided security context on the specified path + SecurityContext::from_c_str( + &security_context.to_c_string().map_err(|e| e.to_string())?, + false, + ) + .set_for_path(path, false, false) + .map_err(|e| format!("Failed to set context: {}", e)) + } else { + // If no context provided, set the default SELinux context for the path + SecurityContext::set_default_for_path(path) + .map_err(|e| format!("Failed to set default context: {}", e)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::NamedTempFile; + + #[test] + fn test_selinux_context_setting() { + let tmpfile = NamedTempFile::new().expect("Failed to create tempfile"); + let path = tmpfile.path(); + + let result = set_selinux_security_context(path, None); + + if result.is_ok() { + // SELinux enabled and successfully set default context + assert!(true, "Successfully set SELinux context"); + } else { + let err = result.unwrap_err(); + let valid_errors = [ + "SELinux is not enabled on this system", + &format!( + "Failed to set default context: selinux_lsetfilecon_default() failed on path '{}'", + path.display() + ), + ]; + + assert!( + valid_errors.contains(&err.as_str()), + "Unexpected error message: {}", + err + ); + } + } + + #[test] + fn test_invalid_context_string_error() { + let tmpfile = NamedTempFile::new().expect("Failed to create tempfile"); + let path = tmpfile.path(); + + // Pass a context string containing a null byte to trigger CString::new error + let invalid_context = String::from("invalid\0context"); + let result = set_selinux_security_context(path, Some(&invalid_context)); + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err(), + "Invalid context string (contains null bytes)" + ); + } +} diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index c8257823a..bc4281979 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -103,6 +103,9 @@ pub use crate::features::fsext; #[cfg(all(unix, feature = "fsxattr"))] pub use crate::features::fsxattr; +#[cfg(all(target_os = "linux", feature = "selinux"))] +pub use crate::features::selinux; + //## core functions #[cfg(unix)]