selinux-0.4.5/.cargo_vcs_info.json0000644000000001450000000000100125310ustar { "git": { "sha1": "e7fd3de0fd3b846a376911e8f29bfb4968d22fa8" }, "path_in_vcs": "selinux" }selinux-0.4.5/Cargo.toml0000644000000031150000000000100105270ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "selinux" version = "0.4.5" authors = ["Koutheir Attouchi "] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "Flexible Mandatory Access Control for Linux" homepage = "https://codeberg.org/koutheir/selinux.git" documentation = "https://docs.rs/selinux" readme = "README.md" keywords = [ "selinux", "security", "access-control", "linux", "filesystem", ] categories = [ "api-bindings", "filesystem", "os", "os::linux-apis", ] license = "MIT" repository = "https://codeberg.org/koutheir/selinux.git" [lib] name = "selinux" path = "src/lib.rs" [dependencies.bitflags] version = "2.6" [dependencies.libc] version = "0.2" [dependencies.once_cell] version = "1.19" [dependencies.reference-counted-singleton] version = "0.1" [dependencies.selinux-sys] version = "0.6" [dependencies.thiserror] version = "1.0" [dev-dependencies.assert_matches] version = "1.5" [dev-dependencies.serial_test] version = "3.1" [dev-dependencies.socketpair] version = "0.19" [dev-dependencies.tempfile] version = "3.10" selinux-0.4.5/Cargo.toml.orig000064400000000000000000000022751046102023000142160ustar 00000000000000# https://rust-lang.github.io/api-guidelines/checklist.html [package] name = "selinux" description = "Flexible Mandatory Access Control for Linux" version = "0.4.5" # Update version in `html_root_url`. authors = ["Koutheir Attouchi "] edition = "2021" readme = "../README.md" license = "MIT" keywords = ["selinux", "security", "access-control", "linux", "filesystem"] categories = ["api-bindings", "filesystem", "os", "os::linux-apis"] documentation = "https://docs.rs/selinux" homepage = "https://codeberg.org/koutheir/selinux.git" repository = "https://codeberg.org/koutheir/selinux.git" [dependencies] thiserror = { version = "1.0" } selinux-sys = { version = "0.6" } libc = { version = "0.2" } bitflags = { version = "2.6" } once_cell = { version = "1.19" } reference-counted-singleton = { version = "0.1" } [dev-dependencies] assert_matches = { version = "1.5" } tempfile = { version = "3.10" } serial_test = { version = "3.1" } socketpair = { version = "0.19" } selinux-0.4.5/LICENSE.txt000064400000000000000000000021151046102023000131430ustar 00000000000000MIT License Copyright (c) 2021-2024 Koutheir Attouchi. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. selinux-0.4.5/README.md000064400000000000000000000112521046102023000126010ustar 00000000000000[![crates.io](https://img.shields.io/crates/v/selinux.svg)](https://crates.io/crates/selinux) [![docs.rs](https://docs.rs/selinux/badge.svg)](https://docs.rs/selinux) [![license](https://img.shields.io/github/license/koutheir/selinux?color=black)](https://raw.githubusercontent.com/koutheir/selinux/master/LICENSE.txt) # 🛡️ Safe Rust bindings for `libselinux` SELinux is a flexible Mandatory Access Control for Linux. This crate supports `libselinux` from version `2.8` to `3.7`. Later versions might still be compatible. This crate exposes neither *deprecated* nor *undocumented* SELinux API functions and types. ⚠️ This crate is Linux-specific. Building it for non-Linux platforms, or for the Linux kernel, results in an empty crate. This documentation is too brief to cover SELinux. Please refer to the [official SELinux documentation], the manual pages of the [`libselinux`] native library, and the [`selinux-sys`] crate for a more complete picture on how to use this crate. If you cannot find a feature you are looking for by its name, but you know which `libselinux` APIs relate to it, then try searching the documentation by that API name. ## ⚓ Backward compatibility This crate requires `libselinux` version `2.8`, at least. However, this crate provides some functions that are based on `libselinux` functions implemented in later versions. When such newer functions are needed, this crate attempts to load them dynamically at runtime. If such functions are implemented by `libselinux`, then the called crate functions run as expected. If the needed functions are not implemented by `libselinux`, then an error is returned indicating that the called crate function is unsupported. ## 🔢 Versioning This project adheres to [Semantic Versioning]. The `CHANGELOG.md` file details notable changes over time. ## 🛠️ Development > This section is only relevant for developers contributing to this crate, > and not for users of this crate. 💡 If you're developing this crate and feel important information is missing in this section, then please create an issue or a pull request to fix that. ### Build system This crate uses only `cargo` as a build system. Usual commands are used to perform most operations, *e.g.*, `build`, `test`, `fmt`. > Code is read many times more that written, so this crate's code is always > formatted using `cargo fmt`. Operations requiring special handling are crafted as cargo [xtask] targets. The full list of these special operations can be determined by running: ```shell $ cargo xtask ``` Each special operation can be executed by running: ```shell $ cargo xtask [parameters...] ``` For example, to generate coverage information, run: ```shell $ cargo xtask coverage ``` ### Testing This crate can only be tested on a Linux distribution that has SELinux supported and enabled at multiple levels: - The Linux kernel must support SELinux, and have it enabled. - The file system must be correctly configured. - The user space must have access to SELinux, usually via `libselinux`. [Red Hat Enterprise Linux]-like distributions (*e.g.*, [Fedora], [CentOS], [RockyLinux]) are suitable for testing this crate, either on hardware or inside virtual machines, but not in containers. Given that coverage information requires running tests, that information can only be successfully obtained on a system with SELinux enabled. ### Behavior This crate uses the `libselinux` API as documented in the manual pages. It tries to avoid assumptions about implementation details as far as possible, even when performance might be improved with such knowledge. The structures and enumerations defined by this crate assume that their user might, at some point, decide to call *raw* `libselinux` APIs (possible using the `selinux-sys` crate) for features not yet provided by this crate, or for some other reasons. That is the reason why methods such as `as_ptr()` are implemented by these structures, exposing the raw values that `libselinux` APIs recognize. ### Change log The [change log] is useful to get a picture of what is going on with the crate in the recent past. [Semantic Versioning]: https://semver.org/spec/v2.0.0.html [official SELinux documentation]: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/using_selinux/index [`libselinux`]: https://man7.org/linux/man-pages/man8/selinux.8.html [`selinux-sys`]: https://docs.rs/selinux-sys/ [xtask]: https://github.com/matklad/cargo-xtask [Red Hat Enterprise Linux]: https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux [Fedora]: https://getfedora.org/ [CentOS]: https://www.centos.org/ [RockyLinux]: https://rockylinux.org/ [change log]: https://github.com/koutheir/selinux/blob/master/CHANGELOG.md selinux-0.4.5/src/avc/mod.rs000064400000000000000000000201371046102023000140110ustar 00000000000000#[cfg(test)] mod tests; use std::convert::TryFrom; use std::marker::PhantomData; use std::mem::MaybeUninit; use std::os::raw::{c_char, c_int, c_uint, c_void}; use std::sync::Once; use std::{io, ptr}; use reference_counted_singleton::{RCSRef, RefCountedSingleton}; use crate::errors::{Error, Result}; use crate::utils::{ret_val_to_result, str_to_c_string}; use crate::SecurityContext; /// Access vector cache. #[derive(Debug, PartialEq, Eq)] pub struct AccessVectorCache(Vec); static AVC_INIT: Once = Once::new(); static mut AVC: MaybeUninit> = MaybeUninit::uninit(); fn get_or_init_access_vector_cache() -> &'static RefCountedSingleton { AVC_INIT.call_once(|| unsafe { AVC = MaybeUninit::new(RefCountedSingleton::default()); }); unsafe { AVC.as_ptr() .as_ref() .expect("Static must have a valid address") } } impl AccessVectorCache { /// Initialize the user space access vector cache. /// /// The `options` parameter produces zero or more `(type, value)` tuples, where: /// - `type` is one of `selinux_sys::AVC_OPT_*` values, /// *e.g.*, [`selinux_sys::AVC_OPT_SETENFORCE`]. /// - `value` is a pointer whose semantics are specific to `type`. /// /// Attempting to initialize the access vector cache while it is still /// initialized succeeds only if the subsequent initialization uses the same /// set of options as the previous, still in scope, one. /// /// See: `avc_open()`. #[doc(alias = "avc_open")] pub fn initialize(options: &[(c_int, *const c_void)]) -> Result> { let mut options: Vec = options .iter() .map(|&(type_, value)| selinux_sys::selinux_opt { type_, value: value.cast(), }) .collect(); options.sort_unstable(); options.dedup(); let count = c_uint::try_from(options.len())?; let options_ptr = if count == 0 { ptr::null_mut() } else { options.as_mut_ptr() }; let mut newly_initialized = false; let avc = get_or_init_access_vector_cache(); let result = avc.get_or_init(|| { if unsafe { selinux_sys::avc_open(options_ptr, count) } == -1_i32 { Err(Error::last_io_error("avc_open()")) } else { newly_initialized = true; Ok(AccessVectorCache(options.clone())) // First initialization succeeded. } }); match result { Ok(value) => { if newly_initialized || value.0 == options { // Either: // 1. First initialization succeeded, or // 2. Initializing, while still initialized, using the same // set of options. Ok(value) } else { // Initializing, while still initialized, with a different // set of options, is an error. let err = io::ErrorKind::AlreadyExists.into(); Err(Error::from_io("AccessVectorCache::initialize()", err)) } } Err(None) => Err(Error::LockPoisoned { operation: "RefCountedSingleton::get_or_init()", }), Err(Some(err)) => Err(err), } } /// Flush the user space access vector cache, causing it to forget any /// cached access decisions. /// /// See: `avc_reset()`. #[doc(alias = "avc_reset")] pub fn reset(&self) -> Result<()> { ret_val_to_result("avc_reset()", unsafe { selinux_sys::avc_reset() }) } /// Attempt to free unused memory within the user space access vector /// cache, but do not flush any cached access decisions. /// /// See: `avc_cleanup()`. #[doc(alias = "avc_cleanup")] pub fn clean_up(&self) { unsafe { selinux_sys::avc_cleanup() } } /// Return a security identifier for the kernel initial security identifier /// specified by `security_identifier_name`. /// /// See: `avc_get_initial_sid()`. #[doc(alias = "avc_get_initial_sid")] pub fn kernel_initial_security_id<'context>( &'context self, security_id_name: &str, raw_format: bool, ) -> Result> { let c_name = str_to_c_string(security_id_name)?; let mut security_id: *mut selinux_sys::security_id = ptr::null_mut(); if unsafe { selinux_sys::avc_get_initial_sid(c_name.as_ptr(), &mut security_id) } == -1_i32 { Err(Error::last_io_error("avc_get_initial_sid()")) } else { Ok(SecurityID { security_id, is_raw: raw_format, _phantom_data: PhantomData, }) } } /// Return a security context for the given security identifier. /// /// See: `avc_sid_to_context()`. #[doc(alias = "avc_sid_to_context")] pub fn security_context_from_security_id<'context>( &'context self, mut security_id: SecurityID, ) -> Result> { let is_raw = security_id.is_raw_format(); let (proc, proc_name): (unsafe extern "C" fn(_, _) -> _, _) = if is_raw { let proc_name = "avc_sid_to_context_raw()"; (selinux_sys::avc_sid_to_context_raw, proc_name) } else { let proc_name = "avc_sid_to_context()"; (selinux_sys::avc_sid_to_context, proc_name) }; let mut context: *mut c_char = ptr::null_mut(); let r = unsafe { proc(security_id.as_mut_ptr(), &mut context) }; SecurityContext::from_result(proc_name, r, context, is_raw) } /// Return a security identifier for the given security context. /// /// See: `avc_context_to_sid()`. #[doc(alias = "avc_context_to_sid")] pub fn security_id_from_security_context<'context>( &'context self, context: SecurityContext, ) -> Result> { let is_raw = context.is_raw_format(); let (proc, proc_name): (unsafe extern "C" fn(_, _) -> _, _) = if is_raw { let proc_name = "avc_context_to_sid_raw()"; (selinux_sys::avc_context_to_sid_raw, proc_name) } else { let proc_name = "avc_context_to_sid()"; (selinux_sys::avc_context_to_sid, proc_name) }; let mut security_id: *mut selinux_sys::security_id = ptr::null_mut(); if unsafe { proc(context.as_ptr(), &mut security_id) } == -1_i32 { Err(Error::last_io_error(proc_name)) } else { Ok(SecurityID { security_id, is_raw, _phantom_data: PhantomData, }) } } } impl Drop for AccessVectorCache { fn drop(&mut self) { unsafe { selinux_sys::avc_destroy() }; } } /// SELinux security identifier. #[derive(Debug)] pub struct SecurityID<'id> { security_id: *mut selinux_sys::security_id, is_raw: bool, _phantom_data: PhantomData<&'id selinux_sys::security_id>, } impl<'id> SecurityID<'id> { /// Return `true` if the security identifier is unspecified. #[must_use] pub fn is_unspecified(&self) -> bool { self.security_id.is_null() } /// 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::security_id`]. #[must_use] pub fn as_ptr(&self) -> *const selinux_sys::security_id { self.security_id.cast() } /// Return the managed raw pointer to [`selinux_sys::security_id`]. #[must_use] pub fn as_mut_ptr(&mut self) -> *mut selinux_sys::security_id { self.security_id } } impl<'id> Default for SecurityID<'id> { /// Return an unspecified security identifier. fn default() -> Self { Self { security_id: ptr::null_mut(), is_raw: false, _phantom_data: PhantomData, } } } selinux-0.4.5/src/avc/tests.rs000064400000000000000000000077431046102023000144040ustar 00000000000000use std::{io, ptr}; use assert_matches::assert_matches; use serial_test::serial; #[test] fn security_id_default() { let mut sid = super::SecurityID::default(); assert!(sid.is_unspecified()); assert!(!sid.is_raw_format()); assert!(sid.as_ptr().is_null()); assert!(sid.as_mut_ptr().is_null()); let _ignored = format!("{:?}", &sid); } #[serial] #[test] fn access_vector_cache_initialize() { match super::AccessVectorCache::initialize(&[]) { Ok(avc) => { let _ignored = format!("{avc:?}"); } Err(err) => { assert_matches!(err, crate::errors::Error::IO { .. }); if let crate::errors::Error::IO { source, .. } = err { assert_eq!(source.kind(), io::ErrorKind::NotFound); } } } let options = &[(selinux_sys::AVC_OPT_SETENFORCE, ptr::null())]; match super::AccessVectorCache::initialize(options) { Ok(avc) => { let _ignored = format!("{avc:?}"); } Err(err) => { assert_matches!(err, crate::errors::Error::IO { .. }); if let crate::errors::Error::IO { source, .. } = err { assert_eq!(source.kind(), io::ErrorKind::NotFound); } } } if let Ok(avc0) = super::AccessVectorCache::initialize(options) { if let Ok(avc1) = super::AccessVectorCache::initialize(options) { if let Ok(avc2) = super::AccessVectorCache::initialize(&[( selinux_sys::AVC_OPT_SETENFORCE, ptr::null(), )]) { assert_eq!(avc0, avc1); assert_eq!(avc0, avc2); super::AccessVectorCache::initialize(&[]).unwrap_err(); } } } } #[serial] #[test] fn access_vector_cache_reset() { let options = &[(selinux_sys::AVC_OPT_SETENFORCE, ptr::null())]; let avc = super::AccessVectorCache::initialize(options).unwrap(); avc.reset().unwrap(); } #[serial] #[test] fn access_vector_cache_clean_up() { let options = &[(selinux_sys::AVC_OPT_SETENFORCE, ptr::null())]; let avc = super::AccessVectorCache::initialize(options).unwrap(); avc.clean_up(); } #[serial] #[test] fn access_vector_cache_kernel_initial_security_id() { let options = &[(selinux_sys::AVC_OPT_SETENFORCE, ptr::null())]; let avc = super::AccessVectorCache::initialize(options).unwrap(); match avc.kernel_initial_security_id("unlabeled", false) { Ok(mut sid) => { assert_eq!(sid.as_ptr(), sid.as_mut_ptr()); assert!(!sid.is_raw_format()); assert!(!sid.is_unspecified()); let mut context = avc.security_context_from_security_id(sid).unwrap(); assert!(!context.is_raw_format()); assert_eq!(context.as_ptr(), context.as_mut_ptr()); assert!(!context.as_bytes().is_empty()); let _ignored = avc.security_id_from_security_context(context).unwrap(); } Err(err) => { assert_matches!(err, crate::errors::Error::IO { .. }); if let crate::errors::Error::IO { source, .. } = err { assert_eq!(source.kind(), io::ErrorKind::NotFound); } } } match avc.kernel_initial_security_id("unlabeled", true) { Ok(mut sid) => { assert_eq!(sid.as_ptr(), sid.as_mut_ptr()); assert!(sid.is_raw_format()); assert!(!sid.is_unspecified()); let mut context = avc.security_context_from_security_id(sid).unwrap(); assert!(context.is_raw_format()); assert_eq!(context.as_ptr(), context.as_mut_ptr()); assert!(!context.as_bytes().is_empty()); let _ignored = avc.security_id_from_security_context(context).unwrap(); } Err(err) => { assert_matches!(err, crate::errors::Error::IO { .. }); if let crate::errors::Error::IO { source, .. } = err { assert_eq!(source.kind(), io::ErrorKind::NotFound); } } } } selinux-0.4.5/src/call_back/mod.rs000064400000000000000000000111541046102023000151320ustar 00000000000000#[cfg(test)] mod tests; use std::os::raw::{c_char, c_int, c_void}; /// Call back for SELinux operations. pub trait CallBack { /// Prototype of call back function. type CallBackType; /// Get the current call back function, if one has been set. /// /// See: `selinux_get_callback()`. #[doc(alias = "selinux_get_callback")] fn get_call_back() -> Option; /// Set or clear the call back function. /// /// See: `selinux_set_callback()`. #[doc(alias = "selinux_set_callback")] fn set_call_back(call_back: Option); } /// Call back used for logging. #[derive(Debug, Default)] #[non_exhaustive] pub struct Log; impl CallBack for Log { type CallBackType = unsafe extern "C" fn(c_int, *const c_char, ...) -> c_int; fn get_call_back() -> Option { unsafe { selinux_sys::selinux_get_callback(selinux_sys::SELINUX_CB_LOG).func_log } } fn set_call_back(func_log: Option) { use selinux_sys::{selinux_callback, selinux_set_callback, SELINUX_CB_LOG}; unsafe { selinux_set_callback(SELINUX_CB_LOG, selinux_callback { func_log }) } } } /// Call back used for supplemental auditing in AVC messages. #[derive(Debug, Default)] #[non_exhaustive] pub struct Audit; impl CallBack for Audit { type CallBackType = unsafe extern "C" fn( *mut c_void, selinux_sys::security_class_t, *mut c_char, usize, ) -> c_int; fn get_call_back() -> Option { unsafe { selinux_sys::selinux_get_callback(selinux_sys::SELINUX_CB_AUDIT).func_audit } } fn set_call_back(func_audit: Option) { use selinux_sys::{selinux_callback, selinux_set_callback, SELINUX_CB_AUDIT}; unsafe { selinux_set_callback(SELINUX_CB_AUDIT, selinux_callback { func_audit }) } } } /// Call back used for context validation. #[derive(Debug, Default)] #[non_exhaustive] pub struct ContextValidation; impl CallBack for ContextValidation { type CallBackType = unsafe extern "C" fn(*mut *mut c_char) -> c_int; fn get_call_back() -> Option { unsafe { selinux_sys::selinux_get_callback(selinux_sys::SELINUX_CB_VALIDATE).func_validate } } fn set_call_back(func_validate: Option) { use selinux_sys::{selinux_callback, selinux_set_callback, SELINUX_CB_VALIDATE}; unsafe { selinux_set_callback(SELINUX_CB_VALIDATE, selinux_callback { func_validate }) } } } /// Call back invoked when the system enforcing state changes. #[derive(Debug, Default)] #[non_exhaustive] pub struct EnforcingChange; impl CallBack for EnforcingChange { type CallBackType = unsafe extern "C" fn(c_int) -> c_int; fn get_call_back() -> Option { use selinux_sys::{selinux_get_callback, SELINUX_CB_SETENFORCE}; unsafe { selinux_get_callback(SELINUX_CB_SETENFORCE).func_setenforce } } fn set_call_back(func_setenforce: Option) { use selinux_sys::{selinux_callback, selinux_set_callback, SELINUX_CB_SETENFORCE}; unsafe { selinux_set_callback(SELINUX_CB_SETENFORCE, selinux_callback { func_setenforce }) } } } /// Call back invoked when the system security policy is reloaded. #[derive(Debug, Default)] #[non_exhaustive] pub struct SecurityPolicyReload; impl CallBack for SecurityPolicyReload { type CallBackType = unsafe extern "C" fn(c_int) -> c_int; fn get_call_back() -> Option { use selinux_sys::{selinux_get_callback, SELINUX_CB_POLICYLOAD}; unsafe { selinux_get_callback(SELINUX_CB_POLICYLOAD).func_policyload } } fn set_call_back(func_policyload: Option) { use selinux_sys::{selinux_callback, selinux_set_callback, SELINUX_CB_POLICYLOAD}; unsafe { selinux_set_callback(SELINUX_CB_POLICYLOAD, selinux_callback { func_policyload }) } } } /// Log type argument indicating the type of message. pub mod log_type { use std::os::raw::c_int; /// Error log entry. pub use selinux_sys::SELINUX_ERROR as ERROR; /// Warning log entry. pub use selinux_sys::SELINUX_WARNING as WARNING; /// Informational log entry. pub use selinux_sys::SELINUX_INFO as INFO; /// AVC log entry. pub use selinux_sys::SELINUX_AVC as AVC; // The rest of the constants were defined after version 2.8, so selinux_sys might not // export them. We therefore define them manually. /// Policy loaded. pub static POLICY_LOAD: c_int = 4_i32; /// SELinux enforcing mode changed. pub static SET_ENFORCE: c_int = 5_i32; } selinux-0.4.5/src/call_back/tests.rs000064400000000000000000000043341046102023000155170ustar 00000000000000use core::mem; use std::fmt; use std::os::raw::{c_char, c_int, c_void}; fn template(call_back: ::CallBackType) where T: super::CallBack + Default + fmt::Debug, ::CallBackType: fmt::Debug + Eq + Copy + Sized, { // TODO: We need to arrange for the call back to be actually called. let _ignored = format!("{:?}", T::default()); let old_call_back = T::get_call_back(); T::set_call_back(None); assert!(T::get_call_back().is_none()); let call_back: Option<::CallBackType> = Some(call_back); assert_ne!(old_call_back, call_back); T::set_call_back(call_back); assert_eq!(T::get_call_back(), call_back); T::set_call_back(old_call_back); assert_eq!(T::get_call_back(), old_call_back); } #[test] fn log() { // # Safety // // For now, stable Rust does not allow defining variadic functions. // Once that becomes possible, we should define a variadic function that // calls libc::abort(), and call that instead. // For the moment, we allow calling abort() with a different prototype, // which only "works" in some ABIs, and probably fails horribly in others. template::(unsafe { mem::transmute(libc::abort as unsafe extern "C" fn() -> !) }); } #[test] fn audit() { template::(audit_call_back); } #[test] fn context_validation() { template::(context_validation_call_back); } #[test] fn enforcing_change() { template::(enforcing_change_call_back); } #[test] fn security_policy_reload() { template::(security_policy_reload_call_back); } // Dummy call back functions, of the correct prototypes. unsafe extern "C" fn audit_call_back( _audit_data: *mut c_void, _security_class: selinux_sys::security_class_t, _message_buffer: *mut c_char, _message_buffer_size: usize, ) -> c_int { 0_i32 } unsafe extern "C" fn context_validation_call_back(_context_ptr: *mut *mut c_char) -> c_int { 0_i32 } unsafe extern "C" fn enforcing_change_call_back(_enforcing: c_int) -> c_int { 0_i32 } unsafe extern "C" fn security_policy_reload_call_back(_seqno: c_int) -> c_int { 0_i32 } selinux-0.4.5/src/context_restore/mod.rs000064400000000000000000000373231046102023000164740ustar 00000000000000#[cfg(test)] mod tests; use std::ffi::CStr; use std::marker::PhantomData; use std::os::raw::{c_int, c_uint}; use std::path::Path; use std::{io, iter, ptr}; use crate::errors::{Error, Result}; use crate::label::Labeler; use crate::utils::*; bitflags! { /// Flags controlling relabeling operations. pub struct RestoreFlags: c_uint { /// Force the checking of labels even if the stored SHA1 digest matches /// the specfile entries SHA1 digest. /// /// The specfile entries digest will be written to the `security.sehash` /// extended attribute once relabeling has been completed successfully /// provided the [`NO_CHANGE`] flag has not been set. /// /// [`NO_CHANGE`]: Self::NO_CHANGE const IGNORE_DIGEST = selinux_sys::SELINUX_RESTORECON_IGNORE_DIGEST as c_uint; /// Don't change any file labels (passive check) or update the digest in /// the `security.sehash` extended attribute. const NO_CHANGE = selinux_sys::SELINUX_RESTORECON_NOCHANGE as c_uint; /// If set, reset the files label to match the default spec file context. /// If not set only reset the files "type" component of the context /// to match the default spec file context. const SET_SPEC_FILE_CTX = selinux_sys::SELINUX_RESTORECON_SET_SPECFILE_CTX as c_uint; /// Change file and directory labels recursively (descend directories) /// and if successful write an SHA1 digest of the spec file entries /// to an extended attribute. const RECURSE = selinux_sys::SELINUX_RESTORECON_RECURSE as c_uint; /// Log file label changes. /// /// Note that if [`VERBOSE`] and [`PROGRESS`] flags are set, /// then [`PROGRESS`] will take precedence. /// /// [`VERBOSE`]: Self::VERBOSE /// [`PROGRESS`]: Self::PROGRESS const VERBOSE = selinux_sys::SELINUX_RESTORECON_VERBOSE as c_uint; /// Show progress by outputting the number of files in 1k blocks /// processed to stdout. /// /// If the [`MASS_RELABEL`] flag is also set then the approximate /// percentage complete will be shown. /// /// [`MASS_RELABEL`]: Self::MASS_RELABEL const PROGRESS = selinux_sys::SELINUX_RESTORECON_PROGRESS as c_uint; /// Convert passed-in path name to the canonical path name using /// `realpath()`. const REAL_PATH = selinux_sys::SELINUX_RESTORECON_REALPATH as c_uint; /// Prevent descending into directories that have a different device /// number than the path name entry from which the descent began. const XDEV = selinux_sys::SELINUX_RESTORECON_XDEV as c_uint; /// Attempt to add an association between an inode and a specification. /// If there is already an association for the inode and it conflicts /// with the specification, then use the last matching specification. const ADD_ASSOC = selinux_sys::SELINUX_RESTORECON_ADD_ASSOC as c_uint; /// Abort on errors during the file tree walk. const ABORT_ON_ERROR = selinux_sys::SELINUX_RESTORECON_ABORT_ON_ERROR as c_uint; /// Log any label changes to `syslog()`. const SYS_LOG_CHANGES = selinux_sys::SELINUX_RESTORECON_SYSLOG_CHANGES as c_uint; /// Log what spec file context matched each file. const LOG_MATCHES = selinux_sys::SELINUX_RESTORECON_LOG_MATCHES as c_uint; /// Ignore files that do not exist. const IGNORE_NO_ENTRY = selinux_sys::SELINUX_RESTORECON_IGNORE_NOENTRY as c_uint; /// Do not read `/proc/mounts` to obtain a list of non-seclabel mounts /// to be excluded from relabeling checks. /// /// Setting [`IGNORE_MOUNTS`] is useful where there is a non-seclabel fs /// mounted with a seclabel fs mounted on a directory below this. /// /// [`IGNORE_MOUNTS`]: Self::IGNORE_MOUNTS const IGNORE_MOUNTS = selinux_sys::SELINUX_RESTORECON_IGNORE_MOUNTS as c_uint; /// Generally set when relabeling the entire OS, that will then show /// the approximate percentage complete. /// /// The [`PROGRESS`] flag must also be set. /// /// [`PROGRESS`]: Self::PROGRESS const MASS_RELABEL = selinux_sys::SELINUX_RESTORECON_MASS_RELABEL as c_uint; // The rest of the constants were defined after version 2.8, so selinux_sys might not // export them. We therefore define them manually. /// Do not check or update any extended attribute security.sehash entries. /// /// This flag is supported only by `libselinux` version `3.0` or later. const SKIP_DIGEST = 0x08000; /// Treat conflicting specifications, such as where two hardlinks for /// the same inode have different contexts, as errors. /// /// This flag is supported only by `libselinux` version `3.1` or later. const CONFLICT_ERROR = 0x10000; /// Count, but otherwise ignore, errors during the file tree walk. /// /// This flag requires `libselinux` version `3.4` or later. const COUNT_ERRORS = 0x20000; } } bitflags! { /// Flags of [`ContextRestore::manage_security_sehash_xattr_entries`]. pub struct XAttrFlags: c_uint { /// Recursively descend directories. const RECURSE = selinux_sys::SELINUX_RESTORECON_XATTR_RECURSE as c_uint; /// Delete non-matching digests from each directory in path name. const DELETE_NON_MATCH_DIGESTS = selinux_sys::SELINUX_RESTORECON_XATTR_DELETE_NONMATCH_DIGESTS as c_uint; /// Delete all digests from each directory in path name. const DELETE_ALL_DIGESTS = selinux_sys::SELINUX_RESTORECON_XATTR_DELETE_ALL_DIGESTS as c_uint; /// Don't read `/proc/mounts` to obtain a list of non-seclabel mounts /// to be excluded from the search. /// /// Setting [`IGNORE_MOUNTS`] is useful where there is a non-seclabel fs /// mounted with a seclabel fs mounted on a directory below this. /// /// [`IGNORE_MOUNTS`]: Self::IGNORE_MOUNTS const IGNORE_MOUNTS = selinux_sys::SELINUX_RESTORECON_XATTR_IGNORE_MOUNTS as c_uint; } } /// Restore file(s) default SELinux security contexts. #[derive(Debug, Default)] pub struct ContextRestore<'labeler, T: crate::label::back_end::BackEnd> { labeler: Option<&'labeler mut Labeler>, } impl<'labeler, T> ContextRestore<'labeler, T> where T: crate::label::back_end::BackEnd, { /// Set a labeling handle for relabeling. /// /// See: `selinux_restorecon_set_sehandle()`. #[doc(alias = "selinux_restorecon_set_sehandle")] pub fn with_labeler(labeler: &'labeler mut Labeler) -> Self { Self { labeler: Some(labeler), } } /// Get the labeling handle to be used for relabeling. #[must_use] pub fn labeler(&self) -> Option<&&'labeler mut Labeler> { self.labeler.as_ref() } /// Set an alternate root path for relabeling. /// /// See: `selinux_restorecon_set_alt_rootpath()`. #[doc(alias = "selinux_restorecon_set_alt_rootpath")] pub fn set_alternative_root_path(&mut self, path: impl AsRef) -> Result<()> { let c_path = os_str_to_c_string(path.as_ref().as_os_str())?; let r = unsafe { selinux_sys::selinux_restorecon_set_alt_rootpath(c_path.as_ptr()) }; ret_val_to_result("selinux_restorecon_set_alt_rootpath()", r) } /// Add to the list of directories to be excluded from relabeling. /// /// See: `selinux_restorecon_set_exclude_list()`. #[doc(alias = "selinux_restorecon_set_exclude_list")] pub fn add_exclude_list

