From c7d5992c0163049eea32dfb495e0382e73f19e97 Mon Sep 17 00:00:00 2001 From: Hiroyuki Moriya <41197469+Gekko0114@users.noreply.github.com> Date: Fri, 16 Aug 2024 21:25:52 +0900 Subject: [PATCH] selinux: implemented remaining selinux functions (#2850) * added selinux functions Signed-off-by: Hiroyuki Moriya <41197469+Gekko0114@users.noreply.github.com> * not use arc Signed-off-by: Hiroyuki Moriya <41197469+Gekko0114@users.noreply.github.com> * follow reviewer comment Signed-off-by: Hiroyuki Moriya <41197469+Gekko0114@users.noreply.github.com> * divided selinux impl into two files Signed-off-by: Hiroyuki Moriya <41197469+Gekko0114@users.noreply.github.com> * fix Signed-off-by: Hiroyuki Moriya <41197469+Gekko0114@users.noreply.github.com> * fix Signed-off-by: Hiroyuki Moriya <41197469+Gekko0114@users.noreply.github.com> * fix Signed-off-by: Hiroyuki Moriya <41197469+Gekko0114@users.noreply.github.com> * use SELinuxLabel struct Signed-off-by: Hiroyuki Moriya <41197469+Gekko0114@users.noreply.github.com> * use pointer instead of clone Signed-off-by: Hiroyuki Moriya <41197469+Gekko0114@users.noreply.github.com> * not loop Signed-off-by: Hiroyuki Moriya <41197469+Gekko0114@users.noreply.github.com> * add main.rs Signed-off-by: Hiroyuki Moriya <41197469+Gekko0114@users.noreply.github.com> --------- Signed-off-by: Hiroyuki Moriya <41197469+Gekko0114@users.noreply.github.com> --- experiment/selinux/Cargo.lock | 23 + experiment/selinux/Cargo.toml | 3 +- experiment/selinux/README.md | 5 + experiment/selinux/src/lib.rs | 5 +- experiment/selinux/src/main.rs | 43 ++ experiment/selinux/src/selinux.rs | 663 ++++++++++-------- experiment/selinux/src/selinux_label.rs | 376 ++++++++++ experiment/selinux/src/tools/mod.rs | 5 + experiment/selinux/src/tools/sockopt.rs | 38 + .../selinux/src/{xattrs => tools}/xattr.rs | 22 +- experiment/selinux/src/xattrs/mod.rs | 3 - 11 files changed, 891 insertions(+), 295 deletions(-) create mode 100644 experiment/selinux/src/main.rs create mode 100644 experiment/selinux/src/selinux_label.rs create mode 100644 experiment/selinux/src/tools/mod.rs create mode 100644 experiment/selinux/src/tools/sockopt.rs rename experiment/selinux/src/{xattrs => tools}/xattr.rs (89%) delete mode 100644 experiment/selinux/src/xattrs/mod.rs diff --git a/experiment/selinux/Cargo.lock b/experiment/selinux/Cargo.lock index 60ecb75d..5a515cc4 100644 --- a/experiment/selinux/Cargo.lock +++ b/experiment/selinux/Cargo.lock @@ -2,6 +2,18 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + [[package]] name = "bitflags" version = "2.5.0" @@ -48,6 +60,15 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "nix" version = "0.29.0" @@ -58,6 +79,7 @@ dependencies = [ "cfg-if", "cfg_aliases", "libc", + "memoffset", ] [[package]] @@ -95,6 +117,7 @@ dependencies = [ name = "selinux" version = "0.1.0" dependencies = [ + "anyhow", "nix", "rustix", "tempfile", diff --git a/experiment/selinux/Cargo.toml b/experiment/selinux/Cargo.toml index 87f87319..785c7cd7 100644 --- a/experiment/selinux/Cargo.toml +++ b/experiment/selinux/Cargo.toml @@ -12,7 +12,8 @@ autoexamples = true keywords = ["youki", "container", "selinux"] [dependencies] -nix = { version = "0.29.0", features = ["process", "fs"] } +anyhow = "1.0.86" +nix = { version = "0.29.0", features = ["process", "fs", "socket"] } rustix = { version = "0.38.34", features = ["fs"] } tempfile = "3.10.1" thiserror = "1.0.61" diff --git a/experiment/selinux/README.md b/experiment/selinux/README.md index 8e0d9065..a4779442 100644 --- a/experiment/selinux/README.md +++ b/experiment/selinux/README.md @@ -3,5 +3,10 @@ Ref: https://github.com/containers/youki/issues/2718. Reimplementation of [opencontainers/selinux](https://github.com/opencontainers/selinux) in Rust. Also selinux depends on xattr, but nix doesn't cover xattr function. Therefore, this PR will implement xattr in Rust. +Referenced the implementation of xattr in [unix](golang.org/x/sys/unix) repo. Please import and use this project. + +```console +$ cargo run +``` diff --git a/experiment/selinux/src/lib.rs b/experiment/selinux/src/lib.rs index eebcd32c..9a9bfd99 100644 --- a/experiment/selinux/src/lib.rs +++ b/experiment/selinux/src/lib.rs @@ -1,2 +1,5 @@ pub mod selinux; -pub mod xattrs; +pub mod selinux_label; +pub mod tools; + +pub use selinux::SELinux; diff --git a/experiment/selinux/src/main.rs b/experiment/selinux/src/main.rs new file mode 100644 index 00000000..35d5a4e1 --- /dev/null +++ b/experiment/selinux/src/main.rs @@ -0,0 +1,43 @@ +use anyhow::Result; +use selinux::selinux::*; +use selinux::selinux_label::*; +use std::fs::File; +use std::path::Path; + +fn main() -> Result<()> { + let mut selinux_instance: SELinux = SELinux::new(); + + if selinux_instance.get_enabled() { + println!("selinux is enabled"); + } else { + println!("selinux is not enabled"); + + match selinux_instance.set_enforce_mode(SELinuxMode::PERMISSIVE) { + Ok(_) => println!("set selinux mode as permissive"), + Err(e) => println!("{}", e), + } + } + println!( + "default enforce mode is: {}", + selinux_instance.default_enforce_mode() + ); + println!( + "current enforce mode is: {}", + selinux_instance.enforce_mode() + ); + + match selinux_instance.current_label() { + Ok(l) => println!("SELinux label of current process is: {}", l), + Err(e) => println!("{}", e), + } + + let file_path = Path::new("./test_file.txt"); + let _file = File::create(file_path)?; + let selinux_label = + SELinuxLabel::try_from("unconfined_u:object_r:public_content_t:s1".to_string())?; + SELinux::set_file_label(file_path, selinux_label)?; + let current_label = SELinux::file_label(file_path)?; + println!("file label is {}", current_label); + + Ok(()) +} diff --git a/experiment/selinux/src/selinux.rs b/experiment/selinux/src/selinux.rs index 9e8a34ba..e724cf99 100644 --- a/experiment/selinux/src/selinux.rs +++ b/experiment/selinux/src/selinux.rs @@ -1,15 +1,65 @@ -use crate::xattrs::*; +use crate::selinux_label::SELinuxLabel; use nix::errno::Errno; use nix::sys::statfs; use nix::unistd::gettid; -use std::fs::File; -use std::io::Read; +use std::collections::HashMap; +use std::convert::From; +use std::fmt; +use std::fs::{self, File, OpenOptions}; +use std::io::{BufRead, BufReader, Read, Write}; use std::os::fd::{AsFd, AsRawFd}; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicBool, Ordering}; -const XATTR_NAME_SELINUX: &str = "security.selinux"; -const ERR_EMPTY_PATH: &str = "empty path"; +#[derive(Debug, Copy, Clone)] +pub enum SELinuxMode { + // ENFORCING constant to indicate SELinux is in enforcing mode + ENFORCING = 1, + // PERMISSIVE constant to indicate SELinux is in permissive mode + PERMISSIVE = 0, + // DISABLED constant to indicate SELinux is disabled + DISABLED = -1, +} + +impl From for SELinuxMode { + fn from(mode: i32) -> Self { + match mode { + 1 => SELinuxMode::ENFORCING, + 0 => SELinuxMode::PERMISSIVE, + -1 => SELinuxMode::DISABLED, + _ => SELinuxMode::DISABLED, + } + } +} + +impl From<&str> for SELinuxMode { + fn from(mode: &str) -> Self { + match mode { + "enforcing" => SELinuxMode::ENFORCING, + "permissive" => SELinuxMode::PERMISSIVE, + _ => SELinuxMode::DISABLED, + } + } +} + +impl fmt::Display for SELinuxMode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = match self { + SELinuxMode::ENFORCING => "enforcing", + SELinuxMode::PERMISSIVE => "permissive", + SELinuxMode::DISABLED => "disabled", + }; + write!(f, "{}", s) + } +} + +pub(crate) const ERR_EMPTY_PATH: &str = "empty path"; +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)] pub enum SELinuxError { @@ -27,11 +77,44 @@ pub enum SELinuxError { ReadConFd(String), #[error("Failed to call read_con for SELinux: {0}")] ReadCon(String), + #[error("Failed to call write_con for SELinux: {0}")] + WriteCon(String), + #[error("Failed to find the index for a given class: {0}")] + ClassIndex(String), + #[error("Failed to call peer_label for SELinux: {0}")] + PeerLabel(String), + #[error("Failed to call open_context_file for SELinux: {0}")] + OpenContextFile(String), + #[error("Failed to set enforce mode of SELinux: {0}")] + SetEnforceMode(String), + #[error("Failed to read config file of SELinux: {0}")] + GetConfigKey(String), + #[error("Invalid format for SELinux label: {0}")] + InvalidSELinuxLabel(String), } pub struct SELinux { + // for attr_path() have_thread_self: AtomicBool, - init_done: AtomicBool, + attr_path_init_done: AtomicBool, + + // for selinuxfs + selinuxfs_init_done: AtomicBool, + selinuxfs: Option, + + // for policy_root() + policy_root_init_done: AtomicBool, + policy_root: Option, + + // for load_labels() + pub(crate) load_labels_init_done: AtomicBool, + pub(crate) labels: HashMap, + + // 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 { @@ -44,268 +127,291 @@ impl SELinux { pub fn new() -> Self { SELinux { have_thread_self: AtomicBool::new(false), - init_done: 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, + + load_labels_init_done: AtomicBool::new(false), + labels: HashMap::new(), + + read_config_init_done: AtomicBool::new(false), + configs: HashMap::new(), + + read_only_file_label: None, } } - // function similar with setDisabled in go-selinux repo. - // set_disabled disables SELinux support for the package. - pub fn set_disabled() { - unimplemented!("not implemented yet") + // 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() + } + + // 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); + } + } + self.read_config_init_done.store(true, Ordering::SeqCst); + } + 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 + ))) } - // function similar with getEnabled in go-selinux repo. // get_enabled returns whether SELinux is enabled or not. - pub fn get_enabled() -> bool { - unimplemented!("not implemented yet") + pub fn get_enabled(&mut self) -> bool { + match Self::get_selinux_mountpoint(self) { + // If there is no SELinux mountpoint, SELinux is not enabled. + None => false, + Some(_) => match Self::current_label(self) { + Ok(con) => { + // Check whether label is "kernel" or not. + if con.user != "kernel" { + return true; + } + false + } + Err(_) => false, + }, + } } - // function similar with classIndex in go-selinux repo. - // classIndex returns the int index for an object class in the loaded policy, - // or -1 and an error. - pub fn class_index(class: &str) -> Result { - unimplemented!("not implemented yet") + // verify_selinux_fs_mount verifies if the specified mount point is + // properly mounted as a writable SELinux filesystem. + fn verify_selinux_fs_mount>(mnt: P) -> bool { + let mnt = mnt.as_ref(); + loop { + match statfs::statfs(mnt) { + Ok(stat) => { + // In go-selinux, return false if it is not read-only, + // but selinux code in SELinuxProject return true even though it is read-only. + // https://github.com/SELinuxProject/selinux/blob/1f080ffd7ab24b0ad2b46f79db63d62c2ae2747c/libselinux/src/init.c#L44 + // Therefore, this function doesn't check whether it is read-only or not. + + // verify if the file is SELinux filesystem + return stat.filesystem_type() == statfs::SELINUX_MAGIC; + } + // check again if there is an issue while calling statfs + Err(Errno::EAGAIN) | Err(Errno::EINTR) => continue, + Err(_) => return false, + } + } } - // function similar with setFileLabel in go-selinux repo. - // set_file_label sets the SELinux label for this path, following symlinks, or returns an error. - pub fn set_file_label(fpath: &Path, label: &str) -> Result<(), SELinuxError> { - if !fpath.exists() { - return Err(SELinuxError::SetFileLabel(ERR_EMPTY_PATH.to_string())); + // check_line_include_selinux_fs_mount_point returns a next selinuxfs mount point found, + // if there is one, or None in case of EOF or error. + fn check_line_include_selinux_fs_mount_point(line: &str) -> Option { + if !line.contains(" - selinuxfs ") { + return None; + } + // Need to return the path like /sys/fs/selinux + // example: 28 24 0:25 / /sys/fs/selinux rw,relatime - selinuxfs selinuxfs rw + let m_pos = 5; + let fields: Vec<&str> = line.splitn(m_pos + 1, ' ').collect(); + if fields.len() < m_pos + 1 { + return None; + } + let mountpoint = fields[m_pos - 1].to_string(); + Some(PathBuf::from(mountpoint)) + } + + // find_selinux_fs finds the SELinux filesystem mount point. + fn find_selinux_fs() -> Option { + // fast path: check the default mount first + let selinux_fs_mount_path = PathBuf::from(SELINUX_FS_MOUNT); + if Self::verify_selinux_fs_mount(&selinux_fs_mount_path) { + return Some(selinux_fs_mount_path); } - loop { - match fpath.set_xattr(XATTR_NAME_SELINUX, label.as_bytes()) { - Ok(_) => break, - // When a system call is interrupted by a signal, it needs to be retried. - Err(XattrError::EINTR(_)) => continue, - Err(e) => { - return Err(SELinuxError::SetFileLabel(format!( - "set_xattr failed: {}", - e - ))); + // check if selinuxfs is available before going the slow path + let fs = fs::read_to_string("/proc/filesystems").unwrap_or_default(); + if !fs.contains("\tselinuxfs\n") { + return None; + } + + // slow path: try to find among the mounts + match File::open("/proc/self/mountinfo") { + Ok(file) => { + let reader = BufReader::new(file); + for line in reader.lines().map_while(Result::ok) { + if let Some(mnt) = Self::check_line_include_selinux_fs_mount_point(&line) { + if Self::verify_selinux_fs_mount(&mnt) { + return Some(mnt); + } + } + } + } + Err(_) => return None, + } + None + } + + // This function returns the path to the mountpoint of an selinuxfs + // filesystem or an empty string if no mountpoint is found. Selinuxfs is + // a proc-like pseudo-filesystem that exposes the SELinux policy API to + // processes. The existence of an seliuxfs mount is used to determine + // whether SELinux is currently enabled or not. + pub fn get_selinux_mountpoint(&mut self) -> Option<&PathBuf> { + // Avoiding code conflicts and ensuring thread-safe execution once only. + if !self.selinuxfs_init_done.load(Ordering::SeqCst) { + self.selinuxfs = Self::find_selinux_fs(); + self.selinuxfs_init_done.store(true, Ordering::SeqCst); + } + self.selinuxfs.as_ref() + } + + // classIndex returns the int index for an object class in the loaded policy, or an error. + // For example, if a class is "file" or "dir", return the corresponding index for selinux. + pub fn class_index(&mut self, class: &str) -> Result { + let permpath = format!("class/{}/index", class); + let mountpoint = Self::get_selinux_mountpoint(self) + .ok_or_else(|| SELinuxError::ClassIndex("SELinux mount point not found".to_string()))?; + let indexpath = mountpoint.join(permpath); + + match fs::read_to_string(indexpath) { + Ok(index_b) => match index_b.parse::() { + Ok(index) => Ok(index), + Err(e) => Err(SELinuxError::ClassIndex(e.to_string())), + }, + Err(e) => Err(SELinuxError::ClassIndex(e.to_string())), + } + } + + // 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 { + 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"); + match File::open(context_on_policy_root) { + Ok(file) => Ok(file), + Err(e) => Err(SELinuxError::OpenContextFile(e.to_string())), } } } - Ok(()) } - // function similar with lSetFileLabel in go-selinux repo. - // lset_file_label sets the SELinux label for this path, not following symlinks, - // or returns an error. - pub fn lset_file_label(fpath: &Path, label: &str) -> Result<(), SELinuxError> { - if !fpath.exists() { - return Err(SELinuxError::LSetFileLabel(ERR_EMPTY_PATH.to_string())); - } + // This returns selinux enforce path by using selinux mountpoint. + // The enforce path dynamically changes SELinux mode at runtime, + // while the config file need OS to reboot after changing the config file. + fn selinux_enforce_path(&mut self) -> Option { + let selinux_mountpoint = Self::get_selinux_mountpoint(self); + selinux_mountpoint.map(|m| m.join("enforce")) + } - loop { - match fpath.lset_xattr(XATTR_NAME_SELINUX, label.as_bytes()) { - Ok(_) => break, - // When a system call is interrupted by a signal, it needs to be retried. - Err(XattrError::EINTR(_)) => continue, - Err(e) => { - return Err(SELinuxError::LSetFileLabel(format!( - "lset_xattr failed: {}", - e - ))); - } + // enforce_mode returns the current SELinux mode Enforcing, Permissive, Disabled + pub fn enforce_mode(&mut self) -> SELinuxMode { + let mode = match Self::selinux_enforce_path(self) { + Some(enforce_path) => match fs::read_to_string(enforce_path) { + Ok(content) => content.trim().parse::().unwrap_or(-1), + Err(_) => -1, + }, + None => -1, + }; + SELinuxMode::from(mode) + } + + // is_mls_enabled checks if MLS is enabled. + pub fn is_mls_enabled(&mut self) -> bool { + if let Some(mountpoint) = Self::get_selinux_mountpoint(self) { + let mls_path = Path::new(&mountpoint).join("mls"); + match fs::read(mls_path) { + Ok(enabled_b) => return enabled_b == vec![b'1'], + Err(_) => return false, } } - Ok(()) + false } - // function similar with fileLabel in go-selinux repo. - // fileLabel returns the SELinux label for this path, following symlinks, - // or returns an error. - pub fn file_label(fpath: &Path) -> Result { - if !fpath.exists() { - return Err(SELinuxError::FileLabel(ERR_EMPTY_PATH.to_string())); + // This function updates the enforce mode of selinux. + // Disabled is not valid, since this needs to be set at boot time. + pub fn set_enforce_mode(&mut self, mode: SELinuxMode) -> Result<(), SELinuxError> { + let enforce_path = Self::selinux_enforce_path(self).ok_or_else(|| { + SELinuxError::SetEnforceMode("can't get selinux enforce path".to_string()) + })?; + fs::write(enforce_path, mode.to_string().as_bytes()) + .map_err(|e| SELinuxError::SetEnforceMode(e.to_string())) + } + + // This returns the systems default SELinux mode Enforcing, Permissive or Disabled. + // note this is just the default at boot time. + // 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) + .unwrap_or_default() + .as_str(), + ) + } + + // write_con writes a specified value to a given file path, handling SELinux context. + pub fn write_con>( + &mut self, + fpath: P, + val: &str, + ) -> Result { + let path = fpath.as_ref(); + if path.as_os_str().is_empty() { + return Err(SELinuxError::WriteCon(ERR_EMPTY_PATH.to_string())); } - fpath - .get_xattr(XATTR_NAME_SELINUX) - .map_err(|e| SELinuxError::FileLabel(e.to_string())) - } - - // function similar with lFileLabel in go-selinux repo. - // lfile_label returns the SELinux label for this path, not following symlinks, - // or returns an error. - pub fn lfile_label(fpath: &Path) -> Result { - if !fpath.exists() { - return Err(SELinuxError::LFileLabel(ERR_EMPTY_PATH.to_string())); + if val.is_empty() && !Self::get_enabled(self) { + return Err(SELinuxError::WriteCon("SELinux is not enabled".to_string())); } - fpath - .lget_xattr(XATTR_NAME_SELINUX) - .map_err(|e| SELinuxError::LFileLabel(e.to_string())) - } - // function similar with setFSCreateLabel in go-selinux repo. - // set_fscreate_label sets the default label the kernel which the kernel is using - // for file system objects. - pub fn set_fscreate_label(&self, label: &str) -> Result<(), SELinuxError> { - return Self::write_con(self.attr_path("fscreate").as_path(), label); - } + let mut out = OpenOptions::new() + .write(true) + .create(false) + .open(fpath) + .map_err(|e| SELinuxError::WriteCon(format!("failed to open file: {}", e)))?; - // function similar with fsCreateLabel in go-selinux repo. - // fscreate_label returns the default label the kernel which the kernel is using - // for file system objects created by this task. "" indicates default. - pub fn fscreate_label(&self) -> Result { - return Self::read_con(self.attr_path("fscreate").as_path()); - } - - // function similar with currentLabel in go-selinux repo. - // current_label returns the SELinux label of the current process thread, or an error. - pub fn current_label(&self) -> Result { - return Self::read_con(self.attr_path("current").as_path()); - } - - // function similar with pidLabel in go-selinux repo. - // pid_label returns the SELinux label of the given pid, or an error. - pub fn pid_label(pid: i64) -> Result { - let file_name = &format!("/proc/{}/attr/current", pid); - let label = Path::new(file_name); - Self::read_con(label) - } - - // function similar with execLabel in go-selinux repo. - // exec_label returns the SELinux label that the kernel will use for any programs - // that are executed by the current process thread, or an error. - pub fn exec_label(&self) -> Result { - return Self::read_con(self.attr_path("exec").as_path()); - } - - // function similar with SetExecLabel in go-selinux repo. - // set_exec_label sets the SELinux label that the kernel will use for any programs - // that are executed by the current process thread, or an error. - pub fn set_exec_label(label: &str) { - unimplemented!("not implemented yet") - } - - // function similar with SetTaskLabel in go-selinux repo. - // set_task_label sets the SELinux label for the current thread, or an error. - // This requires the dyntransition permission. - pub fn set_task_label(label: &str) { - unimplemented!("not implemented yet") - } - - // function similar with SetSocketLabel in go-selinux repo. - // set_socket_label takes a process label and tells the kernel to assign the - // label to the next socket that gets created. - pub fn set_socket_label(label: &str) { - unimplemented!("not implemented yet") - } - - // function similar with SocketLabel in go-selinux repo. - // socket_label retrieves the current socket label setting. - pub fn socket_label() { - unimplemented!("not implemented yet") - } - - // function similar with peerLabel in go-selinux repo. - // peer_label retrieves the label of the client on the other side of a socket. - pub fn peer_label() { - unimplemented!("not implemented yet") - } - - // function similar with setKeyLabel in go-selinux repo. - // set_key_label takes a process label and tells the kernel to assign the - // label to the next kernel keyring that gets created. - pub fn set_key_label(label: &str) -> Result<(), SELinuxError> { - match Self::write_con(Path::new("/proc/self/attr/keycreate"), label) { - Ok(v) => Ok(v), - // TODO: This line will be fixed after implementing write_con. - Err(e) => Err(e), + Self::is_proc_handle(&out)?; + match out.write(val.as_bytes()) { + Ok(u) => Ok(u), + Err(e) => Err(SELinuxError::WriteCon(format!( + "failed to write in file: {}", + e + ))), } } - // function similar with KeyLabel in go-selinux repo. - // key_label retrieves the current kernel keyring label setting - pub fn key_label() { - unimplemented!("not implemented yet") - } - - // function similar with clearLabels in go-selinux repo. - // clear_labels clears all reserved labels. - pub fn clear_labels() { - unimplemented!("not implemented yet") - } - - // function similar with reserveLabel in go-selinux repo. - // reserve_label reserves the MLS/MCS level component of the specified label - pub fn reserve_label(label: &str) { - unimplemented!("not implemented yet") - } - - // function similar with roFileLabel in go-selinux repo. - // ro_file_label returns the specified SELinux readonly file label - pub fn ro_file_label() { - unimplemented!("not implemented yet") - } - - // function similar with kvmContainerLabels in go-selinux repo. - // kvm_container_labels returns the default processLabel and mountLabel to be used - // for kvm containers by the calling process. - pub fn kvm_container_labels() { - unimplemented!("not implemented yet") - } - - // function similar with initContainerLabels in go-selinux repo. - // 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() { - unimplemented!("not implemented yet") - } - - // function similar with containerLabels in go-selinux repo. - // container_labels returns an allocated processLabel and fileLabel to be used for - // container labeling by the calling process. - pub fn container_labels() { - unimplemented!("not implemented yet") - } - - // function similar with PrivContainerMountLabel in go-selinux repo. - // priv_container_mount_label returns mount label for privileged containers. - pub fn priv_container_mount_label() { - unimplemented!("not implemented yet") - } - - // function similar with FormatMountLabel in go-selinux repo. - // format_mount_label returns a string to be used by the mount command. - // Using the SELinux `context` mount option. - // Changing labels of files on mount points with this option can never be changed. - // format_mount_label returns a string to be used by the mount command. - // The format of this string will be used to alter the labeling of the mountpoint. - // The string returned is suitable to be used as the options field of the mount command. - // If you need to have additional mount point options, you can pass them in as - // the first parameter. The second parameter is the label that you wish to apply - // to all content in the mount point. - pub fn format_mount_label(src: &str, mount_label: &str) -> String { - Self::format_mount_label_by_type(src, mount_label, "context") - } - - // function similar with FormatMountLabelByType in go-selinux repo. - // format_mount_label_by_type returns a string to be used by the mount command. - // Allow caller to specify the mount options. For example using the SELinux - // `fscontext` mount option would allow certain container processes to change - // labels of files created on the mount points, where as `context` option does not. - pub fn format_mount_label_by_type(src: &str, mount_label: &str, context_type: &str) -> String { - let mut formatted_src = src.to_owned(); - - if !mount_label.is_empty() { - if formatted_src.is_empty() { - formatted_src = format!("{}=\"{}\"", context_type, mount_label); - } else { - formatted_src = format!("{},{}=\"{}\"", formatted_src, context_type, mount_label); - } - } - formatted_src - } - - // function similar with writeCon in go-selinux repo. - pub fn write_con(fpath: &Path, val: &str) -> Result<(), SELinuxError> { - unimplemented!("not implemented yet"); - } - - // function similar with isProcHandle in go-selinux repo. + // This function checks whether this file is on the procfs filesystem. pub fn is_proc_handle(file: &File) -> Result<(), SELinuxError> { loop { match statfs::fstatfs(file.as_fd()) { @@ -328,8 +434,8 @@ impl SELinux { Ok(()) } - // function similar with readConFd in go-selinux repo. - pub fn read_con_fd(file: &mut File) -> Result { + // This function reads a given file descriptor into a string. + pub fn read_con_fd(file: &mut F) -> Result { let mut data = String::new(); file.read_to_string(&mut data) .map_err(|e| SELinuxError::ReadConFd(e.to_string()))?; @@ -339,9 +445,10 @@ impl SELinux { Ok(trimmed_data.to_string()) } - // function similar with readCon in go-selinux repo. - pub fn read_con(fpath: &Path) -> Result { - if fpath.as_os_str().is_empty() { + // read_con reads a label to a given file path, handling SELinux context. + pub fn read_con>(fpath: P) -> Result { + let path = fpath.as_ref(); + if path.as_os_str().is_empty() { return Err(SELinuxError::ReadCon(ERR_EMPTY_PATH.to_string())); } let mut in_file = File::open(fpath) @@ -351,18 +458,17 @@ impl SELinux { Self::read_con_fd(&mut in_file) } - // function similar with attrPath in go-selinux repo. // attr_path determines the correct file path for accessing SELinux // attributes of a process or thread in a Linux environment. pub fn attr_path(&self, attr: &str) -> PathBuf { // Linux >= 3.17 provides this const THREAD_SELF_PREFIX: &str = "/proc/thread-self/attr"; // Avoiding code conflicts and ensuring thread-safe execution once only. - if !self.init_done.load(Ordering::SeqCst) { + if !self.attr_path_init_done.load(Ordering::SeqCst) { let path = PathBuf::from(THREAD_SELF_PREFIX); let is_dir = path.is_dir(); self.have_thread_self.store(is_dir, Ordering::SeqCst); - self.init_done.store(true, Ordering::SeqCst); + self.attr_path_init_done.store(true, Ordering::SeqCst); } if self.have_thread_self.load(Ordering::SeqCst) { return PathBuf::from(&format!("{}/{}", THREAD_SELF_PREFIX, attr)); @@ -375,9 +481,11 @@ impl SELinux { #[cfg(test)] mod tests { use crate::selinux::*; - use std::fs::{self, File}; + use std::fs::File; use std::io::Write; use std::path::Path; + use std::str; + use tempfile::NamedTempFile; fn create_temp_file(content: &[u8], file_name: &str) { let path = Path::new(file_name); @@ -386,49 +494,21 @@ mod tests { file.sync_all().expect("Failed to sync file"); } - #[test] - fn test_format_mount_label() { - let src_array = ["", "src", "src"]; - let mount_label_array = ["foobar", "foobar", ""]; - let expected_array = ["context=\"foobar\"", "src,context=\"foobar\"", "src"]; - for (i, src) in src_array.iter().enumerate() { - let mount_label = mount_label_array[i]; - let expected = expected_array[i]; - assert_eq!(SELinux::format_mount_label(src, mount_label), expected); - } - } - - #[test] - fn test_format_mount_label_by_type() { - let src_array = ["", "src", "src"]; - let mount_label_array = ["foobar", "foobar", ""]; - let context_array = ["fscontext", "fscontext", "rootcontext"]; - let expected_array = ["fscontext=\"foobar\"", "src,fscontext=\"foobar\"", "src"]; - for (i, src) in src_array.iter().enumerate() { - let mount_label = mount_label_array[i]; - let context = context_array[i]; - let expected = expected_array[i]; - assert_eq!( - SELinux::format_mount_label_by_type(src, mount_label, context), - expected - ); - } - } - #[test] fn test_read_con_fd() { let content_array: Vec<&[u8]> = vec![b"Hello, world\0", b"Hello, world\0\0\0", b"Hello,\0world"]; let expected_array = ["Hello, world", "Hello, world", "Hello,\0world"]; - let file_name = "test.txt"; for (i, content) in content_array.iter().enumerate() { let expected = expected_array[i]; - create_temp_file(content, file_name); + let mut temp_file = NamedTempFile::new().expect("Failed to create temp file"); + temp_file + .write_all(content) + .expect("Failed to write to temp file"); // Need to open again to get read permission. - let mut file = File::open(file_name).expect("Failed to open file"); + let mut file = File::open(temp_file).expect("Failed to open file"); let result = SELinux::read_con_fd(&mut file).expect("Failed to read file"); assert_eq!(result, expected); - fs::remove_file(file_name).expect("Failed to remove test file"); } } @@ -443,7 +523,7 @@ mod tests { assert_eq!(expected_path, actual_path); // Test with not having "/proc/thread-self/attr" path by setting HAVE_THREAD_SELF as false - selinux.init_done.store(true, Ordering::SeqCst); + selinux.attr_path_init_done.store(true, Ordering::SeqCst); selinux.have_thread_self.store(false, Ordering::SeqCst); let thread_id = gettid(); let expected_name = &format!("/proc/self/task/{}/attr/{}", thread_id, attr); @@ -475,4 +555,23 @@ mod tests { } } } + + #[test] + fn test_check_line_include_selinux_fs_mount_point() { + let input_array = [ + "28 24 0:25 / /sys/fs/selinux rw,relatime - selinuxfs selinuxfs rw", + "28 24 0:25 /", + "28 24 0:25 / /sys/fs/selinux rw,relatime selinuxfs rw", + ]; + let expected_array = ["/sys/fs/selinux", "", ""]; + let succeeded_array = [true, false, false]; + + for (i, input) in input_array.iter().enumerate() { + let expected = PathBuf::from(expected_array[i]); + match SELinux::check_line_include_selinux_fs_mount_point(input) { + Some(output) => assert_eq!(expected, output), + None => assert_eq!(succeeded_array[i], false), + } + } + } } diff --git a/experiment/selinux/src/selinux_label.rs b/experiment/selinux/src/selinux_label.rs new file mode 100644 index 00000000..04180fec --- /dev/null +++ b/experiment/selinux/src/selinux_label.rs @@ -0,0 +1,376 @@ +use crate::selinux::*; +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"; + +#[derive(Default, Clone)] +pub struct SELinuxLabel { + pub(crate) user: String, + role: String, + type_: String, + level: Option, +} + +impl std::fmt::Display for SELinuxLabel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.level { + Some(level) => write!(f, "{}:{}:{}:{}", self.user, self.role, self.type_, level), + None => write!(f, "{}:{}:{}", self.user, self.role, self.type_), + } + } +} + +impl TryFrom for SELinuxLabel { + type Error = SELinuxError; + fn try_from(label: String) -> Result { + let fields: Vec<&str> = label.split(':').collect(); + if fields.len() < 3 { + return Err(SELinuxError::InvalidSELinuxLabel(label)); + } + + // It is possible that input label is "", which means no label is set. + let user = fields + .first() + .ok_or(SELinuxError::InvalidSELinuxLabel(label.clone()))? + .to_string(); + let role = fields + .get(1) + .ok_or(SELinuxError::InvalidSELinuxLabel(label.clone()))? + .to_string(); + let type_ = fields + .get(2) + .ok_or(SELinuxError::InvalidSELinuxLabel(label.clone()))? + .to_string(); + let level = fields.get(3).map(|&s| s.to_string()); + Ok(SELinuxLabel { + user, + role, + type_, + level, + }) + } +} + +// This impl is for methods related to labels in SELinux struct. +impl SELinux { + // set_file_label sets the SELinux label for this path, following symlinks, or returns an error. + pub fn set_file_label + PathXattr>( + fpath: P, + label: SELinuxLabel, + ) -> Result<(), SELinuxError> { + let path = fpath.as_ref(); + if !path.exists() { + return Err(SELinuxError::SetFileLabel(ERR_EMPTY_PATH.to_string())); + } + + loop { + match fpath.set_xattr(XATTR_NAME_SELINUX, label.to_string().as_bytes()) { + Ok(_) => break, + // When a system call is interrupted by a signal, it needs to be retried. + Err(XattrError::EINTR(_)) => continue, + Err(e) => { + return Err(SELinuxError::SetFileLabel(e.to_string())); + } + } + } + Ok(()) + } + + // lset_file_label sets the SELinux label for this path, not following symlinks, + // or returns an error. + pub fn lset_file_label + PathXattr>( + fpath: P, + label: SELinuxLabel, + ) -> Result<(), SELinuxError> { + let path = fpath.as_ref(); + if !path.exists() { + return Err(SELinuxError::LSetFileLabel(ERR_EMPTY_PATH.to_string())); + } + + loop { + match fpath.lset_xattr(XATTR_NAME_SELINUX, label.to_string().as_bytes()) { + Ok(_) => break, + // When a system call is interrupted by a signal, it needs to be retried. + Err(XattrError::EINTR(_)) => continue, + Err(e) => { + return Err(SELinuxError::LSetFileLabel(e.to_string())); + } + } + } + Ok(()) + } + + // fileLabel returns the SELinux label for this path, following symlinks, + // or returns an error. + pub fn file_label + PathXattr>(fpath: P) -> Result { + let path = fpath.as_ref(); + if !path.exists() { + return Err(SELinuxError::FileLabel(ERR_EMPTY_PATH.to_string())); + } + let label_str = fpath + .get_xattr(XATTR_NAME_SELINUX) + .map_err(|e| SELinuxError::FileLabel(e.to_string()))?; + SELinuxLabel::try_from(label_str) + } + + // lfile_label returns the SELinux label for this path, not following symlinks, + // or returns an error. + pub fn lfile_label + PathXattr>(fpath: P) -> Result { + let path = fpath.as_ref(); + if !path.exists() { + return Err(SELinuxError::LFileLabel(ERR_EMPTY_PATH.to_string())); + } + let label_str = fpath + .lget_xattr(XATTR_NAME_SELINUX) + .map_err(|e| SELinuxError::LFileLabel(e.to_string()))?; + SELinuxLabel::try_from(label_str) + } + + // set_fscreate_label sets the default label the kernel which the kernel is using + // for file system objects. + pub fn set_fscreate_label(&mut self, label: SELinuxLabel) -> Result { + return Self::write_con( + self, + self.attr_path("fscreate").as_path(), + label.to_string().as_str(), + ); + } + + // fscreate_label returns the default label the kernel which the kernel is using + // for file system objects created by this task. "" indicates default. + pub fn fscreate_label(&self) -> Result { + let label = Self::read_con(self.attr_path("fscreate").as_path())?; + SELinuxLabel::try_from(label) + } + + // pid_label returns the SELinux label of the given pid, or an error. + pub fn pid_label(pid: i64) -> Result { + let file_name = &format!("/proc/{}/attr/current", pid); + let file_path = Path::new(file_name); + let label = Self::read_con(file_path)?; + SELinuxLabel::try_from(label) + } + + // exec_label returns the SELinux label that the kernel will use for any programs + // that are executed by the current process thread, or an error. + pub fn exec_label(&self) -> Result { + let label = Self::read_con(self.attr_path("exec").as_path())?; + SELinuxLabel::try_from(label) + } + + // set_exec_label sets the SELinux label that the kernel will use for any programs + // that are executed by the current process thread, or an error. + pub fn set_exec_label(&mut self, label: SELinuxLabel) -> Result { + Self::write_con( + self, + self.attr_path("exec").as_path(), + label.to_string().as_str(), + ) + } + + // set_task_label sets the SELinux label for the current thread, or an error. + // This requires the dyntransition permission because this changes the context of current thread. + pub fn set_task_label(&mut self, label: SELinuxLabel) -> Result { + Self::write_con( + self, + self.attr_path("current").as_path(), + label.to_string().as_str(), + ) + } + + // set_socket_label takes a process label and tells the kernel to assign the + // label to the next socket that gets created. + pub fn set_socket_label(&mut self, label: SELinuxLabel) -> Result { + Self::write_con( + self, + self.attr_path("sockcreate").as_path(), + label.to_string().as_str(), + ) + } + + // socket_label retrieves the current socket label setting. + pub fn socket_label(&self) -> Result { + let label = Self::read_con(self.attr_path("sockcreate").as_path())?; + SELinuxLabel::try_from(label) + } + + // current_label returns the SELinux label of the current process thread, or an error. + pub fn current_label(&self) -> Result { + let label = SELinux::read_con(self.attr_path("current").as_path())?; + SELinuxLabel::try_from(label) + } + + // peer_label retrieves the label of the client on the other side of a socket. + pub fn peer_label(fd: F) -> Result { + // getsockopt manipulate options for the socket referred to by the file descriptor. + // https://man7.org/linux/man-pages/man2/getsockopt.2.html + match getsockopt(&fd, PeerSec) { + Ok(label) => match label.into_string() { + Ok(label_str) => SELinuxLabel::try_from(label_str), + Err(e) => Err(SELinuxError::PeerLabel(e.to_string())), + }, + Err(e) => Err(SELinuxError::PeerLabel(e.to_string())), + } + } + + // set_key_label takes a process label and tells the kernel to assign the + // label to the next kernel keyring that gets created. + pub fn set_key_label(&mut self, label: SELinuxLabel) -> Result { + Self::write_con(self, Path::new(KEY_LABEL_PATH), label.to_string().as_str()) + } + + // key_label retrieves the current kernel keyring label setting + pub fn key_label() -> Result { + let label = Self::read_con(Path::new(KEY_LABEL_PATH))?; + SELinuxLabel::try_from(label) + } + + // 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) { + let process_label = + Self::label(self, "kvm_process").or_else(|| Self::label(self, "process")); + (process_label, Self::label(self, "file")) + // TODO: use addMcs + } + + // 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) { + let process_label = + Self::label(self, "init_process").or_else(|| Self::label(self, "process")); + (process_label, Self::label(self, "file")) + // TODO: use addMcs + } + + // 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) { + return (None, None); + } + let process_label = Self::label(self, "process"); + let file_label = Self::label(self, "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; + + (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); + } + } + } + } + + // format_mount_label returns a string to be used by the mount command. + // Using the SELinux `context` mount option. + // Changing labels of files on mount points with this option can never be changed. + // format_mount_label returns a string to be used by the mount command. + // The format of this string will be used to alter the labeling of the mountpoint. + // The string returned is suitable to be used as the options field of the mount command. + // If you need to have additional mount point options, you can pass them in as + // the first parameter. The second parameter is the label that you wish to apply + // to all content in the mount point. + pub fn format_mount_label(src: &str, mount_label: &str) -> String { + Self::format_mount_label_by_type(src, mount_label, "context") + } + + // format_mount_label_by_type returns a string to be used by the mount command. + // Allow caller to specify the mount options. For example using the SELinux + // `fscontext` mount option would allow certain container processes to change + // labels of files created on the mount points, where as `context` option does not. + pub fn format_mount_label_by_type(src: &str, mount_label: &str, context_type: &str) -> String { + let mut formatted_src = src.to_owned(); + + if !mount_label.is_empty() { + if formatted_src.is_empty() { + formatted_src = format!("{}=\"{}\"", context_type, mount_label); + } else { + formatted_src = format!("{},{}=\"{}\"", formatted_src, context_type, mount_label); + } + } + formatted_src + } +} + +#[cfg(test)] +mod tests { + use crate::selinux::*; + + #[test] + fn test_format_mount_label() { + let src_array = ["", "src", "src"]; + let mount_label_array = ["foobar", "foobar", ""]; + let expected_array = ["context=\"foobar\"", "src,context=\"foobar\"", "src"]; + for (i, src) in src_array.iter().enumerate() { + let mount_label = mount_label_array[i]; + let expected = expected_array[i]; + assert_eq!(SELinux::format_mount_label(src, mount_label), expected); + } + } + + #[test] + fn test_format_mount_label_by_type() { + let src_array = ["", "src", "src"]; + let mount_label_array = ["foobar", "foobar", ""]; + let context_array = ["fscontext", "fscontext", "rootcontext"]; + let expected_array = ["fscontext=\"foobar\"", "src,fscontext=\"foobar\"", "src"]; + for (i, src) in src_array.iter().enumerate() { + let mount_label = mount_label_array[i]; + let context = context_array[i]; + let expected = expected_array[i]; + assert_eq!( + SELinux::format_mount_label_by_type(src, mount_label, context), + expected + ); + } + } +} diff --git a/experiment/selinux/src/tools/mod.rs b/experiment/selinux/src/tools/mod.rs new file mode 100644 index 00000000..8909a792 --- /dev/null +++ b/experiment/selinux/src/tools/mod.rs @@ -0,0 +1,5 @@ +mod sockopt; +mod xattr; + +pub use sockopt::*; +pub use xattr::*; diff --git a/experiment/selinux/src/tools/sockopt.rs b/experiment/selinux/src/tools/sockopt.rs new file mode 100644 index 00000000..25928642 --- /dev/null +++ b/experiment/selinux/src/tools/sockopt.rs @@ -0,0 +1,38 @@ +use nix::libc; +use nix::sys::socket::GetSockOpt; +use std::ffi::CString; +use std::os::fd::{AsFd, AsRawFd}; + +#[derive(Debug, Copy, Clone)] +pub struct PeerSec; + +// This function implements the GetSockOpt for PeerSec, retrieving the security context label +// of a socket file descriptor into a CString. +// This function utilizes nix's GetSockOpt implementation. +// https://github.com/nix-rust/nix/blob/50e4283b35f3f34e138d138fd889f7e3c424a5c2/src/sys/socket/mod.rs#L2219 +impl GetSockOpt for PeerSec { + type Val = CString; + + fn get(&self, fd: &F) -> nix::Result { + let mut len: libc::socklen_t = libc::c_int::MAX as libc::socklen_t; + let mut buf = vec![0u8; len as usize]; + let fd_i32 = fd.as_fd().as_raw_fd(); + + let ret = unsafe { + libc::getsockopt( + fd_i32, + libc::SOL_SOCKET, + libc::SO_PEERSEC, + buf.as_mut_ptr() as *mut libc::c_void, + &mut len, + ) + }; + + if ret == -1 { + return Err(nix::Error::last()); + } + + buf.truncate(len as usize); + Ok(CString::new(buf).unwrap()) + } +} diff --git a/experiment/selinux/src/xattrs/xattr.rs b/experiment/selinux/src/tools/xattr.rs similarity index 89% rename from experiment/selinux/src/xattrs/xattr.rs rename to experiment/selinux/src/tools/xattr.rs index a9ea43b0..2e44ce00 100644 --- a/experiment/selinux/src/xattrs/xattr.rs +++ b/experiment/selinux/src/tools/xattr.rs @@ -16,6 +16,7 @@ pub enum XattrError { EINTR(i32), } +// SELinux label is not so big, so we allocate 1024 bytes for the buffer. const INITIAL_BUF_SIZE: usize = 1024; pub trait PathXattr { @@ -25,11 +26,15 @@ pub trait PathXattr { fn lget_xattr(&self, attr: &str) -> Result; } -impl PathXattr for Path { +impl

PathXattr for P +where + P: AsRef, +{ // function similar with setxattr in golang.org/x/sys/unix repo. // set_xattr sets extended attributes on a file specified by its path. fn set_xattr(&self, attr: &str, data: &[u8]) -> Result<(), XattrError> { - match rfs::setxattr(self, attr, data, rfs::XattrFlags::CREATE) { + let path = self.as_ref(); + match rfs::setxattr(path, attr, data, rfs::XattrFlags::CREATE) { Ok(_) => Ok(()), Err(e) => { let errno = e.raw_os_error(); @@ -44,7 +49,8 @@ impl PathXattr for Path { // function similar with lsetxattr in golang.org/x/sys/unix repo. // lset_xattr sets extended attributes on a symbolic link. fn lset_xattr(&self, attr: &str, data: &[u8]) -> Result<(), XattrError> { - match rfs::lsetxattr(self, attr, data, rfs::XattrFlags::CREATE) { + let path = self.as_ref(); + match rfs::lsetxattr(path, attr, data, rfs::XattrFlags::CREATE) { Ok(_) => Ok(()), Err(e) => { let errno = e.raw_os_error(); @@ -59,12 +65,12 @@ impl PathXattr for Path { // function similar with getattr in go-selinux repo. // get_xattr returns the value of an extended attribute attr set for path. fn get_xattr(&self, attr: &str) -> Result { - // SELinux label is not so big, so we allocate 1024 bytes for the buffer. + let path = self.as_ref(); let mut buf_size = INITIAL_BUF_SIZE; let mut buf = vec![0u8; buf_size]; loop { - match rfs::getxattr(self, attr, &mut buf) { + match rfs::getxattr(path, attr, &mut buf) { Ok(size) => { if size == buf_size { buf_size *= 2; @@ -85,12 +91,12 @@ impl PathXattr for Path { // function similar with lgetxattr in go-selinux repo. // lget_xattr returns the value of an extended attribute attr set for path. fn lget_xattr(&self, attr: &str) -> Result { - // SELinux label is not so big, so we allocate 1024 bytes for the buffer. + let path = self.as_ref(); let mut buf_size = INITIAL_BUF_SIZE; let mut buf = vec![0u8; buf_size]; loop { - match rfs::lgetxattr(self, attr, &mut buf) { + match rfs::lgetxattr(path, attr, &mut buf) { Ok(size) => { if size == buf_size { buf_size *= 2; @@ -111,7 +117,7 @@ impl PathXattr for Path { #[cfg(test)] mod tests { - use crate::xattrs::*; + use crate::tools::*; use tempfile::NamedTempFile; #[test] diff --git a/experiment/selinux/src/xattrs/mod.rs b/experiment/selinux/src/xattrs/mod.rs deleted file mode 100644 index df4efc21..00000000 --- a/experiment/selinux/src/xattrs/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod xattr; - -pub use xattr::*;