Skip to content

Commit

Permalink
Refactor the selinux crate
Browse files Browse the repository at this point in the history
Signed-off-by: utam0k <[email protected]>
  • Loading branch information
utam0k committed Dec 14, 2024
1 parent ae3f11b commit d908ab4
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 113 deletions.
2 changes: 1 addition & 1 deletion experiment/selinux/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
157 changes: 92 additions & 65 deletions experiment/selinux/src/selinux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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<String, SELinuxLabel>;

pub struct SELinux<'a> {
// for attr_path()
have_thread_self: AtomicBool,
attr_path_init_done: AtomicBool,
Expand All @@ -102,93 +106,116 @@ pub struct SELinux {
selinuxfs_init_done: AtomicBool,
selinuxfs: Option<PathBuf>,

// for policy_root()
policy_root_init_done: AtomicBool,
policy_root: Option<PathBuf>,
policy_root: PathBuf,

// for load_labels()
pub(crate) load_labels_init_done: AtomicBool,
pub(crate) labels: HashMap<String, SELinuxLabel>,
pub(crate) labels: SelinuxLabels,

// for read config and get config key
read_config_init_done: AtomicBool,
configs: HashMap<String, String>,

pub(crate) read_only_file_label: Option<SELinuxLabel>,
}

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<Self, SELinuxError> {
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<PathBuf, SELinuxError> {
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<String, SELinuxError> {
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<HashMap<String, SELinuxLabel>, 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<HashMap<String, String>, 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<String, SELinuxError> {
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.
Expand Down Expand Up @@ -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<File, SELinuxError> {
pub(crate) fn open_context_file(&self) -> Result<File, SELinuxError> {
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())),
Expand Down Expand Up @@ -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(),
)
Expand Down Expand Up @@ -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);
Expand All @@ -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]
Expand Down
62 changes: 15 additions & 47 deletions experiment/selinux/src/selinux_label.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -60,7 +58,7 @@ impl TryFrom<String> 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<P: AsRef<Path> + PathXattr>(
fpath: P,
Expand Down Expand Up @@ -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<SELinuxLabel>, Option<SELinuxLabel>) {
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"))
Expand All @@ -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<SELinuxLabel>, Option<SELinuxLabel>) {
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"))
Expand All @@ -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<SELinuxLabel>, Option<SELinuxLabel>) {
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<SELinuxLabel> {
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.
Expand Down

0 comments on commit d908ab4

Please sign in to comment.