( &mut self, exclusion_patterns: impl IntoIterator, ) -> Result<()> where P: AsRef, { let c_list_storage = exclusion_patterns .into_iter() .map(|p| os_str_to_c_string(p.as_ref().as_os_str())) .collect::>>()?; if !c_list_storage.is_empty() { let mut c_ptr_list: Vec<_> = c_list_storage .iter() .map(AsRef::as_ref) .map(CStr::as_ptr) .chain(iter::once(ptr::null())) .collect(); unsafe { selinux_sys::selinux_restorecon_set_exclude_list(c_ptr_list.as_mut_ptr()) }; } Ok(()) } /// Restore file(s) default SELinux security contexts. /// /// If `threads_count` is zero, then: /// - If `selinux_restorecon_parallel()` is supported by `libselinux` (version 3.4 or later), /// then this operation will use as many threads as the number of online processor /// cores present. /// - Otherwise, this operation will run in one thread. /// /// When this method succeeds: /// - If `flags` includes [`RestoreFlags::COUNT_ERRORS`], then this returns `Ok(Some(N))` /// where `N` is the number of errors that were ignored while walking the file system tree /// specified by `path`. /// - Otherwise, `Ok(None)` is returned. /// /// See: `selinux_restorecon()`, `selinux_restorecon_parallel()`. #[doc(alias = "selinux_restorecon")] #[doc(alias = "selinux_restorecon_parallel")] pub fn restore_context_of_file_system_entry( self, path: impl AsRef, threads_count: usize, flags: RestoreFlags, ) -> Result> { if let Some(labeler) = self.labeler.map(Labeler::as_mut_ptr) { unsafe { selinux_sys::selinux_restorecon_set_sehandle(labeler) }; } let c_path = os_str_to_c_string(path.as_ref().as_os_str())?; match threads_count { 0 => { // Call `selinux_restorecon_parallel()` if possible. Error::clear_errno(); let r = unsafe { (OptionalNativeFunctions::get().selinux_restorecon_parallel)( c_path.as_ptr(), flags.bits(), threads_count, ) }; if r == -1_i32 { if io::Error::last_os_error().raw_os_error() != Some(libc::ENOSYS) { return Err(Error::last_io_error("selinux_restorecon_parallel()")); } // `selinux_restorecon_parallel()` is unsupported. // Call `selinux_restorecon()` instead. let r = unsafe { selinux_sys::selinux_restorecon(c_path.as_ptr(), flags.bits()) }; ret_val_to_result("selinux_restorecon()", r)?; } } 1 => { let r = unsafe { selinux_sys::selinux_restorecon(c_path.as_ptr(), flags.bits()) }; ret_val_to_result("selinux_restorecon()", r)?; } _ => { let r = unsafe { (OptionalNativeFunctions::get().selinux_restorecon_parallel)( c_path.as_ptr(), flags.bits(), threads_count, ) }; ret_val_to_result("selinux_restorecon_parallel()", r)?; } } Ok(flags.contains(RestoreFlags::COUNT_ERRORS).then(|| { #[allow(clippy::useless_conversion)] u64::from(unsafe { (OptionalNativeFunctions::get().selinux_restorecon_get_skipped_errors)() }) })) } /// Manage default `security.sehash` extended attribute entries added by /// `selinux_restorecon()`, `setfiles()` or `restorecon()`. /// /// See: `selinux_restorecon_xattr()`. #[doc(alias = "selinux_restorecon_xattr")] pub fn manage_security_sehash_xattr_entries( dir_path: impl AsRef, flags: XAttrFlags, ) -> Result { let mut xattr_list_ptr: *mut *mut selinux_sys::dir_xattr = ptr::null_mut(); let c_dir_path = os_str_to_c_string(dir_path.as_ref().as_os_str())?; let r: c_int = unsafe { selinux_sys::selinux_restorecon_xattr( c_dir_path.as_ptr(), flags.bits(), &mut xattr_list_ptr, ) }; if r == -1_i32 { Err(Error::last_io_error("selinux_restorecon_xattr()")) } else { let xattr_list = ptr::NonNull::new(xattr_list_ptr).map_or( ptr::null_mut(), |mut xattr_list_ptr| unsafe { let xattr_list = *xattr_list_ptr.as_ref(); // Detach the linked list from libselinux, so that we own it from now on. *xattr_list_ptr.as_mut() = ptr::null_mut(); xattr_list }, ); Ok(DirectoryXAttributesIter(xattr_list)) } } } /// Status of a [`DirectoryXAttributes`]. #[derive(Debug)] #[non_exhaustive] pub enum DirectoryDigestResult { /// Match. Match { /// Matching digest deleted from the directory. deleted: bool, }, /// No match. NoMatch { /// Non-matching digest deleted from the directory. deleted: bool, }, /// Error. Error, /// Unknown status. Unknown(c_uint), } /// Result of [`ContextRestore::manage_security_sehash_xattr_entries`]. #[derive(Debug)] pub struct DirectoryXAttributes { pointer: ptr::NonNull, _phantom_data: PhantomData, } impl DirectoryXAttributes { /// Return the managed raw pointer to [`selinux_sys::dir_xattr`]. #[must_use] pub fn as_ptr(&self) -> *const selinux_sys::dir_xattr { self.pointer.as_ptr() } /// Return the managed raw pointer to [`selinux_sys::dir_xattr`]. #[must_use] pub fn as_mut_ptr(&mut self) -> *mut selinux_sys::dir_xattr { self.pointer.as_ptr() } /// Directory path. #[must_use] pub fn directory_path(&self) -> &Path { c_str_ptr_to_path(unsafe { self.pointer.as_ref().directory }) } /// A hex encoded string that can be printed. pub fn digest(&self) -> Result<&str> { c_str_ptr_to_str(unsafe { self.pointer.as_ref().digest }) } /// Status of this entry. #[must_use] pub fn digest_result(&self) -> DirectoryDigestResult { match unsafe { self.pointer.as_ref().result } { selinux_sys::digest_result::MATCH => DirectoryDigestResult::Match { deleted: false }, selinux_sys::digest_result::NOMATCH => { DirectoryDigestResult::NoMatch { deleted: false } } selinux_sys::digest_result::DELETED_MATCH => { DirectoryDigestResult::Match { deleted: true } } selinux_sys::digest_result::DELETED_NOMATCH => { DirectoryDigestResult::NoMatch { deleted: true } } selinux_sys::digest_result::ERROR => DirectoryDigestResult::Error, value => DirectoryDigestResult::Unknown(value), } } } impl Drop for DirectoryXAttributes { fn drop(&mut self) { unsafe { libc::free(self.pointer.as_ref().directory.cast()); libc::free(self.pointer.as_ref().digest.cast()); libc::free(self.pointer.as_ptr().cast()); } } } /// Iterator producing [`DirectoryXAttributes`] elements. #[derive(Debug)] pub struct DirectoryXAttributesIter(*mut selinux_sys::dir_xattr); impl Iterator for DirectoryXAttributesIter { type Item = DirectoryXAttributes; fn next(&mut self) -> Option { ptr::NonNull::new(self.0).map(|pointer| { self.0 = unsafe { pointer.as_ref().next }; DirectoryXAttributes { pointer, _phantom_data: PhantomData, } }) } } selinux-0.4.5/src/context_restore/tests.rs000064400000000000000000000000241046102023000170430ustar 00000000000000// TODO(KAT): Test. selinux-0.4.5/src/errors.rs000064400000000000000000000073071046102023000140010ustar 00000000000000use std::io; use std::num::TryFromIntError; use std::os::raw::c_int; use std::path::PathBuf; use std::str::Utf8Error; use selinux_sys::pid_t; /// Result of a fallible function. pub type Result = std::result::Result; /// Error. #[derive(thiserror::Error, Debug)] #[non_exhaustive] pub enum Error { /// Path is invalid. #[error("path is invalid: '{}'", .0.display())] PathIsInvalid(PathBuf), /// Input security contexts have different formats. #[error("input security contexts have different formats")] SecurityContextFormatMismatch, /// Security context has an expected format. #[error("security context has an expected format")] UnexpectedSecurityContextFormat, /// Lock was poisoned. #[error("{operation} failed due to poisoned lock")] LockPoisoned { /// Operation. operation: &'static str, }, /// Input/Output operation failed. #[error("{operation} failed")] IO { /// Cause. source: io::Error, /// Operation. operation: &'static str, }, /// Operation failed on a process. #[error("{operation} failed on process with ID '{process_id}'")] IO1Process { /// Cause. source: io::Error, /// Operation. operation: &'static str, /// Process identifier. process_id: pid_t, }, /// Operation failed on a named object. #[error("{operation} failed with '{name}'")] IO1Name { /// Cause. source: io::Error, /// Operation. operation: &'static str, /// Name. name: String, }, /// Operation failed on a file system object. #[error("{operation} failed on path '{path}'")] IO1Path { /// Cause. source: io::Error, /// Operation. operation: &'static str, /// Path. path: PathBuf, }, /// Data is not encoded as UTF-8. #[error(transparent)] NotUTF8(#[from] Utf8Error), /// Integer is out of valid range. #[error(transparent)] IntegerOutOfRange(#[from] TryFromIntError), } impl Error { pub(crate) fn from_io(operation: &'static str, source: io::Error) -> Self { Error::IO { source, operation } } pub(crate) fn last_io_error(operation: &'static str) -> Self { Error::IO { source: io::Error::last_os_error(), operation, } } pub(crate) fn from_io_pid( operation: &'static str, process_id: pid_t, source: io::Error, ) -> Self { Error::IO1Process { source, operation, process_id, } } pub(crate) fn from_io_path( operation: &'static str, path: impl Into, source: io::Error, ) -> Self { Error::IO1Path { source, operation, path: path.into(), } } pub(crate) fn from_io_name( operation: &'static str, name: impl Into, source: io::Error, ) -> Self { Error::IO1Name { source, operation, name: name.into(), } } pub(crate) fn set_errno(errno: c_int) { unsafe { *libc::__errno_location() = errno; } } pub(crate) fn clear_errno() { Self::set_errno(0); } #[allow(dead_code)] // This is used by unit tests. pub(crate) fn io_source(&self) -> Option<&io::Error> { match self { Self::IO { source, .. } => Some(source), Self::IO1Process { source, .. } => Some(source), Self::IO1Name { source, .. } => Some(source), Self::IO1Path { source, .. } => Some(source), _ => None, } } } selinux-0.4.5/src/label/back_end.rs000064400000000000000000000022431046102023000152640ustar 00000000000000use std::os::raw::c_uint; /// Security contexts backend. pub trait BackEnd { /// Security contexts backend index. const BACK_END: c_uint; } /// File contexts backend, described in `selabel_file()`. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[non_exhaustive] pub struct File; impl BackEnd for File { const BACK_END: c_uint = selinux_sys::SELABEL_CTX_FILE as c_uint; } /// Media contexts backend, described in `selabel_media()`. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[non_exhaustive] pub struct Media; impl BackEnd for Media { const BACK_END: c_uint = selinux_sys::SELABEL_CTX_MEDIA as c_uint; } /// X Windows contexts backend, described in `selabel_x()`. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[non_exhaustive] pub struct X; impl BackEnd for X { const BACK_END: c_uint = selinux_sys::SELABEL_CTX_X as c_uint; } /// Database objects contexts backend, described in `selabel_db()`. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[non_exhaustive] pub struct DB; impl BackEnd for DB { const BACK_END: c_uint = selinux_sys::SELABEL_CTX_DB as c_uint; } selinux-0.4.5/src/label/mod.rs000064400000000000000000000326501046102023000143220ustar 00000000000000#[cfg(test)] mod tests; use std::ffi::{CStr, CString}; use std::hash::Hash; use std::marker::PhantomData; use std::os::raw::{c_char, c_int, c_void}; use std::path::Path; use std::{cmp, io, iter, ptr, slice}; use crate::errors::{Error, Result}; use crate::utils::*; 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 { pointer: ptr::NonNull, _phantom_data1: PhantomData, _phantom_data2: PhantomData, is_raw: bool, } impl Labeler { /// 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 { let options: Vec = 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 { 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> { 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 Drop for Labeler { fn drop(&mut self) { let pointer = self.pointer.as_ptr(); self.pointer = ptr::NonNull::dangling(); unsafe { selinux_sys::selabel_close(pointer) }; } } impl PartialOrd> for Labeler { /// Compare this instance to another one. /// /// See: `selabel_cmp()`. #[doc(alias = "selabel_cmp")] fn partial_cmp(&self, other: &Labeler) -> Option { 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 PartialEq for Labeler { fn eq(&self, other: &Self) -> bool { self.partial_cmp(other) == Some(cmp::Ordering::Equal) } } impl Labeler { /// 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 { 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, mode: Option, ) -> Result { 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())?; 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, alias_paths: &[impl AsRef], mode: Option, ) -> Result { 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; 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::>>()?; 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 { 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) -> Result { 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, ) -> Result { 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>, calculated_digest: Option>, 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 } } selinux-0.4.5/src/label/tests.rs000064400000000000000000000164261046102023000147100ustar 00000000000000use std::collections::HashSet; use std::ffi::CStr; use std::os::raw::c_void; use std::path::Path; use std::ptr; use assert_matches::assert_matches; #[test] fn labeler_new_file() { let mut labeler1 = super::Labeler::::new(&[], false).unwrap(); assert_eq!(labeler1.as_ptr(), labeler1.as_mut_ptr()); assert!(!labeler1.is_raw_format()); let _ignored = format!("{:?}", &labeler1); let labeler2 = super::Labeler::::new(&[], false).unwrap(); assert_eq!(labeler1, labeler2); } #[test] fn labeler_log_statistics() { let labeler = super::Labeler::::new(&[], false).unwrap(); labeler.log_statistics(); } #[test] fn labeler_digest() { let options = &[(selinux_sys::SELABEL_OPT_DIGEST, 1 as *const c_void)]; let labeler = super::Labeler::::new(options, false).unwrap(); let digest = labeler.digest().unwrap(); assert!(!digest.digest().is_empty()); assert!(!digest.spec_files().is_empty()); } #[test] fn labeler_restorecon_default() { let _labeler = super::Labeler::restorecon_default(false).unwrap(); } #[test] fn labeler_look_up() { for &raw_format in &[false, true] { let labeler = super::Labeler::::new(&[], raw_format).unwrap(); let path = unsafe { CStr::from_ptr("/lib\0".as_ptr().cast()) }; let _context = labeler.look_up(path, 0).unwrap(); } } #[test] fn labeler_look_up_by_path() { for &raw_format in &[false, true] { let labeler = super::Labeler::::new(&[], raw_format).unwrap(); let _context = labeler.look_up_by_path("/lib", None).unwrap(); } } #[test] fn labeler_look_up_best_match_by_path() { for &raw_format in &[false, true] { for &alias_paths in &[&[] as &[&str], &["/usr/lib"]] { let labeler = super::Labeler::::new(&[], raw_format).unwrap(); let _context = labeler .look_up_best_match_by_path("/lib", alias_paths, None) .unwrap(); } } } #[test] fn labeler_partial_match_by_path() { let labeler = super::Labeler::::new(&[], false).unwrap(); let _is_match = labeler.partial_match_by_path("/lib").unwrap(); } #[test] fn labeler_get_digests_all_partial_matches_by_path() { let labeler = super::Labeler::::new(&[], false).unwrap(); if let Err(r) = labeler.get_digests_all_partial_matches_by_path("/tmp") { let r = r.io_source().unwrap().raw_os_error(); assert_matches!(r, Some(libc::ENOSYS | libc::ENOENT)); } } #[test] fn digest() { let sf_two_nulls = &[ptr::null(), ptr::null()]; for &(dg_ptr, dg_len, sf_ptr, sf_len) in &[ (ptr::null(), 0, ptr::null(), 0), (b"".as_ptr(), 0, ptr::null(), 0), (b"xyz".as_ptr(), 0, ptr::null(), 0), (ptr::null(), 3, ptr::null(), 0), (ptr::null(), 0, ptr::null(), 3), (ptr::null(), 0, &[].as_ptr(), 0), (ptr::null(), 0, sf_two_nulls.as_ptr(), sf_two_nulls.len()), ] { let digest = super::Digest::new(dg_ptr, dg_len, sf_ptr, sf_len); assert!(digest.digest().is_empty()); assert!(digest.spec_files().is_empty()); } let digest = super::Digest::new(b"xyz".as_ptr(), 3, ptr::null_mut(), 0); assert_eq!(digest.digest(), b"xyz"); assert!(digest.spec_files().is_empty()); let spec_files = &["abc\0".as_ptr().cast(), ptr::null(), "A\0".as_ptr().cast()]; let digest = super::Digest::new(ptr::null_mut(), 0, spec_files.as_ptr(), spec_files.len()); assert!(digest.digest().is_empty()); assert_eq!(digest.spec_files().len(), 1); assert_eq!(digest.spec_files()[0], Path::new("abc")); let spec_files = &[ "abc\0".as_ptr().cast(), "abcdef\0".as_ptr().cast(), "A\0".as_ptr().cast(), ]; let digest = super::Digest::new(ptr::null_mut(), 0, spec_files.as_ptr(), spec_files.len()); assert!(digest.digest().is_empty()); assert_eq!(digest.spec_files().len(), 3); assert_eq!(digest.spec_files()[0], Path::new("abc")); assert_eq!(digest.spec_files()[1], Path::new("abcdef")); assert_eq!(digest.spec_files()[2], Path::new("A")); let spec_files = &[ "abc\0".as_ptr().cast(), "abcdef\0".as_ptr().cast(), "A\0".as_ptr().cast(), ]; let digest = super::Digest::new(b"xyz".as_ptr(), 3, spec_files.as_ptr(), spec_files.len()); assert_eq!(digest.digest(), b"xyz"); assert_eq!(digest.spec_files().len(), 3); assert_eq!(digest.spec_files()[0], Path::new("abc")); assert_eq!(digest.spec_files()[1], Path::new("abcdef")); assert_eq!(digest.spec_files()[2], Path::new("A")); let _ignored = format!("{:?}", &digest); let digest_clone = digest.clone(); assert_eq!(digest, digest_clone); assert!(digest >= digest_clone); assert!(digest <= digest_clone); let mut ht = HashSet::new(); ht.insert(digest_clone); } #[test] fn partial_matches_digests() { let pmd = super::PartialMatchesDigests { match_result: super::PartialMatchesResult::NoMatchOrMissing, xattr_digest: None, calculated_digest: None, digest_size: 0, }; assert_eq!( pmd.match_result(), super::PartialMatchesResult::NoMatchOrMissing ); assert!(pmd.is_empty()); assert_eq!(pmd.len(), 0); assert_eq!(pmd.xattr_digest(), None); assert_eq!(pmd.calculated_digest(), None); let pmd = super::PartialMatchesDigests { match_result: super::PartialMatchesResult::Match, xattr_digest: None, calculated_digest: None, digest_size: 10, }; assert_eq!(pmd.match_result(), super::PartialMatchesResult::Match); assert!(!pmd.is_empty()); assert_eq!(pmd.len(), 10); assert_eq!(pmd.xattr_digest(), None); assert_eq!(pmd.calculated_digest(), None); let xattr_digest: *mut u8 = unsafe { libc::malloc(3) }.cast(); unsafe { ptr::copy_nonoverlapping("abc".as_ptr(), xattr_digest, 3) }; let pmd = super::PartialMatchesDigests { match_result: super::PartialMatchesResult::Match, xattr_digest: crate::utils::CAllocatedBlock::new(xattr_digest), calculated_digest: None, digest_size: 3, }; assert_eq!(pmd.match_result(), super::PartialMatchesResult::Match); assert!(!pmd.is_empty()); assert_eq!(pmd.len(), 3); assert_eq!(pmd.xattr_digest(), Some(b"abc" as &[u8])); assert_eq!(pmd.calculated_digest(), None); let xattr_digest: *mut u8 = unsafe { libc::malloc(3) }.cast(); unsafe { ptr::copy_nonoverlapping("abc".as_ptr(), xattr_digest, 3) }; let calculated_digest: *mut u8 = unsafe { libc::malloc(3) }.cast(); unsafe { ptr::copy_nonoverlapping("xyz".as_ptr(), calculated_digest, 3) }; let pmd = super::PartialMatchesDigests { match_result: super::PartialMatchesResult::Match, xattr_digest: crate::utils::CAllocatedBlock::new(xattr_digest), calculated_digest: crate::utils::CAllocatedBlock::new(calculated_digest), digest_size: 3, }; assert_eq!(pmd.match_result(), super::PartialMatchesResult::Match); assert!(!pmd.is_empty()); assert_eq!(pmd.len(), 3); assert_eq!(pmd.xattr_digest(), Some(b"abc" as &[u8])); assert_eq!(pmd.calculated_digest(), Some(b"xyz" as &[u8])); let _ignored = format!("{:?}", &pmd); } selinux-0.4.5/src/lib.rs000064400000000000000000002153461046102023000132370ustar 00000000000000#![cfg(target_os = "linux")] #![doc = include_str!("../README.md")] #![doc(html_root_url = "https://docs.rs/selinux/0.4.5")] #![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 )] // // 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::doc_markdown, clippy::exhaustive_structs, clippy::missing_inline_in_public_items, clippy::implicit_return, clippy::missing_docs_in_private_items, clippy::missing_errors_doc, clippy::undocumented_unsafe_blocks, clippy::shadow_reuse, clippy::shadow_unrelated, clippy::separated_literal_suffix, clippy::expect_used, clippy::unused_self, clippy::mod_module_files, clippy::pub_use, clippy::module_name_repetitions, clippy::indexing_slicing )] */ #[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; use std::borrow::Cow; use std::collections::{hash_map, HashMap}; use std::convert::TryFrom; use std::ffi::{CStr, CString}; use std::marker::PhantomData; use std::mem::MaybeUninit; use std::os::raw::{c_char, c_int, c_uint, c_void}; use std::os::unix::io::AsRawFd; use std::path::Path; use std::{cmp, fmt, io, mem, ptr, slice, str}; use selinux_sys::pid_t; #[macro_use] extern crate bitflags; use errors::{Error, Result}; use utils::*; /// 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, size: Option, 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>> { 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 { 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::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, raw_format: bool) -> Result { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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> { 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> { 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> { 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> { 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, 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::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) -> 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, 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(fd: &T, raw_format: bool) -> Result> 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| { 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(&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(socket: &T, raw_format: bool) -> Result 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 { 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::::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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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, mode: Option, ) -> Result { 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> { 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, size: Option, raw_format: bool) -> Self { 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 { 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 { 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 { 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 { let colors: Vec = 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 <= 0x00ffffff_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<'context> Drop for SecurityContext<'context> { /// 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, } 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 { 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, 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> { 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 { 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 { 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, "") } 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 { 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 { 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 { 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> { 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 for SecurityClass { type Error = Error; /// See: `mode_to_security_class()`. #[doc(alias = "mode_to_security_class")] fn try_from(mode: FileAccessMode) -> Result { 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, _phantom_data: PhantomData, } 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 s = if ptr.is_null() { Cow::Borrowed("") } else { unsafe { CStr::from_ptr(ptr) }.to_string_lossy() }; write!(f, "{s}") } } /// 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 { 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 { 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 { 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> where 'mapping: 'key, 'value: 'key, K: AsRef + 'key, V: AsRef<[O]>, O: AsRef + '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(e) => { let c_name = str_to_c_string(name.as_ref())?; e.insert(c_name).as_ptr() } hash_map::Entry::Occupied(e) => e.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(e) => { let c_permission = str_to_c_string(permission.as_ref())?; e.insert(c_permission).as_ptr() } hash_map::Entry::Occupied(e) => e.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(mapping: &[(K, V)]) -> Result<()> where K: AsRef, V: AsRef<[O]>, O: AsRef, { // 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, CAllocatedBlock)> { 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> { 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())) } } selinux-0.4.5/src/path/mod.rs000064400000000000000000000217511046102023000141770ustar 00000000000000#[cfg(test)] mod tests; use std::path::Path; use crate::errors::Result; use crate::utils::get_static_path; /// Return the top-level SELinux configuration directory. /// /// See: `selinux_path()`. #[doc(alias = "selinux_path")] pub fn selinux() -> Result<&'static Path> { get_static_path(selinux_sys::selinux_path, "selinux_path()") } /// Return the context file mapping roles to default types. /// /// See: `selinux_default_type_path()`. #[doc(alias = "selinux_default_type_path")] pub fn default_type_path() -> Result<&'static Path> { let proc_name = "selinux_default_type_path()"; get_static_path(selinux_sys::selinux_default_type_path, proc_name) } /// Return the fail-safe context for emergency logins. /// /// See: `selinux_failsafe_context_path()`. #[doc(alias = "selinux_failsafe_context_path")] pub fn fail_safe_context() -> Result<&'static Path> { let proc_name = "selinux_failsafe_context_path()"; get_static_path(selinux_sys::selinux_failsafe_context_path, proc_name) } /// Return the file system context for removable media. /// /// See: `selinux_removable_context_path()`. #[doc(alias = "selinux_removable_context_path")] pub fn removable_context() -> Result<&'static Path> { let proc_name = "selinux_removable_context_path()"; get_static_path(selinux_sys::selinux_removable_context_path, proc_name) } /// Return the system-wide default contexts for user sessions. /// /// See: `selinux_default_context_path()`. #[doc(alias = "selinux_default_context_path")] pub fn default_context() -> Result<&'static Path> { let proc_name = "selinux_default_context_path()"; get_static_path(selinux_sys::selinux_default_context_path, proc_name) } /// Return the directory containing per-user default contexts. /// /// See: `selinux_user_contexts_path()`. #[doc(alias = "selinux_user_contexts_path")] pub fn user_contexts() -> Result<&'static Path> { let proc_name = "selinux_user_contexts_path()"; get_static_path(selinux_sys::selinux_user_contexts_path, proc_name) } /// Return the default system file contexts configuration. /// /// See: `selinux_file_context_path()`. #[doc(alias = "selinux_file_context_path")] pub fn file_context() -> Result<&'static Path> { let proc_name = "selinux_file_context_path()"; get_static_path(selinux_sys::selinux_file_context_path, proc_name) } /// Return the home directory file contexts configuration. /// /// See: `selinux_file_context_homedir_path()`. #[doc(alias = "selinux_file_context_homedir_path")] pub fn file_context_homedir() -> Result<&'static Path> { let proc_name = "selinux_file_context_homedir_path()"; get_static_path(selinux_sys::selinux_file_context_homedir_path, proc_name) } /// Return the local customization file contexts configuration. /// /// See: `selinux_file_context_local_path()`. #[doc(alias = "selinux_file_context_local_path")] pub fn file_context_local() -> Result<&'static Path> { let proc_name = "selinux_file_context_local_path()"; get_static_path(selinux_sys::selinux_file_context_local_path, proc_name) } /// See: `selinux_file_context_subs_path()`. #[doc(alias = "selinux_file_context_subs_path")] pub fn file_context_subs() -> Result<&'static Path> { let proc_name = "selinux_file_context_subs_path()"; get_static_path(selinux_sys::selinux_file_context_subs_path, proc_name) } /// See: `selinux_file_context_subs_dist_path()`. #[doc(alias = "selinux_file_context_subs_dist_path")] pub fn file_context_subs_dist() -> Result<&'static Path> { let proc_name = "selinux_file_context_subs_dist_path()"; get_static_path(selinux_sys::selinux_file_context_subs_dist_path, proc_name) } /// See: `selinux_homedir_context_path()`. #[doc(alias = "selinux_homedir_context_path")] pub fn home_dir_context() -> Result<&'static Path> { let proc_name = "selinux_homedir_context_path()"; get_static_path(selinux_sys::selinux_homedir_context_path, proc_name) } /// Return the file contexts for media device nodes. /// /// See: `selinux_media_context_path()`. #[doc(alias = "selinux_media_context_path")] pub fn media_context() -> Result<&'static Path> { let proc_name = "selinux_media_context_path()"; get_static_path(selinux_sys::selinux_media_context_path, proc_name) } /// See: `selinux_virtual_domain_context_path()`. #[doc(alias = "selinux_virtual_domain_context_path")] pub fn virtual_domain_context() -> Result<&'static Path> { let proc_name = "selinux_virtual_domain_context_path()"; get_static_path(selinux_sys::selinux_virtual_domain_context_path, proc_name) } /// See: `selinux_virtual_image_context_path()`. #[doc(alias = "selinux_virtual_image_context_path")] pub fn virtual_image_context() -> Result<&'static Path> { let proc_name = "selinux_virtual_image_context_path()"; get_static_path(selinux_sys::selinux_virtual_image_context_path, proc_name) } /// See: `selinux_lxc_contexts_path()`. #[doc(alias = "selinux_lxc_contexts_path")] pub fn lxc_contexts() -> Result<&'static Path> { let proc_name = "selinux_lxc_contexts_path()"; get_static_path(selinux_sys::selinux_lxc_contexts_path, proc_name) } /// Return the file containing configuration for XSELinux extension. /// /// See: `selinux_x_context_path()`. #[doc(alias = "selinux_x_context_path")] pub fn x_context() -> Result<&'static Path> { let proc_name = "selinux_x_context_path()"; get_static_path(selinux_sys::selinux_x_context_path, proc_name) } /// Return the file containing configuration for SE-PostgreSQL. /// /// See: `selinux_sepgsql_context_path()`. #[doc(alias = "selinux_sepgsql_context_path")] pub fn sepgsql_context() -> Result<&'static Path> { let proc_name = "selinux_sepgsql_context_path()"; get_static_path(selinux_sys::selinux_sepgsql_context_path, proc_name) } /// See: `selinux_openrc_contexts_path()`. #[doc(alias = "selinux_openrc_contexts_path")] pub fn openrc_contexts() -> Result<&'static Path> { let proc_name = "selinux_openrc_contexts_path()"; get_static_path(selinux_sys::selinux_openrc_contexts_path, proc_name) } /// See: `selinux_openssh_contexts_path()`. #[doc(alias = "selinux_openssh_contexts_path")] pub fn openssh_contexts() -> Result<&'static Path> { let proc_name = "selinux_openssh_contexts_path()"; get_static_path(selinux_sys::selinux_openssh_contexts_path, proc_name) } /// See: `selinux_snapperd_contexts_path()`. #[doc(alias = "selinux_snapperd_contexts_path")] pub fn snapperd_contexts() -> Result<&'static Path> { let proc_name = "selinux_snapperd_contexts_path()"; get_static_path(selinux_sys::selinux_snapperd_contexts_path, proc_name) } /// See: `selinux_systemd_contexts_path()`. #[doc(alias = "selinux_systemd_contexts_path")] pub fn systemd_contexts() -> Result<&'static Path> { let proc_name = "selinux_systemd_contexts_path()"; get_static_path(selinux_sys::selinux_systemd_contexts_path, proc_name) } /// Return the directory containing all of the context configuration files. /// /// See: `selinux_contexts_path()`. #[doc(alias = "selinux_contexts_path")] pub fn contexts() -> Result<&'static Path> { let proc_name = "selinux_contexts_path()"; get_static_path(selinux_sys::selinux_contexts_path, proc_name) } /// Return the defines tty types for newrole securettys. /// /// See: `selinux_securetty_types_path()`. #[doc(alias = "selinux_securetty_types_path")] pub fn securetty_types() -> Result<&'static Path> { let proc_name = "selinux_securetty_types_path()"; get_static_path(selinux_sys::selinux_securetty_types_path, proc_name) } /// See: `selinux_booleans_subs_path()`. #[doc(alias = "selinux_booleans_subs_path")] pub fn booleans_subs() -> Result<&'static Path> { let proc_name = "selinux_booleans_subs_path()"; get_static_path(selinux_sys::selinux_booleans_subs_path, proc_name) } /// See: `selinux_customizable_types_path()`. #[doc(alias = "selinux_customizable_types_path")] pub fn customizable_types() -> Result<&'static Path> { let proc_name = "selinux_customizable_types_path()"; get_static_path(selinux_sys::selinux_customizable_types_path, proc_name) } /// Return the file containing mapping between Linux users and SELinux users. /// /// See: `selinux_usersconf_path()`. #[doc(alias = "selinux_usersconf_path")] pub fn users_conf() -> Result<&'static Path> { let proc_name = "selinux_usersconf_path()"; get_static_path(selinux_sys::selinux_usersconf_path, proc_name) } /// See: `selinux_translations_path()`. #[doc(alias = "selinux_translations_path")] pub fn translations() -> Result<&'static Path> { let proc_name = "selinux_translations_path()"; get_static_path(selinux_sys::selinux_translations_path, proc_name) } /// See: `selinux_colors_path()`. #[doc(alias = "selinux_colors_path")] pub fn colors() -> Result<&'static Path> { get_static_path(selinux_sys::selinux_colors_path, "selinux_colors_path()") } /// Return the default netfilter context. /// /// See: `selinux_netfilter_context_path()`. #[doc(alias = "selinux_netfilter_context_path")] pub fn netfilter_context() -> Result<&'static Path> { let proc_name = "selinux_netfilter_context_path()"; get_static_path(selinux_sys::selinux_netfilter_context_path, proc_name) } selinux-0.4.5/src/path/tests.rs000064400000000000000000000144231046102023000145600ustar 00000000000000#[test] fn selinux() { let path = super::selinux().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn default_type_path() { let path = super::default_type_path().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn fail_safe_context() { let path = super::fail_safe_context().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn removable_context() { let path = super::removable_context().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn default_context() { let path = super::default_context().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn user_contexts() { let path = super::user_contexts().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn file_context() { let path = super::file_context().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn file_context_homedir() { let path = super::file_context_homedir().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn file_context_local() { let path = super::file_context_local().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn file_context_subs() { let path = super::file_context_subs().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn file_context_subs_dist() { let path = super::file_context_subs_dist().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn home_dir_context() { let path = super::home_dir_context().unwrap(); assert!(!path.as_os_str().is_empty()); //assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn media_context() { let path = super::media_context().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn virtual_domain_context() { let path = super::virtual_domain_context().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn virtual_image_context() { let path = super::virtual_image_context().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn lxc_contexts() { let path = super::lxc_contexts().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn x_context() { let path = super::x_context().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn sepgsql_context() { let path = super::sepgsql_context().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn openrc_contexts() { let path = super::openrc_contexts().unwrap(); assert!(!path.as_os_str().is_empty()); //assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn openssh_contexts() { let path = super::openssh_contexts().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn snapperd_contexts() { let path = super::snapperd_contexts().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn systemd_contexts() { let path = super::systemd_contexts().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn contexts() { let path = super::contexts().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn securetty_types() { let path = super::securetty_types().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn booleans_subs() { let path = super::booleans_subs().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn customizable_types() { let path = super::customizable_types().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn users_conf() { let path = super::users_conf().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn translations() { let path = super::translations().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn colors() { let path = super::colors().unwrap(); assert!(!path.as_os_str().is_empty()); //assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn netfilter_context() { let path = super::netfilter_context().unwrap(); assert!(!path.as_os_str().is_empty()); //assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } selinux-0.4.5/src/policy/mod.rs000064400000000000000000000073071046102023000145430ustar 00000000000000#[cfg(test)] mod tests; use std::os::raw::{c_char, c_int, c_uint, c_void}; use std::path::Path; use std::{io, ptr}; use crate::errors::{Error, Result}; use crate::utils::*; /// Load a new SELinux policy. /// /// See: `security_load_policy()`. #[doc(alias = "security_load_policy")] pub fn load(policy_bytes: &[u8]) -> Result<()> { // security_load_policy() declares "data" as a constant pointer starting from libselinux // version 3.5. // Previous supported versions have the same security_load_policy() implementation, but declare // "data" as a mutable pointer, even though it is never modified. let data = policy_bytes.as_ptr() as *mut c_void; let r = unsafe { selinux_sys::security_load_policy(data.cast(), policy_bytes.len()) }; ret_val_to_result("security_load_policy()", r) } /// Make a policy image and load it. /// /// See: `selinux_mkload_policy()`. #[doc(alias = "selinux_mkload_policy")] pub fn make_and_load() -> Result<()> { let r = unsafe { selinux_sys::selinux_mkload_policy(0) }; ret_val_to_result("selinux_mkload_policy()", r) } /// Perform the initial policy load. /// /// See: `selinux_init_load_policy()`. #[doc(alias = "selinux_init_load_policy")] pub fn load_initial() -> Result { let mut enforce: c_int = 0; if unsafe { selinux_sys::selinux_init_load_policy(&mut enforce) } == -1_i32 { Err(Error::last_io_error("selinux_init_load_policy()")) } else { Ok(enforce) } } /// Get the type of SELinux policy running on the system. /// /// See: `selinux_getpolicytype()`. #[doc(alias = "selinux_getpolicytype")] pub fn policy_type() -> Result> { let mut name_ptr: *mut c_char = ptr::null_mut(); if unsafe { selinux_sys::selinux_getpolicytype(&mut name_ptr) } == -1_i32 { Err(Error::last_io_error("selinux_getpolicytype()")) } else { CAllocatedBlock::new(name_ptr).ok_or_else(|| { Error::from_io("selinux_getpolicytype()", io::ErrorKind::InvalidData.into()) }) } } /// Get the version of the SELinux policy. /// /// See: `security_policyvers()`. #[doc(alias = "security_policyvers")] pub fn version_number() -> Result { let r: c_int = unsafe { selinux_sys::security_policyvers() }; if r == -1_i32 { Err(Error::last_io_error("security_policyvers()")) } else { Ok(r as c_uint) } } /// Return the path of the SELinux policy files for this machine. /// /// See: `selinux_policy_root()`. #[doc(alias = "selinux_policy_root")] pub fn root_path() -> Result<&'static Path> { get_static_path(selinux_sys::selinux_policy_root, "selinux_policy_root()") } /// Set an alternate SELinux root path for the SELinux policy files for this machine. /// /// See: `selinux_set_policy_root()`. #[doc(alias = "selinux_set_policy_root")] pub fn set_root_path(path: impl AsRef) -> Result<()> { let c_path = os_str_to_c_string(path.as_ref().as_os_str())?; let r = unsafe { selinux_sys::selinux_set_policy_root(c_path.as_ptr()) }; ret_val_to_result_with_path("selinux_set_policy_root()", r, path.as_ref()) } /// Return the currently loaded policy file from the kernel. /// /// See: `selinux_current_policy_path()`. #[doc(alias = "selinux_current_policy_path")] pub fn current_policy_path() -> Result<&'static Path> { let proc_name = "selinux_current_policy_path()"; get_static_path(selinux_sys::selinux_current_policy_path, proc_name) } /// Return the binary policy file loaded into kernel. /// /// See: `selinux_binary_policy_path()`. #[doc(alias = "selinux_binary_policy_path")] pub fn binary_policy_path() -> Result<&'static Path> { let proc_name = "selinux_binary_policy_path()"; get_static_path(selinux_sys::selinux_binary_policy_path, proc_name) } selinux-0.4.5/src/policy/tests.rs000064400000000000000000000030501046102023000151150ustar 00000000000000#[test] fn version_number() { match super::version_number() { Ok(version) => assert_ne!(version, 0), Err(_err) => assert_eq!(crate::current_mode(), crate::SELinuxMode::NotRunning), } } #[test] fn policy_type() { match super::policy_type() { Ok(name) => assert!(!name.as_c_str().to_bytes().is_empty()), Err(_err) => assert_eq!(crate::current_mode(), crate::SELinuxMode::NotRunning), } } #[test] fn root_path() { let path = super::root_path().unwrap(); assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn current_policy_path() { match super::current_policy_path() { Ok(path) => { assert!(!path.as_os_str().is_empty()); assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } Err(_err) => assert!(crate::current_mode() == crate::SELinuxMode::NotRunning), } } #[test] fn binary_policy_path() { let path = super::binary_policy_path().unwrap(); assert!(!path.as_os_str().is_empty()); //assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); } #[test] fn load() { let policy_bytes = []; super::load(&policy_bytes).unwrap_err(); } #[test] fn make_and_load() { super::make_and_load().unwrap_err(); } #[test] fn load_initial() { super::load_initial().unwrap_err(); } #[test] fn set_root_path() { let path = super::current_policy_path().unwrap(); super::set_root_path(path).unwrap(); } selinux-0.4.5/src/tests.rs000064400000000000000000000662121046102023000136270ustar 00000000000000use std::collections::{HashMap, HashSet}; use std::convert::TryFrom; use std::ffi::CStr; use std::io::Write; use std::os::raw::{c_char, c_int}; use std::path::Path; use std::{fs, io, process, ptr}; use assert_matches::assert_matches; use crate::utils::*; #[test] fn security_context_from_c_str() { let ptr: *const c_char = "xyz\0".as_ptr().cast(); let s = unsafe { CStr::from_ptr(ptr) }; let mut context = super::SecurityContext::from_c_str(s, false); assert_eq!(context.as_ptr(), ptr); assert_eq!(context.as_mut_ptr(), ptr as *mut c_char); assert_eq!(context.as_bytes().len(), 3); assert!(!context.is_raw_format()); let _ignored = format!("{:?}", &context); } #[test] fn security_context_from_result() { super::SecurityContext::from_result("xyz", 0, ptr::null_mut(), false).unwrap_err(); crate::errors::Error::set_errno(1); super::SecurityContext::from_result("xyz", -1, 0x1000 as *mut c_char, false).unwrap_err(); crate::errors::Error::clear_errno(); } #[test] fn security_context_from_result_with_name() { super::SecurityContext::from_result_with_name("xyz", 0, ptr::null_mut(), "abc", false) .unwrap_err(); crate::errors::Error::set_errno(1); super::SecurityContext::from_result_with_name("xyz", -1, 0x1000 as *mut c_char, "abc", false) .unwrap_err(); crate::errors::Error::clear_errno(); } #[test] fn security_context_from_result_with_pid() { super::SecurityContext::from_result_with_pid("xyz", 0, ptr::null_mut(), 1, false).unwrap_err(); crate::errors::Error::set_errno(1); super::SecurityContext::from_result_with_pid("xyz", -1, 0x1000 as *mut c_char, 1, false) .unwrap_err(); crate::errors::Error::clear_errno(); } #[test] fn security_context_parse_context_color() { use super::{LayerColors, SecurityContext, SecurityContextColors, RGB}; for &bytes in &[ b"" as &[u8], b" ", b" ", b"s", b"s t", b"s t u", b"s t u v", b"s t u v w", b"s t u v w x", b"s t u v w x y", b"s t u v w x y z", b"# # # # # # # #", b"#s #t #u #v #w #x #y #z", b"#0", b"#0 #0", b"#0 #0 #0", b"#0 #0 #0 #0", b"#0 #0 #0 #0 #0", b"#0 #0 #0 #0 #0 #0", b"#0 #0 #0 #0 #0 #0 #0", b"#-1 #0 #0 #0 #0 #0 #0 #0", b"#100000000 #0 #0 #0 #0 #0 #0 #0", b"#1000000 #0 #0 #0 #0 #0 #0 #0", ] { SecurityContext::parse_context_color(bytes).unwrap_err(); } let bytes = b"#11 #22 #aa #bb #cc #dd #ee #ff"; let colors = SecurityContext::parse_context_color(bytes).unwrap(); let expected_colors = SecurityContextColors::new( LayerColors::new(RGB::new(0x22, 0, 0), RGB::new(0x11, 0, 0)), LayerColors::new(RGB::new(0xbb, 0, 0), RGB::new(0xaa, 0, 0)), LayerColors::new(RGB::new(0xdd, 0, 0), RGB::new(0xcc, 0, 0)), LayerColors::new(RGB::new(0xff, 0, 0), RGB::new(0xee, 0, 0)), ); assert_eq!(colors, expected_colors); } #[test] fn security_context_color() { use super::{LayerColors, SecurityContextColors, RGB}; let scc = SecurityContextColors::new( LayerColors::new(RGB::new(0x22, 0, 0), RGB::new(0x11, 0, 0)), LayerColors::new(RGB::new(0xbb, 0, 0), RGB::new(0xaa, 0, 0)), LayerColors::new(RGB::new(0xdd, 0, 0), RGB::new(0xcc, 0, 0)), LayerColors::new(RGB::new(0xff, 0, 0), RGB::new(0xee, 0, 0)), ); let scc_clone = super::SecurityContextColors::clone(&scc); assert_eq!(scc, scc_clone); assert!(scc >= scc_clone); assert!(scc <= scc_clone); assert_ne!(scc, super::SecurityContextColors::default()); let _ignored = format!("{:?}", &scc); let mut ht = HashSet::new(); ht.insert(scc_clone); } #[test] fn security_context_current() { let mut context = super::SecurityContext::current(false).unwrap(); assert!(!context.as_ptr().is_null()); assert!(!context.as_mut_ptr().is_null()); assert!(!context.as_bytes().is_empty()); if let Err(err) = context.is_customizable() { assert_matches!(err, crate::errors::Error::IO { .. }); if let crate::errors::Error::IO { source, .. } = err { let errno = source.raw_os_error().unwrap(); assert!(errno == libc::EINVAL || errno == libc::ENOTDIR); } } let r = context.check(); assert!(r.is_none() || r == Some(true)); let _canon_context = context.canonicalize().unwrap(); let _securetty = context.check_securetty_context(); //let _color = context.to_color().unwrap(); context.to_translated_format().unwrap_err(); let raw_context = context.to_raw_format().unwrap(); raw_context.to_raw_format().unwrap_err(); let _canon_raw_context = raw_context.canonicalize().unwrap(); let r = raw_context.check(); assert!(r.is_none() || r == Some(true)); let _context = raw_context.to_translated_format().unwrap(); let _raw_context = super::SecurityContext::current(true).unwrap(); let _cmp = context.compare_user_insensitive(&raw_context); } #[test] fn security_context_previous() { let _context = super::SecurityContext::previous(false).unwrap(); let _context = super::SecurityContext::previous(true).unwrap(); } #[test] fn security_context_set_as_current() { for &raw_format in &[false, true] { let context = super::SecurityContext::current(raw_format).unwrap(); context.set_as_current().unwrap(); } } #[test] fn security_context_of_next_exec() { let _context = super::SecurityContext::of_next_exec(false).unwrap(); let _context = super::SecurityContext::of_next_exec(true).unwrap(); } #[test] fn security_context_set_default_context_for_next_exec() { super::SecurityContext::set_default_context_for_next_exec().unwrap(); } #[test] fn security_context_set_for_next_exec() { for &raw_format in &[false, true] { let old_context = super::SecurityContext::of_next_exec(raw_format).unwrap(); let context = super::SecurityContext::current(raw_format).unwrap(); context.set_for_next_exec().unwrap(); if let Some(context) = old_context { context.set_for_next_exec().unwrap(); } else { super::SecurityContext::set_default_context_for_next_exec().unwrap(); } } } #[test] fn security_context_of_new_file_system_objects() { let _context = super::SecurityContext::of_new_file_system_objects(false).unwrap(); let _context = super::SecurityContext::of_new_file_system_objects(true).unwrap(); } #[test] fn security_context_set_default_context_for_new_file_system_objects() { super::SecurityContext::set_default_context_for_new_file_system_objects().unwrap(); } #[test] fn security_context_set_for_new_file_system_objects() { for &raw_format in &[false, true] { let old_context = super::SecurityContext::of_new_file_system_objects(raw_format).unwrap(); let context = super::SecurityContext::current(raw_format).unwrap(); context.set_for_new_file_system_objects(raw_format).unwrap(); if let Some(context) = old_context { context.set_for_new_file_system_objects(raw_format).unwrap(); } else { super::SecurityContext::set_default_context_for_new_file_system_objects().unwrap(); } } } #[test] fn security_context_of_new_kernel_key_rings() { let _context = super::SecurityContext::of_new_kernel_key_rings(false).unwrap(); let _context = super::SecurityContext::of_new_kernel_key_rings(true).unwrap(); } #[test] fn security_context_set_default_context_for_new_kernel_key_rings() { super::SecurityContext::set_default_context_for_new_kernel_key_rings().unwrap(); } #[test] fn security_context_set_for_new_kernel_key_rings() { for &raw_format in &[false, true] { let old_context = super::SecurityContext::of_new_kernel_key_rings(raw_format).unwrap(); let context = super::SecurityContext::current(raw_format).unwrap(); context.set_for_new_kernel_key_rings(raw_format).unwrap(); if let Some(context) = old_context { context.set_for_new_kernel_key_rings(raw_format).unwrap(); } else { super::SecurityContext::set_default_context_for_new_kernel_key_rings().unwrap(); } } } #[test] fn security_context_of_new_labeled_sockets() { let _context = super::SecurityContext::of_new_labeled_sockets(false).unwrap(); let _context = super::SecurityContext::of_new_labeled_sockets(true).unwrap(); } #[test] fn security_context_set_default_context_for_new_labeled_sockets() { super::SecurityContext::set_default_context_for_new_labeled_sockets().unwrap(); } #[test] fn security_context_set_for_new_labeled_sockets() { for &raw_format in &[false, true] { let old_context = super::SecurityContext::of_new_labeled_sockets(raw_format).unwrap(); let context = super::SecurityContext::current(raw_format).unwrap(); context.set_for_new_labeled_sockets(raw_format).unwrap(); if let Some(context) = old_context { context.set_for_new_labeled_sockets(raw_format).unwrap(); } else { super::SecurityContext::set_default_context_for_new_labeled_sockets().unwrap(); } } } #[test] fn security_context_of_initial_kernel_context() { for &raw_format in &[false, true] { let _context = super::SecurityContext::of_initial_kernel_context("unlabeled", raw_format).unwrap(); } } #[test] fn security_context_of_process() { let pid = process::id() as c_int; for &raw_format in &[false, true] { let _context = super::SecurityContext::of_process(pid, raw_format).unwrap(); } } #[test] fn security_context_of_se_user_with_selected_context() { //let _context = // super::SecurityContext::of_se_user_with_selected_context("unconfined_u", false).unwrap(); } #[test] fn security_context_default_for_se_user() { let _context = super::SecurityContext::default_for_se_user("unconfined_u", None, None, None, false) .unwrap(); let _context = super::SecurityContext::default_for_se_user( "unconfined_u", Some("unconfined_r"), None, None, false, ) .unwrap(); let _context = super::SecurityContext::default_for_se_user("unconfined_u", None, Some("low"), None, false) .unwrap(); let _context = super::SecurityContext::default_for_se_user( "unconfined_u", Some("unconfined_r"), Some("low"), None, false, ) .unwrap(); let context = super::SecurityContext::current(false).unwrap(); let _context = super::SecurityContext::default_for_se_user( "unconfined_u", None, None, Some(&context), false, ) .unwrap(); } #[test] fn security_context_of_media_type() { super::SecurityContext::of_media_type("invalid").unwrap_err(); //let _context = super::SecurityContext::of_media_type("unlabeled").unwrap(); } #[test] fn security_context_of_labeling_decision() { let context = super::SecurityContext::current(false).unwrap(); let raw_context = super::SecurityContext::current(true).unwrap(); let target_class = super::SecurityClass::from_name("process").unwrap(); context .of_labeling_decision(&raw_context, target_class, "process") .unwrap_err(); let _new_context = context .of_labeling_decision(&context, target_class, "process") .unwrap(); let _new_context = raw_context .of_labeling_decision(&raw_context, target_class, "process") .unwrap(); } #[test] fn security_context_of_relabeling_decision() { let context = super::SecurityContext::current(false).unwrap(); let raw_context = super::SecurityContext::current(true).unwrap(); let target_class = super::SecurityClass::from_name("process").unwrap(); context .of_relabeling_decision(&raw_context, target_class) .unwrap_err(); let _new_context = context .of_relabeling_decision(&context, target_class) .unwrap(); let _new_context = raw_context .of_relabeling_decision(&raw_context, target_class) .unwrap(); } #[test] fn security_context_of_polyinstantiation_member_decision() { let context = super::SecurityContext::current(false).unwrap(); let raw_context = super::SecurityContext::current(true).unwrap(); let target_class = super::SecurityClass::from_name("process").unwrap(); context .of_polyinstantiation_member_decision(&raw_context, target_class) .unwrap_err(); let _new_context = context .of_polyinstantiation_member_decision(&context, target_class) .unwrap(); let _new_context = raw_context .of_polyinstantiation_member_decision(&raw_context, target_class) .unwrap(); } #[test] fn security_context_validate_transition() { let context = super::SecurityContext::current(false).unwrap(); let raw_context = super::SecurityContext::current(true).unwrap(); let target_class = super::SecurityClass::from_name("process").unwrap(); context .validate_transition(&raw_context, target_class, &raw_context) .unwrap_err(); if let Err(r) = context.validate_transition(&context, target_class, &context) { assert_eq!(r.io_source().unwrap().raw_os_error(), Some(libc::ENOSYS)); } if let Err(r) = raw_context.validate_transition(&raw_context, target_class, &raw_context) { assert_eq!(r.io_source().unwrap().raw_os_error(), Some(libc::ENOSYS)); } } #[test] fn security_context_query_access_decision() { let context = super::SecurityContext::current(false).unwrap(); let raw_context = super::SecurityContext::current(true).unwrap(); let target_class = super::SecurityClass::from_name("process").unwrap(); context .query_access_decision(&raw_context, target_class, 0) .unwrap_err(); let _new_context = context .query_access_decision(&context, target_class, 0) .unwrap(); let _new_context = raw_context .query_access_decision(&raw_context, target_class, 0) .unwrap(); } #[test] fn security_context_check_access() { let context = super::SecurityContext::current(false).unwrap(); let raw_context = super::SecurityContext::current(true).unwrap(); let _new_context = context .check_access(&context, "process", "read", ptr::null_mut()) .unwrap(); let _new_context = raw_context .check_access(&raw_context, "process", "read", ptr::null_mut()) .unwrap(); } #[test] fn security_context_of_path() { for &raw_format in &[false, true] { for &follow_symbolic_links in &[false, true] { for &path in &["/", "/etc/fstab"] { let r = super::SecurityContext::of_path(path, follow_symbolic_links, raw_format); let _context = r.unwrap(); } } } let _context = super::SecurityContext::of_path("/non-existent", false, false).unwrap_err(); } #[test] fn security_context_set_default_for_path() { super::SecurityContext::set_default_for_path("non-existent").unwrap_err(); /* let file = tempfile::NamedTempFile::new().unwrap(); super::SecurityContext::set_default_for_path(file.path()).unwrap(); */ } #[test] fn security_context_set_for_path() { use std::os::unix::fs::symlink; let context = super::SecurityContext::current(false).unwrap(); context .set_for_path(Path::new("/non-existent"), false, false) .unwrap_err(); let context = unsafe { CStr::from_ptr("unconfined_u:object_r:user_tmp_t:s0\0".as_ptr().cast()) }; let context = super::SecurityContext::from_c_str(context, false); let dir = tempfile::TempDir::new().unwrap(); let a = dir.path().join("a.txt"); let la = dir.path().join("la.txt"); fs::write(&a, "empty file").unwrap(); symlink(&a, &la).unwrap(); for &raw_format in &[false, true] { for &follow_symbolic_links in &[false, true] { context .set_for_path(la.as_path(), follow_symbolic_links, raw_format) .unwrap(); } } super::SecurityContext::verify_file_context("/non-existent", None).unwrap_err(); /* super::SecurityContext::verify_file_context( &la, super::FileAccessMode::new(libc::S_IFREG | libc::S_IRUSR), ) .unwrap(); */ } #[test] fn security_context_of_file() { let mut file = tempfile::tempfile().unwrap(); writeln!(file, "empty file").unwrap(); let optional_context = super::SecurityContext::of_file(&file, false).unwrap(); let optional_raw_context = super::SecurityContext::of_file(&file, true).unwrap(); if let Some(context) = optional_context { context.set_for_file(&file).unwrap(); } if let Some(raw_context) = optional_raw_context { raw_context.set_for_file(&file).unwrap(); } } #[test] fn security_context_of_peer_socket() { let (s1, s2) = socketpair::socketpair_stream().unwrap(); let _context = super::SecurityContext::of_peer_socket(&s1, false).unwrap(); let _raw_context = super::SecurityContext::of_peer_socket(&s2, true).unwrap(); } #[test] fn rgb() { let rgb = super::RGB::new(0x22, 0, 0); let rgb_clone = super::RGB::clone(&rgb); assert_eq!(rgb, rgb_clone); assert!(rgb >= rgb_clone); assert!(rgb <= rgb_clone); assert_ne!(rgb, super::RGB::default()); let _ignored = format!("{:?}", &rgb); let mut ht = HashSet::new(); ht.insert(rgb_clone); } #[test] fn layer_colors() { let lc = super::LayerColors::new(super::RGB::new(0x22, 0, 0), super::RGB::new(0x11, 0, 0)); let lc_clone = super::LayerColors::clone(&lc); assert_eq!(lc, lc_clone); assert!(lc >= lc_clone); assert!(lc <= lc_clone); assert_ne!(lc, super::LayerColors::default()); let _ignored = format!("{:?}", &lc); let mut ht = HashSet::new(); ht.insert(lc_clone); } #[test] fn file_access_mode() { assert!(super::FileAccessMode::new(0).is_none()); let m = super::FileAccessMode::new(42); assert_eq!(m, Some(super::FileAccessMode(42))); assert_eq!(m.unwrap().mode(), 42); let _ignored = format!("{:?}", &m); } #[test] fn security_class_new() { super::SecurityClass::new(0).unwrap_err(); let sc = super::SecurityClass::new(1).unwrap(); assert_eq!(sc.value(), 1); let _ignored = format!("{:?}", &sc); let _ignored = format!("{}", &sc); let _sc = super::SecurityClass::from_name("invalid").unwrap_err(); let _sc = super::SecurityClass::try_from( super::FileAccessMode::new(libc::S_IFREG | libc::S_IRUSR).unwrap(), ) .unwrap(); super::SecurityClass::try_from(super::FileAccessMode::new(1).unwrap()).unwrap_err(); let sc = super::SecurityClass::from_name("process").unwrap(); let _ignored = format!("{:?}", &sc); let _ignored = format!("{}", &sc); unsafe { sc.access_vector_bit_name(0) }.unwrap_err(); let _name = unsafe { sc.access_vector_bit_name(1) }.unwrap(); let _name = sc.full_access_vector_name(0).unwrap(); let _name = sc.full_access_vector_name(1).unwrap(); let _name = sc.full_access_vector_name(u32::MAX).unwrap(); sc.access_vector_bit("invalid").unwrap_err(); let _av = sc.access_vector_bit("signal").unwrap(); } #[test] fn opaque_security_context() { for &context in &[ "", "user1", "user1:role1", "user1:role1:type1:range1:other1:other2:other3", ] { super::OpaqueSecurityContext::new(context).unwrap_err(); } for &context in &[ "user1:role1:type1", "user1:role1:type1:range1", "user1:role1:type1:range1:other1", "user1:role1:type1:range1:other1:other2", ] { let mut osc = super::OpaqueSecurityContext::new(context).unwrap(); assert!(!osc.as_ptr().is_null()); assert!(!osc.as_mut_ptr().is_null()); let s = osc.to_c_string().unwrap(); assert!(!s.as_bytes().is_empty()); assert_eq!(s.to_string_lossy(), format!("{}", &osc)); assert_eq!(osc.user().unwrap().to_str().ok(), Some("user1")); assert_eq!(osc.role().unwrap().to_str().ok(), Some("role1")); assert_eq!(osc.the_type().unwrap().to_str().ok(), Some("type1")); let expected_range = context.splitn(4, |c| c == ':').nth(3); if let Ok(range) = osc.range() { assert_eq!(range.to_str().ok(), expected_range); } else { assert!(expected_range.is_none()); } osc.set_user_str("user2").unwrap(); assert_eq!(osc.user().unwrap().to_str().ok(), Some("user2")); assert_eq!(osc.role().unwrap().to_str().ok(), Some("role1")); assert_eq!(osc.the_type().unwrap().to_str().ok(), Some("type1")); if let Ok(range) = osc.range() { assert_eq!(range.to_str().ok(), expected_range); } else { assert!(expected_range.is_none()); } osc.set_role_str("role2").unwrap(); assert_eq!(osc.user().unwrap().to_str().ok(), Some("user2")); assert_eq!(osc.role().unwrap().to_str().ok(), Some("role2")); assert_eq!(osc.the_type().unwrap().to_str().ok(), Some("type1")); if let Ok(range) = osc.range() { assert_eq!(range.to_str().ok(), expected_range); } else { assert!(expected_range.is_none()); } osc.set_type_str("type2").unwrap(); assert_eq!(osc.user().unwrap().to_str().ok(), Some("user2")); assert_eq!(osc.role().unwrap().to_str().ok(), Some("role2")); assert_eq!(osc.the_type().unwrap().to_str().ok(), Some("type2")); if let Ok(range) = osc.range() { assert_eq!(range.to_str().ok(), expected_range); } else { assert!(expected_range.is_none()); } osc.set_range_str("range2").unwrap(); assert_eq!(osc.user().unwrap().to_str().ok(), Some("user2")); assert_eq!(osc.role().unwrap().to_str().ok(), Some("role2")); assert_eq!(osc.the_type().unwrap().to_str().ok(), Some("type2")); assert_eq!(osc.range().unwrap().to_str().ok(), Some("range2")); let _ignored = format!("{:?}", &osc); } } #[test] fn kernel_support() { let r = super::kernel_support(); let _ignored = format!("{r:?}"); } #[test] fn boot_mode() { if let Err(err) = super::boot_mode() { assert_matches!(err, crate::errors::Error::IO { .. }); if let crate::errors::Error::IO { source, .. } = err { assert_eq!(source.kind(), io::ErrorKind::NotFound); } assert!(fs::symlink_metadata("/etc/selinux/config").is_err()); } } #[test] fn current_mode() { let r = super::current_mode(); let _ignored = format!("{r:?}"); } #[test] fn undefined_handling() { if let Err(err) = super::undefined_handling() { assert_matches!(err, crate::errors::Error::IO { .. }); if let crate::errors::Error::IO { source, .. } = err { assert_eq!(source.kind(), io::ErrorKind::NotFound); } } } #[test] fn protection_checking_mode() { if let Err(err) = super::protection_checking_mode() { assert_matches!(err, crate::errors::Error::IO { .. }); if let crate::errors::Error::IO { source, .. } = err { assert_eq!(source.kind(), io::ErrorKind::NotFound); } } } #[test] fn dynamic_mapping_into_native_form() { let mut c_string_storage = HashMap::default(); let empty: &[(&str, &[&str]); 0] = &[]; let c_map = super::dynamic_mapping_into_native_form(empty, &mut c_string_storage).unwrap(); assert_eq!(c_map.len(), 1 + empty.len()); assert!(c_map[0].name.is_null()); let mapping: &[(&str, &[&str]); 1] = &[("", &[])]; let c_map = super::dynamic_mapping_into_native_form(mapping, &mut c_string_storage).unwrap(); assert_eq!(c_map.len(), 1 + mapping.len()); assert!(c_str_ptr_to_str(c_map[0].name).unwrap().is_empty()); assert!(c_map[0].perms[0].is_null()); assert!(c_map[1].name.is_null()); let mapping: &[(&str, &[&str]); 1] = &[("", &["", ""])]; let c_map = super::dynamic_mapping_into_native_form(mapping, &mut c_string_storage).unwrap(); assert_eq!(c_map.len(), 1 + mapping.len()); assert!(c_str_ptr_to_str(c_map[0].name).unwrap().is_empty()); assert!(c_str_ptr_to_str(c_map[0].perms[0]).unwrap().is_empty()); assert!(c_str_ptr_to_str(c_map[0].perms[1]).unwrap().is_empty()); assert!(c_map[0].perms[2].is_null()); assert!(c_map[1].name.is_null()); let mapping: &[(&str, &[&str]); 1] = &[("socket", &["bind"])]; let c_map = super::dynamic_mapping_into_native_form(mapping, &mut c_string_storage).unwrap(); assert_eq!(c_map.len(), 1 + mapping.len()); assert_eq!(c_str_ptr_to_str(c_map[0].name).unwrap(), "socket"); assert_eq!(c_str_ptr_to_str(c_map[0].perms[0]).unwrap(), "bind"); assert!(c_map[0].perms[1].is_null()); assert!(c_map[1].name.is_null()); let mapping: &[(&str, &[&str]); 2] = &[ ("socket", &["bind"]), ("file", &["create", "unlink", "read", "write"]), ]; let c_map = super::dynamic_mapping_into_native_form(mapping, &mut c_string_storage).unwrap(); assert_eq!(c_map.len(), 1 + mapping.len()); assert_eq!(c_str_ptr_to_str(c_map[0].name).unwrap(), "socket"); assert_eq!(c_str_ptr_to_str(c_map[0].perms[0]).unwrap(), "bind"); assert!(c_map[0].perms[1].is_null()); assert_eq!(c_str_ptr_to_str(c_map[1].name).unwrap(), "file"); assert_eq!(c_str_ptr_to_str(c_map[1].perms[0]).unwrap(), "create"); assert_eq!(c_str_ptr_to_str(c_map[1].perms[1]).unwrap(), "unlink"); assert_eq!(c_str_ptr_to_str(c_map[1].perms[2]).unwrap(), "read"); assert_eq!(c_str_ptr_to_str(c_map[1].perms[3]).unwrap(), "write"); assert!(c_map[1].perms[4].is_null()); assert!(c_map[2].name.is_null()); } #[test] fn security_context_list_of_se_user() { let mut se_list = super::SecurityContextList::of_se_user("unconfined_u", None, None).unwrap(); assert_eq!(se_list.as_ptr(), se_list.as_mut_ptr().cast()); assert!(!se_list.is_empty()); assert_ne!(se_list.len(), 0); assert!(se_list.get(usize::MAX, false).is_none()); let _context = se_list.get(0, false).unwrap(); let _ignored = format!("{:?}", &se_list); super::SecurityContextList::of_se_user("invalid", None, None).unwrap_err(); let _se_list = super::SecurityContextList::of_se_user("unconfined_u", Some("low"), None).unwrap(); let context = super::SecurityContext::current(false).unwrap(); let _se_list = super::SecurityContextList::of_se_user("unconfined_u", None, Some(&context)).unwrap(); } #[test] fn set_current_mode() { super::set_current_mode(super::SELinuxMode::NotRunning).unwrap_err(); super::set_current_mode(super::SELinuxMode::Permissive).unwrap_err(); super::set_current_mode(super::SELinuxMode::Enforcing).unwrap_err(); } #[test] fn se_user_and_level() { let (se_user, level) = super::se_user_and_level("root", None).unwrap(); assert!(!se_user.as_c_str().to_bytes().is_empty()); assert!(!level.as_c_str().to_bytes().is_empty()); let (_se_user, _level) = super::se_user_and_level("root", Some("file")).unwrap(); } #[test] fn reset_config() { super::reset_config(); } #[test] fn default_type_for_role() { //let _type = super::default_type_for_role("unconfined_r").unwrap(); } #[test] fn set_dynamic_mapping() { super::set_dynamic_mapping(&[] as &[(&str, &[&str])]).unwrap(); super::set_dynamic_mapping(&[("file", &["read", "write"] as &[&str])]).unwrap(); } selinux-0.4.5/src/utils/mod.rs000064400000000000000000000267211046102023000144050ustar 00000000000000#[cfg(test)] mod tests; use std::ffi::{CStr, CString, OsStr}; use std::marker::PhantomData; use std::os::raw::{c_char, c_int, c_uint, c_ulong, c_void}; use std::path::{Path, PathBuf}; use std::{io, mem, ptr}; use once_cell::sync::OnceCell; use crate::errors::{Error, Result}; pub(crate) fn str_to_c_string(s: &str) -> Result { CString::new(s).map_err(|_r| Error::IO1Name { operation: "CString::new", name: s.into(), source: io::ErrorKind::InvalidInput.into(), }) } #[cfg(unix)] pub(crate) fn os_str_to_c_string(s: &OsStr) -> Result { use std::os::unix::ffi::OsStrExt; CString::new(s.as_bytes()).map_err(|_r| Error::PathIsInvalid(PathBuf::from(s))) } pub(crate) fn c_str_ptr_to_str<'string>(s: *const c_char) -> Result<&'string str> { if s.is_null() { let err = io::ErrorKind::InvalidInput.into(); Err(Error::from_io("utils::c_str_ptr_to_string()", err)) } else { unsafe { CStr::from_ptr(s) }.to_str().map_err(Into::into) } } #[cfg(unix)] pub(crate) fn c_str_ptr_to_path<'string>(path_ptr: *const c_char) -> &'string Path { use std::os::unix::ffi::OsStrExt; let c_path = unsafe { CStr::from_ptr(path_ptr) }; Path::new(OsStr::from_bytes(c_path.to_bytes())) } pub(crate) fn c_str_to_non_null_ptr(s: &CStr) -> ptr::NonNull { unsafe { ptr::NonNull::new_unchecked(s.as_ptr() as *mut c_char) } } pub(crate) fn get_static_path( proc: unsafe extern "C" fn() -> *const c_char, proc_name: &'static str, ) -> Result<&'static Path> { let path_ptr = unsafe { proc() }; if path_ptr.is_null() { Err(Error::from_io(proc_name, io::ErrorKind::InvalidData.into())) } else { Ok(c_str_ptr_to_path(path_ptr)) } } pub(crate) fn ret_val_to_result(proc_name: &'static str, result: c_int) -> Result<()> { if result == -1_i32 { Err(Error::last_io_error(proc_name)) } else { Ok(()) } } pub(crate) fn ret_val_to_result_with_path( proc_name: &'static str, result: c_int, path: &Path, ) -> Result<()> { if result == -1_i32 { let err = io::Error::last_os_error(); Err(Error::from_io_path(proc_name, path, err)) } else { Ok(()) } } /// An owned block of memory, allocated with [`libc::malloc`]. /// /// Dropping this instance calls [`libc::free`] on the managed pointer. #[derive(Debug)] pub struct CAllocatedBlock { pub(crate) pointer: ptr::NonNull, _phantom_data: PhantomData, } /// # Safety /// /// - [`libc::malloc()`]-allocated memory blocks are accessible from any thread. /// - [`libc::free()`] supports deallocating memory blocks allocated in /// a different thread. unsafe impl Send for CAllocatedBlock {} impl CAllocatedBlock { pub(crate) fn new(pointer: *mut T) -> Option { ptr::NonNull::new(pointer).map(|pointer| Self { pointer, _phantom_data: PhantomData, }) } /// Return the managed raw pointer. #[must_use] pub fn as_ptr(&self) -> *const T { self.pointer.as_ptr() } /// Return the managed raw pointer. #[must_use] pub fn as_mut_ptr(&mut self) -> *mut T { self.pointer.as_ptr() } } impl CAllocatedBlock { /// Return the managed null-terminated C string. #[must_use] pub fn as_c_str(&self) -> &CStr { unsafe { CStr::from_ptr(self.pointer.as_ptr()) } } } impl Drop for CAllocatedBlock { fn drop(&mut self) { let pointer = self.pointer.as_ptr(); self.pointer = ptr::NonNull::dangling(); unsafe { libc::free(pointer.cast()) }; } } /// Holds addresses of `libselinux`'s optionally-implemented functions. #[derive(Debug)] pub(crate) struct OptionalNativeFunctions { /// Since version 2.9 pub(crate) security_reject_unknown: unsafe extern "C" fn() -> c_int, /// Since version 3.0 pub(crate) selabel_get_digests_all_partial_matches: unsafe extern "C" fn( rec: *mut selinux_sys::selabel_handle, key: *const c_char, calculated_digest: *mut *mut u8, xattr_digest: *mut *mut u8, digest_len: *mut usize, ) -> bool, /// Since version 3.0 pub(crate) selabel_hash_all_partial_matches: unsafe extern "C" fn( rec: *mut selinux_sys::selabel_handle, key: *const c_char, digest: *mut u8, ) -> bool, /// Since version 3.0 pub(crate) security_validatetrans: unsafe extern "C" fn( scon: *const c_char, tcon: *const c_char, tclass: selinux_sys::security_class_t, newcon: *const c_char, ) -> c_int, /// Since version 3.0 pub(crate) security_validatetrans_raw: unsafe extern "C" fn( scon: *const c_char, tcon: *const c_char, tclass: selinux_sys::security_class_t, newcon: *const c_char, ) -> c_int, /// Since version 3.1 pub(crate) selinux_flush_class_cache: unsafe extern "C" fn(), /// Since version 3.4 pub(crate) selinux_restorecon_parallel: unsafe extern "C" fn( pathname: *const c_char, restorecon_flags: c_uint, nthreads: usize, ) -> c_int, /// Since version 3.4 pub(crate) selinux_restorecon_get_skipped_errors: unsafe extern "C" fn() -> c_ulong, /// Since version 3.5 pub(crate) getpidprevcon: unsafe extern "C" fn(pid: selinux_sys::pid_t, con: *mut *mut c_char) -> c_int, /// Since version 3.5 pub(crate) getpidprevcon_raw: unsafe extern "C" fn(pid: selinux_sys::pid_t, con: *mut *mut c_char) -> c_int, } /// Addresses of optionally-implemented functions by libselinux. pub(crate) static OPT_NATIVE_FN: OnceCell = OnceCell::new(); impl Default for OptionalNativeFunctions { fn default() -> Self { Self { security_reject_unknown: Self::not_impl_security_reject_unknown, selabel_get_digests_all_partial_matches: Self::not_impl_selabel_get_digests_all_partial_matches, selabel_hash_all_partial_matches: Self::not_impl_selabel_hash_all_partial_matches, security_validatetrans: Self::not_impl_security_validatetrans, security_validatetrans_raw: Self::not_impl_security_validatetrans, selinux_flush_class_cache: Self::not_impl_selinux_flush_class_cache, selinux_restorecon_parallel: Self::not_impl_selinux_restorecon_parallel, selinux_restorecon_get_skipped_errors: Self::not_impl_selinux_restorecon_get_skipped_errors, getpidprevcon: Self::not_impl_getpidprevcon, getpidprevcon_raw: Self::not_impl_getpidprevcon, } } } impl OptionalNativeFunctions { pub(crate) fn get() -> &'static Self { OPT_NATIVE_FN.get_or_init(Self::initialize) } fn initialize() -> Self { let mut r = Self::default(); let lib_handle = Self::get_libselinux_handle(); if !lib_handle.is_null() { r.load_functions_addresses(lib_handle); } Error::clear_errno(); r } fn get_libselinux_handle() -> *mut c_void { // Ensure libselinux is loaded. unsafe { selinux_sys::is_selinux_enabled() }; // Get a handle to the already-loaded libselinux. let flags = libc::RTLD_NOW | libc::RTLD_GLOBAL | libc::RTLD_NOLOAD | libc::RTLD_NODELETE; for &lib_name in &[ "libselinux.so.1\0", "libselinux.so\0", "libselinux\0", "selinux\0", ] { let lib_handle = unsafe { libc::dlopen(lib_name.as_ptr().cast(), flags) }; if !lib_handle.is_null() { return lib_handle; } } ptr::null_mut() } fn load_functions_addresses(&mut self, lib_handle: *mut c_void) { let f = unsafe { libc::dlsym(lib_handle, "security_reject_unknown\0".as_ptr().cast()) }; if !f.is_null() { self.security_reject_unknown = unsafe { mem::transmute(f) }; } let c_name = "selabel_get_digests_all_partial_matches\0"; let f = unsafe { libc::dlsym(lib_handle, c_name.as_ptr().cast()) }; if !f.is_null() { self.selabel_get_digests_all_partial_matches = unsafe { mem::transmute(f) }; } let c_name = "selabel_hash_all_partial_matches\0"; let f = unsafe { libc::dlsym(lib_handle, c_name.as_ptr().cast()) }; if !f.is_null() { self.selabel_hash_all_partial_matches = unsafe { mem::transmute(f) }; } let f = unsafe { libc::dlsym(lib_handle, "security_validatetrans\0".as_ptr().cast()) }; if !f.is_null() { self.security_validatetrans = unsafe { mem::transmute(f) }; } let f = unsafe { libc::dlsym(lib_handle, "security_validatetrans_raw\0".as_ptr().cast()) }; if !f.is_null() { self.security_validatetrans_raw = unsafe { mem::transmute(f) }; } let f = unsafe { libc::dlsym(lib_handle, "selinux_flush_class_cache\0".as_ptr().cast()) }; if !f.is_null() { self.selinux_flush_class_cache = unsafe { mem::transmute(f) }; } let f = unsafe { libc::dlsym(lib_handle, "selinux_restorecon_parallel\0".as_ptr().cast()) }; if !f.is_null() { self.selinux_restorecon_parallel = unsafe { mem::transmute(f) }; } let c_name = "selinux_restorecon_get_skipped_errors\0"; let f = unsafe { libc::dlsym(lib_handle, c_name.as_ptr().cast()) }; if !f.is_null() { self.selinux_restorecon_get_skipped_errors = unsafe { mem::transmute(f) }; } let f = unsafe { libc::dlsym(lib_handle, "getpidprevcon\0".as_ptr().cast()) }; if !f.is_null() { self.getpidprevcon = unsafe { mem::transmute(f) }; } let f = unsafe { libc::dlsym(lib_handle, "getpidprevcon_raw\0".as_ptr().cast()) }; if !f.is_null() { self.getpidprevcon_raw = unsafe { mem::transmute(f) }; } } unsafe extern "C" fn not_impl_security_reject_unknown() -> c_int { Error::set_errno(libc::ENOSYS); -1_i32 } unsafe extern "C" fn not_impl_selabel_get_digests_all_partial_matches( _rec: *mut selinux_sys::selabel_handle, _key: *const c_char, _calculated_digest: *mut *mut u8, _xattr_digest: *mut *mut u8, _digest_len: *mut usize, ) -> bool { Error::set_errno(libc::ENOSYS); false } unsafe extern "C" fn not_impl_selabel_hash_all_partial_matches( _rec: *mut selinux_sys::selabel_handle, _key: *const c_char, _digest: *mut u8, ) -> bool { Error::set_errno(libc::ENOSYS); false } unsafe extern "C" fn not_impl_security_validatetrans( _scon: *const c_char, _tcon: *const c_char, _tclass: selinux_sys::security_class_t, _newcon: *const c_char, ) -> c_int { Error::set_errno(libc::ENOSYS); -1_i32 } unsafe extern "C" fn not_impl_selinux_flush_class_cache() { Error::set_errno(libc::ENOSYS); } unsafe extern "C" fn not_impl_selinux_restorecon_parallel( _pathname: *const c_char, _restorecon_flags: c_uint, _nthreads: usize, ) -> c_int { Error::set_errno(libc::ENOSYS); -1_i32 } unsafe extern "C" fn not_impl_selinux_restorecon_get_skipped_errors() -> c_ulong { 0 } unsafe extern "C" fn not_impl_getpidprevcon( _pid: selinux_sys::pid_t, _con: *mut *mut c_char, ) -> c_int { Error::set_errno(libc::ENOSYS); -1_i32 } } selinux-0.4.5/src/utils/tests.rs000064400000000000000000000065771046102023000147770ustar 00000000000000use core::slice; use std::ffi::{CStr, CString, OsStr}; use std::io::Write; use std::os::raw::{c_char, c_void}; use std::path::Path; use std::{io, mem, ptr}; use assert_matches::assert_matches; #[test] fn str_to_c_string() { assert_eq!( super::str_to_c_string("").unwrap(), CString::new("").unwrap() ); assert_eq!( super::str_to_c_string("xyz").unwrap(), CString::new("xyz").unwrap() ); super::str_to_c_string("abc\0xyz").unwrap_err(); } #[test] fn os_str_to_c_string() { assert_eq!( super::os_str_to_c_string(OsStr::new("")).unwrap(), CString::new("").unwrap() ); assert_eq!( super::os_str_to_c_string(OsStr::new("xyz")).unwrap(), CString::new("xyz").unwrap() ); super::os_str_to_c_string(OsStr::new("abc\0xyz")).unwrap_err(); } #[test] fn c_str_ptr_to_str() { super::c_str_ptr_to_str(ptr::null()).unwrap_err(); assert_eq!(super::c_str_ptr_to_str("\0".as_ptr().cast()).unwrap(), ""); assert_eq!( super::c_str_ptr_to_str("xyz\0".as_ptr().cast()).unwrap(), "xyz" ); } #[test] fn c_str_ptr_to_path() { assert_eq!( super::c_str_ptr_to_path("\0".as_ptr().cast()), Path::new("") ); assert_eq!( super::c_str_ptr_to_path("xyz\0".as_ptr().cast()), Path::new("xyz") ); } #[test] fn ret_val_to_result() { super::ret_val_to_result("xyz", 0).unwrap(); super::ret_val_to_result("xyz", 1).unwrap(); crate::errors::Error::set_errno(1); let err = super::ret_val_to_result("xyz", -1).unwrap_err(); assert_matches!(err, crate::errors::Error::IO { .. }); if let crate::errors::Error::IO { source, .. } = err { assert_eq!(source.kind(), io::ErrorKind::PermissionDenied); } } #[test] fn ret_val_to_result_with_path() { let test_path = Path::new("/abc"); super::ret_val_to_result_with_path("xyz", 0, test_path).unwrap(); super::ret_val_to_result_with_path("xyz", 1, test_path).unwrap(); crate::errors::Error::set_errno(1); let err = super::ret_val_to_result_with_path("xyz", -1, test_path).unwrap_err(); crate::errors::Error::clear_errno(); assert_matches!(err, crate::errors::Error::IO1Path { .. }); if let crate::errors::Error::IO1Path { source, path, .. } = err { assert_eq!(source.kind(), io::ErrorKind::PermissionDenied); assert_eq!(path, test_path); } } #[test] fn c_allocated_block() { assert!(super::CAllocatedBlock::::new(ptr::null_mut()).is_none()); let block_ptr: *mut u64 = unsafe { libc::calloc(16, mem::size_of::()) }.cast(); let block = super::CAllocatedBlock::new(block_ptr).unwrap(); assert_eq!(block.as_ptr(), block_ptr); let block_ptr: *mut c_char = unsafe { libc::calloc(8, 1) }.cast(); { let mut block_slice: &mut [u8] = unsafe { slice::from_raw_parts_mut(block_ptr.cast(), 8) }; write!(block_slice, "xyz\0").unwrap(); } let mut block = super::CAllocatedBlock::new(block_ptr).unwrap(); assert_eq!(block.as_ptr(), block_ptr); assert_eq!(block.as_mut_ptr(), block_ptr); assert_eq!( block.as_c_str(), CStr::from_bytes_with_nul("xyz\0".as_bytes()).unwrap() ); let _ignored = format!("{:?}", &block); } unsafe extern "C" fn null_ptr() -> *const c_char { ptr::null() } #[test] fn get_static_path() { super::get_static_path(null_ptr, "null_ptr()").unwrap_err(); }