1
0
mirror of https://github.com/containers/youki synced 2024-11-23 01:11:58 +01:00

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>
This commit is contained in:
Hiroyuki Moriya 2024-08-16 21:25:52 +09:00 committed by GitHub
parent e8b2af9b84
commit c7d5992c01
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 891 additions and 295 deletions

@ -2,6 +2,18 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 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]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.5.0" version = "2.5.0"
@ -48,6 +60,15 @@ version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "memoffset"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.29.0" version = "0.29.0"
@ -58,6 +79,7 @@ dependencies = [
"cfg-if", "cfg-if",
"cfg_aliases", "cfg_aliases",
"libc", "libc",
"memoffset",
] ]
[[package]] [[package]]
@ -95,6 +117,7 @@ dependencies = [
name = "selinux" name = "selinux"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"nix", "nix",
"rustix", "rustix",
"tempfile", "tempfile",

@ -12,7 +12,8 @@ autoexamples = true
keywords = ["youki", "container", "selinux"] keywords = ["youki", "container", "selinux"]
[dependencies] [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"] } rustix = { version = "0.38.34", features = ["fs"] }
tempfile = "3.10.1" tempfile = "3.10.1"
thiserror = "1.0.61" thiserror = "1.0.61"

@ -3,5 +3,10 @@ Ref: https://github.com/containers/youki/issues/2718.
Reimplementation of [opencontainers/selinux](https://github.com/opencontainers/selinux) in Rust. Reimplementation of [opencontainers/selinux](https://github.com/opencontainers/selinux) in Rust.
Also selinux depends on xattr, but nix doesn't cover xattr function. Also selinux depends on xattr, but nix doesn't cover xattr function.
Therefore, this PR will implement xattr in Rust. 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. Please import and use this project.
```console
$ cargo run
```

@ -1,2 +1,5 @@
pub mod selinux; pub mod selinux;
pub mod xattrs; pub mod selinux_label;
pub mod tools;
pub use selinux::SELinux;

@ -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(())
}

@ -1,15 +1,65 @@
use crate::xattrs::*; use crate::selinux_label::SELinuxLabel;
use nix::errno::Errno; use nix::errno::Errno;
use nix::sys::statfs; use nix::sys::statfs;
use nix::unistd::gettid; use nix::unistd::gettid;
use std::fs::File; use std::collections::HashMap;
use std::io::Read; 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::os::fd::{AsFd, AsRawFd};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
const XATTR_NAME_SELINUX: &str = "security.selinux"; #[derive(Debug, Copy, Clone)]
const ERR_EMPTY_PATH: &str = "empty path"; 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<i32> 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)] #[derive(Debug, thiserror::Error)]
pub enum SELinuxError { pub enum SELinuxError {
@ -27,11 +77,44 @@ pub enum SELinuxError {
ReadConFd(String), ReadConFd(String),
#[error("Failed to call read_con for SELinux: {0}")] #[error("Failed to call read_con for SELinux: {0}")]
ReadCon(String), 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 { pub struct SELinux {
// for attr_path()
have_thread_self: AtomicBool, have_thread_self: AtomicBool,
init_done: AtomicBool, attr_path_init_done: AtomicBool,
// for selinuxfs
selinuxfs_init_done: AtomicBool,
selinuxfs: Option<PathBuf>,
// for policy_root()
policy_root_init_done: AtomicBool,
policy_root: Option<PathBuf>,
// for load_labels()
pub(crate) load_labels_init_done: AtomicBool,
pub(crate) labels: HashMap<String, SELinuxLabel>,
// 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 { impl Default for SELinux {
@ -44,268 +127,291 @@ impl SELinux {
pub fn new() -> Self { pub fn new() -> Self {
SELinux { SELinux {
have_thread_self: AtomicBool::new(false), 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. // This function returns policy_root.
// set_disabled disables SELinux support for the package. // Directories under policy root has configuration files etc.
pub fn set_disabled() { fn policy_root(&mut self) -> Option<&PathBuf> {
unimplemented!("not implemented yet") // 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<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);
}
}
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. // get_enabled returns whether SELinux is enabled or not.
pub fn get_enabled() -> bool { pub fn get_enabled(&mut self) -> bool {
unimplemented!("not implemented yet") 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. // verify_selinux_fs_mount verifies if the specified mount point is
// classIndex returns the int index for an object class in the loaded policy, // properly mounted as a writable SELinux filesystem.
// or -1 and an error. fn verify_selinux_fs_mount<P: AsRef<Path>>(mnt: P) -> bool {
pub fn class_index(class: &str) -> Result<i64, String> { let mnt = mnt.as_ref();
unimplemented!("not implemented yet") 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. // check_line_include_selinux_fs_mount_point returns a next selinuxfs mount point found,
// set_file_label sets the SELinux label for this path, following symlinks, or returns an error. // if there is one, or None in case of EOF or error.
pub fn set_file_label(fpath: &Path, label: &str) -> Result<(), SELinuxError> { fn check_line_include_selinux_fs_mount_point(line: &str) -> Option<PathBuf> {
if !fpath.exists() { if !line.contains(" - selinuxfs ") {
return Err(SELinuxError::SetFileLabel(ERR_EMPTY_PATH.to_string())); 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<PathBuf> {
// 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 { // check if selinuxfs is available before going the slow path
match fpath.set_xattr(XATTR_NAME_SELINUX, label.as_bytes()) { let fs = fs::read_to_string("/proc/filesystems").unwrap_or_default();
Ok(_) => break, if !fs.contains("\tselinuxfs\n") {
// When a system call is interrupted by a signal, it needs to be retried. return None;
Err(XattrError::EINTR(_)) => continue, }
Err(e) => {
return Err(SELinuxError::SetFileLabel(format!( // slow path: try to find among the mounts
"set_xattr failed: {}", match File::open("/proc/self/mountinfo") {
e 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<i64, SELinuxError> {
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::<i64>() {
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<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");
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. // This returns selinux enforce path by using selinux mountpoint.
// lset_file_label sets the SELinux label for this path, not following symlinks, // The enforce path dynamically changes SELinux mode at runtime,
// or returns an error. // while the config file need OS to reboot after changing the config file.
pub fn lset_file_label(fpath: &Path, label: &str) -> Result<(), SELinuxError> { fn selinux_enforce_path(&mut self) -> Option<PathBuf> {
if !fpath.exists() { let selinux_mountpoint = Self::get_selinux_mountpoint(self);
return Err(SELinuxError::LSetFileLabel(ERR_EMPTY_PATH.to_string())); selinux_mountpoint.map(|m| m.join("enforce"))
} }
loop { // enforce_mode returns the current SELinux mode Enforcing, Permissive, Disabled
match fpath.lset_xattr(XATTR_NAME_SELINUX, label.as_bytes()) { pub fn enforce_mode(&mut self) -> SELinuxMode {
Ok(_) => break, let mode = match Self::selinux_enforce_path(self) {
// When a system call is interrupted by a signal, it needs to be retried. Some(enforce_path) => match fs::read_to_string(enforce_path) {
Err(XattrError::EINTR(_)) => continue, Ok(content) => content.trim().parse::<i32>().unwrap_or(-1),
Err(e) => { Err(_) => -1,
return Err(SELinuxError::LSetFileLabel(format!( },
"lset_xattr failed: {}", None => -1,
e };
))); 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. // This function updates the enforce mode of selinux.
// fileLabel returns the SELinux label for this path, following symlinks, // Disabled is not valid, since this needs to be set at boot time.
// or returns an error. pub fn set_enforce_mode(&mut self, mode: SELinuxMode) -> Result<(), SELinuxError> {
pub fn file_label(fpath: &Path) -> Result<String, SELinuxError> { let enforce_path = Self::selinux_enforce_path(self).ok_or_else(|| {
if !fpath.exists() { SELinuxError::SetEnforceMode("can't get selinux enforce path".to_string())
return Err(SELinuxError::FileLabel(ERR_EMPTY_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<P: AsRef<Path>>(
&mut self,
fpath: P,
val: &str,
) -> Result<usize, SELinuxError> {
let path = fpath.as_ref();
if path.as_os_str().is_empty() {
return Err(SELinuxError::WriteCon(ERR_EMPTY_PATH.to_string()));
} }
fpath if val.is_empty() && !Self::get_enabled(self) {
.get_xattr(XATTR_NAME_SELINUX) return Err(SELinuxError::WriteCon("SELinux is not enabled".to_string()));
.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<String, SELinuxError> {
if !fpath.exists() {
return Err(SELinuxError::LFileLabel(ERR_EMPTY_PATH.to_string()));
} }
fpath
.lget_xattr(XATTR_NAME_SELINUX)
.map_err(|e| SELinuxError::LFileLabel(e.to_string()))
}
// function similar with setFSCreateLabel in go-selinux repo. let mut out = OpenOptions::new()
// set_fscreate_label sets the default label the kernel which the kernel is using .write(true)
// for file system objects. .create(false)
pub fn set_fscreate_label(&self, label: &str) -> Result<(), SELinuxError> { .open(fpath)
return Self::write_con(self.attr_path("fscreate").as_path(), label); .map_err(|e| SELinuxError::WriteCon(format!("failed to open file: {}", e)))?;
}
// function similar with fsCreateLabel in go-selinux repo. Self::is_proc_handle(&out)?;
// fscreate_label returns the default label the kernel which the kernel is using match out.write(val.as_bytes()) {
// for file system objects created by this task. "" indicates default. Ok(u) => Ok(u),
pub fn fscreate_label(&self) -> Result<String, SELinuxError> { Err(e) => Err(SELinuxError::WriteCon(format!(
return Self::read_con(self.attr_path("fscreate").as_path()); "failed to write in file: {}",
} e
))),
// 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<String, SELinuxError> {
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<String, SELinuxError> {
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<String, SELinuxError> {
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),
} }
} }
// function similar with KeyLabel in go-selinux repo. // This function checks whether this file is on the procfs filesystem.
// 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.
pub fn is_proc_handle(file: &File) -> Result<(), SELinuxError> { pub fn is_proc_handle(file: &File) -> Result<(), SELinuxError> {
loop { loop {
match statfs::fstatfs(file.as_fd()) { match statfs::fstatfs(file.as_fd()) {
@ -328,8 +434,8 @@ impl SELinux {
Ok(()) Ok(())
} }
// function similar with readConFd in go-selinux repo. // This function reads a given file descriptor into a string.
pub fn read_con_fd(file: &mut File) -> Result<String, SELinuxError> { pub fn read_con_fd<F: AsFd + Read>(file: &mut F) -> Result<String, SELinuxError> {
let mut data = String::new(); let mut data = String::new();
file.read_to_string(&mut data) file.read_to_string(&mut data)
.map_err(|e| SELinuxError::ReadConFd(e.to_string()))?; .map_err(|e| SELinuxError::ReadConFd(e.to_string()))?;
@ -339,9 +445,10 @@ impl SELinux {
Ok(trimmed_data.to_string()) Ok(trimmed_data.to_string())
} }
// function similar with readCon in go-selinux repo. // read_con reads a label to a given file path, handling SELinux context.
pub fn read_con(fpath: &Path) -> Result<String, SELinuxError> { pub fn read_con<P: AsRef<Path>>(fpath: P) -> Result<String, SELinuxError> {
if fpath.as_os_str().is_empty() { let path = fpath.as_ref();
if path.as_os_str().is_empty() {
return Err(SELinuxError::ReadCon(ERR_EMPTY_PATH.to_string())); return Err(SELinuxError::ReadCon(ERR_EMPTY_PATH.to_string()));
} }
let mut in_file = File::open(fpath) let mut in_file = File::open(fpath)
@ -351,18 +458,17 @@ impl SELinux {
Self::read_con_fd(&mut in_file) 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 // attr_path determines the correct file path for accessing SELinux
// attributes of a process or thread in a Linux environment. // attributes of a process or thread in a Linux environment.
pub fn attr_path(&self, attr: &str) -> PathBuf { pub fn attr_path(&self, attr: &str) -> PathBuf {
// Linux >= 3.17 provides this // Linux >= 3.17 provides this
const THREAD_SELF_PREFIX: &str = "/proc/thread-self/attr"; const THREAD_SELF_PREFIX: &str = "/proc/thread-self/attr";
// Avoiding code conflicts and ensuring thread-safe execution once only. // 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 path = PathBuf::from(THREAD_SELF_PREFIX);
let is_dir = path.is_dir(); let is_dir = path.is_dir();
self.have_thread_self.store(is_dir, Ordering::SeqCst); 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) { if self.have_thread_self.load(Ordering::SeqCst) {
return PathBuf::from(&format!("{}/{}", THREAD_SELF_PREFIX, attr)); return PathBuf::from(&format!("{}/{}", THREAD_SELF_PREFIX, attr));
@ -375,9 +481,11 @@ impl SELinux {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::selinux::*; use crate::selinux::*;
use std::fs::{self, File}; use std::fs::File;
use std::io::Write; use std::io::Write;
use std::path::Path; use std::path::Path;
use std::str;
use tempfile::NamedTempFile;
fn create_temp_file(content: &[u8], file_name: &str) { fn create_temp_file(content: &[u8], file_name: &str) {
let path = Path::new(file_name); let path = Path::new(file_name);
@ -386,49 +494,21 @@ mod tests {
file.sync_all().expect("Failed to sync file"); 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] #[test]
fn test_read_con_fd() { fn test_read_con_fd() {
let content_array: Vec<&[u8]> = let content_array: Vec<&[u8]> =
vec![b"Hello, world\0", b"Hello, world\0\0\0", b"Hello,\0world"]; vec![b"Hello, world\0", b"Hello, world\0\0\0", b"Hello,\0world"];
let expected_array = ["Hello, world", "Hello, world", "Hello,\0world"]; let expected_array = ["Hello, world", "Hello, world", "Hello,\0world"];
let file_name = "test.txt";
for (i, content) in content_array.iter().enumerate() { for (i, content) in content_array.iter().enumerate() {
let expected = expected_array[i]; 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. // 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"); let result = SELinux::read_con_fd(&mut file).expect("Failed to read file");
assert_eq!(result, expected); 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); assert_eq!(expected_path, actual_path);
// Test with not having "/proc/thread-self/attr" path by setting HAVE_THREAD_SELF as false // 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); selinux.have_thread_self.store(false, Ordering::SeqCst);
let thread_id = gettid(); let thread_id = gettid();
let expected_name = &format!("/proc/self/task/{}/attr/{}", thread_id, attr); 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),
}
}
}
} }

@ -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<String>,
}
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<String> for SELinuxLabel {
type Error = SELinuxError;
fn try_from(label: String) -> Result<Self, SELinuxError> {
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<P: AsRef<Path> + 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<P: AsRef<Path> + 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<P: AsRef<Path> + PathXattr>(fpath: P) -> Result<SELinuxLabel, SELinuxError> {
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<P: AsRef<Path> + PathXattr>(fpath: P) -> Result<SELinuxLabel, SELinuxError> {
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<usize, SELinuxError> {
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<SELinuxLabel, SELinuxError> {
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<SELinuxLabel, SELinuxError> {
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<SELinuxLabel, SELinuxError> {
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<usize, SELinuxError> {
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<usize, SELinuxError> {
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<usize, SELinuxError> {
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<SELinuxLabel, SELinuxError> {
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<SELinuxLabel, SELinuxError> {
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<F: AsFd>(fd: F) -> Result<SELinuxLabel, SELinuxError> {
// 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<usize, SELinuxError> {
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<SELinuxLabel, SELinuxError> {
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<SELinuxLabel>, Option<SELinuxLabel>) {
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<SELinuxLabel>, Option<SELinuxLabel>) {
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<SELinuxLabel>, Option<SELinuxLabel>) {
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<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);
}
}
}
}
// 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
);
}
}
}

@ -0,0 +1,5 @@
mod sockopt;
mod xattr;
pub use sockopt::*;
pub use xattr::*;

@ -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<F: AsFd>(&self, fd: &F) -> nix::Result<Self::Val> {
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())
}
}

@ -16,6 +16,7 @@ pub enum XattrError {
EINTR(i32), EINTR(i32),
} }
// SELinux label is not so big, so we allocate 1024 bytes for the buffer.
const INITIAL_BUF_SIZE: usize = 1024; const INITIAL_BUF_SIZE: usize = 1024;
pub trait PathXattr { pub trait PathXattr {
@ -25,11 +26,15 @@ pub trait PathXattr {
fn lget_xattr(&self, attr: &str) -> Result<String, XattrError>; fn lget_xattr(&self, attr: &str) -> Result<String, XattrError>;
} }
impl PathXattr for Path { impl<P> PathXattr for P
where
P: AsRef<Path>,
{
// function similar with setxattr in golang.org/x/sys/unix repo. // function similar with setxattr in golang.org/x/sys/unix repo.
// set_xattr sets extended attributes on a file specified by its path. // set_xattr sets extended attributes on a file specified by its path.
fn set_xattr(&self, attr: &str, data: &[u8]) -> Result<(), XattrError> { 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(()), Ok(_) => Ok(()),
Err(e) => { Err(e) => {
let errno = e.raw_os_error(); 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. // function similar with lsetxattr in golang.org/x/sys/unix repo.
// lset_xattr sets extended attributes on a symbolic link. // lset_xattr sets extended attributes on a symbolic link.
fn lset_xattr(&self, attr: &str, data: &[u8]) -> Result<(), XattrError> { 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(()), Ok(_) => Ok(()),
Err(e) => { Err(e) => {
let errno = e.raw_os_error(); let errno = e.raw_os_error();
@ -59,12 +65,12 @@ impl PathXattr for Path {
// function similar with getattr in go-selinux repo. // function similar with getattr in go-selinux repo.
// get_xattr returns the value of an extended attribute attr set for path. // get_xattr returns the value of an extended attribute attr set for path.
fn get_xattr(&self, attr: &str) -> Result<String, XattrError> { fn get_xattr(&self, attr: &str) -> Result<String, XattrError> {
// 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_size = INITIAL_BUF_SIZE;
let mut buf = vec![0u8; buf_size]; let mut buf = vec![0u8; buf_size];
loop { loop {
match rfs::getxattr(self, attr, &mut buf) { match rfs::getxattr(path, attr, &mut buf) {
Ok(size) => { Ok(size) => {
if size == buf_size { if size == buf_size {
buf_size *= 2; buf_size *= 2;
@ -85,12 +91,12 @@ impl PathXattr for Path {
// function similar with lgetxattr in go-selinux repo. // function similar with lgetxattr in go-selinux repo.
// lget_xattr returns the value of an extended attribute attr set for path. // lget_xattr returns the value of an extended attribute attr set for path.
fn lget_xattr(&self, attr: &str) -> Result<String, XattrError> { fn lget_xattr(&self, attr: &str) -> Result<String, XattrError> {
// 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_size = INITIAL_BUF_SIZE;
let mut buf = vec![0u8; buf_size]; let mut buf = vec![0u8; buf_size];
loop { loop {
match rfs::lgetxattr(self, attr, &mut buf) { match rfs::lgetxattr(path, attr, &mut buf) {
Ok(size) => { Ok(size) => {
if size == buf_size { if size == buf_size {
buf_size *= 2; buf_size *= 2;
@ -111,7 +117,7 @@ impl PathXattr for Path {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::xattrs::*; use crate::tools::*;
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
#[test] #[test]

@ -1,3 +0,0 @@
mod xattr;
pub use xattr::*;