1
0
mirror of https://github.com/containers/youki synced 2024-09-15 00:20:50 +02: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

View File

@ -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",

View File

@ -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"

View File

@ -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
```

View File

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

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

View File

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

View 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
);
}
}
}

View File

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

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

View File

@ -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]

View File

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