diff --git a/experiment/selinux/src/main.rs b/experiment/selinux/src/main.rs index 9a437a3b2..3de9cb2b5 100644 --- a/experiment/selinux/src/main.rs +++ b/experiment/selinux/src/main.rs @@ -4,7 +4,7 @@ use std::fs::File; use std::path::Path; fn main() -> Result<(), SELinuxError> { - let mut selinux_instance: SELinux = SELinux::new(); + let mut selinux_instance: SELinux = SELinux::new(Path::new(DEFAULT_SELINUX_DIR))?; if selinux_instance.get_enabled() { println!("selinux is enabled"); diff --git a/experiment/selinux/src/selinux.rs b/experiment/selinux/src/selinux.rs index ac2c91da2..58a46c46d 100644 --- a/experiment/selinux/src/selinux.rs +++ b/experiment/selinux/src/selinux.rs @@ -54,11 +54,11 @@ impl fmt::Display for SELinuxMode { } pub(crate) const ERR_EMPTY_PATH: &str = "empty path"; +pub const DEFAULT_SELINUX_DIR: &str = "/etc/selinux/"; const SELINUX_FS_MOUNT: &str = "/sys/fs/selinux"; const CONTEXT_FILE: &str = "/usr/share/containers/selinux/contexts"; const SELINUX_TYPE_TAG: &str = "SELINUXTYPE"; const SELINUX_TAG: &str = "SELINUX"; -const SELINUX_DIR: &str = "/etc/selinux/"; const SELINUX_CONFIG: &str = "config"; #[derive(Debug, thiserror::Error)] @@ -91,9 +91,13 @@ pub enum SELinuxError { GetConfigKey(String), #[error("Invalid format for SELinux label: {0}")] InvalidSELinuxLabel(String), + #[error("Failed to load SELinux labels: {0}")] + LoadLabels(String), } -pub struct SELinux { +type SelinuxLabels = HashMap; + +pub struct SELinux<'a> { // for attr_path() have_thread_self: AtomicBool, attr_path_init_done: AtomicBool, @@ -102,93 +106,116 @@ pub struct SELinux { selinuxfs_init_done: AtomicBool, selinuxfs: Option, - // for policy_root() - policy_root_init_done: AtomicBool, - policy_root: Option, + policy_root: PathBuf, // for load_labels() - pub(crate) load_labels_init_done: AtomicBool, - pub(crate) labels: HashMap, + pub(crate) labels: SelinuxLabels, // for read config and get config key - read_config_init_done: AtomicBool, configs: HashMap, - pub(crate) read_only_file_label: Option, -} - -impl Default for SELinux { - fn default() -> Self { - SELinux::new() - } + pub(crate) read_only_file_label: Option<&'a SELinuxLabel>, } -impl SELinux { - pub fn new() -> Self { - SELinux { +impl<'a> SELinux<'a> { + pub fn new(selinux_dir: &Path) -> Result { + let mut selinux = SELinux { have_thread_self: AtomicBool::new(false), attr_path_init_done: AtomicBool::new(false), selinuxfs_init_done: AtomicBool::new(false), selinuxfs: None, - policy_root_init_done: AtomicBool::new(false), - policy_root: None, + policy_root: PathBuf::new(), - load_labels_init_done: AtomicBool::new(false), labels: HashMap::new(), - read_config_init_done: AtomicBool::new(false), - configs: HashMap::new(), + configs: Self::load_configs(selinux_dir.into())?, read_only_file_label: None, - } + }; + + selinux.policy_root = selinux.look_up_policy_root()?; + selinux.labels = selinux.load_labels()?; + + Ok(selinux) } // This function returns policy_root. // Directories under policy root has configuration files etc. - fn policy_root(&mut self) -> Option<&PathBuf> { - // Avoiding code conflicts and ensuring thread-safe execution once only. - if !self.policy_root_init_done.load(Ordering::SeqCst) { - let policy_root_path = Self::get_config_key(self, SELINUX_TYPE_TAG).unwrap_or_default(); - self.policy_root = Some(PathBuf::from(policy_root_path)); - self.policy_root_init_done.store(true, Ordering::SeqCst); - } - self.policy_root.as_ref() + fn look_up_policy_root(&self) -> Result { + let policy_root_path = self.get_config_key(SELINUX_TYPE_TAG)?; + Ok(PathBuf::from(policy_root_path)) } - // This function reads SELinux config file and returns the value with a specified key. - fn get_config_key(&mut self, target_key: &str) -> Result { - if !self.read_config_init_done.load(Ordering::SeqCst) { - let config_path = Path::new(SELINUX_DIR).join(SELINUX_CONFIG); - if let Ok(file) = File::open(config_path) { - let reader = BufReader::new(file); - for line in reader.lines().map_while(Result::ok) { - if line.is_empty() { - continue; - } - if (line.starts_with(';')) || (line.starts_with('#')) { - continue; - } - let fields: Vec<&str> = line.splitn(2, '=').collect(); - if fields.len() < 2 { - continue; - } - let key = fields[0].trim().to_string(); - let value = fields[1].trim().to_string(); - self.configs.insert(key, value); + // This function loads context file and reads labels and stores it. + fn load_labels(&self) -> Result, SELinuxError> { + // The context file should have pairs of key and value like below. + // ---------- + // process = "system_u:system_r:container_t:s0" + // file = "system_u:object_r:container_file_t:s0" + // ---------- + let file = + Self::open_context_file(self).map_err(|e| SELinuxError::LoadLabels(e.to_string()))?; + let reader = BufReader::new(file); + Ok(reader + .lines() + .map_while(Result::ok) + .fold(HashMap::new(), |mut acc, line| { + let line = line.trim(); + if line.is_empty() { + return acc; } - } - self.read_config_init_done.store(true, Ordering::SeqCst); - } - self.configs + let fields: Vec<&str> = line.splitn(2, '=').collect(); + if fields.len() != 2 { + return acc; + } + let key = fields[0].trim().to_string(); + let value = fields[1].trim().to_string(); + if let Ok(value_label) = SELinuxLabel::try_from(value) { + acc.insert(key, value_label); + } + acc + })) + } + + fn load_configs(selinux_dir: &Path) -> Result, SELinuxError> { + let config_path = selinux_dir.join(SELINUX_CONFIG); + let file = File::open(config_path).map_err(|e| SELinuxError::LoadLabels(e.to_string()))?; + let reader = BufReader::new(file); + Ok(reader + .lines() + .map_while(Result::ok) + .fold(HashMap::new(), |mut acc, line| { + let line = line.trim(); + if line.is_empty() { + return acc; + } + if line.starts_with(';') || line.starts_with('#') { + return acc; + } + let fields: Vec<&str> = line.splitn(2, '=').collect(); + if fields.len() < 2 { + return acc; + } + let key = fields[0].trim().to_string(); + let value = fields[1].trim().to_string(); + acc.insert(key, value); + acc + })) + } + + // This function reads SELinux config file and returns the value with a specified key. + fn get_config_key(&self, target_key: &str) -> Result { + return self + .configs .get(target_key) .cloned() .filter(|s| !s.is_empty()) .ok_or(SELinuxError::GetConfigKey(format!( "can't find the target label in the config file: {}", target_key - ))) + ))); } // get_enabled returns whether SELinux is enabled or not. @@ -312,14 +339,11 @@ impl SELinux { // This function attempts to open a selinux context file, and if it fails, it tries to open another file // under policy root's directory. - pub(crate) fn open_context_file(&mut self) -> Result { + pub(crate) fn open_context_file(&self) -> Result { match File::open(CONTEXT_FILE) { Ok(file) => Ok(file), Err(_) => { - let policy_path = Self::policy_root(self).ok_or_else(|| { - SELinuxError::OpenContextFile("can't get policy root".to_string()) - })?; - let context_on_policy_root = policy_path.join("contexts").join("lxc_contexts"); + let context_on_policy_root = self.policy_root.join("contexts").join("lxc_contexts"); match File::open(context_on_policy_root) { Ok(file) => Ok(file), Err(e) => Err(SELinuxError::OpenContextFile(e.to_string())), @@ -375,7 +399,7 @@ impl SELinux { // enforce_mode function tells you the system current mode. pub fn default_enforce_mode(&mut self) -> SELinuxMode { SELinuxMode::from( - Self::get_config_key(self, SELINUX_TAG) + self.get_config_key(SELINUX_TAG) .unwrap_or_default() .as_str(), ) @@ -513,8 +537,9 @@ mod tests { } #[test] - fn test_attr_path() { - let selinux = SELinux::new(); + fn test_attr_path() -> Result<(), SELinuxError> { + // TODO: Fix + let selinux = SELinux::new(Path::new(DEFAULT_SELINUX_DIR))?; // Test with "/proc/thread-self/attr" path (Linux >= 3.17) let attr = "bar"; let expected_name = &format!("/proc/thread-self/attr/{}", attr); @@ -530,6 +555,8 @@ mod tests { let expected_path = Path::new(expected_name); let actual_path = selinux.attr_path(attr); assert_eq!(expected_path, actual_path); + + Ok(()) } #[test] diff --git a/experiment/selinux/src/selinux_label.rs b/experiment/selinux/src/selinux_label.rs index 04180fec6..e93a0093c 100644 --- a/experiment/selinux/src/selinux_label.rs +++ b/experiment/selinux/src/selinux_label.rs @@ -3,10 +3,8 @@ use crate::tools::PathXattr; use crate::tools::*; use nix::sys::socket::getsockopt; use std::convert::TryFrom; -use std::io::{BufRead, BufReader}; use std::os::fd::AsFd; use std::path::Path; -use std::sync::atomic::Ordering; const XATTR_NAME_SELINUX: &str = "security.selinux"; const KEY_LABEL_PATH: &str = "/proc/self/attr/keycreate"; @@ -60,7 +58,7 @@ impl TryFrom for SELinuxLabel { } // This impl is for methods related to labels in SELinux struct. -impl SELinux { +impl<'a> SELinux<'a> { // set_file_label sets the SELinux label for this path, following symlinks, or returns an error. pub fn set_file_label + PathXattr>( fpath: P, @@ -235,7 +233,7 @@ impl SELinux { // kvm_container_labels returns the default processLabel and mountLabel to be used // for kvm containers by the calling process. - pub fn kvm_container_labels(&mut self) -> (Option, Option) { + pub fn kvm_container_labels(&'a mut self) -> (Option<&SELinuxLabel>, Option<&SELinuxLabel>) { let process_label = Self::label(self, "kvm_process").or_else(|| Self::label(self, "process")); (process_label, Self::label(self, "file")) @@ -244,7 +242,7 @@ impl SELinux { // init_container_labels returns the default processLabel and file labels to be // used for containers running an init system like systemd by the calling process. - pub fn init_container_labels(&mut self) -> (Option, Option) { + pub fn init_container_labels(&'a mut self) -> (Option<&SELinuxLabel>, Option<&SELinuxLabel>) { let process_label = Self::label(self, "init_process").or_else(|| Self::label(self, "process")); (process_label, Self::label(self, "file")) @@ -253,61 +251,31 @@ impl SELinux { // container_labels returns an allocated processLabel and fileLabel to be used for // container labeling by the calling process. - pub fn container_labels(&mut self) -> (Option, Option) { - if !Self::get_enabled(self) { + pub fn container_labels(&'a mut self) -> (Option<&SELinuxLabel>, Option<&SELinuxLabel>) { + if !self.get_enabled() { return (None, None); } - let process_label = Self::label(self, "process"); - let file_label = Self::label(self, "file"); + // let process_label = Self::label(self, "process"); + // let file_label = Self::label(self, "file"); + let process_label = self.labels.get("process"); + let file_label = self.labels.get("file"); if process_label.is_none() || file_label.is_none() { return (process_label, file_label); } - let mut read_only_file_label = Self::label(self, "ro_file"); - if read_only_file_label.is_none() { - read_only_file_label = file_label.clone(); - } - self.read_only_file_label = read_only_file_label; + self.read_only_file_label = match self.labels.get("ro_file") { + None => file_label, + Some(ro_file_label) => Some(ro_file_label), + }; (process_label, file_label) // TODO: use addMcs } // This function returns the value of given key on selinux context - fn label(&mut self, key: &str) -> Option { - if !self.load_labels_init_done.load(Ordering::SeqCst) { - Self::load_labels(self); - self.load_labels_init_done.store(true, Ordering::SeqCst); - } - self.labels.get(key).cloned() - } - - // This function loads context file and reads labels and stores it. - fn load_labels(&mut self) { - // The context file should have pairs of key and value like below. - // ---------- - // process = "system_u:system_r:container_t:s0" - // file = "system_u:object_r:container_file_t:s0" - // ---------- - if let Ok(file) = Self::open_context_file(self) { - let reader = BufReader::new(file); - for line in reader.lines().map_while(Result::ok) { - let line = line.trim(); - if line.is_empty() || line.starts_with(';') || line.starts_with('#') { - continue; - } - let fields: Vec<&str> = line.splitn(2, '=').collect(); - if fields.len() != 2 { - continue; - } - let key = fields[0].trim().to_string(); - let value = fields[1].trim_matches('"').trim().to_string(); - if let Ok(value_label) = SELinuxLabel::try_from(value) { - self.labels.insert(key, value_label); - } - } - } + fn label(&'a self, key: &str) -> Option<&'a SELinuxLabel> { + self.labels.get(key) } // format_mount_label returns a string to be used by the mount command.