mirror of
https://github.com/containers/youki
synced 2024-11-22 17:02:00 +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:
parent
e8b2af9b84
commit
c7d5992c01
23
experiment/selinux/Cargo.lock
generated
23
experiment/selinux/Cargo.lock
generated
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
```
|
||||
|
@ -1,2 +1,5 @@
|
||||
pub mod selinux;
|
||||
pub mod xattrs;
|
||||
pub mod selinux_label;
|
||||
pub mod tools;
|
||||
|
||||
pub use selinux::SELinux;
|
||||
|
43
experiment/selinux/src/main.rs
Normal file
43
experiment/selinux/src/main.rs
Normal file
@ -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::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<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)]
|
||||
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<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 {
|
||||
@ -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<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.
|
||||
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<i64, String> {
|
||||
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<P: AsRef<Path>>(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<PathBuf> {
|
||||
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<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 {
|
||||
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<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.
|
||||
// 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<PathBuf> {
|
||||
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::<i32>().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<String, SELinuxError> {
|
||||
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<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
|
||||
.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<String, SELinuxError> {
|
||||
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<String, SELinuxError> {
|
||||
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<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),
|
||||
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<String, SELinuxError> {
|
||||
// This function reads a given file descriptor into a string.
|
||||
pub fn read_con_fd<F: AsFd + Read>(file: &mut F) -> Result<String, SELinuxError> {
|
||||
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<String, SELinuxError> {
|
||||
if fpath.as_os_str().is_empty() {
|
||||
// read_con reads a label to a given file path, handling SELinux context.
|
||||
pub fn read_con<P: AsRef<Path>>(fpath: P) -> Result<String, SELinuxError> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
376
experiment/selinux/src/selinux_label.rs
Normal file
376
experiment/selinux/src/selinux_label.rs
Normal file
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
5
experiment/selinux/src/tools/mod.rs
Normal file
5
experiment/selinux/src/tools/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod sockopt;
|
||||
mod xattr;
|
||||
|
||||
pub use sockopt::*;
|
||||
pub use xattr::*;
|
38
experiment/selinux/src/tools/sockopt.rs
Normal file
38
experiment/selinux/src/tools/sockopt.rs
Normal file
@ -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),
|
||||
}
|
||||
|
||||
// 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<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.
|
||||
// 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<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 = 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<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 = 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]
|
@ -1,3 +0,0 @@
|
||||
mod xattr;
|
||||
|
||||
pub use xattr::*;
|
Loading…
Reference in New Issue
Block a user