From 8220f061efd0885117792b69ac689eaef95b7e72 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 13 Apr 2025 20:47:25 +0200 Subject: [PATCH] selinux: add function get_selinux_security_context to uucore Co-authored-by: Daniel Hofstetter --- src/uucore/src/lib/features/selinux.rs | 120 +++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/src/uucore/src/lib/features/selinux.rs b/src/uucore/src/lib/features/selinux.rs index d708fe715..2f2d2b6a1 100644 --- a/src/uucore/src/lib/features/selinux.rs +++ b/src/uucore/src/lib/features/selinux.rs @@ -10,6 +10,20 @@ use selinux::SecurityContext; #[derive(Debug)] pub enum Error { SELinuxNotEnabled, + FileOpenFailure, + ContextRetrievalFailure, + ContextConversionFailure, +} + +impl From for i32 { + fn from(error: Error) -> i32 { + match error { + Error::SELinuxNotEnabled => 1, + Error::FileOpenFailure => 2, + Error::ContextRetrievalFailure => 3, + Error::ContextConversionFailure => 4, + } + } } /// Checks if SELinux is enabled on the system. @@ -109,6 +123,73 @@ pub fn set_selinux_security_context(path: &Path, context: Option<&String>) -> Re } } +/// Gets the SELinux security context for the given filesystem path. +/// +/// Retrieves the security context of the specified filesystem path if SELinux is enabled +/// on the system. +/// +/// # Arguments +/// +/// * `path` - Filesystem path for which to retrieve the SELinux context. +/// +/// # Returns +/// +/// * `Ok(String)` - The SELinux context string if successfully retrieved. Returns an empty +/// string if no context was found. +/// * `Err(Error)` - An error variant indicating the type of failure: +/// - `Error::SELinuxNotEnabled` - SELinux is not enabled on the system. +/// - `Error::FileOpenFailure` - Failed to open the specified file. +/// - `Error::ContextRetrievalFailure` - Failed to retrieve the security context. +/// - `Error::ContextConversionFailure` - Failed to convert the security context to a string. +/// +/// # Examples +/// +/// ``` +/// use std::path::Path; +/// use uucore::selinux::{get_selinux_security_context, Error}; +/// +/// // Get the SELinux context for a file +/// match get_selinux_security_context(Path::new("/path/to/file")) { +/// Ok(context) => { +/// if context.is_empty() { +/// println!("No SELinux context found for the file"); +/// } else { +/// println!("SELinux context: {}", context); +/// } +/// }, +/// Err(Error::SELinuxNotEnabled) => println!("SELinux is not enabled on this system"), +/// Err(Error::FileOpenFailure) => println!("Failed to open the file"), +/// Err(Error::ContextRetrievalFailure) => println!("Failed to retrieve the security context"), +/// Err(Error::ContextConversionFailure) => println!("Failed to convert the security context to a string"), +/// } +/// ``` + +pub fn get_selinux_security_context(path: &Path) -> Result { + if selinux::kernel_support() == selinux::KernelSupport::Unsupported { + return Err(Error::SELinuxNotEnabled); + } + + let f = std::fs::File::open(path).map_err(|_| Error::FileOpenFailure)?; + + // Get the security context of the file + let context = match SecurityContext::of_file(&f, false) { + Ok(Some(ctx)) => ctx, + Ok(None) => return Ok(String::new()), // No context found, return empty string + Err(_) => return Err(Error::ContextRetrievalFailure), + }; + + let context_c_string = context + .to_c_string() + .map_err(|_| Error::ContextConversionFailure)?; + + if let Some(c_str) = context_c_string { + // Convert the C string to a Rust String + Ok(c_str.to_string_lossy().to_string()) + } else { + Ok(String::new()) + } +} + #[cfg(test)] mod tests { use super::*; @@ -171,4 +252,43 @@ mod tests { } } } + + #[test] + fn test_get_selinux_security_context() { + let tmpfile = NamedTempFile::new().expect("Failed to create tempfile"); + let path = tmpfile.path(); + + std::fs::write(path, b"test content").expect("Failed to write to tempfile"); + + let result = get_selinux_security_context(path); + + if result.is_ok() { + println!("Retrieved SELinux context: {}", result.unwrap()); + } else { + let err = result.unwrap_err(); + + // Valid error types + match err { + Error::SELinuxNotEnabled => assert!(true, "SELinux not supported"), + Error::ContextRetrievalFailure => assert!(true, "Context retrieval failure"), + Error::ContextConversionFailure => assert!(true, "Context conversion failure"), + Error::FileOpenFailure => { + panic!("File open failure occurred despite file being created") + } + } + } + } + + #[test] + fn test_get_selinux_context_nonexistent_file() { + let path = Path::new("/nonexistent/file/that/does/not/exist"); + + let result = get_selinux_security_context(path); + + assert!(result.is_err()); + assert!( + matches!(result.unwrap_err(), Error::FileOpenFailure), + "Expected file open error for nonexistent file" + ); + } }