| #[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::{ |
| c_str_ptr_to_path, os_str_to_c_string, CAllocatedBlock, OptionalNativeFunctions, |
| }; |
| use crate::{FileAccessMode, SecurityContext}; |
| |
| /// Security contexts back-ends. |
| pub mod back_end; |
| |
| use crate::label::back_end::BackEnd; |
| |
| /// Labeling handle used for look up operations. |
| #[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> { |
| /// Return `false` if security context translation must be performed. |
| #[must_use] |
| pub fn is_raw_format(&self) -> bool { |
| self.is_raw |
| } |
| |
| /// Return the managed raw pointer to [`selinux_sys::selabel_handle`]. |
| #[must_use] |
| pub fn as_ptr(&self) -> *const selinux_sys::selabel_handle { |
| self.pointer.as_ptr() |
| } |
| |
| /// Return the managed raw pointer to [`selinux_sys::selabel_handle`]. |
| #[must_use] |
| pub fn as_mut_ptr(&mut self) -> *mut selinux_sys::selabel_handle { |
| self.pointer.as_ptr() |
| } |
| |
| /// Initialize a labeling handle to be used for lookup operations. |
| /// |
| /// See: `selabel_open()`. |
| #[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()")) |
| } |
| |
| /// Obtain SELinux security context from a string label. |
| /// |
| /// See: `selabel_lookup()`. |
| #[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, &mut context, key.as_ptr(), key_type) }; |
| SecurityContext::from_result(proc_name, r, context, self.is_raw) |
| } |
| |
| /// Return digest of spec files and list of files used. |
| /// |
| /// See: `selabel_digest()`. |
| #[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(), |
| &mut digest_ptr, |
| &mut digest_size, |
| &mut spec_files_ptr, |
| &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, |
| )) |
| } |
| } |
| |
| /// Print SELinux labeling statistics. |
| /// |
| /// See: `selabel_stats()`. |
| #[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> { |
| /// Compare this instance to another one. |
| /// |
| /// See: `selabel_cmp()`. |
| #[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> { |
| /// Return [`Labeler`] with default parameters for `selinux_restorecon()`. |
| /// |
| /// See: `selinux_restorecon_default_handle()`. |
| #[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()")) |
| } |
| |
| /// Obtain SELinux security context from a path. |
| /// |
| /// See: `selabel_lookup()`. |
| #[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, &mut context, c_path.as_ptr(), mode) }; |
| SecurityContext::from_result(proc_name, r, context, self.is_raw) |
| } |
| |
| /// Obtain a best match SELinux security context. |
| /// |
| /// See: `selabel_lookup_best_match()`. |
| #[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(), |
| &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) |
| } |
| |
| /// Determine whether a direct or partial match is possible on a file path. |
| /// |
| /// See: `selabel_partial_match()`. |
| #[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()) }) |
| } |
| |
| /// Retrieve the partial matches digest and the xattr digest that applies |
| /// to the supplied path. |
| /// |
| /// This function requires `libselinux` version `3.0` or later. |
| /// |
| /// See: `selabel_get_digests_all_partial_matches()`. |
| #[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(), |
| &mut calculated_digest_ptr, |
| &mut xattr_digest_ptr, |
| &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, |
| }) |
| } |
| } |
| |
| /// Digest of spec files and list of files used. |
| /// |
| /// ⚠️ This instance does **NOT** own the `digest` or the `spec_files`. |
| #[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 } |
| } |
| |
| /// Digest of spec files. |
| #[must_use] |
| pub fn digest(&self) -> &[u8] { |
| self.digest |
| } |
| |
| /// List of files used. |
| #[must_use] |
| pub fn spec_files(&self) -> &[&'list Path] { |
| &self.spec_files |
| } |
| } |
| |
| /// Result of a partial match. |
| #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] |
| #[non_exhaustive] |
| pub enum PartialMatchesResult { |
| /// Digest matches. |
| Match, |
| /// Either digest does not match, or both digests are missing. |
| NoMatchOrMissing, |
| } |
| |
| /// Result of [`Labeler::get_digests_all_partial_matches_by_path`]. |
| #[derive(Debug)] |
| pub struct PartialMatchesDigests { |
| match_result: PartialMatchesResult, |
| xattr_digest: Option<CAllocatedBlock<u8>>, |
| calculated_digest: Option<CAllocatedBlock<u8>>, |
| digest_size: usize, |
| } |
| |
| impl PartialMatchesDigests { |
| /// Return match result. |
| #[must_use] |
| pub fn match_result(&self) -> PartialMatchesResult { |
| self.match_result |
| } |
| |
| /// Return xattr digest. |
| #[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) }) |
| } |
| |
| /// Return calculated digest. |
| #[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) }) |
| } |
| |
| /// Return digest length. |
| #[must_use] |
| pub fn len(&self) -> usize { |
| self.digest_size |
| } |
| |
| /// Return `true` if digest is empty. |
| #[must_use] |
| pub fn is_empty(&self) -> bool { |
| self.digest_size == 0 |
| } |
| } |