blob: f36d8a2d03731c2ffd98549cc2161fbf18610409 [file] [log] [blame]
#![cfg(target_os = "linux")]
#![doc = include_str!("../README.md")]
#![doc(html_root_url = "https://docs.rs/selinux/0.4.6")]
#![allow(clippy::upper_case_acronyms)]
#![warn(
unsafe_op_in_unsafe_fn,
missing_docs,
clippy::must_use_candidate,
clippy::default_numeric_fallback,
clippy::single_char_lifetime_names,
clippy::alloc_instead_of_core,
clippy::std_instead_of_core,
clippy::std_instead_of_alloc
)]
//
// https://rust-lang.github.io/api-guidelines/checklist.html
//
// Activate these lints to clean up the code and hopefully detect some issues.
//#![warn(clippy::all, clippy::pedantic, clippy::restriction)]
#![allow(
clippy::absolute_paths,
clippy::arbitrary_source_item_ordering,
clippy::doc_markdown,
clippy::error_impl_error,
clippy::exhaustive_structs,
clippy::expect_used,
clippy::field_scoped_visibility_modifiers,
clippy::impl_trait_in_params,
clippy::implicit_return,
clippy::indexing_slicing,
clippy::min_ident_chars,
clippy::missing_docs_in_private_items,
clippy::missing_errors_doc,
clippy::missing_inline_in_public_items,
clippy::missing_trait_methods,
clippy::mod_module_files,
clippy::module_name_repetitions,
clippy::multiple_unsafe_ops_per_block,
clippy::needless_pass_by_value,
clippy::pattern_type_mismatch,
clippy::pub_use,
clippy::pub_with_shorthand,
clippy::question_mark_used,
clippy::separated_literal_suffix,
clippy::shadow_reuse,
clippy::shadow_unrelated,
clippy::single_call_fn,
clippy::undocumented_unsafe_blocks,
clippy::unused_self,
clippy::unused_trait_names
)]
#![cfg_attr(test, allow(clippy::unwrap_used))]
#[cfg(test)]
mod tests;
/// Access Vector Cache.
pub mod avc;
/// SELinux call backs.
pub mod call_back;
/// Restore file(s) default SELinux security contexts.
pub mod context_restore;
/// Errors.
pub mod errors;
/// Labeling files.
pub mod label;
/// SELinux paths.
pub mod path;
/// SELinux policies.
pub mod policy;
/// Utilities.
pub mod utils;
extern crate alloc;
use alloc::borrow::Cow;
use alloc::ffi::CString;
use core::ffi::CStr;
use core::marker::PhantomData;
use core::mem::MaybeUninit;
use core::{cmp, fmt, mem, ptr, slice, str};
use std::collections::{hash_map, HashMap};
use std::io;
use std::os::raw::{c_char, c_int, c_uint, c_void};
use std::os::unix::io::AsRawFd;
use std::path::Path;
use selinux_sys::pid_t;
#[macro_use]
extern crate bitflags;
use errors::{Error, Result};
use utils::{
c_str_to_non_null_ptr, os_str_to_c_string, ret_val_to_result, ret_val_to_result_with_path,
str_to_c_string, CAllocatedBlock, OptionalNativeFunctions,
};
/// Red, green and blue components of a color.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct RGB {
/// Red component.
pub red: u8,
/// Green component.
pub green: u8,
/// Blue component.
pub blue: u8,
}
impl RGB {
/// Create a new instance.
#[must_use]
pub fn new(red: u8, green: u8, blue: u8) -> Self {
Self { red, green, blue }
}
}
/// Background and foreground colors.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct LayerColors {
/// Background color.
pub background: RGB,
/// Foreground color.
pub foreground: RGB,
}
impl LayerColors {
/// Create a new instance.
#[must_use]
pub fn new(background: RGB, foreground: RGB) -> Self {
Self {
background,
foreground,
}
}
}
/// Colors of a security context.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct SecurityContextColors {
/// Background and foreground colors of SELinux user.
pub user: LayerColors,
/// Background and foreground colors of SELinux role.
pub role: LayerColors,
/// Background and foreground colors of SELinux type.
pub the_type: LayerColors,
/// Background and foreground colors of SELinux range.
pub range: LayerColors,
}
impl SecurityContextColors {
/// Create a new instance.
#[must_use]
pub fn new(
user: LayerColors,
role: LayerColors,
the_type: LayerColors,
range: LayerColors,
) -> Self {
Self {
user,
role,
the_type,
range,
}
}
}
/// SELinux security context.
#[derive(Debug)]
pub struct SecurityContext<'context> {
context: ptr::NonNull<c_char>,
size: Option<usize>,
is_raw: bool,
context_owned: bool,
_phantom_data: PhantomData<&'context c_char>,
}
impl<'context> SecurityContext<'context> {
/// 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 [`c_char`].
#[must_use]
pub fn as_ptr(&self) -> *const c_char {
self.context.as_ptr()
}
/// Return the managed raw pointer to [`c_char`].
#[must_use]
pub fn as_mut_ptr(&mut self) -> *mut c_char {
self.context.as_ptr()
}
/// Return the security context's byte slice.
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
self.size.map_or_else(
|| unsafe { CStr::from_ptr(self.context.as_ptr()).to_bytes() },
|size| unsafe { slice::from_raw_parts(self.context.as_ptr().cast(), size) },
)
}
/// Return the string value of this security context.
///
/// If the context is empty, then this returns `Ok(None)`.
pub fn to_c_string(&self) -> Result<Option<Cow<CStr>>> {
if let Some(size) = self.size {
let bytes = unsafe { slice::from_raw_parts(self.context.as_ptr().cast(), size) };
if bytes.is_empty() {
Ok(None)
} else if bytes.last().copied() == Some(0) {
if let Ok(result) = CStr::from_bytes_with_nul(bytes) {
Ok(Some(Cow::Borrowed(result)))
} else {
let op = "CStr::from_bytes_with_nul()";
Err(Error::from_io(op, io::ErrorKind::InvalidData.into()))
}
} else if let Ok(result) = CString::new(bytes) {
Ok(Some(Cow::Owned(result)))
} else {
let op = "CString::new()";
Err(Error::from_io(op, io::ErrorKind::InvalidData.into()))
}
} else {
let result = unsafe { CStr::from_ptr(self.context.as_ptr()) };
Ok(Some(Cow::Borrowed(result)))
}
}
/// Return the security context identified by `context`.
///
/// ⚠️ The returned instance does **NOT** own the provided context.
/// When the returned instance get dropped, it will **NOT** deallocate
/// the provided context.
#[must_use]
pub fn from_c_str(c_context: &'context CStr, raw_format: bool) -> SecurityContext<'context> {
Self {
context: c_str_to_non_null_ptr(c_context),
size: Some(c_context.to_bytes().len()),
is_raw: raw_format,
context_owned: false,
_phantom_data: PhantomData,
}
}
/// Return the security context of the current process.
///
/// See: `getcon()`.
#[doc(alias = "getcon")]
pub fn current(raw_format: bool) -> Result<Self> {
let (proc, proc_name): (unsafe extern "C" fn(_) -> _, _) = if raw_format {
(selinux_sys::getcon_raw, "getcon_raw()")
} else {
(selinux_sys::getcon, "getcon()")
};
let mut context: *mut c_char = ptr::null_mut();
let r = unsafe { proc(&mut context) };
Self::from_result(proc_name, r, context, raw_format)
}
/// Return the security context of the current process before the last exec.
///
/// See: `getprevcon()`.
#[doc(alias = "getprevcon")]
pub fn previous(raw_format: bool) -> Result<Self> {
Self::previous_of_process(None, raw_format)
}
/// Return the security context, of the current or specified process, before the last exec.
///
/// If `process_id` is `None`, then the current process is queried.
///
/// Specifying a particular process id (`process_id.is_some()`) requires `libselinux` version
/// `3.5` or later.
///
/// See: `getprevcon()`, `getpidprevcon()`.
#[doc(alias = "getprevcon")]
#[doc(alias = "getpidprevcon")]
pub fn previous_of_process(process_id: Option<pid_t>, raw_format: bool) -> Result<Self> {
let mut context: *mut c_char = ptr::null_mut();
if let Some(process_id) = process_id {
let onf = OptionalNativeFunctions::get();
let (proc, proc_name): (unsafe extern "C" fn(_, _) -> _, _) = if raw_format {
(onf.getpidprevcon_raw, "getpidprevcon_raw()")
} else {
(onf.getpidprevcon, "getpidprevcon()")
};
let r = unsafe { proc(process_id, &mut context) };
Self::from_result_with_pid(proc_name, r, context, process_id, raw_format)
} else {
let (proc, proc_name): (unsafe extern "C" fn(_) -> _, _) = if raw_format {
(selinux_sys::getprevcon_raw, "getprevcon_raw()")
} else {
(selinux_sys::getprevcon, "getprevcon()")
};
let r = unsafe { proc(&mut context) };
Self::from_result(proc_name, r, context, raw_format)
}
}
/// Set the current security context of the process to this context.
///
/// See: `setcon()`.
#[doc(alias = "setcon")]
pub fn set_as_current(&self) -> Result<()> {
let (proc, proc_name): (unsafe extern "C" fn(_) -> _, _) = if self.is_raw {
(selinux_sys::setcon_raw, "setcon_raw()")
} else {
(selinux_sys::setcon, "setcon()")
};
ret_val_to_result(proc_name, unsafe { proc(self.context.as_ptr()) })
}
/// Get the context of a kernel initial security identifier specified by name.
///
/// See: `security_get_initial_context()`.
#[doc(alias = "security_get_initial_context")]
pub fn of_initial_kernel_context(name: &str, raw_format: bool) -> Result<Self> {
let (proc, proc_name): (unsafe extern "C" fn(_, _) -> _, _) = if raw_format {
let proc_name = "security_get_initial_context_raw()";
(selinux_sys::security_get_initial_context_raw, proc_name)
} else {
let proc_name = "security_get_initial_context()";
(selinux_sys::security_get_initial_context, proc_name)
};
let c_name = str_to_c_string(name)?;
let mut context: *mut c_char = ptr::null_mut();
let r = unsafe { proc(c_name.as_ptr(), &mut context) };
Self::from_result_with_name(proc_name, r, context, name, raw_format)
}
/// Get the default SELinux security context for the specified media type
/// from the policy.
///
/// See: `matchmediacon()`.
#[doc(alias = "matchmediacon")]
pub fn of_media_type(name: &str) -> Result<Self> {
let c_name = str_to_c_string(name)?;
let mut context: *mut c_char = ptr::null_mut();
let r = unsafe { selinux_sys::matchmediacon(c_name.as_ptr(), &mut context) };
Self::from_result_with_name("matchmediacon()", r, context, name, false)
}
/// Return the process context for the specified process identifier.
///
/// See: `getpidcon()`.
#[doc(alias = "getpidcon")]
pub fn of_process(process_id: pid_t, raw_format: bool) -> Result<Self> {
let (proc, proc_name): (unsafe extern "C" fn(_, _) -> _, _) = if raw_format {
(selinux_sys::getpidcon_raw, "getpidcon_raw()")
} else {
(selinux_sys::getpidcon, "getpidcon()")
};
let mut context: *mut c_char = ptr::null_mut();
let r = unsafe { proc(process_id, &mut context) };
Self::from_result_with_pid(proc_name, r, context, process_id, raw_format)
}
/// Perform context translation from the human-readable format (translated)
/// to the internal system format (raw).
///
/// See: `selinux_trans_to_raw_context()`.
#[doc(alias = "selinux_trans_to_raw_context")]
pub fn to_raw_format(&self) -> Result<Self> {
if self.is_raw {
return Err(Error::UnexpectedSecurityContextFormat);
}
let mut context: *mut c_char = ptr::null_mut();
let r = unsafe {
selinux_sys::selinux_trans_to_raw_context(self.context.as_ptr(), &mut context)
};
Self::from_result("selinux_trans_to_raw_context()", r, context, true)
}
/// Perform context translation from the internal system format (raw) to
/// the human-readable format (translated).
///
/// See: `selinux_raw_to_trans_context()`.
#[doc(alias = "selinux_raw_to_trans_context")]
pub fn to_translated_format(&self) -> Result<Self> {
if !self.is_raw {
return Err(Error::UnexpectedSecurityContextFormat);
}
let mut context: *mut c_char = ptr::null_mut();
let r = unsafe {
selinux_sys::selinux_raw_to_trans_context(self.context.as_ptr(), &mut context)
};
Self::from_result("selinux_raw_to_trans_context()", r, context, false)
}
/// Ask the user to manually enter a context as a fallback if a list of
/// authorized contexts could not be obtained.
///
/// See: `manual_user_enter_context()`.
#[doc(alias = "manual_user_enter_context")]
pub fn of_se_user_with_selected_context(se_user: &str, raw_format: bool) -> Result<Self> {
let mut context: *mut c_char = ptr::null_mut();
let c_se_user = str_to_c_string(se_user)?;
let r = unsafe { selinux_sys::manual_user_enter_context(c_se_user.as_ptr(), &mut context) };
Self::from_result("manual_user_enter_context()", r, context, raw_format)
}
/// Obtain a context, for the specified SELinux user identity, that is
/// reachable from the specified `reachable_from_context`.
///
/// See: `get_default_context()`, `get_default_context_with_level()`,
/// `get_default_context_with_role()`, `get_default_context_with_rolelevel()`.
#[doc(alias = "get_default_context")]
#[doc(alias = "get_default_context_with_level")]
#[doc(alias = "get_default_context_with_role")]
#[doc(alias = "get_default_context_with_rolelevel")]
pub fn default_for_se_user(
se_user: &str,
role: Option<&str>,
level: Option<&str>,
reachable_from_context: Option<&Self>,
raw_format: bool,
) -> Result<Self> {
let c_se_user = str_to_c_string(se_user)?;
let c_role = if let Some(role) = role {
str_to_c_string(role).map(Some)?
} else {
None
};
let c_level = if let Some(level) = level {
str_to_c_string(level).map(Some)?
} else {
None
};
let reachable_from_context =
reachable_from_context.map_or(ptr::null_mut(), |c| c.context.as_ptr());
let mut context: *mut c_char = ptr::null_mut();
let (r, proc_name) = unsafe {
match (c_role, c_level) {
(None, None) => (
selinux_sys::get_default_context(
c_se_user.as_ptr(),
reachable_from_context,
&mut context,
),
"get_default_context()",
),
(None, Some(c_level)) => (
selinux_sys::get_default_context_with_level(
c_se_user.as_ptr(),
c_level.as_ptr(),
reachable_from_context,
&mut context,
),
"get_default_context_with_level()",
),
(Some(c_role), None) => (
selinux_sys::get_default_context_with_role(
c_se_user.as_ptr(),
c_role.as_ptr(),
reachable_from_context,
&mut context,
),
"get_default_context_with_role()",
),
(Some(c_role), Some(c_level)) => (
selinux_sys::get_default_context_with_rolelevel(
c_se_user.as_ptr(),
c_role.as_ptr(),
c_level.as_ptr(),
reachable_from_context,
&mut context,
),
"get_default_context_with_rolelevel()",
),
}
};
Self::from_result(proc_name, r, context, raw_format)
}
/// Get the context used for executing a new process.
///
/// See: `getexeccon()`.
#[doc(alias = "getexeccon")]
pub fn of_next_exec(raw_format: bool) -> Result<Option<Self>> {
let (proc, proc_name): (unsafe extern "C" fn(_) -> _, _) = if raw_format {
(selinux_sys::getexeccon_raw, "getexeccon_raw()")
} else {
(selinux_sys::getexeccon, "getexeccon()")
};
Self::of_new_operations(proc, proc_name, raw_format)
}
/// Reset the context, used for the next `execve()` call, to the default
/// policy behavior.
///
/// See: `setexeccon()`.
#[doc(alias = "setexeccon")]
pub fn set_default_context_for_next_exec() -> Result<()> {
let r = unsafe { selinux_sys::setexeccon(ptr::null()) };
ret_val_to_result("setexeccon()", r)
}
/// Set the context used for the next `execve()` call.
///
/// See: `setexeccon()`.
#[doc(alias = "setexeccon")]
pub fn set_for_next_exec(&self) -> Result<()> {
let (proc, proc_name): (unsafe extern "C" fn(_) -> _, _) = if self.is_raw {
(selinux_sys::setexeccon_raw, "setexeccon_raw()")
} else {
(selinux_sys::setexeccon, "setexeccon()")
};
ret_val_to_result(proc_name, unsafe { proc(self.context.as_ptr()) })
}
/// Get the context used for creating a new file system object.
///
/// See: `getfscreatecon()`.
#[doc(alias = "getfscreatecon")]
pub fn of_new_file_system_objects(raw_format: bool) -> Result<Option<Self>> {
let (proc, proc_name): (unsafe extern "C" fn(_) -> _, _) = if raw_format {
(selinux_sys::getfscreatecon_raw, "getfscreatecon_raw()")
} else {
(selinux_sys::getfscreatecon, "getfscreatecon()")
};
Self::of_new_operations(proc, proc_name, raw_format)
}
/// Reset the context, used for creating a new file system object, to the
/// default policy behavior.
///
/// See: `setfscreatecon()`.
#[doc(alias = "setfscreatecon")]
pub fn set_default_context_for_new_file_system_objects() -> Result<()> {
let r = unsafe { selinux_sys::setfscreatecon(ptr::null()) };
ret_val_to_result("setfscreatecon()", r)
}
/// Set the context used for creating a new file system object.
///
/// See: `setfscreatecon()`.
#[doc(alias = "setfscreatecon")]
pub fn set_for_new_file_system_objects(&self, raw_format: bool) -> Result<()> {
let (proc, proc_name): (unsafe extern "C" fn(_) -> _, _) = if raw_format {
(selinux_sys::setfscreatecon_raw, "setfscreatecon_raw()")
} else {
(selinux_sys::setfscreatecon, "setfscreatecon()")
};
ret_val_to_result(proc_name, unsafe { proc(self.context.as_ptr()) })
}
/// Get the context used for creating a new kernel key ring.
///
/// See: `getkeycreatecon()`.
#[doc(alias = "getkeycreatecon")]
pub fn of_new_kernel_key_rings(raw_format: bool) -> Result<Option<Self>> {
let (proc, proc_name): (unsafe extern "C" fn(_) -> _, _) = if raw_format {
(selinux_sys::getkeycreatecon_raw, "getkeycreatecon_raw()")
} else {
(selinux_sys::getkeycreatecon, "getkeycreatecon()")
};
Self::of_new_operations(proc, proc_name, raw_format)
}
/// Set the context, used for creating a new kernel key ring, to the
/// default policy behavior.
///
/// See: `setkeycreatecon()`.
#[doc(alias = "setkeycreatecon")]
pub fn set_default_context_for_new_kernel_key_rings() -> Result<()> {
let r = unsafe { selinux_sys::setkeycreatecon(ptr::null()) };
ret_val_to_result("setkeycreatecon()", r)
}
/// Set the context used for creating a new kernel key ring.
///
/// See: `setkeycreatecon()`.
#[doc(alias = "setkeycreatecon")]
pub fn set_for_new_kernel_key_rings(&self, raw_format: bool) -> Result<()> {
let (proc, proc_name): (unsafe extern "C" fn(_) -> _, _) = if raw_format {
(selinux_sys::setkeycreatecon_raw, "setkeycreatecon_raw()")
} else {
(selinux_sys::setkeycreatecon, "setkeycreatecon()")
};
ret_val_to_result(proc_name, unsafe { proc(self.context.as_ptr()) })
}
/// Get the context used for creating a new labeled network socket.
///
/// See: `getsockcreatecon()`.
#[doc(alias = "getsockcreatecon")]
pub fn of_new_labeled_sockets(raw_format: bool) -> Result<Option<Self>> {
let (proc, proc_name): (unsafe extern "C" fn(_) -> _, _) = if raw_format {
(selinux_sys::getsockcreatecon_raw, "getsockcreatecon_raw()")
} else {
(selinux_sys::getsockcreatecon, "getsockcreatecon()")
};
Self::of_new_operations(proc, proc_name, raw_format)
}
/// Set the context, used for creating a new labeled network sockets, to the
/// default policy behavior.
///
/// See: `setsockcreatecon()`.
#[doc(alias = "setsockcreatecon")]
pub fn set_default_context_for_new_labeled_sockets() -> Result<()> {
let r = unsafe { selinux_sys::setsockcreatecon(ptr::null()) };
ret_val_to_result("setsockcreatecon()", r)
}
/// Set the context used for creating a new labeled network sockets.
///
/// See: `setsockcreatecon()`.
#[doc(alias = "setsockcreatecon")]
pub fn set_for_new_labeled_sockets(&self, raw_format: bool) -> Result<()> {
let (proc, proc_name): (unsafe extern "C" fn(_) -> _, _) = if raw_format {
(selinux_sys::setsockcreatecon_raw, "setsockcreatecon_raw()")
} else {
(selinux_sys::setsockcreatecon, "setsockcreatecon()")
};
ret_val_to_result(proc_name, unsafe { proc(self.context.as_ptr()) })
}
/// Get the context associated with the given path in the file system.
///
/// See: `lgetfilecon()`, `getfilecon()`.
#[doc(alias = "lgetfilecon")]
#[doc(alias = "getfilecon")]
pub fn of_path(
path: impl AsRef<Path>,
follow_symbolic_links: bool,
raw_format: bool,
) -> Result<Option<Self>> {
let (proc, proc_name): (unsafe extern "C" fn(_, _) -> _, _) =
match (follow_symbolic_links, raw_format) {
(false, false) => (selinux_sys::lgetfilecon, "lgetfilecon()"),
(false, true) => (selinux_sys::lgetfilecon_raw, "lgetfilecon_raw()"),
(true, false) => (selinux_sys::getfilecon, "getfilecon()"),
(true, true) => (selinux_sys::getfilecon_raw, "getfilecon_raw()"),
};
let c_path = os_str_to_c_string(path.as_ref().as_os_str())?;
let mut context: *mut c_char = ptr::null_mut();
let r: c_int = unsafe { proc(c_path.as_ptr(), &mut context) };
if r == -1_i32 {
let err = io::Error::last_os_error();
if let Some(libc::ENODATA) = err.raw_os_error() {
Ok(None)
} else {
Err(Error::from_io_path(proc_name, path.as_ref(), err))
}
} else {
Ok(ptr::NonNull::new(context).map(|context| {
let size = (r >= 0_i32).then_some(r as c_uint);
Self::from_ptr(context, size, raw_format)
}))
}
}
/// Set the file context to the system defaults.
///
/// See: `selinux_lsetfilecon_default()`.
#[doc(alias = "selinux_lsetfilecon_default")]
pub fn set_default_for_path(path: impl AsRef<Path>) -> Result<()> {
let c_path = os_str_to_c_string(path.as_ref().as_os_str())?;
let r = unsafe { selinux_sys::selinux_lsetfilecon_default(c_path.as_ptr()) };
ret_val_to_result_with_path("selinux_lsetfilecon_default()", r, path.as_ref())
}
/// Set the SELinux security context of a file system object.
///
/// See: `lsetfilecon()`, `setfilecon()`.
#[doc(alias = "lsetfilecon")]
#[doc(alias = "setfilecon")]
pub fn set_for_path(
&self,
path: impl AsRef<Path>,
follow_symbolic_links: bool,
raw_format: bool,
) -> Result<()> {
let (proc, proc_name): (unsafe extern "C" fn(_, _) -> _, _) =
match (follow_symbolic_links, raw_format) {
(false, false) => (selinux_sys::lsetfilecon, "lsetfilecon()"),
(false, true) => (selinux_sys::lsetfilecon_raw, "lsetfilecon_raw()"),
(true, false) => (selinux_sys::setfilecon, "setfilecon()"),
(true, true) => (selinux_sys::setfilecon_raw, "setfilecon_raw()"),
};
let c_path = os_str_to_c_string(path.as_ref().as_os_str())?;
let r = unsafe { proc(c_path.as_ptr(), self.context.as_ptr()) };
ret_val_to_result_with_path(proc_name, r, path.as_ref())
}
/// Get the SELinux security context of a file system object.
///
/// See: `fgetfilecon()`.
#[doc(alias = "fgetfilecon")]
pub fn of_file<T>(fd: &T, raw_format: bool) -> Result<Option<Self>>
where
T: AsRawFd,
{
let (proc, proc_name): (unsafe extern "C" fn(_, _) -> _, _) = if raw_format {
(selinux_sys::fgetfilecon_raw, "fgetfilecon_raw()")
} else {
(selinux_sys::fgetfilecon, "fgetfilecon()")
};
let mut context: *mut c_char = ptr::null_mut();
let r: c_int = unsafe { proc(fd.as_raw_fd(), &mut context) };
if r == -1_i32 {
let err = io::Error::last_os_error();
if let Some(libc::ENODATA) = err.raw_os_error() {
Ok(None)
} else {
Err(Error::from_io(proc_name, err))
}
} else {
Ok(ptr::NonNull::new(context).map(|context| {
#[allow(clippy::cast_sign_loss, clippy::as_conversions)]
let size = (r >= 0_i32).then_some(r as c_uint);
Self::from_ptr(context, size, raw_format)
}))
}
}
/// Set the SELinux security context of the file system object identified
/// by an open file descriptor.
///
/// See: `fsetfilecon()`.
#[doc(alias = "fsetfilecon")]
pub fn set_for_file<T>(&self, fd: &T) -> Result<()>
where
T: AsRawFd,
{
let (proc, proc_name): (unsafe extern "C" fn(_, _) -> _, _) = if self.is_raw {
(selinux_sys::fsetfilecon_raw, "fsetfilecon_raw()")
} else {
(selinux_sys::fsetfilecon, "fsetfilecon()")
};
let r = unsafe { proc(fd.as_raw_fd(), self.context.as_ptr()) };
ret_val_to_result(proc_name, r)
}
/// Set the SELinux security context of the peer socket identified by an
/// open file descriptor.
///
/// See: `getpeercon()`.
#[doc(alias = "getpeercon")]
pub fn of_peer_socket<T>(socket: &T, raw_format: bool) -> Result<Self>
where
T: AsRawFd,
{
let (proc, proc_name): (unsafe extern "C" fn(_, _) -> _, _) = if raw_format {
(selinux_sys::getpeercon_raw, "getpeercon_raw()")
} else {
(selinux_sys::getpeercon, "getpeercon()")
};
let mut context: *mut c_char = ptr::null_mut();
let r = unsafe { proc(socket.as_raw_fd(), &mut context) };
Self::from_result(proc_name, r, context, raw_format)
}
/// Return whether the policy permits this source context to access
/// `target_context` via `target_class` with the requested access vector.
///
/// See: `security_compute_av_flags()`.
#[doc(alias = "security_compute_av_flags")]
pub fn query_access_decision(
&self,
target_context: &Self,
target_class: SecurityClass,
requested_access: selinux_sys::access_vector_t,
) -> Result<selinux_sys::av_decision> {
if self.is_raw != target_context.is_raw {
return Err(Error::SecurityContextFormatMismatch);
}
let (proc, proc_name): (unsafe extern "C" fn(_, _, _, _, _) -> _, _) = if self.is_raw {
let proc_name = "security_compute_av_flags_raw()";
(selinux_sys::security_compute_av_flags_raw, proc_name)
} else {
let proc_name = "security_compute_av_flags()";
(selinux_sys::security_compute_av_flags, proc_name)
};
let mut result = MaybeUninit::<selinux_sys::av_decision>::uninit();
let r: c_int = unsafe {
proc(
self.context.as_ptr(),
target_context.context.as_ptr(),
target_class.0,
requested_access,
result.as_mut_ptr(),
)
};
if r == -1_i32 {
Err(Error::last_io_error(proc_name))
} else {
Ok(unsafe { result.assume_init() })
}
}
/// Compute a context to use for labeling a new named object in a particular
/// class based on a SID pair.
///
/// See: `security_compute_create_name()`.
#[doc(alias = "security_compute_create_name")]
pub fn of_labeling_decision(
&self,
target_context: &Self,
target_class: SecurityClass,
object_name: &str,
) -> Result<Self> {
if self.is_raw != target_context.is_raw {
return Err(Error::SecurityContextFormatMismatch);
}
let (proc, proc_name): (unsafe extern "C" fn(_, _, _, _, _) -> _, _) = if self.is_raw {
let proc_name = "security_compute_create_name_raw()";
(selinux_sys::security_compute_create_name_raw, proc_name)
} else {
let proc_name = "security_compute_create_name()";
(selinux_sys::security_compute_create_name, proc_name)
};
let c_object_name = str_to_c_string(object_name)?;
let mut context: *mut c_char = ptr::null_mut();
let r = unsafe {
proc(
self.context.as_ptr(),
target_context.context.as_ptr(),
target_class.0,
c_object_name.as_ptr(),
&mut context,
)
};
Self::from_result_with_name(proc_name, r, context, object_name, self.is_raw)
}
/// Compute the new context to use when relabeling an object.
///
/// See: `security_compute_relabel()`.
#[doc(alias = "security_compute_relabel")]
pub fn of_relabeling_decision(
&self,
target_context: &Self,
target_class: SecurityClass,
) -> Result<Self> {
if self.is_raw != target_context.is_raw {
return Err(Error::SecurityContextFormatMismatch);
}
let (proc, proc_name): (unsafe extern "C" fn(_, _, _, _) -> _, _) = if self.is_raw {
let proc_name = "security_compute_relabel_raw()";
(selinux_sys::security_compute_relabel_raw, proc_name)
} else {
let proc_name = "security_compute_relabel()";
(selinux_sys::security_compute_relabel, proc_name)
};
let mut context: *mut c_char = ptr::null_mut();
let r = unsafe {
proc(
self.context.as_ptr(),
target_context.context.as_ptr(),
target_class.0,
&mut context,
)
};
Self::from_result(proc_name, r, context, self.is_raw)
}
/// Compute the context to use when labeling a polyinstantiated
/// object instance.
///
/// See: `security_compute_member()`.
#[doc(alias = "security_compute_member")]
pub fn of_polyinstantiation_member_decision(
&self,
target_context: &Self,
target_class: SecurityClass,
) -> Result<Self> {
if self.is_raw != target_context.is_raw {
return Err(Error::SecurityContextFormatMismatch);
}
let (proc, proc_name): (unsafe extern "C" fn(_, _, _, _) -> _, _) = if self.is_raw {
let proc_name = "security_compute_member_raw()";
(selinux_sys::security_compute_member_raw, proc_name)
} else {
let proc_name = "security_compute_member()";
(selinux_sys::security_compute_member, proc_name)
};
let mut context: *mut c_char = ptr::null_mut();
let r = unsafe {
proc(
self.context.as_ptr(),
target_context.context.as_ptr(),
target_class.0,
&mut context,
)
};
Self::from_result(proc_name, r, context, self.is_raw)
}
/// Determine if a transition from this context to `new_context` using
/// `target_context` as the object is valid for object class `target_class`.
///
/// This checks against the `mlsvalidatetrans` and `validatetrans`
/// constraints in the loaded policy.
///
/// This function requires `libselinux` version `3.0` or later.
///
/// See: `security_validatetrans()`.
#[doc(alias = "security_validatetrans")]
pub fn validate_transition(
&self,
target_context: &Self,
target_class: SecurityClass,
new_context: &Self,
) -> Result<()> {
if self.is_raw != target_context.is_raw {
return Err(Error::SecurityContextFormatMismatch);
}
let onf = OptionalNativeFunctions::get();
let (proc, proc_name) = if self.is_raw {
let proc_name = "security_validatetrans_raw()";
(onf.security_validatetrans_raw, proc_name)
} else {
let proc_name = "security_validatetrans()";
(onf.security_validatetrans, proc_name)
};
let r = unsafe {
proc(
self.context.as_ptr(),
target_context.context.as_ptr(),
target_class.0,
new_context.context.as_ptr(),
)
};
ret_val_to_result(proc_name, r)
}
/// Check the validity of an SELinux context.
///
/// See: `security_check_context()`, `is_selinux_enabled()`.
#[doc(alias = "security_check_context")]
#[doc(alias = "is_selinux_enabled")]
#[must_use]
pub fn check(&self) -> Option<bool> {
let proc: unsafe extern "C" fn(_) -> _ = if self.is_raw {
selinux_sys::security_check_context_raw
} else {
selinux_sys::security_check_context
};
if unsafe { proc(self.context.as_ptr()) } == -1_i32 {
if unsafe { selinux_sys::is_selinux_enabled() } == 0_i32 {
None
} else {
Some(false)
}
} else {
Some(true)
}
}
/// Canonicalize this security context.
///
/// See: `security_canonicalize_context()`.
#[doc(alias = "security_canonicalize_context")]
pub fn canonicalize(&self) -> Result<Self> {
let (proc, proc_name): (unsafe extern "C" fn(_, _) -> _, _) = if self.is_raw {
let proc_name = "security_canonicalize_context_raw()";
(selinux_sys::security_canonicalize_context_raw, proc_name)
} else {
let proc_name = "security_canonicalize_context()";
(selinux_sys::security_canonicalize_context, proc_name)
};
let mut context: *mut c_char = ptr::null_mut();
let r = unsafe { proc(self.context.as_ptr(), &mut context) };
Self::from_result(proc_name, r, context, self.is_raw)
}
/// Check if this context has the access permission for the specified class
/// on the target context.
///
/// See: `selinux_check_access()`.
#[doc(alias = "selinux_check_access")]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn check_access(
&self,
target_context: &Self,
target_class: &str,
requested_permission: &str,
audit_data: *mut c_void,
) -> Result<bool> {
let c_target_class = str_to_c_string(target_class)?;
let c_requested_permission = str_to_c_string(requested_permission)?;
let r: c_int = unsafe {
selinux_sys::selinux_check_access(
self.context.as_ptr(),
target_context.context.as_ptr(),
c_target_class.as_ptr(),
c_requested_permission.as_ptr(),
audit_data,
)
};
Ok(r == 0_i32)
}
/// Check whether a SELinux tty security context is defined as
/// a securetty context.
///
/// See: `selinux_check_securetty_context()`.
#[doc(alias = "selinux_check_securetty_context")]
#[must_use]
pub fn check_securetty_context(&self) -> bool {
unsafe { selinux_sys::selinux_check_securetty_context(self.context.as_ptr()) == 0_i32 }
}
/// Check whether SELinux context type is customizable by the administrator.
///
/// See: `is_context_customizable()`.
#[doc(alias = "is_context_customizable")]
pub fn is_customizable(&self) -> Result<bool> {
let r: c_int = unsafe { selinux_sys::is_context_customizable(self.context.as_ptr()) };
if r == -1_i32 {
Err(Error::last_io_error("is_context_customizable()"))
} else {
Ok(r != 0_i32)
}
}
/// Return the color string for this SELinux security context.
///
/// See: `selinux_raw_context_to_color()`.
#[doc(alias = "selinux_raw_context_to_color")]
pub fn to_color(&self) -> Result<SecurityContextColors> {
if !self.is_raw {
let raw_context = self.to_raw_format()?;
return raw_context.to_color();
}
let mut color_ptr: *mut c_char = ptr::null_mut();
let r: c_int = unsafe {
selinux_sys::selinux_raw_context_to_color(self.context.as_ptr(), &mut color_ptr)
};
if r == -1_i32 {
Err(Error::last_io_error("selinux_raw_context_to_color()"))
} else {
CAllocatedBlock::new(color_ptr).map_or_else(
|| {
let err = io::ErrorKind::InvalidData.into();
Err(Error::from_io("selinux_raw_context_to_color()", err))
},
|c_color| Self::parse_context_color(c_color.as_c_str().to_bytes()),
)
}
}
/// Compare this SELinux security context with another one, excluding
/// the `user` component.
///
/// See: `selinux_file_context_cmp()`.
#[doc(alias = "selinux_file_context_cmp")]
#[must_use]
pub fn compare_user_insensitive(&self, other: &Self) -> cmp::Ordering {
let r: c_int = unsafe {
selinux_sys::selinux_file_context_cmp(self.context.as_ptr(), other.context.as_ptr())
};
r.cmp(&0_i32)
}
/// Compare the SELinux security context on disk to the default security
/// context required by the policy file contexts file.
///
/// See: `selinux_file_context_verify()`.
#[doc(alias = "selinux_file_context_verify")]
pub fn verify_file_context(
path: impl AsRef<Path>,
mode: Option<FileAccessMode>,
) -> Result<bool> {
let c_path = os_str_to_c_string(path.as_ref().as_os_str())?;
let mode = mode.map_or(0, FileAccessMode::mode);
Error::clear_errno();
match unsafe { selinux_sys::selinux_file_context_verify(c_path.as_ptr(), mode) } {
-1_i32 => Err(Error::from_io_path(
"selinux_file_context_verify()",
path.as_ref(),
io::Error::last_os_error(),
)),
0_i32 => {
let err = io::Error::last_os_error();
match err.raw_os_error() {
None | Some(0_i32) => Ok(false),
_ => Err(Error::from_io_path(
"selinux_file_context_verify()",
path.as_ref(),
err,
)),
}
}
_ => Ok(true),
}
}
fn of_new_operations(
proc: unsafe extern "C" fn(*mut *mut c_char) -> c_int,
proc_name: &'static str,
raw_format: bool,
) -> Result<Option<Self>> {
let mut context: *mut c_char = ptr::null_mut();
if unsafe { proc(&mut context) } == -1_i32 {
Err(Error::last_io_error(proc_name))
} else {
Ok(ptr::NonNull::new(context).map(|c| Self::from_ptr(c, None, raw_format)))
}
}
fn from_ptr(context: ptr::NonNull<c_char>, size: Option<c_uint>, raw_format: bool) -> Self {
#[allow(clippy::as_conversions)]
Self {
context,
size: size.map(|size| size as usize),
is_raw: raw_format,
context_owned: true,
_phantom_data: PhantomData,
}
}
fn from_result(
proc_name: &'static str,
result: c_int,
context: *mut c_char,
raw_format: bool,
) -> Result<Self> {
if result == -1_i32 {
Err(Error::last_io_error(proc_name))
} else {
ptr::NonNull::new(context).map_or_else(
|| Err(Error::from_io(proc_name, io::ErrorKind::InvalidData.into())),
|context| Ok(Self::from_ptr(context, None, raw_format)),
)
}
}
fn from_result_with_name(
proc_name: &'static str,
result: c_int,
context: *mut c_char,
name: &str,
raw_format: bool,
) -> Result<Self> {
if result == -1_i32 {
Err(Error::last_io_error(proc_name))
} else {
ptr::NonNull::new(context).map_or_else(
|| {
Err(Error::IO1Name {
operation: proc_name,
name: name.into(),
source: io::ErrorKind::InvalidData.into(),
})
},
|context| Ok(Self::from_ptr(context, None, raw_format)),
)
}
}
fn from_result_with_pid(
proc_name: &'static str,
result: c_int,
context: *mut c_char,
process_id: pid_t,
raw_format: bool,
) -> Result<Self> {
if result == -1_i32 {
let err = io::Error::last_os_error();
Err(Error::from_io_pid(proc_name, process_id, err))
} else {
ptr::NonNull::new(context).map_or_else(
|| {
let err = io::ErrorKind::InvalidData.into();
Err(Error::from_io_pid(proc_name, process_id, err))
},
|context| Ok(Self::from_ptr(context, None, raw_format)),
)
}
}
fn parse_context_color(bytes: &[u8]) -> Result<SecurityContextColors> {
#[allow(clippy::as_conversions)]
let colors: Vec<RGB> = bytes
.split(u8::is_ascii_whitespace)
.filter(|&bytes| !bytes.is_empty())
.take(8)
.flat_map(|bytes| bytes.strip_prefix(b"#"))
.filter(|&bytes| !bytes.is_empty())
.flat_map(|bytes| str::from_utf8(bytes).ok())
.flat_map(|s| u32::from_str_radix(s, 16).ok())
.filter(|&n| n <= 0x00ff_ffff_u32)
.map(|n| RGB {
red: (n & 0xff_u32) as u8,
green: ((n >> 8) & 0xff_u32) as u8,
blue: ((n >> 16) & 0xff_u32) as u8,
})
.collect();
if let Ok(colors) = <[RGB; 8]>::try_from(colors) {
Ok(SecurityContextColors {
user: LayerColors {
background: colors[1],
foreground: colors[0],
},
role: LayerColors {
background: colors[3],
foreground: colors[2],
},
the_type: LayerColors {
background: colors[5],
foreground: colors[4],
},
range: LayerColors {
background: colors[7],
foreground: colors[6],
},
})
} else {
Err(Error::from_io_name(
"selinux_raw_context_to_color()",
String::from_utf8_lossy(bytes),
io::ErrorKind::InvalidData.into(),
))
}
}
}
impl Drop for SecurityContext<'_> {
/// See: `freecon()`.
#[doc(alias = "freecon")]
fn drop(&mut self) {
let context = self.context.as_ptr();
self.context = ptr::NonNull::dangling();
if self.context_owned {
unsafe { selinux_sys::freecon(context) }
}
}
}
/// List of security contexts.
#[derive(Debug)]
pub struct SecurityContextList {
context_list: ptr::NonNull<*mut c_char>,
count: usize,
_phantom_data: PhantomData<c_char>,
}
impl SecurityContextList {
/// Return the managed raw pointer to [`c_char`].
#[must_use]
pub fn as_ptr(&self) -> *const *const c_char {
self.context_list.as_ptr().cast()
}
/// Return the managed raw pointer to [`c_char`].
#[must_use]
pub fn as_mut_ptr(&mut self) -> *mut *mut c_char {
self.context_list.as_ptr()
}
/// Number of security contexts present in this list.
#[must_use]
pub fn len(&self) -> usize {
self.count
}
/// Return `true` if this list is empty.
#[must_use]
pub fn is_empty(&self) -> bool {
self.count == 0
}
/// Obtain a list of contexts, for the specified SELinux user identity,
/// that are reachable from the specified `reachable_from_context`.
///
/// See: `get_ordered_context_list()`, `get_ordered_context_list_with_level()`.
#[doc(alias = "get_ordered_context_list")]
#[doc(alias = "get_ordered_context_list_with_level")]
pub fn of_se_user(
se_user: &str,
level: Option<&str>,
reachable_from_context: Option<&SecurityContext>,
) -> Result<Self> {
let c_se_user = str_to_c_string(se_user)?;
let c_level = if let Some(level) = level {
str_to_c_string(level).map(Some)?
} else {
None
};
let reachable_from_context =
reachable_from_context.map_or(ptr::null_mut(), |c| c.context.as_ptr());
let mut context_list: *mut *mut c_char = ptr::null_mut();
let (r, proc_name) = unsafe {
if let Some(c_level) = c_level {
let r = selinux_sys::get_ordered_context_list_with_level(
c_se_user.as_ptr(),
c_level.as_ptr(),
reachable_from_context,
&mut context_list,
);
(r, "get_ordered_context_list_with_level()")
} else {
let r = selinux_sys::get_ordered_context_list(
c_se_user.as_ptr(),
reachable_from_context,
&mut context_list,
);
(r, "get_ordered_context_list()")
}
};
if r == -1_i32 {
Err(Error::last_io_error(proc_name))
} else {
ptr::NonNull::new(context_list).map_or_else(
|| Err(Error::from_io(proc_name, io::ErrorKind::InvalidData.into())),
|context_list| {
Ok(Self {
context_list,
#[allow(clippy::cast_sign_loss, clippy::as_conversions)]
count: r as c_uint as usize,
_phantom_data: PhantomData,
})
},
)
}
}
/// Return the security context at the given index, if the index is valid.
///
/// ⚠️ The returned instance does **NOT** own the context.
/// When the returned instance get dropped, it will **NOT** deallocate the
/// provided context.
/// Deallocation of the context will only happen when the whole list gets
/// dropped.
#[must_use]
pub fn get(&'_ self, index: usize, raw_format: bool) -> Option<SecurityContext<'_>> {
if index < self.count {
let context = unsafe { *self.context_list.as_ptr().wrapping_add(index) };
ptr::NonNull::new(context).map(|context| SecurityContext {
context,
size: None,
is_raw: raw_format,
context_owned: false,
_phantom_data: PhantomData,
})
} else {
None
}
}
/// Ask the user via `stdin`/`stdout` as to which context they want from
/// this list of contexts, and return a new context as selected by the user.
///
/// See: `query_user_context()`.
#[doc(alias = "query_user_context")]
pub fn user_selected_context(&self, raw_format: bool) -> Result<SecurityContext> {
let mut context: *mut c_char = ptr::null_mut();
let r =
unsafe { selinux_sys::query_user_context(self.context_list.as_ptr(), &mut context) };
SecurityContext::from_result("query_user_context()", r, context, raw_format)
}
}
impl Drop for SecurityContextList {
/// See: `freeconary()`.
#[doc(alias = "freeconary")]
fn drop(&mut self) {
let context_list = self.context_list.as_ptr();
self.context_list = ptr::NonNull::dangling();
unsafe { selinux_sys::freeconary(context_list) }
}
}
/// File access mode.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct FileAccessMode(selinux_sys::mode_t);
impl FileAccessMode {
/// Create a new file access mode, if given a non-zero `mode`.
#[must_use]
pub fn new(mode: selinux_sys::mode_t) -> Option<Self> {
if mode == 0 {
None
} else {
Some(Self(mode))
}
}
/// Return the mode value.
#[must_use]
pub fn mode(self) -> selinux_sys::mode_t {
self.0
}
}
/// SELinux security class.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct SecurityClass(selinux_sys::security_class_t);
impl fmt::Display for SecurityClass {
/// See: `security_class_to_string()`.
#[doc(alias = "security_class_to_string")]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name_ptr = unsafe { selinux_sys::security_class_to_string(self.0) };
if name_ptr.is_null() {
write!(f, "<invalid-class>")
} else {
let c_name = unsafe { CStr::from_ptr(name_ptr) };
write!(f, "{}", c_name.to_string_lossy())
}
}
}
impl SecurityClass {
/// Return the security class value.
#[must_use]
pub fn value(&self) -> selinux_sys::security_class_t {
self.0
}
/// Create a new security class, if given a non-zero `class`.
pub fn new(class: selinux_sys::security_class_t) -> Result<Self> {
if class == 0 {
let err = io::ErrorKind::NotFound.into();
Err(Error::from_io("SecurityClass::new()", err))
} else {
Ok(Self(class))
}
}
/// Return the security class corresponding to the string name,
/// if such class exists.
///
/// See: `string_to_security_class()`.
#[doc(alias = "string_to_security_class")]
pub fn from_name(name: &str) -> Result<Self> {
let c_name = str_to_c_string(name)?;
let r = unsafe { selinux_sys::string_to_security_class(c_name.as_ptr()) };
if r == 0 {
let err = io::ErrorKind::NotFound.into();
Err(Error::from_io("string_to_security_class()", err))
} else {
Ok(Self(r))
}
}
/// Return the name of the `access_vector` of this security class.
///
/// See: `security_av_perm_to_string()`.
///
/// # Safety
///
/// The returned string must not be **modified** or **freed**.
#[doc(alias = "security_av_perm_to_string")]
pub unsafe fn access_vector_bit_name(
&self,
access_vector: selinux_sys::access_vector_t,
) -> Result<&'static CStr> {
let name_ptr = unsafe { selinux_sys::security_av_perm_to_string(self.0, access_vector) };
if name_ptr.is_null() {
let err = io::ErrorKind::NotFound.into();
Err(Error::from_io("security_av_perm_to_string()", err))
} else {
unsafe { Ok(CStr::from_ptr(name_ptr)) }
}
}
/// Return the access vector bit corresponding to the given name and this
/// security class.
///
/// See: `string_to_av_perm()`.
#[doc(alias = "string_to_av_perm")]
pub fn access_vector_bit(&self, name: &str) -> Result<selinux_sys::access_vector_t> {
let c_name = str_to_c_string(name)?;
let r = unsafe { selinux_sys::string_to_av_perm(self.0, c_name.as_ptr()) };
if r == 0 {
let err = io::ErrorKind::NotFound.into();
Err(Error::from_io("string_to_av_perm()", err))
} else {
Ok(r)
}
}
/// Compute a full access vector string representation using this security
/// class and `access_vector`, which may have multiple bits set.
///
/// See: `security_av_string()`.
#[doc(alias = "security_av_string")]
pub fn full_access_vector_name(
&self,
access_vector: selinux_sys::access_vector_t,
) -> Result<CAllocatedBlock<c_char>> {
let mut name_ptr: *mut c_char = ptr::null_mut();
let r: c_int =
unsafe { selinux_sys::security_av_string(self.0, access_vector, &mut name_ptr) };
if r == -1_i32 {
Err(Error::last_io_error("security_av_string()"))
} else {
CAllocatedBlock::new(name_ptr).ok_or_else(|| {
Error::from_io("security_av_string()", io::ErrorKind::NotFound.into())
})
}
}
}
impl TryFrom<FileAccessMode> for SecurityClass {
type Error = Error;
/// See: `mode_to_security_class()`.
#[doc(alias = "mode_to_security_class")]
fn try_from(mode: FileAccessMode) -> Result<Self> {
let r = unsafe { selinux_sys::mode_to_security_class(mode.mode()) };
if r == 0 {
let err = io::ErrorKind::NotFound.into();
Err(Error::from_io("mode_to_security_class()", err))
} else {
Ok(Self(r))
}
}
}
/// Opaque security context.
#[derive(Debug)]
pub struct OpaqueSecurityContext {
context: ptr::NonNull<selinux_sys::context_s_t>,
_phantom_data: PhantomData<selinux_sys::context_s_t>,
}
impl OpaqueSecurityContext {
/// Return the managed raw pointer to [`selinux_sys::context_s_t`].
#[must_use]
pub fn as_ptr(&self) -> *const selinux_sys::context_s_t {
self.context.as_ptr()
}
/// Return the managed raw pointer to [`selinux_sys::context_s_t`].
#[must_use]
pub fn as_mut_ptr(&mut self) -> *mut selinux_sys::context_s_t {
self.context.as_ptr()
}
/// Return a new context initialized to a context string.
///
/// See: `context_new()`.
#[doc(alias = "context_new")]
pub fn new(context: &str) -> Result<Self> {
let c_context = str_to_c_string(context)?;
Self::from_c_str(&c_context)
}
/// Return a new context initialized to a context string.
///
/// See: `context_new()`.
#[doc(alias = "context_new")]
pub fn from_c_str(context: &CStr) -> Result<Self> {
let context = unsafe { selinux_sys::context_new(context.as_ptr()) };
ptr::NonNull::new(context).map_or_else(
|| Err(Error::last_io_error("context_new()")),
|context| {
Ok(Self {
context,
_phantom_data: PhantomData,
})
},
)
}
/// Return the string value of this security context.
///
/// See: `context_str()`.
#[doc(alias = "context_str")]
pub fn to_c_string(&self) -> Result<CString> {
let r = unsafe { selinux_sys::context_str(self.context.as_ptr()) };
if r.is_null() {
Err(Error::last_io_error("context_str()"))
} else {
Ok(unsafe { CStr::from_ptr(r) }.into())
}
}
/// Return the string value of this security context's type.
///
/// See: `context_type_get()`.
#[doc(alias = "context_type_get")]
pub fn the_type(&self) -> Result<CString> {
self.get(selinux_sys::context_type_get, "context_type_get()")
}
/// Set the type of this security context.
///
/// See: `context_type_set()`.
#[doc(alias = "context_type_set")]
pub fn set_type_str(&self, new_value: &str) -> Result<()> {
let c_new_value = str_to_c_string(new_value)?;
self.set(
selinux_sys::context_type_set,
"context_type_set()",
c_new_value.as_ref(),
)
}
/// Set the type of this security context.
///
/// See: `context_type_set()`.
#[doc(alias = "context_type_set")]
pub fn set_type(&self, new_value: &CStr) -> Result<()> {
let proc_name = "context_type_set()";
self.set(selinux_sys::context_type_set, proc_name, new_value)
}
/// Return the string value of this security context's range.
///
/// See: `context_range_get()`.
#[doc(alias = "context_range_get")]
pub fn range(&self) -> Result<CString> {
self.get(selinux_sys::context_range_get, "context_range_get()")
}
/// Set the range of this security context.
///
/// See: `context_range_set()`.
#[doc(alias = "context_range_set")]
pub fn set_range_str(&self, new_value: &str) -> Result<()> {
let c_new_value = str_to_c_string(new_value)?;
self.set(
selinux_sys::context_range_set,
"context_range_set()",
c_new_value.as_ref(),
)
}
/// Set the range of this security context.
///
/// See: `context_range_set()`.
#[doc(alias = "context_range_set")]
pub fn set_range(&self, new_value: &CStr) -> Result<()> {
let proc_name = "context_range_set()";
self.set(selinux_sys::context_range_set, proc_name, new_value)
}
/// Return the string value of this security context's role.
///
/// See: `context_role_get()`.
#[doc(alias = "context_role_get")]
pub fn role(&self) -> Result<CString> {
self.get(selinux_sys::context_role_get, "context_role_get()")
}
/// Set the role of this security context.
///
/// See: `context_role_set()`.
#[doc(alias = "context_role_set")]
pub fn set_role_str(&self, new_value: &str) -> Result<()> {
let c_new_value = str_to_c_string(new_value)?;
self.set(
selinux_sys::context_role_set,
"context_role_set()",
c_new_value.as_ref(),
)
}
/// Set the role of this security context.
///
/// See: `context_role_set()`.
#[doc(alias = "context_role_set")]
pub fn set_role(&self, new_value: &CStr) -> Result<()> {
let proc_name = "context_role_set()";
self.set(selinux_sys::context_role_set, proc_name, new_value)
}
/// Return the string value of this security context's user.
///
/// See: `context_user_get()`.
#[doc(alias = "context_user_get")]
pub fn user(&self) -> Result<CString> {
self.get(selinux_sys::context_user_get, "context_user_get()")
}
/// Set the user of this security context.
///
/// See: `context_user_set()`.
#[doc(alias = "context_user_set")]
pub fn set_user_str(&self, new_value: &str) -> Result<()> {
let c_new_value = str_to_c_string(new_value)?;
self.set(
selinux_sys::context_user_set,
"context_user_set()",
c_new_value.as_ref(),
)
}
/// Set the user of this security context.
///
/// See: `context_user_set()`.
#[doc(alias = "context_user_set")]
pub fn set_user(&self, new_value: &CStr) -> Result<()> {
let proc_name = "context_user_set()";
self.set(selinux_sys::context_user_set, proc_name, new_value)
}
fn get(
&self,
proc: unsafe extern "C" fn(selinux_sys::context_t) -> *const c_char,
proc_name: &'static str,
) -> Result<CString> {
let r = unsafe { proc(self.context.as_ptr()) };
if r.is_null() {
Err(Error::last_io_error(proc_name))
} else {
Ok(unsafe { CStr::from_ptr(r) }.into())
}
}
fn set(
&self,
proc: unsafe extern "C" fn(selinux_sys::context_t, *const c_char) -> c_int,
proc_name: &'static str,
new_value: &CStr,
) -> Result<()> {
if unsafe { proc(self.context.as_ptr(), new_value.as_ptr()) } == 0_i32 {
Ok(())
} else {
Err(Error::last_io_error(proc_name))
}
}
}
impl Drop for OpaqueSecurityContext {
/// See: `context_free()`.
#[doc(alias = "context_free")]
fn drop(&mut self) {
let context = self.context.as_ptr();
self.context = ptr::NonNull::dangling();
unsafe { selinux_sys::context_free(context) };
}
}
impl fmt::Display for OpaqueSecurityContext {
/// See: `context_str()`.
#[doc(alias = "context_str")]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ptr = unsafe { selinux_sys::context_str(self.context.as_ptr()) };
let context = if ptr.is_null() {
Cow::Borrowed("<null>")
} else {
unsafe { CStr::from_ptr(ptr) }.to_string_lossy()
};
write!(f, "{context}")
}
}
/// Support of SELinux in the running kernel.
#[non_exhaustive]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum KernelSupport {
/// SELinux is unsupported.
Unsupported,
/// SELinux is supported.
SELinux,
/// SELinux is supported, with Multi Level Security.
SELinuxMLS,
}
/// SELinux enforcing mode.
#[non_exhaustive]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum SELinuxMode {
/// SELinux is not enforcing.
NotRunning,
/// SELinux is permissive.
Permissive,
/// SELinux is enforcing.
Enforcing,
}
/// SELinux handling of undefined object classes and permissions.
#[non_exhaustive]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum UndefinedHandling {
/// Undefined object classes and permissions are allowed.
Allowed,
/// Undefined object classes and permissions are deined at run time.
DeniedAtRunTime,
/// Undefined object classes and permissions are rejected at policy load time.
RejectedAtLoadTime,
}
/// Protection checked by SELinux on `mmap()` and `mprotect()` calls.
#[non_exhaustive]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum ProtectionCheckingMode {
/// Actual protection that will be applied by the kernel
/// (including the effects of `READ_IMPLIES_EXEC`).
CheckingActualProtection,
/// Protection requested by the application.
CheckingRequestedProtection,
}
/// Determine the support of SELinux in the running kernel.
///
/// See: `is_selinux_enabled()`, `is_selinux_mls_enabled()`.
#[doc(alias = "is_selinux_enabled")]
#[doc(alias = "is_selinux_mls_enabled")]
#[must_use]
pub fn kernel_support() -> KernelSupport {
if unsafe { selinux_sys::is_selinux_mls_enabled() } != 0_i32 {
KernelSupport::SELinuxMLS
} else if unsafe { selinux_sys::is_selinux_enabled() } != 0_i32 {
KernelSupport::SELinux
} else {
KernelSupport::Unsupported
}
}
/// Determine how the system was set up to run SELinux.
///
/// See: `selinux_getenforcemode()`.
#[doc(alias = "selinux_getenforcemode")]
pub fn boot_mode() -> Result<SELinuxMode> {
let mut enforce: c_int = -1;
if unsafe { selinux_sys::selinux_getenforcemode(&mut enforce) } == -1_i32 {
Err(Error::last_io_error("selinux_getenforcemode()"))
} else {
match enforce {
-1_i32 => Ok(SELinuxMode::NotRunning),
0_i32 => Ok(SELinuxMode::Permissive),
_ => Ok(SELinuxMode::Enforcing),
}
}
}
/// Determine the current SELinux enforcing mode.
///
/// See: `security_getenforce()`.
#[doc(alias = "security_getenforce")]
#[must_use]
pub fn current_mode() -> SELinuxMode {
match unsafe { selinux_sys::security_getenforce() } {
-1_i32 => SELinuxMode::NotRunning,
0_i32 => SELinuxMode::Permissive,
_ => SELinuxMode::Enforcing,
}
}
/// Set the current SELinux enforcing mode.
///
/// See: `security_disable()`, `security_setenforce()`.
#[doc(alias = "security_disable")]
#[doc(alias = "security_setenforce")]
pub fn set_current_mode(new_mode: SELinuxMode) -> Result<()> {
let (r, proc_name) = match new_mode {
SELinuxMode::NotRunning => {
let r = unsafe { selinux_sys::security_disable() };
(r, "security_disable()")
}
SELinuxMode::Permissive => {
let r = unsafe { selinux_sys::security_setenforce(0) };
(r, "security_setenforce()")
}
SELinuxMode::Enforcing => {
let r = unsafe { selinux_sys::security_setenforce(1) };
(r, "security_setenforce()")
}
};
ret_val_to_result(proc_name, r)
}
/// Return the current SELinux handling of undefined object classes
/// and permissions.
///
/// See: `security_reject_unknown()`, `security_deny_unknown()`.
#[doc(alias = "security_reject_unknown")]
#[doc(alias = "security_deny_unknown")]
pub fn undefined_handling() -> Result<UndefinedHandling> {
let mut reject_unknown: c_int =
unsafe { (OptionalNativeFunctions::get().security_reject_unknown)() };
if reject_unknown == -1_i32 {
let err = io::Error::last_os_error();
if err.raw_os_error() == Some(libc::ENOSYS) {
reject_unknown = 0_i32;
} else {
return Err(Error::from_io("security_reject_unknown()", err));
}
}
if reject_unknown == 0_i32 {
match unsafe { selinux_sys::security_deny_unknown() } {
-1_i32 => Err(Error::last_io_error("security_deny_unknown()")),
0_i32 => Ok(UndefinedHandling::Allowed),
_ => Ok(UndefinedHandling::DeniedAtRunTime),
}
} else {
Ok(UndefinedHandling::RejectedAtLoadTime)
}
}
/// Determine the protection currently checked by SELinux on `mmap()` and
/// `mprotect()` calls.
///
/// See: `security_get_checkreqprot()`.
#[doc(alias = "security_get_checkreqprot")]
pub fn protection_checking_mode() -> Result<ProtectionCheckingMode> {
match unsafe { selinux_sys::security_get_checkreqprot() } {
-1_i32 => Err(Error::last_io_error("security_get_checkreqprot()")),
0_i32 => Ok(ProtectionCheckingMode::CheckingActualProtection),
_ => Ok(ProtectionCheckingMode::CheckingRequestedProtection),
}
}
fn dynamic_mapping_into_native_form<'mapping, 'key, 'value, K, V, O>(
mapping: &'mapping [(K, V)],
c_string_storage: &mut HashMap<&'key str, CString>,
) -> Result<Vec<selinux_sys::security_class_mapping>>
where
'mapping: 'key,
'value: 'key,
K: AsRef<str> + 'key,
V: AsRef<[O]>,
O: AsRef<str> + 'value,
{
let mut c_map = Vec::with_capacity(mapping.len() + 1);
for (name, permissions) in mapping {
let mut element: selinux_sys::security_class_mapping = unsafe { mem::zeroed() };
if permissions.as_ref().len() >= element.perms.len() {
let err = io::ErrorKind::InvalidInput.into();
return Err(Error::from_io("SELinux::set_dynamic_mapping()", err));
}
element.name = match c_string_storage.entry(name.as_ref()) {
hash_map::Entry::Vacant(entry) => {
let c_name = str_to_c_string(name.as_ref())?;
entry.insert(c_name).as_ptr()
}
hash_map::Entry::Occupied(entry) => entry.get().as_ptr(),
};
for (index, permission) in permissions.as_ref().iter().enumerate() {
element.perms[index] = match c_string_storage.entry(permission.as_ref()) {
hash_map::Entry::Vacant(entry) => {
let c_permission = str_to_c_string(permission.as_ref())?;
entry.insert(c_permission).as_ptr()
}
hash_map::Entry::Occupied(entry) => entry.get().as_ptr(),
};
}
c_map.push(element);
}
c_map.push(unsafe { mem::zeroed() }); // End of the array.
Ok(c_map)
}
/// Establishes a mapping from a user-provided ordering of object classes
/// and permissions to the numbers actually used by the loaded system policy.
///
/// See: `selinux_set_mapping()`.
#[doc(alias = "selinux_set_mapping")]
pub fn set_dynamic_mapping<K, V, O>(mapping: &[(K, V)]) -> Result<()>
where
K: AsRef<str>,
V: AsRef<[O]>,
O: AsRef<str>,
{
// The `selinux_set_mapping()` parameter holds pointers to null-terminated strings.
//
// We transform the input `mapping` into an array of `security_class_mapping`
// by transforming `&str` instances into CString instances and storing
// them into `c_string_storage`. Pointers to these `CString`s are then stored
// into the `security_class_mapping` structures.
let mut c_string_storage = HashMap::<&str, CString>::with_capacity(mapping.len() * 3);
let mut c_map = dynamic_mapping_into_native_form(mapping, &mut c_string_storage)?;
let r = unsafe { selinux_sys::selinux_set_mapping(c_map.as_mut_ptr()) };
ret_val_to_result("selinux_set_mapping()", r)
}
/// Flush the SELinux class cache, *e.g.*, upon a policy reload.
///
/// This function requires `libselinux` version `3.1` or later.
///
/// See: `selinux_flush_class_cache()`.
#[doc(alias = "selinux_flush_class_cache")]
pub fn flush_class_cache() -> Result<()> {
Error::clear_errno();
unsafe { (OptionalNativeFunctions::get().selinux_flush_class_cache)() }
let err = io::Error::last_os_error();
match err.raw_os_error() {
None | Some(0_i32) => Ok(()),
_ => Err(Error::from_io("selinux_flush_class_cache()", err)),
}
}
/// Get the SELinux user name and level for a given Linux user name.
///
/// See: `getseuser()`, `getseuserbyname()`.
#[doc(alias = "getseuser")]
#[doc(alias = "getseuserbyname")]
pub fn se_user_and_level(
user_name: &str,
service: Option<&str>,
) -> Result<(CAllocatedBlock<c_char>, CAllocatedBlock<c_char>)> {
let c_user_name = str_to_c_string(user_name)?;
let c_service = if let Some(service) = service {
Some(str_to_c_string(service)?)
} else {
None
};
let mut se_user_ptr: *mut c_char = ptr::null_mut();
let mut level_ptr: *mut c_char = ptr::null_mut();
let (r, proc_name) = if let Some(c_service) = c_service {
let r: c_int = unsafe {
selinux_sys::getseuser(
c_user_name.as_ptr(),
c_service.as_ptr(),
&mut se_user_ptr,
&mut level_ptr,
)
};
(r, "getseuser()")
} else {
let r = unsafe {
selinux_sys::getseuserbyname(c_user_name.as_ptr(), &mut se_user_ptr, &mut level_ptr)
};
(r, "getseuserbyname()")
};
if r == -1_i32 {
Err(Error::last_io_error(proc_name))
} else if se_user_ptr.is_null() || level_ptr.is_null() {
Err(Error::from_io(proc_name, io::ErrorKind::InvalidData.into()))
} else {
let se_user = CAllocatedBlock::new(se_user_ptr)
.ok_or_else(|| Error::from_io(proc_name, io::ErrorKind::InvalidInput.into()))?;
let level = CAllocatedBlock::new(level_ptr)
.ok_or_else(|| Error::from_io(proc_name, io::ErrorKind::InvalidInput.into()))?;
Ok((se_user, level))
}
}
/// Force a reset of the loaded configuration.
///
/// See: `selinux_reset_config()`.
#[doc(alias = "selinux_reset_config")]
pub fn reset_config() {
unsafe { selinux_sys::selinux_reset_config() }
}
/// Get the default type (domain) for role, and set type to refer to it.
///
/// See: `get_default_type()`.
#[doc(alias = "get_default_type")]
pub fn default_type_for_role(role: &str) -> Result<CAllocatedBlock<c_char>> {
let mut c_type: *mut c_char = ptr::null_mut();
let c_role = str_to_c_string(role)?;
if unsafe { selinux_sys::get_default_type(c_role.as_ptr(), &mut c_type) } == -1_i32 {
Err(Error::last_io_error("get_default_type()"))
} else {
CAllocatedBlock::new(c_type)
.ok_or_else(|| Error::from_io("get_default_type()", io::ErrorKind::InvalidData.into()))
}
}