#[cfg(test)]
mod tests;
use alloc::ffi::CString;
use core::ffi::CStr;
use core::hash::Hash;
use core::marker::PhantomData;
use core::{cmp, iter, ptr, slice};
use std::io;
use std::os::raw::{c_char, c_int, c_void};
use std::path::Path;
use crate::errors::{Error, Result};
use crate::utils::{
CAllocatedBlock, OptionalNativeFunctions, c_str_ptr_to_path, os_str_to_c_string,
};
use crate::{FileAccessMode, SecurityContext};
pub mod back_end;
use crate::label::back_end::BackEnd;
#[derive(Debug)]
pub struct Labeler<T: BackEnd> {
pointer: ptr::NonNull<selinux_sys::selabel_handle>,
_phantom_data1: PhantomData<selinux_sys::selabel_handle>,
_phantom_data2: PhantomData<T>,
is_raw: bool,
}
impl<T: BackEnd> Labeler<T> {
#[must_use]
pub fn is_raw_format(&self) -> bool {
self.is_raw
}
#[must_use]
pub fn as_ptr(&self) -> *const selinux_sys::selabel_handle {
self.pointer.as_ptr()
}
#[must_use]
pub fn as_mut_ptr(&mut self) -> *mut selinux_sys::selabel_handle {
self.pointer.as_ptr()
}
#[doc(alias = "selabel_open")]
pub fn new(options: &[(c_int, *const c_void)], raw_format: bool) -> Result<Self> {
let options: Vec<selinux_sys::selinux_opt> = options
.iter()
.map(|&(type_, value)| selinux_sys::selinux_opt {
type_,
value: value.cast(),
})
.collect();
let count = options.len().try_into()?;
let options_ptr = if count == 0 {
ptr::null()
} else {
options.as_ptr()
};
let pointer = unsafe { selinux_sys::selabel_open(T::BACK_END, options_ptr, count) };
ptr::NonNull::new(pointer)
.map(|pointer| Self {
pointer,
_phantom_data1: PhantomData,
_phantom_data2: PhantomData,
is_raw: raw_format,
})
.ok_or_else(|| Error::last_io_error("selabel_open()"))
}
#[doc(alias = "selabel_lookup")]
pub fn look_up(&self, key: &CStr, key_type: c_int) -> Result<SecurityContext<'_>> {
let (proc, proc_name): (unsafe extern "C" fn(_, _, _, _) -> _, _) = if self.is_raw {
(selinux_sys::selabel_lookup_raw, "selabel_lookup_raw()")
} else {
(selinux_sys::selabel_lookup, "selabel_lookup()")
};
let handle = self.pointer.as_ptr();
let mut context: *mut c_char = ptr::null_mut();
let r = unsafe { proc(handle, &raw mut context, key.as_ptr(), key_type) };
SecurityContext::from_result(proc_name, r, context, self.is_raw)
}
#[doc(alias = "selabel_digest")]
pub fn digest(&'_ self) -> Result<Digest<'_>> {
let mut digest_ptr: *mut u8 = ptr::null_mut();
let mut digest_size = 0;
let mut spec_files_ptr: *mut *mut c_char = ptr::null_mut();
let mut num_spec_files = 0;
let r: c_int = unsafe {
selinux_sys::selabel_digest(
self.pointer.as_ptr(),
&raw mut digest_ptr,
&raw mut digest_size,
&raw mut spec_files_ptr,
&raw mut num_spec_files,
)
};
if r == -1_i32 {
Err(Error::last_io_error("selabel_digest()"))
} else {
Ok(Digest::new(
digest_ptr,
digest_size,
spec_files_ptr.cast(),
num_spec_files,
))
}
}
#[doc(alias = "selabel_stats")]
pub fn log_statistics(&self) {
unsafe { selinux_sys::selabel_stats(self.pointer.as_ptr()) }
}
}
impl<T: BackEnd> Drop for Labeler<T> {
fn drop(&mut self) {
let pointer = self.pointer.as_ptr();
self.pointer = ptr::NonNull::dangling();
unsafe { selinux_sys::selabel_close(pointer) };
}
}
impl<T: BackEnd> PartialOrd<Labeler<T>> for Labeler<T> {
#[doc(alias = "selabel_cmp")]
fn partial_cmp(&self, other: &Labeler<T>) -> Option<cmp::Ordering> {
let r = unsafe { selinux_sys::selabel_cmp(self.pointer.as_ptr(), other.pointer.as_ptr()) };
match r {
selinux_sys::selabel_cmp_result::SELABEL_SUBSET => Some(cmp::Ordering::Less),
selinux_sys::selabel_cmp_result::SELABEL_EQUAL => Some(cmp::Ordering::Equal),
selinux_sys::selabel_cmp_result::SELABEL_SUPERSET => Some(cmp::Ordering::Greater),
_ => None,
}
}
}
impl<T: BackEnd> PartialEq for Labeler<T> {
fn eq(&self, other: &Self) -> bool {
self.partial_cmp(other) == Some(cmp::Ordering::Equal)
}
}
impl Labeler<back_end::File> {
#[doc(alias = "selinux_restorecon_default_handle")]
pub fn restorecon_default(raw_format: bool) -> Result<Self> {
let pointer = unsafe { selinux_sys::selinux_restorecon_default_handle() };
ptr::NonNull::new(pointer)
.map(|pointer| Self {
pointer,
_phantom_data1: PhantomData,
_phantom_data2: PhantomData,
is_raw: raw_format,
})
.ok_or_else(|| Error::last_io_error("selinux_restorecon_default_handle()"))
}
#[doc(alias = "selabel_lookup")]
pub fn look_up_by_path(
&self,
path: impl AsRef<Path>,
mode: Option<FileAccessMode>,
) -> Result<SecurityContext<'_>> {
let (proc, proc_name): (unsafe extern "C" fn(_, _, _, _) -> _, _) = if self.is_raw {
(selinux_sys::selabel_lookup_raw, "selabel_lookup_raw()")
} else {
(selinux_sys::selabel_lookup, "selabel_lookup()")
};
let handle = self.pointer.as_ptr();
let mut context: *mut c_char = ptr::null_mut();
let c_path = os_str_to_c_string(path.as_ref().as_os_str())?;
#[allow(clippy::cast_possible_wrap, clippy::as_conversions)]
let mode = mode.map_or(0, FileAccessMode::mode) as c_int;
let r = unsafe { proc(handle, &raw mut context, c_path.as_ptr(), mode) };
SecurityContext::from_result(proc_name, r, context, self.is_raw)
}
#[doc(alias = "selabel_lookup_best_match")]
pub fn look_up_best_match_by_path(
&self,
path: impl AsRef<Path>,
alias_paths: &[impl AsRef<Path>],
mode: Option<FileAccessMode>,
) -> Result<SecurityContext<'_>> {
let (proc, proc_name): (unsafe extern "C" fn(_, _, _, _, _) -> _, _) = if self.is_raw {
let proc_name = "selabel_lookup_best_match_raw()";
(selinux_sys::selabel_lookup_best_match_raw, proc_name)
} else {
let proc_name = "selabel_lookup_best_match()";
(selinux_sys::selabel_lookup_best_match, proc_name)
};
let aliases_storage: Vec<CString>;
let mut aliases: Vec<*const c_char>;
let aliases_ptr = if alias_paths.is_empty() {
ptr::null_mut()
} else {
aliases_storage = alias_paths
.iter()
.map(AsRef::as_ref)
.map(Path::as_os_str)
.map(os_str_to_c_string)
.collect::<Result<Vec<CString>>>()?;
aliases = aliases_storage
.iter()
.map(CString::as_c_str)
.map(CStr::as_ptr)
.chain(iter::once(ptr::null()))
.collect();
aliases.as_mut_ptr()
};
let mut context: *mut c_char = ptr::null_mut();
let c_path = os_str_to_c_string(path.as_ref().as_os_str())?;
let r = unsafe {
#[allow(clippy::cast_possible_wrap, clippy::as_conversions)]
proc(
self.pointer.as_ptr(),
&raw mut context,
c_path.as_ptr(),
aliases_ptr,
mode.map_or(0, FileAccessMode::mode) as c_int,
)
};
SecurityContext::from_result(proc_name, r, context, self.is_raw)
}
#[doc(alias = "selabel_partial_match")]
pub fn partial_match_by_path(&self, path: impl AsRef<Path>) -> Result<bool> {
let c_path = os_str_to_c_string(path.as_ref().as_os_str())?;
Ok(unsafe { selinux_sys::selabel_partial_match(self.pointer.as_ptr(), c_path.as_ptr()) })
}
#[doc(alias = "selabel_get_digests_all_partial_matches")]
pub fn get_digests_all_partial_matches_by_path(
&self,
path: impl AsRef<Path>,
) -> Result<PartialMatchesDigests> {
let c_path = os_str_to_c_string(path.as_ref().as_os_str())?;
let mut calculated_digest_ptr: *mut u8 = ptr::null_mut();
let mut xattr_digest_ptr: *mut u8 = ptr::null_mut();
let mut digest_size = 0;
let r = unsafe {
(OptionalNativeFunctions::get().selabel_get_digests_all_partial_matches)(
self.pointer.as_ptr(),
c_path.as_ptr(),
&raw mut calculated_digest_ptr,
&raw mut xattr_digest_ptr,
&raw mut digest_size,
)
};
let match_result = if r {
PartialMatchesResult::Match
} else {
let err = io::Error::last_os_error();
match err.raw_os_error() {
None | Some(0_i32) => PartialMatchesResult::NoMatchOrMissing,
_ => {
let proc_name = "selabel_get_digests_all_partial_matches()";
return Err(Error::from_io_path(proc_name, path.as_ref(), err));
}
}
};
Ok(PartialMatchesDigests {
match_result,
xattr_digest: CAllocatedBlock::new(xattr_digest_ptr),
calculated_digest: CAllocatedBlock::new(calculated_digest_ptr),
digest_size,
})
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Digest<'list> {
digest: &'list [u8],
spec_files: Vec<&'list Path>,
}
impl<'list> Digest<'list> {
fn new(
digest: *const u8,
digest_size: usize,
spec_files: *const *const c_char,
num_spec_files: usize,
) -> Self {
let digest = if digest.is_null() || digest_size == 0 {
&[]
} else {
unsafe { slice::from_raw_parts(digest, digest_size) }
};
let spec_files = if spec_files.is_null() || num_spec_files == 0 {
Vec::default()
} else {
unsafe { slice::from_raw_parts(spec_files, num_spec_files) }
.iter()
.take_while(|&&ptr| !ptr.is_null())
.map(|&ptr| c_str_ptr_to_path(ptr))
.collect()
};
Self { digest, spec_files }
}
#[must_use]
pub fn digest(&self) -> &[u8] {
self.digest
}
#[must_use]
pub fn spec_files(&self) -> &[&'list Path] {
&self.spec_files
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[non_exhaustive]
pub enum PartialMatchesResult {
Match,
NoMatchOrMissing,
}
#[derive(Debug)]
pub struct PartialMatchesDigests {
match_result: PartialMatchesResult,
xattr_digest: Option<CAllocatedBlock<u8>>,
calculated_digest: Option<CAllocatedBlock<u8>>,
digest_size: usize,
}
impl PartialMatchesDigests {
#[must_use]
pub fn match_result(&self) -> PartialMatchesResult {
self.match_result
}
#[must_use]
pub fn xattr_digest(&self) -> Option<&[u8]> {
self.xattr_digest
.as_ref()
.map(|block| unsafe { slice::from_raw_parts(block.pointer.as_ptr(), self.digest_size) })
}
#[must_use]
pub fn calculated_digest(&self) -> Option<&[u8]> {
self.calculated_digest
.as_ref()
.map(|block| unsafe { slice::from_raw_parts(block.pointer.as_ptr(), self.digest_size) })
}
#[must_use]
pub fn len(&self) -> usize {
self.digest_size
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.digest_size == 0
}
}