├── selinux ├── LICENSE.txt ├── README.md ├── src │ ├── context_restore │ │ ├── tests.rs │ │ └── mod.rs │ ├── label │ │ ├── back_end.rs │ │ ├── tests.rs │ │ └── mod.rs │ ├── policy │ │ ├── tests.rs │ │ └── mod.rs │ ├── call_back │ │ ├── tests.rs │ │ └── mod.rs │ ├── utils │ │ ├── tests.rs │ │ └── mod.rs │ ├── errors.rs │ ├── avc │ │ ├── tests.rs │ │ └── mod.rs │ ├── path │ │ ├── tests.rs │ │ └── mod.rs │ └── tests.rs └── Cargo.toml ├── .cargo └── config.toml ├── Cargo.toml ├── .gitignore ├── xtask ├── Cargo.toml └── src │ ├── errors.rs │ ├── main.rs │ ├── utils.rs │ └── coverage.rs ├── LICENSE.txt ├── coverage-style.css.patch ├── CHANGELOG.md └── README.md /selinux/LICENSE.txt: -------------------------------------------------------------------------------- 1 | ../LICENSE.txt -------------------------------------------------------------------------------- /selinux/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --package xtask --" 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["xtask", "selinux"] 3 | resolver = "2" 4 | -------------------------------------------------------------------------------- /selinux/src/context_restore/tests.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(test, target_os = "linux", not(target_env = "kernel")))] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .vscode/ 4 | *.code-workspace 5 | libselinux-*.tar.gz 6 | *.pdf 7 | .directory 8 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | serde = { version = "1.0" } 10 | serde_derive = { version = "1.0" } 11 | serde_json = { version = "1.0" } 12 | log = { version = "0.4" } 13 | simplelog = { version = "0.12" } 14 | walkdir = { version = "2.5" } 15 | thiserror = { version = "1.0" } 16 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2024 Koutheir Attouchi. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /xtask/src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::path::PathBuf; 3 | use std::str::Utf8Error; 4 | 5 | pub type Result = std::result::Result; 6 | 7 | #[derive(thiserror::Error, Debug)] 8 | #[non_exhaustive] 9 | pub enum Error { 10 | #[error("'{name}' command failed")] 11 | CommandFailed { name: &'static str }, 12 | 13 | #[error(transparent)] 14 | NotUTF8(#[from] Utf8Error), 15 | 16 | #[error("{operation} failed")] 17 | IO { 18 | source: io::Error, 19 | operation: &'static str, 20 | }, 21 | 22 | #[error("{operation} failed on '{path}'")] 23 | IO1Path { 24 | source: io::Error, 25 | operation: &'static str, 26 | path: PathBuf, 27 | }, 28 | } 29 | 30 | impl Error { 31 | pub(crate) fn from_io(operation: &'static str, source: io::Error) -> Self { 32 | Error::IO { source, operation } 33 | } 34 | 35 | pub(crate) fn from_io_path( 36 | operation: &'static str, 37 | path: impl Into, 38 | source: io::Error, 39 | ) -> Self { 40 | Error::IO1Path { 41 | source, 42 | operation, 43 | path: path.into(), 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /selinux/Cargo.toml: -------------------------------------------------------------------------------- 1 | # https://rust-lang.github.io/api-guidelines/checklist.html 2 | 3 | [package] 4 | name = "selinux" 5 | description = "Flexible Mandatory Access Control for Linux" 6 | version = "0.4.4" # Update version in `html_root_url`. 7 | authors = ["Koutheir Attouchi "] 8 | edition = "2021" 9 | readme = "../README.md" 10 | license = "MIT" 11 | keywords = ["selinux", "security", "access-control", "linux", "filesystem"] 12 | categories = ["api-bindings", "filesystem", "os", "os::linux-apis"] 13 | documentation = "https://docs.rs/selinux" 14 | homepage = "https://codeberg.org/koutheir/selinux.git" 15 | repository = "https://codeberg.org/koutheir/selinux.git" 16 | 17 | [dependencies] 18 | thiserror = { version = "1.0" } 19 | selinux-sys = { version = "0.6" } 20 | libc = { version = "0.2" } 21 | bitflags = { version = "2.5" } 22 | once_cell = { version = "1.19" } 23 | reference-counted-singleton = { version = "0.1" } 24 | 25 | [dev-dependencies] 26 | assert_matches = { version = "1.5" } 27 | tempfile = { version = "3.10" } 28 | serial_test = { version = "3.0" } 29 | socketpair = { version = "0.19" } 30 | -------------------------------------------------------------------------------- /selinux/src/label/back_end.rs: -------------------------------------------------------------------------------- 1 | use std::os::raw::c_uint; 2 | 3 | /// Security contexts backend. 4 | pub trait BackEnd { 5 | /// Security contexts backend index. 6 | const BACK_END: c_uint; 7 | } 8 | 9 | /// File contexts backend, described in `selabel_file()`. 10 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 11 | #[non_exhaustive] 12 | pub struct File; 13 | 14 | impl BackEnd for File { 15 | const BACK_END: c_uint = selinux_sys::SELABEL_CTX_FILE as c_uint; 16 | } 17 | 18 | /// Media contexts backend, described in `selabel_media()`. 19 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 20 | #[non_exhaustive] 21 | pub struct Media; 22 | 23 | impl BackEnd for Media { 24 | const BACK_END: c_uint = selinux_sys::SELABEL_CTX_MEDIA as c_uint; 25 | } 26 | 27 | /// X Windows contexts backend, described in `selabel_x()`. 28 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 29 | #[non_exhaustive] 30 | pub struct X; 31 | 32 | impl BackEnd for X { 33 | const BACK_END: c_uint = selinux_sys::SELABEL_CTX_X as c_uint; 34 | } 35 | 36 | /// Database objects contexts backend, described in `selabel_db()`. 37 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 38 | #[non_exhaustive] 39 | pub struct DB; 40 | 41 | impl BackEnd for DB { 42 | const BACK_END: c_uint = selinux_sys::SELABEL_CTX_DB as c_uint; 43 | } 44 | -------------------------------------------------------------------------------- /selinux/src/policy/tests.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(test, target_os = "linux", not(target_env = "kernel")))] 2 | 3 | #[test] 4 | fn version_number() { 5 | match super::version_number() { 6 | Ok(version) => assert_ne!(version, 0), 7 | Err(_err) => assert_eq!(crate::current_mode(), crate::SELinuxMode::NotRunning), 8 | } 9 | } 10 | 11 | #[test] 12 | fn policy_type() { 13 | match super::policy_type() { 14 | Ok(name) => assert!(!name.as_c_str().to_bytes().is_empty()), 15 | Err(_err) => assert_eq!(crate::current_mode(), crate::SELinuxMode::NotRunning), 16 | } 17 | } 18 | 19 | #[test] 20 | fn root_path() { 21 | let path = super::root_path().unwrap(); 22 | assert!(!path.as_os_str().is_empty()); 23 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 24 | } 25 | 26 | #[test] 27 | fn current_policy_path() { 28 | match super::current_policy_path() { 29 | Ok(path) => { 30 | assert!(!path.as_os_str().is_empty()); 31 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 32 | } 33 | 34 | Err(_err) => assert!(crate::current_mode() == crate::SELinuxMode::NotRunning), 35 | } 36 | } 37 | 38 | #[test] 39 | fn binary_policy_path() { 40 | let path = super::binary_policy_path().unwrap(); 41 | assert!(!path.as_os_str().is_empty()); 42 | //assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 43 | } 44 | 45 | #[test] 46 | fn load() { 47 | let policy_bytes = []; 48 | super::load(&policy_bytes).unwrap_err(); 49 | } 50 | 51 | #[test] 52 | fn make_and_load() { 53 | super::make_and_load().unwrap_err(); 54 | } 55 | 56 | #[test] 57 | fn load_initial() { 58 | super::load_initial().unwrap_err(); 59 | } 60 | 61 | #[test] 62 | fn set_root_path() { 63 | let path = super::current_policy_path().unwrap(); 64 | super::set_root_path(path).unwrap(); 65 | } 66 | -------------------------------------------------------------------------------- /coverage-style.css.patch: -------------------------------------------------------------------------------- 1 | --- a/style.css 2020-12-01 18:44:28.612937212 -0500 2 | +++ b/style.css 2020-12-01 14:25:39.616532796 -0500 3 | @@ -1,22 +1,24 @@ 4 | .red { 5 | - background-color: #ffd0d0; 6 | + background-color: #880000; 7 | } 8 | .cyan { 9 | background-color: cyan; 10 | } 11 | body { 12 | font-family: -apple-system, sans-serif; 13 | + color: #ffffff; 14 | + background-color: #222222; 15 | } 16 | pre { 17 | margin-top: 0px !important; 18 | margin-bottom: 0px !important; 19 | } 20 | .source-name-title { 21 | padding: 5px 10px; 22 | border-bottom: 1px solid #dbdbdb; 23 | - background-color: #eee; 24 | + background-color: #00007f; 25 | line-height: 35px; 26 | } 27 | .centered { 28 | display: table; 29 | margin-left: left; 30 | @@ -52,25 +54,25 @@ 31 | font-weight: bold; 32 | text-align: left; 33 | } 34 | .column-entry-yellow { 35 | text-align: left; 36 | - background-color: #ffffd0; 37 | + background-color: #808000; 38 | } 39 | .column-entry-yellow:hover { 40 | background-color: #fffff0; 41 | } 42 | .column-entry-red { 43 | text-align: left; 44 | - background-color: #ffd0d0; 45 | + background-color: #aa0000; 46 | } 47 | .column-entry-red:hover { 48 | background-color: #fff0f0; 49 | } 50 | .column-entry-green { 51 | text-align: left; 52 | - background-color: #d0ffd0; 53 | + background-color: #005500; 54 | } 55 | .column-entry-green:hover { 56 | background-color: #f0fff0; 57 | } 58 | .line-number { 59 | @@ -86,11 +88,11 @@ 60 | color: #ff3300; 61 | } 62 | .tooltip { 63 | position: relative; 64 | display: inline; 65 | - background-color: #b3e6ff; 66 | + background-color: #006699; 67 | text-decoration: none; 68 | } 69 | .tooltip span.tooltip-content { 70 | position: absolute; 71 | width: 100px; 72 | @@ -126,10 +128,11 @@ 73 | padding: 2px 8px; 74 | border-collapse: collapse; 75 | border-right: solid 1px #eee; 76 | border-left: solid 1px #eee; 77 | text-align: left; 78 | + background-color: #222222; 79 | } 80 | td pre { 81 | display: inline-block; 82 | } 83 | td:first-child { 84 | @@ -139,5 +142,9 @@ 85 | border-right: none; 86 | } 87 | tr:hover { 88 | background-color: #f0f0f0; 89 | } 90 | +a { 91 | + color: yellow; 92 | + text-decoration: none; 93 | +} 94 | -------------------------------------------------------------------------------- /selinux/src/call_back/tests.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(test, target_os = "linux", not(target_env = "kernel")))] 2 | 3 | use core::mem; 4 | use std::fmt; 5 | use std::os::raw::{c_char, c_int, c_void}; 6 | 7 | fn template(call_back: ::CallBackType) 8 | where 9 | T: super::CallBack + Default + fmt::Debug, 10 | ::CallBackType: fmt::Debug + Eq + Copy + Sized, 11 | { 12 | // TODO: We need to arrange for the call back to be actually called. 13 | 14 | let _ignored = format!("{:?}", T::default()); 15 | 16 | let old_call_back = T::get_call_back(); 17 | 18 | T::set_call_back(None); 19 | assert!(T::get_call_back().is_none()); 20 | 21 | let call_back: Option<::CallBackType> = Some(call_back); 22 | assert_ne!(old_call_back, call_back); 23 | 24 | T::set_call_back(call_back); 25 | assert_eq!(T::get_call_back(), call_back); 26 | 27 | T::set_call_back(old_call_back); 28 | assert_eq!(T::get_call_back(), old_call_back); 29 | } 30 | 31 | #[test] 32 | fn log() { 33 | // # Safety 34 | // 35 | // For now, stable Rust does not allow defining variadic functions. 36 | // Once that becomes possible, we should define a variadic function that 37 | // calls libc::abort(), and call that instead. 38 | // For the moment, we allow calling abort() with a different prototype, 39 | // which only "works" in some ABIs, and probably fails horribly in others. 40 | template::(unsafe { mem::transmute(libc::abort as unsafe extern "C" fn() -> !) }); 41 | } 42 | 43 | #[test] 44 | fn audit() { 45 | template::(audit_call_back); 46 | } 47 | 48 | #[test] 49 | fn context_validation() { 50 | template::(context_validation_call_back); 51 | } 52 | 53 | #[test] 54 | fn enforcing_change() { 55 | template::(enforcing_change_call_back); 56 | } 57 | 58 | #[test] 59 | fn security_policy_reload() { 60 | template::(security_policy_reload_call_back); 61 | } 62 | 63 | // Dummy call back functions, of the correct prototypes. 64 | 65 | unsafe extern "C" fn audit_call_back( 66 | _audit_data: *mut c_void, 67 | _security_class: selinux_sys::security_class_t, 68 | _message_buffer: *mut c_char, 69 | _message_buffer_size: usize, 70 | ) -> c_int { 71 | 0_i32 72 | } 73 | 74 | unsafe extern "C" fn context_validation_call_back(_context_ptr: *mut *mut c_char) -> c_int { 75 | 0_i32 76 | } 77 | 78 | unsafe extern "C" fn enforcing_change_call_back(_enforcing: c_int) -> c_int { 79 | 0_i32 80 | } 81 | 82 | unsafe extern "C" fn security_policy_reload_call_back(_seqno: c_int) -> c_int { 83 | 0_i32 84 | } 85 | -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{OsStr, OsString}; 2 | use std::path::{Path, PathBuf}; 3 | use std::{env, io}; 4 | 5 | use log::info; 6 | 7 | mod coverage; 8 | mod errors; 9 | mod utils; 10 | 11 | use crate::coverage::coverage; 12 | use crate::errors::{Error, Result}; 13 | 14 | fn main() -> Result<()> { 15 | let mut args = env::args_os(); 16 | let target = args.nth(1); 17 | if let Some(target) = target.as_deref().and_then(OsStr::to_str) { 18 | let verbose = target != "clippy"; 19 | init_logging(verbose)?; 20 | 21 | let config = Config::new(args.collect())?; 22 | run_target(&config, target)?; 23 | info!("Done."); 24 | Ok(()) 25 | } else { 26 | usage() 27 | } 28 | } 29 | 30 | /// Initialize logging based on the logging level specified on the command line. 31 | pub(crate) fn init_logging(verbose: bool) -> Result<()> { 32 | use simplelog::{ 33 | ColorChoice, ConfigBuilder, LevelFilter, SimpleLogger, TermLogger, TerminalMode, 34 | }; 35 | 36 | let level_filter = if verbose { 37 | LevelFilter::Trace 38 | } else { 39 | LevelFilter::Warn 40 | }; 41 | 42 | let config = ConfigBuilder::new() 43 | .set_time_level(LevelFilter::Trace) 44 | .set_max_level(LevelFilter::Error) 45 | .set_target_level(LevelFilter::Error) 46 | .set_location_level(LevelFilter::Trace) 47 | .build(); 48 | 49 | // The build server does not have a terminal, use a logger to STDERR as fallback. 50 | TermLogger::init( 51 | level_filter, 52 | config.clone(), 53 | TerminalMode::Mixed, 54 | ColorChoice::Auto, 55 | ) 56 | .or_else(move |_err| SimpleLogger::init(level_filter, config)) 57 | .map_err(|_| { 58 | let err = io::ErrorKind::AlreadyExists.into(); 59 | Error::from_io("simplelog::loggers::simplelog::SimpleLogger::init", err) 60 | }) 61 | } 62 | 63 | struct Config { 64 | target_args: Vec, 65 | workspace_dir: &'static Path, 66 | coverage_dir: PathBuf, 67 | coverage_profdata: PathBuf, 68 | } 69 | 70 | impl Config { 71 | fn new(target_args: Vec) -> Result { 72 | let workspace_dir = Path::new(env!("CARGO_MANIFEST_DIR")) 73 | .parent() 74 | .ok_or_else(|| { 75 | let err = io::ErrorKind::NotFound.into(); 76 | Error::from_io_path("std::path::Path::parent", env!("CARGO_MANIFEST_DIR"), err) 77 | })?; 78 | 79 | let target_dir = workspace_dir.join("target"); 80 | let coverage_dir = target_dir.join("coverage"); 81 | let coverage_profdata = coverage_dir.join("coverage.profdata"); 82 | 83 | Ok(Self { 84 | target_args, 85 | workspace_dir, 86 | coverage_dir, 87 | coverage_profdata, 88 | }) 89 | } 90 | } 91 | 92 | fn usage() -> Result<()> { 93 | eprintln!("Please specify a target name, from one of the following targets:"); 94 | eprintln!(" coverage."); 95 | eprintln!("You can also specify parameters after targets."); 96 | Ok(()) 97 | } 98 | 99 | fn run_target(config: &Config, target: &str) -> Result<()> { 100 | match target { 101 | "coverage" => coverage(config), 102 | 103 | _ => usage(), 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /xtask/src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{OsStr, OsString}; 2 | use std::path::{Path, PathBuf}; 3 | use std::{fs, io, process}; 4 | 5 | use log::{debug, info}; 6 | 7 | use crate::errors::{Error, Result}; 8 | use crate::Config; 9 | 10 | pub(crate) fn run_cmd(mut cmd: process::Command, name: &'static str) -> Result<()> { 11 | debug!("Running: {:?}", &cmd); 12 | 13 | if cmd 14 | .status() 15 | .map_err(|r| Error::from_io_path("std::process::Command::status", name, r))? 16 | .success() 17 | { 18 | Ok(()) 19 | } else { 20 | Err(Error::CommandFailed { name }) 21 | } 22 | } 23 | 24 | pub(crate) fn list_files(dir: &Path, extension: &str) -> Result> { 25 | let entries = 26 | fs::read_dir(dir).map_err(|r| Error::from_io_path("std::fs::read_dir", dir, r))?; 27 | 28 | let mut result = Vec::with_capacity(16); 29 | for r_entry in entries { 30 | let entry = r_entry.map_err(|r| Error::from_io_path("std::fs::read_dir", dir, r))?; 31 | 32 | let file_type = entry 33 | .file_type() 34 | .map_err(|r| Error::from_io_path("std::fs::DirEntry::file_type", dir, r))?; 35 | 36 | let file_name = PathBuf::from(entry.file_name()); 37 | if file_type.is_file() && file_name.extension() == Some(OsStr::new(extension)) { 38 | result.push(entry.path()); 39 | } 40 | } 41 | Ok(result) 42 | } 43 | 44 | pub(crate) fn find_executable_file(dir: &Path, file_name: &str) -> Result { 45 | // TODO(KAT): Try to ensure the found file is executable. 46 | walkdir::WalkDir::new(dir) 47 | .into_iter() 48 | .filter_map(std::result::Result::ok) 49 | .find(|e| e.file_type().is_file() && e.file_name() == file_name) 50 | .map(walkdir::DirEntry::into_path) 51 | .ok_or_else(|| { 52 | Error::from_io_path("find_executable_file", dir, io::ErrorKind::NotFound.into()) 53 | }) 54 | } 55 | 56 | #[cfg(unix)] 57 | pub(crate) fn pathbuf_from_vec(bytes: Vec) -> PathBuf { 58 | use std::os::unix::ffi::OsStringExt; 59 | PathBuf::from(OsString::from_vec(bytes)) 60 | } 61 | 62 | fn cargo_version(toolchain: &str) -> Result<()> { 63 | let mut cmd = process::Command::new("cargo"); 64 | cmd.stdout(process::Stdio::null()) 65 | .args([&format!("+{toolchain}"), "--version"]); 66 | run_cmd(cmd, "cargo --version") 67 | } 68 | 69 | pub(crate) fn cargo_command(config: &Config, toolchain: &str, args: &[&str]) -> Result<()> { 70 | info!("cargo '{}'...", args.join("' '")); 71 | 72 | let cargo = if toolchain.is_empty() { 73 | env!("CARGO") 74 | } else { 75 | let mut result = cargo_version(toolchain); 76 | if result.is_err() { 77 | info!("Installing toolchain '{}'...", toolchain); 78 | rustup(config, &["install", toolchain])?; 79 | 80 | result = cargo_version(toolchain); 81 | } 82 | result?; 83 | 84 | "cargo" 85 | }; 86 | 87 | let mut cmd = process::Command::new(cargo); 88 | cmd.current_dir(config.workspace_dir) 89 | .env("RUST_BACKTRACE", "1"); 90 | 91 | if !toolchain.is_empty() { 92 | cmd.arg(&format!("+{toolchain}")); 93 | } 94 | 95 | cmd.args(args).args(&config.target_args); 96 | run_cmd(cmd, "cargo") 97 | } 98 | 99 | pub(crate) fn rustup(_config: &Config, args: &[&str]) -> Result<()> { 100 | let mut cmd = process::Command::new("rustup"); 101 | cmd.args(args); 102 | run_cmd(cmd, "rustup") 103 | } 104 | -------------------------------------------------------------------------------- /selinux/src/utils/tests.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(test, target_os = "linux", not(target_env = "kernel")))] 2 | 3 | use core::slice; 4 | use std::ffi::{CStr, CString, OsStr}; 5 | use std::io::Write; 6 | use std::os::raw::{c_char, c_void}; 7 | use std::path::Path; 8 | use std::{io, mem, ptr}; 9 | 10 | use assert_matches::assert_matches; 11 | 12 | #[test] 13 | fn str_to_c_string() { 14 | assert_eq!( 15 | super::str_to_c_string("").unwrap(), 16 | CString::new("").unwrap() 17 | ); 18 | 19 | assert_eq!( 20 | super::str_to_c_string("xyz").unwrap(), 21 | CString::new("xyz").unwrap() 22 | ); 23 | 24 | super::str_to_c_string("abc\0xyz").unwrap_err(); 25 | } 26 | 27 | #[test] 28 | fn os_str_to_c_string() { 29 | assert_eq!( 30 | super::os_str_to_c_string(OsStr::new("")).unwrap(), 31 | CString::new("").unwrap() 32 | ); 33 | 34 | assert_eq!( 35 | super::os_str_to_c_string(OsStr::new("xyz")).unwrap(), 36 | CString::new("xyz").unwrap() 37 | ); 38 | 39 | super::os_str_to_c_string(OsStr::new("abc\0xyz")).unwrap_err(); 40 | } 41 | 42 | #[test] 43 | fn c_str_ptr_to_str() { 44 | super::c_str_ptr_to_str(ptr::null()).unwrap_err(); 45 | 46 | assert_eq!(super::c_str_ptr_to_str("\0".as_ptr().cast()).unwrap(), ""); 47 | 48 | assert_eq!( 49 | super::c_str_ptr_to_str("xyz\0".as_ptr().cast()).unwrap(), 50 | "xyz" 51 | ); 52 | } 53 | 54 | #[test] 55 | fn c_str_ptr_to_path() { 56 | assert_eq!( 57 | super::c_str_ptr_to_path("\0".as_ptr().cast()), 58 | Path::new("") 59 | ); 60 | 61 | assert_eq!( 62 | super::c_str_ptr_to_path("xyz\0".as_ptr().cast()), 63 | Path::new("xyz") 64 | ); 65 | } 66 | 67 | #[test] 68 | fn ret_val_to_result() { 69 | super::ret_val_to_result("xyz", 0).unwrap(); 70 | super::ret_val_to_result("xyz", 1).unwrap(); 71 | 72 | crate::errors::Error::set_errno(1); 73 | let err = super::ret_val_to_result("xyz", -1).unwrap_err(); 74 | assert_matches!(err, crate::errors::Error::IO { .. }); 75 | if let crate::errors::Error::IO { source, .. } = err { 76 | assert_eq!(source.kind(), io::ErrorKind::PermissionDenied); 77 | } 78 | } 79 | 80 | #[test] 81 | fn ret_val_to_result_with_path() { 82 | let test_path = Path::new("/abc"); 83 | 84 | super::ret_val_to_result_with_path("xyz", 0, test_path).unwrap(); 85 | super::ret_val_to_result_with_path("xyz", 1, test_path).unwrap(); 86 | 87 | crate::errors::Error::set_errno(1); 88 | let err = super::ret_val_to_result_with_path("xyz", -1, test_path).unwrap_err(); 89 | crate::errors::Error::clear_errno(); 90 | 91 | assert_matches!(err, crate::errors::Error::IO1Path { .. }); 92 | if let crate::errors::Error::IO1Path { source, path, .. } = err { 93 | assert_eq!(source.kind(), io::ErrorKind::PermissionDenied); 94 | assert_eq!(path, test_path); 95 | } 96 | } 97 | 98 | #[test] 99 | fn c_allocated_block() { 100 | assert!(super::CAllocatedBlock::::new(ptr::null_mut()).is_none()); 101 | 102 | let block_ptr: *mut u64 = unsafe { libc::calloc(16, mem::size_of::()) }.cast(); 103 | let block = super::CAllocatedBlock::new(block_ptr).unwrap(); 104 | assert_eq!(block.as_ptr(), block_ptr); 105 | 106 | let block_ptr: *mut c_char = unsafe { libc::calloc(8, 1) }.cast(); 107 | { 108 | let mut block_slice: &mut [u8] = unsafe { slice::from_raw_parts_mut(block_ptr.cast(), 8) }; 109 | write!(block_slice, "xyz\0").unwrap(); 110 | } 111 | let mut block = super::CAllocatedBlock::new(block_ptr).unwrap(); 112 | assert_eq!(block.as_ptr(), block_ptr); 113 | assert_eq!(block.as_mut_ptr(), block_ptr); 114 | assert_eq!( 115 | block.as_c_str(), 116 | CStr::from_bytes_with_nul("xyz\0".as_bytes()).unwrap() 117 | ); 118 | let _ignored = format!("{:?}", &block); 119 | } 120 | 121 | unsafe extern "C" fn null_ptr() -> *const c_char { 122 | ptr::null() 123 | } 124 | 125 | #[test] 126 | fn get_static_path() { 127 | super::get_static_path(null_ptr, "null_ptr()").unwrap_err(); 128 | } 129 | -------------------------------------------------------------------------------- /selinux/src/policy/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests; 3 | 4 | use std::os::raw::{c_char, c_int, c_uint, c_void}; 5 | use std::path::Path; 6 | use std::{io, ptr}; 7 | 8 | use crate::errors::{Error, Result}; 9 | use crate::utils::*; 10 | 11 | /// Load a new SELinux policy. 12 | /// 13 | /// See: `security_load_policy()`. 14 | #[doc(alias = "security_load_policy")] 15 | pub fn load(policy_bytes: &[u8]) -> Result<()> { 16 | // security_load_policy() declares "data" as a constant pointer starting from libselinux 17 | // version 3.5. 18 | // Previous supported versions have the same security_load_policy() implementation, but declare 19 | // "data" as a mutable pointer, even though it is never modified. 20 | let data = policy_bytes.as_ptr() as *mut c_void; 21 | let r = unsafe { selinux_sys::security_load_policy(data.cast(), policy_bytes.len()) }; 22 | ret_val_to_result("security_load_policy()", r) 23 | } 24 | 25 | /// Make a policy image and load it. 26 | /// 27 | /// See: `selinux_mkload_policy()`. 28 | #[doc(alias = "selinux_mkload_policy")] 29 | pub fn make_and_load() -> Result<()> { 30 | let r = unsafe { selinux_sys::selinux_mkload_policy(0) }; 31 | ret_val_to_result("selinux_mkload_policy()", r) 32 | } 33 | 34 | /// Perform the initial policy load. 35 | /// 36 | /// See: `selinux_init_load_policy()`. 37 | #[doc(alias = "selinux_init_load_policy")] 38 | pub fn load_initial() -> Result { 39 | let mut enforce: c_int = 0; 40 | if unsafe { selinux_sys::selinux_init_load_policy(&mut enforce) } == -1_i32 { 41 | Err(Error::last_io_error("selinux_init_load_policy()")) 42 | } else { 43 | Ok(enforce) 44 | } 45 | } 46 | 47 | /// Get the type of SELinux policy running on the system. 48 | /// 49 | /// See: `selinux_getpolicytype()`. 50 | #[doc(alias = "selinux_getpolicytype")] 51 | pub fn policy_type() -> Result> { 52 | let mut name_ptr: *mut c_char = ptr::null_mut(); 53 | if unsafe { selinux_sys::selinux_getpolicytype(&mut name_ptr) } == -1_i32 { 54 | Err(Error::last_io_error("selinux_getpolicytype()")) 55 | } else { 56 | CAllocatedBlock::new(name_ptr).ok_or_else(|| { 57 | Error::from_io("selinux_getpolicytype()", io::ErrorKind::InvalidData.into()) 58 | }) 59 | } 60 | } 61 | 62 | /// Get the version of the SELinux policy. 63 | /// 64 | /// See: `security_policyvers()`. 65 | #[doc(alias = "security_policyvers")] 66 | pub fn version_number() -> Result { 67 | let r: c_int = unsafe { selinux_sys::security_policyvers() }; 68 | if r == -1_i32 { 69 | Err(Error::last_io_error("security_policyvers()")) 70 | } else { 71 | Ok(r as c_uint) 72 | } 73 | } 74 | 75 | /// Return the path of the SELinux policy files for this machine. 76 | /// 77 | /// See: `selinux_policy_root()`. 78 | #[doc(alias = "selinux_policy_root")] 79 | pub fn root_path() -> Result<&'static Path> { 80 | get_static_path(selinux_sys::selinux_policy_root, "selinux_policy_root()") 81 | } 82 | 83 | /// Set an alternate SELinux root path for the SELinux policy files for this machine. 84 | /// 85 | /// See: `selinux_set_policy_root()`. 86 | #[doc(alias = "selinux_set_policy_root")] 87 | pub fn set_root_path(path: impl AsRef) -> Result<()> { 88 | let c_path = os_str_to_c_string(path.as_ref().as_os_str())?; 89 | let r = unsafe { selinux_sys::selinux_set_policy_root(c_path.as_ptr()) }; 90 | ret_val_to_result_with_path("selinux_set_policy_root()", r, path.as_ref()) 91 | } 92 | 93 | /// Return the currently loaded policy file from the kernel. 94 | /// 95 | /// See: `selinux_current_policy_path()`. 96 | #[doc(alias = "selinux_current_policy_path")] 97 | pub fn current_policy_path() -> Result<&'static Path> { 98 | let proc_name = "selinux_current_policy_path()"; 99 | get_static_path(selinux_sys::selinux_current_policy_path, proc_name) 100 | } 101 | 102 | /// Return the binary policy file loaded into kernel. 103 | /// 104 | /// See: `selinux_binary_policy_path()`. 105 | #[doc(alias = "selinux_binary_policy_path")] 106 | pub fn binary_policy_path() -> Result<&'static Path> { 107 | let proc_name = "selinux_binary_policy_path()"; 108 | get_static_path(selinux_sys::selinux_binary_policy_path, proc_name) 109 | } 110 | -------------------------------------------------------------------------------- /selinux/src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::num::TryFromIntError; 3 | use std::os::raw::c_int; 4 | use std::path::PathBuf; 5 | use std::str::Utf8Error; 6 | 7 | use selinux_sys::pid_t; 8 | 9 | /// Result of a fallible function. 10 | pub type Result = std::result::Result; 11 | 12 | /// Error. 13 | #[derive(thiserror::Error, Debug)] 14 | #[non_exhaustive] 15 | pub enum Error { 16 | /// Path is invalid. 17 | #[error("path is invalid: '{}'", .0.display())] 18 | PathIsInvalid(PathBuf), 19 | 20 | /// Input security contexts have different formats. 21 | #[error("input security contexts have different formats")] 22 | SecurityContextFormatMismatch, 23 | 24 | /// Security context has an expected format. 25 | #[error("security context has an expected format")] 26 | UnexpectedSecurityContextFormat, 27 | 28 | /// Lock was poisoned. 29 | #[error("{operation} failed due to poisoned lock")] 30 | LockPoisoned { 31 | /// Operation. 32 | operation: &'static str, 33 | }, 34 | 35 | /// Input/Output operation failed. 36 | #[error("{operation} failed")] 37 | IO { 38 | /// Cause. 39 | source: io::Error, 40 | /// Operation. 41 | operation: &'static str, 42 | }, 43 | 44 | /// Operation failed on a process. 45 | #[error("{operation} failed on process with ID '{process_id}'")] 46 | IO1Process { 47 | /// Cause. 48 | source: io::Error, 49 | /// Operation. 50 | operation: &'static str, 51 | /// Process identifier. 52 | process_id: pid_t, 53 | }, 54 | 55 | /// Operation failed on a named object. 56 | #[error("{operation} failed with '{name}'")] 57 | IO1Name { 58 | /// Cause. 59 | source: io::Error, 60 | /// Operation. 61 | operation: &'static str, 62 | /// Name. 63 | name: String, 64 | }, 65 | 66 | /// Operation failed on a file system object. 67 | #[error("{operation} failed on path '{path}'")] 68 | IO1Path { 69 | /// Cause. 70 | source: io::Error, 71 | /// Operation. 72 | operation: &'static str, 73 | /// Path. 74 | path: PathBuf, 75 | }, 76 | 77 | /// Data is not encoded as UTF-8. 78 | #[error(transparent)] 79 | NotUTF8(#[from] Utf8Error), 80 | 81 | /// Integer is out of valid range. 82 | #[error(transparent)] 83 | IntegerOutOfRange(#[from] TryFromIntError), 84 | } 85 | 86 | impl Error { 87 | pub(crate) fn from_io(operation: &'static str, source: io::Error) -> Self { 88 | Error::IO { source, operation } 89 | } 90 | 91 | pub(crate) fn last_io_error(operation: &'static str) -> Self { 92 | Error::IO { 93 | source: io::Error::last_os_error(), 94 | operation, 95 | } 96 | } 97 | 98 | pub(crate) fn from_io_pid( 99 | operation: &'static str, 100 | process_id: pid_t, 101 | source: io::Error, 102 | ) -> Self { 103 | Error::IO1Process { 104 | source, 105 | operation, 106 | process_id, 107 | } 108 | } 109 | 110 | pub(crate) fn from_io_path( 111 | operation: &'static str, 112 | path: impl Into, 113 | source: io::Error, 114 | ) -> Self { 115 | Error::IO1Path { 116 | source, 117 | operation, 118 | path: path.into(), 119 | } 120 | } 121 | 122 | pub(crate) fn from_io_name( 123 | operation: &'static str, 124 | name: impl Into, 125 | source: io::Error, 126 | ) -> Self { 127 | Error::IO1Name { 128 | source, 129 | operation, 130 | name: name.into(), 131 | } 132 | } 133 | 134 | pub(crate) fn set_errno(errno: c_int) { 135 | unsafe { 136 | *libc::__errno_location() = errno; 137 | } 138 | } 139 | 140 | pub(crate) fn clear_errno() { 141 | Self::set_errno(0); 142 | } 143 | 144 | #[allow(dead_code)] // This is used by unit tests. 145 | pub(crate) fn io_source(&self) -> Option<&io::Error> { 146 | match self { 147 | Self::IO { source, .. } => Some(source), 148 | Self::IO1Process { source, .. } => Some(source), 149 | Self::IO1Name { source, .. } => Some(source), 150 | Self::IO1Path { source, .. } => Some(source), 151 | _ => None, 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /selinux/src/avc/tests.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(test, target_os = "linux", not(target_env = "kernel")))] 2 | 3 | use std::{io, ptr}; 4 | 5 | use assert_matches::assert_matches; 6 | use serial_test::serial; 7 | 8 | #[test] 9 | fn security_id_default() { 10 | let mut sid = super::SecurityID::default(); 11 | assert!(sid.is_unspecified()); 12 | assert!(!sid.is_raw_format()); 13 | assert!(sid.as_ptr().is_null()); 14 | assert!(sid.as_mut_ptr().is_null()); 15 | 16 | let _ignored = format!("{:?}", &sid); 17 | } 18 | 19 | #[serial] 20 | #[test] 21 | fn access_vector_cache_initialize() { 22 | match super::AccessVectorCache::initialize(&[]) { 23 | Ok(avc) => { 24 | let _ignored = format!("{avc:?}"); 25 | } 26 | 27 | Err(err) => { 28 | assert_matches!(err, crate::errors::Error::IO { .. }); 29 | if let crate::errors::Error::IO { source, .. } = err { 30 | assert_eq!(source.kind(), io::ErrorKind::NotFound); 31 | } 32 | } 33 | } 34 | 35 | let options = &[(selinux_sys::AVC_OPT_SETENFORCE, ptr::null())]; 36 | 37 | match super::AccessVectorCache::initialize(options) { 38 | Ok(avc) => { 39 | let _ignored = format!("{avc:?}"); 40 | } 41 | 42 | Err(err) => { 43 | assert_matches!(err, crate::errors::Error::IO { .. }); 44 | if let crate::errors::Error::IO { source, .. } = err { 45 | assert_eq!(source.kind(), io::ErrorKind::NotFound); 46 | } 47 | } 48 | } 49 | 50 | if let Ok(avc0) = super::AccessVectorCache::initialize(options) { 51 | if let Ok(avc1) = super::AccessVectorCache::initialize(options) { 52 | if let Ok(avc2) = super::AccessVectorCache::initialize(&[( 53 | selinux_sys::AVC_OPT_SETENFORCE, 54 | ptr::null(), 55 | )]) { 56 | assert_eq!(avc0, avc1); 57 | assert_eq!(avc0, avc2); 58 | 59 | super::AccessVectorCache::initialize(&[]).unwrap_err(); 60 | } 61 | } 62 | } 63 | } 64 | 65 | #[serial] 66 | #[test] 67 | fn access_vector_cache_reset() { 68 | let options = &[(selinux_sys::AVC_OPT_SETENFORCE, ptr::null())]; 69 | let avc = super::AccessVectorCache::initialize(options).unwrap(); 70 | avc.reset().unwrap(); 71 | } 72 | 73 | #[serial] 74 | #[test] 75 | fn access_vector_cache_clean_up() { 76 | let options = &[(selinux_sys::AVC_OPT_SETENFORCE, ptr::null())]; 77 | let avc = super::AccessVectorCache::initialize(options).unwrap(); 78 | avc.clean_up(); 79 | } 80 | 81 | #[serial] 82 | #[test] 83 | fn access_vector_cache_kernel_initial_security_id() { 84 | let options = &[(selinux_sys::AVC_OPT_SETENFORCE, ptr::null())]; 85 | let avc = super::AccessVectorCache::initialize(options).unwrap(); 86 | match avc.kernel_initial_security_id("unlabeled", false) { 87 | Ok(mut sid) => { 88 | assert_eq!(sid.as_ptr(), sid.as_mut_ptr()); 89 | assert!(!sid.is_raw_format()); 90 | assert!(!sid.is_unspecified()); 91 | 92 | let mut context = avc.security_context_from_security_id(sid).unwrap(); 93 | assert!(!context.is_raw_format()); 94 | assert_eq!(context.as_ptr(), context.as_mut_ptr()); 95 | assert!(!context.as_bytes().is_empty()); 96 | 97 | let _ignored = avc.security_id_from_security_context(context).unwrap(); 98 | } 99 | 100 | Err(err) => { 101 | assert_matches!(err, crate::errors::Error::IO { .. }); 102 | if let crate::errors::Error::IO { source, .. } = err { 103 | assert_eq!(source.kind(), io::ErrorKind::NotFound); 104 | } 105 | } 106 | } 107 | 108 | match avc.kernel_initial_security_id("unlabeled", true) { 109 | Ok(mut sid) => { 110 | assert_eq!(sid.as_ptr(), sid.as_mut_ptr()); 111 | assert!(sid.is_raw_format()); 112 | assert!(!sid.is_unspecified()); 113 | 114 | let mut context = avc.security_context_from_security_id(sid).unwrap(); 115 | assert!(context.is_raw_format()); 116 | assert_eq!(context.as_ptr(), context.as_mut_ptr()); 117 | assert!(!context.as_bytes().is_empty()); 118 | 119 | let _ignored = avc.security_id_from_security_context(context).unwrap(); 120 | } 121 | 122 | Err(err) => { 123 | assert_matches!(err, crate::errors::Error::IO { .. }); 124 | if let crate::errors::Error::IO { source, .. } = err { 125 | assert_eq!(source.kind(), io::ErrorKind::NotFound); 126 | } 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 4 | 5 | ## [0.4.4] - 2024-03-27 6 | 7 | ### Changed 8 | 9 | - Updated dependencies. 10 | - Moved repository to `codeberg.org`. 11 | 12 | ## [0.4.3] - 2024-01-07 13 | 14 | ### Changed 15 | 16 | - Updated dependencies. 17 | - Updated copyright years. 18 | 19 | ## [0.4.2] - 2023-08-09 20 | 21 | ### Changed 22 | 23 | - Updated dependencies. 24 | 25 | ## [0.4.1] - 2023-04-18 26 | 27 | ### Changed 28 | 29 | - Updated dependencies. 30 | 31 | ## [0.4.0] - 2023-02-23 32 | 33 | ### Added 34 | 35 | - Added `selinux::SecurityContext::previous_of_process()` method. 36 | 37 | ### Changed 38 | 39 | - The prototype of `selinux::policy::load()` changed, promising not to change its input. 40 | This mirrors a change to `security_load_policy()` introduced by `libselinux` version `3.5`. 41 | 42 | > ⚠️ **This is a breaking change**. 43 | 44 | - Updated dependencies. 45 | 46 | ## [0.3.3] - 2023-01-05 47 | 48 | ### Changed 49 | 50 | - `selinux::call_back::tests::log()` performed wrong manipulation of function pointers. 51 | The situation now is a little better for some ABIs, but this will have to be fixed for good once 52 | Rust allows defining variadic functions. 53 | This was a bug in unit tests, not in the crate implementation. 54 | 55 | Thank you, [*Ralf Jung*](https://github.com/RalfJung). 56 | 57 | - Updated dependencies. 58 | - Updated copyright years. 59 | 60 | ## [0.3.2] - 2022-11-26 61 | 62 | ### Changed 63 | 64 | - Return value of `selinux_restorecon_get_skipped_errors()` on 32-bits architectures requires 65 | lossless casting to `u64`. 66 | 67 | Thank you, [*plugwash*](https://github.com/plugwash). 68 | 69 | ## [0.3.1] - 2022-11-14 70 | 71 | ### Changed 72 | 73 | - Updated dependencies. 74 | 75 | ## [0.3.0] - 2022-09-18 76 | 77 | ### Changed 78 | 79 | - The prototype of `selinux::ContextRestore::restore_context_of_file_system_entry()` changed, 80 | in order to add support for the new features introduced by `libselinux` version `3.4`. 81 | 82 | > ⚠️ **This is a breaking change**. 83 | 84 | Calling the new prototype with `threads_count` set to `1` reproduces the old behavior. 85 | 86 | - Switched to Rust's 2021 edition. 87 | - Updated dependencies. 88 | 89 | ## [0.2.7] - 2022-04-09 90 | 91 | ### Changed 92 | 93 | - Test coverage generation now uses the *stable* tool chain. 94 | 95 | ## [0.2.6] - 2022-03-27 96 | 97 | ### Changed 98 | 99 | - Updated dependencies. 100 | - Updated copyright years. 101 | 102 | ## [0.2.5] - 2021-09-22 103 | 104 | ### Changed 105 | 106 | - Replaced all shell scripts by an *xtask*. 107 | 108 | To generate coverage information, run: 109 | ``` 110 | $ cargo xtask coverage 111 | ``` 112 | 113 | ## [0.2.4] - 2021-09-21 114 | 115 | ### Changed 116 | 117 | - Replace each `doc(alias(..))` directive by `doc(alias = ...)` directive. 118 | This enhances compatibility with older versions of Rust. 119 | 120 | Thank you, [*Matt Rice*](https://github.com/ratmice). 121 | 122 | - Updated dependencies. 123 | 124 | ## [0.2.3] - 2021-08-22 125 | 126 | ### Changed 127 | 128 | - Replace each single `doc(alias)` directive specifying multiple aliases by 129 | multiple `doc(alias)` directives each specifying a single alias. 130 | This reduces the minimum supported Rust version for this crate. 131 | 132 | ## [0.2.2] - 2021-08-18 133 | 134 | ### Added 135 | 136 | - Added documentation aliases to `libselinux` functions. 137 | This should make it easier to search in crate functionality by `libselinux` API name. 138 | 139 | ### Changed 140 | 141 | - Updated dependencies. 142 | 143 | ## [0.2.1] - 2021-08-10 144 | 145 | ### Added 146 | 147 | - Added `selinux::SecurityContext::to_c_string()` method. 148 | 149 | ## [0.2.0] - 2021-08-09 150 | 151 | ### Added 152 | 153 | - Added new versions of `set_type()`, `set_range()`, `set_role()` and `set_user()` 154 | in `selinux::OpaqueSecurityContext` where the new value is a `CStr`. 155 | 156 | ### Changed 157 | 158 | - Renamed multiple methods in `selinux::OpaqueSecurityContext`: 159 | - `set_type()` to `set_type_str()`. 160 | - `set_range()` to `set_range_str()`. 161 | - `set_role()` to `set_role_str()`. 162 | - `set_user()` to `set_user_str()`. 163 | 164 | > ⚠️ **This is a breaking change**. 165 | 166 | ## [0.1.3] - 2021-08-02 167 | 168 | ### Changed 169 | 170 | - Stopped using `std::slice::strip_prefix()`, in order to reduce the minimum 171 | supported Rust version for this crate. 172 | 173 | ## [0.1.2] - 2021-07-28 174 | 175 | ### Added 176 | 177 | - Implemented `Send` for `selinux::utils::CAllocatedBlock`. 178 | 179 | ### Changed 180 | 181 | - Updated dependencies. 182 | - Updated nightly compiler version for coverage analysis. 183 | 184 | ## [0.1.1] - 2021-06-19 185 | 186 | ### Changed 187 | 188 | - Removed dependency on the `arrayvec` crate, and avoided the use 189 | of `std::io::ErrorKind::Unsupported`. 190 | These changes allow some dependent crates to reduce their minimum supported 191 | Rust version. 192 | This doesn't add any limitations to this crate. 193 | 194 | ## [0.1.0] - 2021-06-19 195 | 196 | ### Added 197 | 198 | - Initial release. 199 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![crates.io](https://img.shields.io/crates/v/selinux.svg)](https://crates.io/crates/selinux) 2 | [![docs.rs](https://docs.rs/selinux/badge.svg)](https://docs.rs/selinux) 3 | [![license](https://img.shields.io/github/license/koutheir/selinux?color=black)](https://raw.githubusercontent.com/koutheir/selinux/master/LICENSE.txt) 4 | 5 | # 🛡️ Safe Rust bindings for `libselinux` 6 | 7 | SELinux is a flexible Mandatory Access Control for Linux. 8 | 9 | This crate supports `libselinux` from version `2.8` to `3.6`. 10 | Later versions might still be compatible. 11 | This crate exposes neither *deprecated* nor *undocumented* SELinux API functions 12 | and types. 13 | 14 | ⚠️ This crate is Linux-specific. Building it for non-Linux platforms, or for 15 | the Linux kernel, results in an empty crate. 16 | 17 | This documentation is too brief to cover SELinux. 18 | Please refer to the [official SELinux documentation], the manual pages of 19 | the [`libselinux`] native library, and the [`selinux-sys`] crate for a more 20 | complete picture on how to use this crate. 21 | 22 | If you cannot find a feature you are looking for by its name, but you know 23 | which `libselinux` APIs relate to it, then try searching the documentation 24 | by that API name. 25 | 26 | ## ⚓ Backward compatibility 27 | 28 | This crate requires `libselinux` version `2.8`, at least. 29 | However, this crate provides some functions that are based on `libselinux` 30 | functions implemented in later versions. 31 | When such newer functions are needed, this crate attempts to load them 32 | dynamically at runtime. 33 | If such functions are implemented by `libselinux`, then the called crate 34 | functions run as expected. 35 | If the needed functions are not implemented by `libselinux`, then an error is 36 | returned indicating that the called crate function is unsupported. 37 | 38 | ## 🔢 Versioning 39 | 40 | This project adheres to [Semantic Versioning]. 41 | The `CHANGELOG.md` file details notable changes over time. 42 | 43 | ## 🛠️ Development 44 | 45 | > This section is only relevant for developers contributing to this crate, 46 | > and not for users of this crate. 47 | 48 | 💡 If you're developing this crate and feel important information is missing 49 | in this section, then please create an issue or a pull request to fix that. 50 | 51 | ### Build system 52 | 53 | This crate uses only `cargo` as a build system. Usual commands are used to 54 | perform most operations, *e.g.*, `build`, `test`, `fmt`. 55 | 56 | > Code is read many times more that written, so this crate's code is always 57 | > formatted using `cargo fmt`. 58 | 59 | Operations requiring special handling are crafted as cargo [xtask] targets. 60 | The full list of these special operations can be determined by running: 61 | ```shell 62 | $ cargo xtask 63 | ``` 64 | Each special operation can be executed by running: 65 | ```shell 66 | $ cargo xtask [parameters...] 67 | ``` 68 | For example, to generate coverage information, run: 69 | ```shell 70 | $ cargo xtask coverage 71 | ``` 72 | 73 | ### Testing 74 | 75 | This crate can only be tested on a Linux distribution that has SELinux 76 | supported and enabled at multiple levels: 77 | - The Linux kernel must support SELinux, and have it enabled. 78 | - The file system must be correctly configured. 79 | - The user space must have access to SELinux, usually via `libselinux`. 80 | 81 | [Red Hat Enterprise Linux]-like distributions (*e.g.*, [Fedora], [CentOS], 82 | [RockyLinux]) are suitable for testing this crate, either on hardware or inside 83 | virtual machines, but not in containers. 84 | 85 | Given that coverage information requires running tests, that information 86 | can only be successfully obtained on a system with SELinux enabled. 87 | 88 | ### Behavior 89 | 90 | This crate uses the `libselinux` API as documented in the manual pages. 91 | It tries to avoid assumptions about implementation details as far as possible, 92 | even when performance might be improved with such knowledge. 93 | 94 | The structures and enumerations defined by this crate assume that their user 95 | might, at some point, decide to call *raw* `libselinux` APIs (possible using 96 | the `selinux-sys` crate) for features not yet provided by this crate, or for 97 | some other reasons. That is the reason why methods such as `as_ptr()` are 98 | implemented by these structures, exposing the raw values that `libselinux` 99 | APIs recognize. 100 | 101 | ### Change log 102 | 103 | The [change log] is useful to get a picture of what is going on with the 104 | crate in the recent past. 105 | 106 | [Semantic Versioning]: https://semver.org/spec/v2.0.0.html 107 | [official SELinux documentation]: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/using_selinux/index 108 | [`libselinux`]: https://man7.org/linux/man-pages/man8/selinux.8.html 109 | [`selinux-sys`]: https://docs.rs/selinux-sys/ 110 | [xtask]: https://github.com/matklad/cargo-xtask 111 | [Red Hat Enterprise Linux]: https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux 112 | [Fedora]: https://getfedora.org/ 113 | [CentOS]: https://www.centos.org/ 114 | [RockyLinux]: https://rockylinux.org/ 115 | [change log]: https://github.com/koutheir/selinux/blob/master/CHANGELOG.md 116 | -------------------------------------------------------------------------------- /selinux/src/call_back/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests; 3 | 4 | use std::os::raw::{c_char, c_int, c_void}; 5 | 6 | /// Call back for SELinux operations. 7 | pub trait CallBack { 8 | /// Prototype of call back function. 9 | type CallBackType; 10 | 11 | /// Get the current call back function, if one has been set. 12 | /// 13 | /// See: `selinux_get_callback()`. 14 | #[doc(alias = "selinux_get_callback")] 15 | fn get_call_back() -> Option; 16 | 17 | /// Set or clear the call back function. 18 | /// 19 | /// See: `selinux_set_callback()`. 20 | #[doc(alias = "selinux_set_callback")] 21 | fn set_call_back(call_back: Option); 22 | } 23 | 24 | /// Call back used for logging. 25 | #[derive(Debug, Default)] 26 | #[non_exhaustive] 27 | pub struct Log; 28 | 29 | impl CallBack for Log { 30 | type CallBackType = unsafe extern "C" fn(c_int, *const c_char, ...) -> c_int; 31 | 32 | fn get_call_back() -> Option { 33 | unsafe { selinux_sys::selinux_get_callback(selinux_sys::SELINUX_CB_LOG).func_log } 34 | } 35 | 36 | fn set_call_back(func_log: Option) { 37 | use selinux_sys::{selinux_callback, selinux_set_callback, SELINUX_CB_LOG}; 38 | unsafe { selinux_set_callback(SELINUX_CB_LOG, selinux_callback { func_log }) } 39 | } 40 | } 41 | 42 | /// Call back used for supplemental auditing in AVC messages. 43 | #[derive(Debug, Default)] 44 | #[non_exhaustive] 45 | pub struct Audit; 46 | 47 | impl CallBack for Audit { 48 | type CallBackType = unsafe extern "C" fn( 49 | *mut c_void, 50 | selinux_sys::security_class_t, 51 | *mut c_char, 52 | usize, 53 | ) -> c_int; 54 | 55 | fn get_call_back() -> Option { 56 | unsafe { selinux_sys::selinux_get_callback(selinux_sys::SELINUX_CB_AUDIT).func_audit } 57 | } 58 | 59 | fn set_call_back(func_audit: Option) { 60 | use selinux_sys::{selinux_callback, selinux_set_callback, SELINUX_CB_AUDIT}; 61 | unsafe { selinux_set_callback(SELINUX_CB_AUDIT, selinux_callback { func_audit }) } 62 | } 63 | } 64 | 65 | /// Call back used for context validation. 66 | #[derive(Debug, Default)] 67 | #[non_exhaustive] 68 | pub struct ContextValidation; 69 | 70 | impl CallBack for ContextValidation { 71 | type CallBackType = unsafe extern "C" fn(*mut *mut c_char) -> c_int; 72 | 73 | fn get_call_back() -> Option { 74 | unsafe { selinux_sys::selinux_get_callback(selinux_sys::SELINUX_CB_VALIDATE).func_validate } 75 | } 76 | 77 | fn set_call_back(func_validate: Option) { 78 | use selinux_sys::{selinux_callback, selinux_set_callback, SELINUX_CB_VALIDATE}; 79 | unsafe { selinux_set_callback(SELINUX_CB_VALIDATE, selinux_callback { func_validate }) } 80 | } 81 | } 82 | 83 | /// Call back invoked when the system enforcing state changes. 84 | #[derive(Debug, Default)] 85 | #[non_exhaustive] 86 | pub struct EnforcingChange; 87 | 88 | impl CallBack for EnforcingChange { 89 | type CallBackType = unsafe extern "C" fn(c_int) -> c_int; 90 | 91 | fn get_call_back() -> Option { 92 | use selinux_sys::{selinux_get_callback, SELINUX_CB_SETENFORCE}; 93 | unsafe { selinux_get_callback(SELINUX_CB_SETENFORCE).func_setenforce } 94 | } 95 | 96 | fn set_call_back(func_setenforce: Option) { 97 | use selinux_sys::{selinux_callback, selinux_set_callback, SELINUX_CB_SETENFORCE}; 98 | unsafe { selinux_set_callback(SELINUX_CB_SETENFORCE, selinux_callback { func_setenforce }) } 99 | } 100 | } 101 | 102 | /// Call back invoked when the system security policy is reloaded. 103 | #[derive(Debug, Default)] 104 | #[non_exhaustive] 105 | pub struct SecurityPolicyReload; 106 | 107 | impl CallBack for SecurityPolicyReload { 108 | type CallBackType = unsafe extern "C" fn(c_int) -> c_int; 109 | 110 | fn get_call_back() -> Option { 111 | use selinux_sys::{selinux_get_callback, SELINUX_CB_POLICYLOAD}; 112 | unsafe { selinux_get_callback(SELINUX_CB_POLICYLOAD).func_policyload } 113 | } 114 | 115 | fn set_call_back(func_policyload: Option) { 116 | use selinux_sys::{selinux_callback, selinux_set_callback, SELINUX_CB_POLICYLOAD}; 117 | unsafe { selinux_set_callback(SELINUX_CB_POLICYLOAD, selinux_callback { func_policyload }) } 118 | } 119 | } 120 | 121 | /// Log type argument indicating the type of message. 122 | pub mod log_type { 123 | use std::os::raw::c_int; 124 | 125 | /// Error log entry. 126 | pub use selinux_sys::SELINUX_ERROR as ERROR; 127 | 128 | /// Warning log entry. 129 | pub use selinux_sys::SELINUX_WARNING as WARNING; 130 | 131 | /// Informational log entry. 132 | pub use selinux_sys::SELINUX_INFO as INFO; 133 | 134 | /// AVC log entry. 135 | pub use selinux_sys::SELINUX_AVC as AVC; 136 | 137 | // The rest of the constants were defined after version 2.8, so selinux_sys might not 138 | // export them. We therefore define them manually. 139 | 140 | /// Policy loaded. 141 | pub static POLICY_LOAD: c_int = 4_i32; 142 | 143 | /// SELinux enforcing mode changed. 144 | pub static SET_ENFORCE: c_int = 5_i32; 145 | } 146 | -------------------------------------------------------------------------------- /selinux/src/path/tests.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(test, target_os = "linux", not(target_env = "kernel")))] 2 | 3 | #[test] 4 | fn selinux() { 5 | let path = super::selinux().unwrap(); 6 | assert!(!path.as_os_str().is_empty()); 7 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 8 | } 9 | 10 | #[test] 11 | fn default_type_path() { 12 | let path = super::default_type_path().unwrap(); 13 | assert!(!path.as_os_str().is_empty()); 14 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 15 | } 16 | 17 | #[test] 18 | fn fail_safe_context() { 19 | let path = super::fail_safe_context().unwrap(); 20 | assert!(!path.as_os_str().is_empty()); 21 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 22 | } 23 | 24 | #[test] 25 | fn removable_context() { 26 | let path = super::removable_context().unwrap(); 27 | assert!(!path.as_os_str().is_empty()); 28 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 29 | } 30 | 31 | #[test] 32 | fn default_context() { 33 | let path = super::default_context().unwrap(); 34 | assert!(!path.as_os_str().is_empty()); 35 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 36 | } 37 | 38 | #[test] 39 | fn user_contexts() { 40 | let path = super::user_contexts().unwrap(); 41 | assert!(!path.as_os_str().is_empty()); 42 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 43 | } 44 | 45 | #[test] 46 | fn file_context() { 47 | let path = super::file_context().unwrap(); 48 | assert!(!path.as_os_str().is_empty()); 49 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 50 | } 51 | 52 | #[test] 53 | fn file_context_homedir() { 54 | let path = super::file_context_homedir().unwrap(); 55 | assert!(!path.as_os_str().is_empty()); 56 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 57 | } 58 | 59 | #[test] 60 | fn file_context_local() { 61 | let path = super::file_context_local().unwrap(); 62 | assert!(!path.as_os_str().is_empty()); 63 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 64 | } 65 | 66 | #[test] 67 | fn file_context_subs() { 68 | let path = super::file_context_subs().unwrap(); 69 | assert!(!path.as_os_str().is_empty()); 70 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 71 | } 72 | 73 | #[test] 74 | fn file_context_subs_dist() { 75 | let path = super::file_context_subs_dist().unwrap(); 76 | assert!(!path.as_os_str().is_empty()); 77 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 78 | } 79 | 80 | #[test] 81 | fn home_dir_context() { 82 | let path = super::home_dir_context().unwrap(); 83 | assert!(!path.as_os_str().is_empty()); 84 | //assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 85 | } 86 | 87 | #[test] 88 | fn media_context() { 89 | let path = super::media_context().unwrap(); 90 | assert!(!path.as_os_str().is_empty()); 91 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 92 | } 93 | 94 | #[test] 95 | fn virtual_domain_context() { 96 | let path = super::virtual_domain_context().unwrap(); 97 | assert!(!path.as_os_str().is_empty()); 98 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 99 | } 100 | 101 | #[test] 102 | fn virtual_image_context() { 103 | let path = super::virtual_image_context().unwrap(); 104 | assert!(!path.as_os_str().is_empty()); 105 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 106 | } 107 | 108 | #[test] 109 | fn lxc_contexts() { 110 | let path = super::lxc_contexts().unwrap(); 111 | assert!(!path.as_os_str().is_empty()); 112 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 113 | } 114 | 115 | #[test] 116 | fn x_context() { 117 | let path = super::x_context().unwrap(); 118 | assert!(!path.as_os_str().is_empty()); 119 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 120 | } 121 | 122 | #[test] 123 | fn sepgsql_context() { 124 | let path = super::sepgsql_context().unwrap(); 125 | assert!(!path.as_os_str().is_empty()); 126 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 127 | } 128 | 129 | #[test] 130 | fn openrc_contexts() { 131 | let path = super::openrc_contexts().unwrap(); 132 | assert!(!path.as_os_str().is_empty()); 133 | //assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 134 | } 135 | 136 | #[test] 137 | fn openssh_contexts() { 138 | let path = super::openssh_contexts().unwrap(); 139 | assert!(!path.as_os_str().is_empty()); 140 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 141 | } 142 | 143 | #[test] 144 | fn snapperd_contexts() { 145 | let path = super::snapperd_contexts().unwrap(); 146 | assert!(!path.as_os_str().is_empty()); 147 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 148 | } 149 | 150 | #[test] 151 | fn systemd_contexts() { 152 | let path = super::systemd_contexts().unwrap(); 153 | assert!(!path.as_os_str().is_empty()); 154 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 155 | } 156 | 157 | #[test] 158 | fn contexts() { 159 | let path = super::contexts().unwrap(); 160 | assert!(!path.as_os_str().is_empty()); 161 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 162 | } 163 | 164 | #[test] 165 | fn securetty_types() { 166 | let path = super::securetty_types().unwrap(); 167 | assert!(!path.as_os_str().is_empty()); 168 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 169 | } 170 | 171 | #[test] 172 | fn booleans_subs() { 173 | let path = super::booleans_subs().unwrap(); 174 | assert!(!path.as_os_str().is_empty()); 175 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 176 | } 177 | 178 | #[test] 179 | fn customizable_types() { 180 | let path = super::customizable_types().unwrap(); 181 | assert!(!path.as_os_str().is_empty()); 182 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 183 | } 184 | 185 | #[test] 186 | fn users_conf() { 187 | let path = super::users_conf().unwrap(); 188 | assert!(!path.as_os_str().is_empty()); 189 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 190 | } 191 | 192 | #[test] 193 | fn translations() { 194 | let path = super::translations().unwrap(); 195 | assert!(!path.as_os_str().is_empty()); 196 | assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 197 | } 198 | 199 | #[test] 200 | fn colors() { 201 | let path = super::colors().unwrap(); 202 | assert!(!path.as_os_str().is_empty()); 203 | //assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 204 | } 205 | 206 | #[test] 207 | fn netfilter_context() { 208 | let path = super::netfilter_context().unwrap(); 209 | assert!(!path.as_os_str().is_empty()); 210 | //assert!(path.exists() || crate::current_mode() == crate::SELinuxMode::NotRunning); 211 | } 212 | -------------------------------------------------------------------------------- /selinux/src/label/tests.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(test, target_os = "linux", not(target_env = "kernel")))] 2 | 3 | use std::collections::HashSet; 4 | use std::ffi::CStr; 5 | use std::os::raw::c_void; 6 | use std::path::Path; 7 | use std::ptr; 8 | 9 | use assert_matches::assert_matches; 10 | 11 | #[test] 12 | fn labeler_new_file() { 13 | let mut labeler1 = super::Labeler::::new(&[], false).unwrap(); 14 | assert_eq!(labeler1.as_ptr(), labeler1.as_mut_ptr()); 15 | assert!(!labeler1.is_raw_format()); 16 | 17 | let _ignored = format!("{:?}", &labeler1); 18 | 19 | let labeler2 = super::Labeler::::new(&[], false).unwrap(); 20 | assert_eq!(labeler1, labeler2); 21 | } 22 | 23 | #[test] 24 | fn labeler_log_statistics() { 25 | let labeler = super::Labeler::::new(&[], false).unwrap(); 26 | labeler.log_statistics(); 27 | } 28 | 29 | #[test] 30 | fn labeler_digest() { 31 | let options = &[(selinux_sys::SELABEL_OPT_DIGEST, 1 as *const c_void)]; 32 | let labeler = super::Labeler::::new(options, false).unwrap(); 33 | 34 | let digest = labeler.digest().unwrap(); 35 | assert!(!digest.digest().is_empty()); 36 | assert!(!digest.spec_files().is_empty()); 37 | } 38 | 39 | #[test] 40 | fn labeler_restorecon_default() { 41 | let _labeler = super::Labeler::restorecon_default(false).unwrap(); 42 | } 43 | 44 | #[test] 45 | fn labeler_look_up() { 46 | for &raw_format in &[false, true] { 47 | let labeler = super::Labeler::::new(&[], raw_format).unwrap(); 48 | let path = unsafe { CStr::from_ptr("/lib\0".as_ptr().cast()) }; 49 | let _context = labeler.look_up(path, 0).unwrap(); 50 | } 51 | } 52 | 53 | #[test] 54 | fn labeler_look_up_by_path() { 55 | for &raw_format in &[false, true] { 56 | let labeler = super::Labeler::::new(&[], raw_format).unwrap(); 57 | let _context = labeler.look_up_by_path("/lib", None).unwrap(); 58 | } 59 | } 60 | 61 | #[test] 62 | fn labeler_look_up_best_match_by_path() { 63 | for &raw_format in &[false, true] { 64 | for &alias_paths in &[&[] as &[&str], &["/usr/lib"]] { 65 | let labeler = super::Labeler::::new(&[], raw_format).unwrap(); 66 | let _context = labeler 67 | .look_up_best_match_by_path("/lib", alias_paths, None) 68 | .unwrap(); 69 | } 70 | } 71 | } 72 | 73 | #[test] 74 | fn labeler_partial_match_by_path() { 75 | let labeler = super::Labeler::::new(&[], false).unwrap(); 76 | let _is_match = labeler.partial_match_by_path("/lib").unwrap(); 77 | } 78 | 79 | #[test] 80 | fn labeler_get_digests_all_partial_matches_by_path() { 81 | let labeler = super::Labeler::::new(&[], false).unwrap(); 82 | 83 | if let Err(r) = labeler.get_digests_all_partial_matches_by_path("/tmp") { 84 | let r = r.io_source().unwrap().raw_os_error(); 85 | assert_matches!(r, Some(libc::ENOSYS | libc::ENOENT)); 86 | } 87 | } 88 | 89 | #[test] 90 | fn digest() { 91 | let sf_two_nulls = &[ptr::null(), ptr::null()]; 92 | 93 | for &(dg_ptr, dg_len, sf_ptr, sf_len) in &[ 94 | (ptr::null(), 0, ptr::null(), 0), 95 | (b"".as_ptr(), 0, ptr::null(), 0), 96 | (b"xyz".as_ptr(), 0, ptr::null(), 0), 97 | (ptr::null(), 3, ptr::null(), 0), 98 | (ptr::null(), 0, ptr::null(), 3), 99 | (ptr::null(), 0, &[].as_ptr(), 0), 100 | (ptr::null(), 0, sf_two_nulls.as_ptr(), sf_two_nulls.len()), 101 | ] { 102 | let digest = super::Digest::new(dg_ptr, dg_len, sf_ptr, sf_len); 103 | assert!(digest.digest().is_empty()); 104 | assert!(digest.spec_files().is_empty()); 105 | } 106 | 107 | let digest = super::Digest::new(b"xyz".as_ptr(), 3, ptr::null_mut(), 0); 108 | assert_eq!(digest.digest(), b"xyz"); 109 | assert!(digest.spec_files().is_empty()); 110 | 111 | let spec_files = &["abc\0".as_ptr().cast(), ptr::null(), "A\0".as_ptr().cast()]; 112 | let digest = super::Digest::new(ptr::null_mut(), 0, spec_files.as_ptr(), spec_files.len()); 113 | assert!(digest.digest().is_empty()); 114 | assert_eq!(digest.spec_files().len(), 1); 115 | assert_eq!(digest.spec_files()[0], Path::new("abc")); 116 | 117 | let spec_files = &[ 118 | "abc\0".as_ptr().cast(), 119 | "abcdef\0".as_ptr().cast(), 120 | "A\0".as_ptr().cast(), 121 | ]; 122 | let digest = super::Digest::new(ptr::null_mut(), 0, spec_files.as_ptr(), spec_files.len()); 123 | assert!(digest.digest().is_empty()); 124 | assert_eq!(digest.spec_files().len(), 3); 125 | assert_eq!(digest.spec_files()[0], Path::new("abc")); 126 | assert_eq!(digest.spec_files()[1], Path::new("abcdef")); 127 | assert_eq!(digest.spec_files()[2], Path::new("A")); 128 | 129 | let spec_files = &[ 130 | "abc\0".as_ptr().cast(), 131 | "abcdef\0".as_ptr().cast(), 132 | "A\0".as_ptr().cast(), 133 | ]; 134 | let digest = super::Digest::new(b"xyz".as_ptr(), 3, spec_files.as_ptr(), spec_files.len()); 135 | assert_eq!(digest.digest(), b"xyz"); 136 | assert_eq!(digest.spec_files().len(), 3); 137 | assert_eq!(digest.spec_files()[0], Path::new("abc")); 138 | assert_eq!(digest.spec_files()[1], Path::new("abcdef")); 139 | assert_eq!(digest.spec_files()[2], Path::new("A")); 140 | 141 | let _ignored = format!("{:?}", &digest); 142 | let digest_clone = digest.clone(); 143 | assert_eq!(digest, digest_clone); 144 | assert!(digest >= digest_clone); 145 | assert!(digest <= digest_clone); 146 | let mut ht = HashSet::new(); 147 | ht.insert(digest_clone); 148 | } 149 | 150 | #[test] 151 | fn partial_matches_digests() { 152 | let pmd = super::PartialMatchesDigests { 153 | match_result: super::PartialMatchesResult::NoMatchOrMissing, 154 | xattr_digest: None, 155 | calculated_digest: None, 156 | digest_size: 0, 157 | }; 158 | assert_eq!( 159 | pmd.match_result(), 160 | super::PartialMatchesResult::NoMatchOrMissing 161 | ); 162 | assert!(pmd.is_empty()); 163 | assert_eq!(pmd.len(), 0); 164 | assert_eq!(pmd.xattr_digest(), None); 165 | assert_eq!(pmd.calculated_digest(), None); 166 | 167 | let pmd = super::PartialMatchesDigests { 168 | match_result: super::PartialMatchesResult::Match, 169 | xattr_digest: None, 170 | calculated_digest: None, 171 | digest_size: 10, 172 | }; 173 | assert_eq!(pmd.match_result(), super::PartialMatchesResult::Match); 174 | assert!(!pmd.is_empty()); 175 | assert_eq!(pmd.len(), 10); 176 | assert_eq!(pmd.xattr_digest(), None); 177 | assert_eq!(pmd.calculated_digest(), None); 178 | 179 | let xattr_digest: *mut u8 = unsafe { libc::malloc(3) }.cast(); 180 | unsafe { ptr::copy_nonoverlapping("abc".as_ptr(), xattr_digest, 3) }; 181 | 182 | let pmd = super::PartialMatchesDigests { 183 | match_result: super::PartialMatchesResult::Match, 184 | xattr_digest: crate::utils::CAllocatedBlock::new(xattr_digest), 185 | calculated_digest: None, 186 | digest_size: 3, 187 | }; 188 | assert_eq!(pmd.match_result(), super::PartialMatchesResult::Match); 189 | assert!(!pmd.is_empty()); 190 | assert_eq!(pmd.len(), 3); 191 | assert_eq!(pmd.xattr_digest(), Some(b"abc" as &[u8])); 192 | assert_eq!(pmd.calculated_digest(), None); 193 | 194 | let xattr_digest: *mut u8 = unsafe { libc::malloc(3) }.cast(); 195 | unsafe { ptr::copy_nonoverlapping("abc".as_ptr(), xattr_digest, 3) }; 196 | 197 | let calculated_digest: *mut u8 = unsafe { libc::malloc(3) }.cast(); 198 | unsafe { ptr::copy_nonoverlapping("xyz".as_ptr(), calculated_digest, 3) }; 199 | 200 | let pmd = super::PartialMatchesDigests { 201 | match_result: super::PartialMatchesResult::Match, 202 | xattr_digest: crate::utils::CAllocatedBlock::new(xattr_digest), 203 | calculated_digest: crate::utils::CAllocatedBlock::new(calculated_digest), 204 | digest_size: 3, 205 | }; 206 | assert_eq!(pmd.match_result(), super::PartialMatchesResult::Match); 207 | assert!(!pmd.is_empty()); 208 | assert_eq!(pmd.len(), 3); 209 | assert_eq!(pmd.xattr_digest(), Some(b"abc" as &[u8])); 210 | assert_eq!(pmd.calculated_digest(), Some(b"xyz" as &[u8])); 211 | 212 | let _ignored = format!("{:?}", &pmd); 213 | } 214 | -------------------------------------------------------------------------------- /xtask/src/coverage.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::OsStr; 2 | use std::fs::File; 3 | use std::path::{Path, PathBuf}; 4 | use std::{fs, process}; 5 | 6 | use log::{debug, info}; 7 | 8 | use crate::errors::{Error, Result}; 9 | use crate::utils::*; 10 | use crate::Config; 11 | 12 | // https://doc.rust-lang.org/stable/rustc/instrument-coverage.html 13 | 14 | #[derive(Debug, serde_derive::Deserialize)] 15 | struct CargoTestMessageProfile { 16 | test: bool, 17 | } 18 | 19 | #[derive(Debug, serde_derive::Deserialize)] 20 | struct CargoTestMessage { 21 | profile: CargoTestMessageProfile, 22 | filenames: Vec, 23 | } 24 | 25 | pub(crate) fn coverage(config: &Config) -> Result<()> { 26 | let coverage_dir = config 27 | .coverage_dir 28 | .to_str() 29 | .expect("Path is not valid UTF-8"); 30 | 31 | let llvm_cov_common_args: [&str; 10] = [ 32 | "--Xdemangler", 33 | "rustfilt", 34 | "--ignore-filename-regex", 35 | r"/\.cargo/registry/", 36 | "--ignore-filename-regex", 37 | r#"/rustc/"#, 38 | "--ignore-filename-regex", 39 | r#"/tests.rs$$"#, 40 | "--ignore-filename-regex", 41 | &format!("^{coverage_dir}/"), 42 | ]; 43 | 44 | let rustc_flags = OsStr::new("-Cinstrument-coverage -Clink-dead-code"); 45 | 46 | let coverage_common_env: [(&str, &OsStr); 4] = [ 47 | ("RUST_BACKTRACE", OsStr::new("1")), 48 | ("CARGO_INCREMENTAL", OsStr::new("0")), 49 | ("RUSTFLAGS", rustc_flags), 50 | ("RUSTDOCFLAGS", rustc_flags), 51 | ]; 52 | 53 | let coverage_common_args: [&str; 5] = [ 54 | "test", 55 | "--workspace", 56 | "--tests", 57 | "--target-dir", 58 | coverage_dir, 59 | ]; 60 | 61 | rustfilt_version(config)?; 62 | 63 | let sys_root = sys_root_of_toolchain(config)?; 64 | 65 | let mut result = find_executable_file(&sys_root, "llvm-profdata"); 66 | if result.is_err() { 67 | info!("Installing component 'llvm-tools-preview'..."); 68 | let args = ["--quiet", "component", "add", "llvm-tools-preview"]; 69 | rustup(config, &args)?; 70 | 71 | result = find_executable_file(&sys_root, "llvm-profdata"); 72 | } 73 | let llvm_profdata = result?; 74 | let llvm_cov = find_executable_file(&sys_root, "llvm-cov")?; 75 | 76 | fs::create_dir_all(&config.coverage_dir) 77 | .map_err(|r| Error::from_io_path("std::fs::create_dir_all", &config.coverage_dir, r))?; 78 | 79 | info!("Cleaning up old coverage files..."); 80 | let profraw_files = list_files(&config.coverage_dir, "profraw")?; 81 | profraw_files.into_iter().for_each(|p| { 82 | let _ignored = fs::remove_file(p); 83 | }); 84 | 85 | let tests_paths = build_coverage_binaries(config, &coverage_common_env, &coverage_common_args)?; 86 | run_coverage_binaries(config, &coverage_common_env, &coverage_common_args)?; 87 | 88 | merge_coverage_profraw_files(config, &llvm_profdata)?; 89 | 90 | export_coverage_lcov(config, &llvm_cov, &llvm_cov_common_args, &tests_paths)?; 91 | export_coverage_html(config, &llvm_cov, &llvm_cov_common_args, &tests_paths) 92 | } 93 | 94 | fn rustfilt_version(config: &Config) -> Result<()> { 95 | let mut cmd = process::Command::new("rustfilt"); 96 | cmd.stdout(process::Stdio::null()).arg("--version"); 97 | 98 | let mut result = run_cmd(cmd, "rustfilt"); 99 | if result.is_err() { 100 | info!("Installing 'rustfilt'..."); 101 | cargo_command(config, "", &["--quiet", "install", "rustfilt"])?; 102 | 103 | let mut cmd = process::Command::new("rustfilt"); 104 | cmd.stdout(process::Stdio::null()).arg("--version"); 105 | result = run_cmd(cmd, "rustfilt"); 106 | } 107 | result 108 | } 109 | 110 | fn build_coverage_binaries( 111 | config: &Config, 112 | common_env: &[(&str, &OsStr)], 113 | common_args: &[&str], 114 | ) -> Result> { 115 | info!("Building coverage binaries..."); 116 | 117 | let mut cmd = process::Command::new("cargo"); 118 | cmd.current_dir(config.workspace_dir) 119 | .stdout(process::Stdio::piped()) 120 | .envs(common_env.iter().map(|(k, v)| (*k, *v))) 121 | .env("LLVM_PROFILE_FILE", "/dev/null") 122 | .args(common_args) 123 | .args(["--no-run", "--message-format=json"]); 124 | 125 | debug!("Running: {:?}", cmd); 126 | let output = cmd 127 | .spawn() 128 | .map_err(|r| Error::from_io_path("std::process::Command::spawn", "cargo", r))? 129 | .wait_with_output() 130 | .map_err(|r| Error::from_io_path("std::process::Child::wait_with_output", "cargo", r))?; 131 | if output.status.success() { 132 | Ok(test_binaries_from_cargo_test_messages(&output.stdout)) 133 | } else { 134 | Err(Error::CommandFailed { name: "cargo" }) 135 | } 136 | } 137 | 138 | fn run_coverage_binaries( 139 | config: &Config, 140 | common_env: &[(&str, &OsStr)], 141 | common_args: &[&str], 142 | ) -> Result<()> { 143 | info!("Running coverage binaries..."); 144 | 145 | let mut cmd = process::Command::new("cargo"); 146 | cmd.current_dir(config.workspace_dir) 147 | .envs(common_env.iter().map(|(k, v)| (*k, *v))) 148 | .env("LLVM_PROFILE_FILE", &config.coverage_dir.join("%m.profraw")) 149 | .args(common_args); 150 | run_cmd(cmd, "cargo") 151 | } 152 | 153 | fn merge_coverage_profraw_files(config: &Config, llvm_profdata: &Path) -> Result<()> { 154 | info!("Merging coverage data..."); 155 | 156 | let profraw_files = list_files(&config.coverage_dir, "profraw")?; 157 | 158 | let mut cmd = process::Command::new(llvm_profdata); 159 | cmd.args(["merge", "--sparse", "--output"]) 160 | .arg(&config.coverage_profdata) 161 | .args(&profraw_files); 162 | run_cmd(cmd, "llvm-profdata") 163 | } 164 | 165 | fn export_coverage_lcov( 166 | config: &Config, 167 | llvm_cov: &Path, 168 | llvm_cov_common_args: &[&str], 169 | tests_paths: &[PathBuf], 170 | ) -> Result<()> { 171 | info!("Exporting coverage LCOV..."); 172 | 173 | let lcov_path = config.coverage_dir.join("lcov.info"); 174 | let lcov_info = File::create(&lcov_path) 175 | .map_err(|r| Error::from_io_path("std::fs::File::create", &lcov_path, r))?; 176 | 177 | let mut cmd = process::Command::new(llvm_cov); 178 | cmd.stdout(lcov_info) 179 | .args(["export", "--format", "lcov"]) 180 | .args(llvm_cov_common_args) 181 | .arg("--instr-profile") 182 | .arg(&config.coverage_profdata); 183 | for path in tests_paths { 184 | cmd.arg("--object").arg(path); 185 | } 186 | run_cmd(cmd, "llvm-cov") 187 | } 188 | 189 | fn export_coverage_html( 190 | config: &Config, 191 | llvm_cov: &Path, 192 | llvm_cov_common_args: &[&str], 193 | tests_paths: &[PathBuf], 194 | ) -> Result<()> { 195 | info!("Exporting coverage HTML..."); 196 | 197 | let mut cmd = process::Command::new(llvm_cov); 198 | cmd.args(["show", "--format", "html"]) 199 | .args(["--show-line-counts-or-regions", "--show-instantiations"]) 200 | .args(llvm_cov_common_args) 201 | .arg("--instr-profile") 202 | .arg(&config.coverage_profdata) 203 | .arg("--output-dir") 204 | .arg(&config.coverage_dir); 205 | for path in tests_paths { 206 | cmd.arg("--object").arg(path); 207 | } 208 | run_cmd(cmd, "llvm-cov")?; 209 | 210 | let mut cmd = process::Command::new("patch"); 211 | cmd.current_dir(&config.coverage_dir) 212 | .arg("--input") 213 | .arg(&config.workspace_dir.join("coverage-style.css.patch")); 214 | run_cmd(cmd, "patch") 215 | } 216 | 217 | fn rustc_print_sysroot(config: &Config) -> Result> { 218 | let name = "rustc --print sysroot"; 219 | 220 | let mut cmd = process::Command::new("rustc"); 221 | cmd.current_dir(config.workspace_dir) 222 | .stdout(process::Stdio::piped()) 223 | .args(["--print", "sysroot"]); 224 | 225 | debug!("Running: {:?}", cmd); 226 | let output = cmd 227 | .spawn() 228 | .map_err(|r| Error::from_io_path("std::process::Command::spawn", name, r))? 229 | .wait_with_output() 230 | .map_err(|r| Error::from_io_path("std::process::Child::wait_with_output", name, r))?; 231 | if output.status.success() { 232 | Ok(output.stdout) 233 | } else { 234 | Err(Error::CommandFailed { name }) 235 | } 236 | } 237 | 238 | fn sys_root_of_toolchain(config: &Config) -> Result { 239 | let mut bytes = rustc_print_sysroot(config)?; 240 | if let Some(line_len) = bytes 241 | .as_slice() 242 | .split(|&c| c == b'\n' || c == b'\r') 243 | .next() 244 | .map(|s| s.len()) 245 | { 246 | bytes.resize(line_len, 0); // Keep only the first line. 247 | } 248 | 249 | Ok(pathbuf_from_vec(bytes)) 250 | } 251 | 252 | fn test_binaries_from_cargo_test_messages(bytes: &[u8]) -> Vec { 253 | bytes 254 | .split(|&c| c == b'\r' || c == b'\n') 255 | .map(serde_json::from_slice::) 256 | .filter_map(std::result::Result::ok) 257 | .filter(|obj| obj.profile.test) 258 | .flat_map(|obj| obj.filenames) 259 | .collect() 260 | } 261 | -------------------------------------------------------------------------------- /selinux/src/avc/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests; 3 | 4 | use std::convert::TryFrom; 5 | use std::marker::PhantomData; 6 | use std::mem::MaybeUninit; 7 | use std::os::raw::{c_char, c_int, c_uint, c_void}; 8 | use std::sync::Once; 9 | use std::{io, ptr}; 10 | 11 | use reference_counted_singleton::{RCSRef, RefCountedSingleton}; 12 | 13 | use crate::errors::{Error, Result}; 14 | use crate::utils::{ret_val_to_result, str_to_c_string}; 15 | use crate::SecurityContext; 16 | 17 | /// Access vector cache. 18 | #[derive(Debug, PartialEq, Eq)] 19 | pub struct AccessVectorCache(Vec); 20 | 21 | static AVC_INIT: Once = Once::new(); 22 | static mut AVC: MaybeUninit> = MaybeUninit::uninit(); 23 | 24 | fn get_or_init_access_vector_cache() -> &'static RefCountedSingleton { 25 | AVC_INIT.call_once(|| unsafe { 26 | AVC = MaybeUninit::new(RefCountedSingleton::default()); 27 | }); 28 | 29 | unsafe { 30 | AVC.as_ptr() 31 | .as_ref() 32 | .expect("Static must have a valid address") 33 | } 34 | } 35 | 36 | impl AccessVectorCache { 37 | /// Initialize the user space access vector cache. 38 | /// 39 | /// The `options` parameter produces zero or more `(type, value)` tuples, where: 40 | /// - `type` is one of `selinux_sys::AVC_OPT_*` values, 41 | /// *e.g.*, [`selinux_sys::AVC_OPT_SETENFORCE`]. 42 | /// - `value` is a pointer whose semantics are specific to `type`. 43 | /// 44 | /// Attempting to initialize the access vector cache while it is still 45 | /// initialized succeeds only if the subsequent initialization uses the same 46 | /// set of options as the previous, still in scope, one. 47 | /// 48 | /// See: `avc_open()`. 49 | #[doc(alias = "avc_open")] 50 | pub fn initialize(options: &[(c_int, *const c_void)]) -> Result> { 51 | let mut options: Vec = options 52 | .iter() 53 | .map(|&(type_, value)| selinux_sys::selinux_opt { 54 | type_, 55 | value: value.cast(), 56 | }) 57 | .collect(); 58 | options.sort_unstable(); 59 | options.dedup(); 60 | 61 | let count = c_uint::try_from(options.len())?; 62 | let options_ptr = if count == 0 { 63 | ptr::null_mut() 64 | } else { 65 | options.as_mut_ptr() 66 | }; 67 | 68 | let mut newly_initialized = false; 69 | let avc = get_or_init_access_vector_cache(); 70 | 71 | let result = avc.get_or_init(|| { 72 | if unsafe { selinux_sys::avc_open(options_ptr, count) } == -1_i32 { 73 | Err(Error::last_io_error("avc_open()")) 74 | } else { 75 | newly_initialized = true; 76 | Ok(AccessVectorCache(options.clone())) // First initialization succeeded. 77 | } 78 | }); 79 | 80 | match result { 81 | Ok(value) => { 82 | if newly_initialized || value.0 == options { 83 | // Either: 84 | // 1. First initialization succeeded, or 85 | // 2. Initializing, while still initialized, using the same 86 | // set of options. 87 | Ok(value) 88 | } else { 89 | // Initializing, while still initialized, with a different 90 | // set of options, is an error. 91 | let err = io::ErrorKind::AlreadyExists.into(); 92 | Err(Error::from_io("AccessVectorCache::initialize()", err)) 93 | } 94 | } 95 | 96 | Err(None) => Err(Error::LockPoisoned { 97 | operation: "RefCountedSingleton::get_or_init()", 98 | }), 99 | 100 | Err(Some(err)) => Err(err), 101 | } 102 | } 103 | 104 | /// Flush the user space access vector cache, causing it to forget any 105 | /// cached access decisions. 106 | /// 107 | /// See: `avc_reset()`. 108 | #[doc(alias = "avc_reset")] 109 | pub fn reset(&self) -> Result<()> { 110 | ret_val_to_result("avc_reset()", unsafe { selinux_sys::avc_reset() }) 111 | } 112 | 113 | /// Attempt to free unused memory within the user space access vector 114 | /// cache, but do not flush any cached access decisions. 115 | /// 116 | /// See: `avc_cleanup()`. 117 | #[doc(alias = "avc_cleanup")] 118 | pub fn clean_up(&self) { 119 | unsafe { selinux_sys::avc_cleanup() } 120 | } 121 | 122 | /// Return a security identifier for the kernel initial security identifier 123 | /// specified by `security_identifier_name`. 124 | /// 125 | /// See: `avc_get_initial_sid()`. 126 | #[doc(alias = "avc_get_initial_sid")] 127 | pub fn kernel_initial_security_id<'context>( 128 | &'context self, 129 | security_id_name: &str, 130 | raw_format: bool, 131 | ) -> Result> { 132 | let c_name = str_to_c_string(security_id_name)?; 133 | let mut security_id: *mut selinux_sys::security_id = ptr::null_mut(); 134 | if unsafe { selinux_sys::avc_get_initial_sid(c_name.as_ptr(), &mut security_id) } == -1_i32 135 | { 136 | Err(Error::last_io_error("avc_get_initial_sid()")) 137 | } else { 138 | Ok(SecurityID { 139 | security_id, 140 | is_raw: raw_format, 141 | _phantom_data: PhantomData, 142 | }) 143 | } 144 | } 145 | 146 | /// Return a security context for the given security identifier. 147 | /// 148 | /// See: `avc_sid_to_context()`. 149 | #[doc(alias = "avc_sid_to_context")] 150 | pub fn security_context_from_security_id<'context>( 151 | &'context self, 152 | mut security_id: SecurityID, 153 | ) -> Result> { 154 | let is_raw = security_id.is_raw_format(); 155 | let (proc, proc_name): (unsafe extern "C" fn(_, _) -> _, _) = if is_raw { 156 | let proc_name = "avc_sid_to_context_raw()"; 157 | (selinux_sys::avc_sid_to_context_raw, proc_name) 158 | } else { 159 | let proc_name = "avc_sid_to_context()"; 160 | (selinux_sys::avc_sid_to_context, proc_name) 161 | }; 162 | 163 | let mut context: *mut c_char = ptr::null_mut(); 164 | let r = unsafe { proc(security_id.as_mut_ptr(), &mut context) }; 165 | SecurityContext::from_result(proc_name, r, context, is_raw) 166 | } 167 | 168 | /// Return a security identifier for the given security context. 169 | /// 170 | /// See: `avc_context_to_sid()`. 171 | #[doc(alias = "avc_context_to_sid")] 172 | pub fn security_id_from_security_context<'context>( 173 | &'context self, 174 | context: SecurityContext, 175 | ) -> Result> { 176 | let is_raw = context.is_raw_format(); 177 | let (proc, proc_name): (unsafe extern "C" fn(_, _) -> _, _) = if is_raw { 178 | let proc_name = "avc_context_to_sid_raw()"; 179 | (selinux_sys::avc_context_to_sid_raw, proc_name) 180 | } else { 181 | let proc_name = "avc_context_to_sid()"; 182 | (selinux_sys::avc_context_to_sid, proc_name) 183 | }; 184 | 185 | let mut security_id: *mut selinux_sys::security_id = ptr::null_mut(); 186 | if unsafe { proc(context.as_ptr(), &mut security_id) } == -1_i32 { 187 | Err(Error::last_io_error(proc_name)) 188 | } else { 189 | Ok(SecurityID { 190 | security_id, 191 | is_raw, 192 | _phantom_data: PhantomData, 193 | }) 194 | } 195 | } 196 | } 197 | 198 | impl Drop for AccessVectorCache { 199 | fn drop(&mut self) { 200 | unsafe { selinux_sys::avc_destroy() }; 201 | } 202 | } 203 | 204 | /// SELinux security identifier. 205 | #[derive(Debug)] 206 | pub struct SecurityID<'id> { 207 | security_id: *mut selinux_sys::security_id, 208 | is_raw: bool, 209 | _phantom_data: PhantomData<&'id selinux_sys::security_id>, 210 | } 211 | 212 | impl<'id> SecurityID<'id> { 213 | /// Return `true` if the security identifier is unspecified. 214 | #[must_use] 215 | pub fn is_unspecified(&self) -> bool { 216 | self.security_id.is_null() 217 | } 218 | 219 | /// Return `false` if security context translation must be performed. 220 | #[must_use] 221 | pub fn is_raw_format(&self) -> bool { 222 | self.is_raw 223 | } 224 | 225 | /// Return the managed raw pointer to [`selinux_sys::security_id`]. 226 | #[must_use] 227 | pub fn as_ptr(&self) -> *const selinux_sys::security_id { 228 | self.security_id.cast() 229 | } 230 | 231 | /// Return the managed raw pointer to [`selinux_sys::security_id`]. 232 | #[must_use] 233 | pub fn as_mut_ptr(&mut self) -> *mut selinux_sys::security_id { 234 | self.security_id 235 | } 236 | } 237 | 238 | impl<'id> Default for SecurityID<'id> { 239 | /// Return an unspecified security identifier. 240 | fn default() -> Self { 241 | Self { 242 | security_id: ptr::null_mut(), 243 | is_raw: false, 244 | _phantom_data: PhantomData, 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /selinux/src/path/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests; 3 | 4 | use std::path::Path; 5 | 6 | use crate::errors::Result; 7 | use crate::utils::get_static_path; 8 | 9 | /// Return the top-level SELinux configuration directory. 10 | /// 11 | /// See: `selinux_path()`. 12 | #[doc(alias = "selinux_path")] 13 | pub fn selinux() -> Result<&'static Path> { 14 | get_static_path(selinux_sys::selinux_path, "selinux_path()") 15 | } 16 | 17 | /// Return the context file mapping roles to default types. 18 | /// 19 | /// See: `selinux_default_type_path()`. 20 | #[doc(alias = "selinux_default_type_path")] 21 | pub fn default_type_path() -> Result<&'static Path> { 22 | let proc_name = "selinux_default_type_path()"; 23 | get_static_path(selinux_sys::selinux_default_type_path, proc_name) 24 | } 25 | 26 | /// Return the fail-safe context for emergency logins. 27 | /// 28 | /// See: `selinux_failsafe_context_path()`. 29 | #[doc(alias = "selinux_failsafe_context_path")] 30 | pub fn fail_safe_context() -> Result<&'static Path> { 31 | let proc_name = "selinux_failsafe_context_path()"; 32 | get_static_path(selinux_sys::selinux_failsafe_context_path, proc_name) 33 | } 34 | 35 | /// Return the file system context for removable media. 36 | /// 37 | /// See: `selinux_removable_context_path()`. 38 | #[doc(alias = "selinux_removable_context_path")] 39 | pub fn removable_context() -> Result<&'static Path> { 40 | let proc_name = "selinux_removable_context_path()"; 41 | get_static_path(selinux_sys::selinux_removable_context_path, proc_name) 42 | } 43 | 44 | /// Return the system-wide default contexts for user sessions. 45 | /// 46 | /// See: `selinux_default_context_path()`. 47 | #[doc(alias = "selinux_default_context_path")] 48 | pub fn default_context() -> Result<&'static Path> { 49 | let proc_name = "selinux_default_context_path()"; 50 | get_static_path(selinux_sys::selinux_default_context_path, proc_name) 51 | } 52 | 53 | /// Return the directory containing per-user default contexts. 54 | /// 55 | /// See: `selinux_user_contexts_path()`. 56 | #[doc(alias = "selinux_user_contexts_path")] 57 | pub fn user_contexts() -> Result<&'static Path> { 58 | let proc_name = "selinux_user_contexts_path()"; 59 | get_static_path(selinux_sys::selinux_user_contexts_path, proc_name) 60 | } 61 | 62 | /// Return the default system file contexts configuration. 63 | /// 64 | /// See: `selinux_file_context_path()`. 65 | #[doc(alias = "selinux_file_context_path")] 66 | pub fn file_context() -> Result<&'static Path> { 67 | let proc_name = "selinux_file_context_path()"; 68 | get_static_path(selinux_sys::selinux_file_context_path, proc_name) 69 | } 70 | 71 | /// Return the home directory file contexts configuration. 72 | /// 73 | /// See: `selinux_file_context_homedir_path()`. 74 | #[doc(alias = "selinux_file_context_homedir_path")] 75 | pub fn file_context_homedir() -> Result<&'static Path> { 76 | let proc_name = "selinux_file_context_homedir_path()"; 77 | get_static_path(selinux_sys::selinux_file_context_homedir_path, proc_name) 78 | } 79 | 80 | /// Return the local customization file contexts configuration. 81 | /// 82 | /// See: `selinux_file_context_local_path()`. 83 | #[doc(alias = "selinux_file_context_local_path")] 84 | pub fn file_context_local() -> Result<&'static Path> { 85 | let proc_name = "selinux_file_context_local_path()"; 86 | get_static_path(selinux_sys::selinux_file_context_local_path, proc_name) 87 | } 88 | 89 | /// See: `selinux_file_context_subs_path()`. 90 | #[doc(alias = "selinux_file_context_subs_path")] 91 | pub fn file_context_subs() -> Result<&'static Path> { 92 | let proc_name = "selinux_file_context_subs_path()"; 93 | get_static_path(selinux_sys::selinux_file_context_subs_path, proc_name) 94 | } 95 | 96 | /// See: `selinux_file_context_subs_dist_path()`. 97 | #[doc(alias = "selinux_file_context_subs_dist_path")] 98 | pub fn file_context_subs_dist() -> Result<&'static Path> { 99 | let proc_name = "selinux_file_context_subs_dist_path()"; 100 | get_static_path(selinux_sys::selinux_file_context_subs_dist_path, proc_name) 101 | } 102 | 103 | /// See: `selinux_homedir_context_path()`. 104 | #[doc(alias = "selinux_homedir_context_path")] 105 | pub fn home_dir_context() -> Result<&'static Path> { 106 | let proc_name = "selinux_homedir_context_path()"; 107 | get_static_path(selinux_sys::selinux_homedir_context_path, proc_name) 108 | } 109 | 110 | /// Return the file contexts for media device nodes. 111 | /// 112 | /// See: `selinux_media_context_path()`. 113 | #[doc(alias = "selinux_media_context_path")] 114 | pub fn media_context() -> Result<&'static Path> { 115 | let proc_name = "selinux_media_context_path()"; 116 | get_static_path(selinux_sys::selinux_media_context_path, proc_name) 117 | } 118 | 119 | /// See: `selinux_virtual_domain_context_path()`. 120 | #[doc(alias = "selinux_virtual_domain_context_path")] 121 | pub fn virtual_domain_context() -> Result<&'static Path> { 122 | let proc_name = "selinux_virtual_domain_context_path()"; 123 | get_static_path(selinux_sys::selinux_virtual_domain_context_path, proc_name) 124 | } 125 | 126 | /// See: `selinux_virtual_image_context_path()`. 127 | #[doc(alias = "selinux_virtual_image_context_path")] 128 | pub fn virtual_image_context() -> Result<&'static Path> { 129 | let proc_name = "selinux_virtual_image_context_path()"; 130 | get_static_path(selinux_sys::selinux_virtual_image_context_path, proc_name) 131 | } 132 | 133 | /// See: `selinux_lxc_contexts_path()`. 134 | #[doc(alias = "selinux_lxc_contexts_path")] 135 | pub fn lxc_contexts() -> Result<&'static Path> { 136 | let proc_name = "selinux_lxc_contexts_path()"; 137 | get_static_path(selinux_sys::selinux_lxc_contexts_path, proc_name) 138 | } 139 | 140 | /// Return the file containing configuration for XSELinux extension. 141 | /// 142 | /// See: `selinux_x_context_path()`. 143 | #[doc(alias = "selinux_x_context_path")] 144 | pub fn x_context() -> Result<&'static Path> { 145 | let proc_name = "selinux_x_context_path()"; 146 | get_static_path(selinux_sys::selinux_x_context_path, proc_name) 147 | } 148 | 149 | /// Return the file containing configuration for SE-PostgreSQL. 150 | /// 151 | /// See: `selinux_sepgsql_context_path()`. 152 | #[doc(alias = "selinux_sepgsql_context_path")] 153 | pub fn sepgsql_context() -> Result<&'static Path> { 154 | let proc_name = "selinux_sepgsql_context_path()"; 155 | get_static_path(selinux_sys::selinux_sepgsql_context_path, proc_name) 156 | } 157 | 158 | /// See: `selinux_openrc_contexts_path()`. 159 | #[doc(alias = "selinux_openrc_contexts_path")] 160 | pub fn openrc_contexts() -> Result<&'static Path> { 161 | let proc_name = "selinux_openrc_contexts_path()"; 162 | get_static_path(selinux_sys::selinux_openrc_contexts_path, proc_name) 163 | } 164 | 165 | /// See: `selinux_openssh_contexts_path()`. 166 | #[doc(alias = "selinux_openssh_contexts_path")] 167 | pub fn openssh_contexts() -> Result<&'static Path> { 168 | let proc_name = "selinux_openssh_contexts_path()"; 169 | get_static_path(selinux_sys::selinux_openssh_contexts_path, proc_name) 170 | } 171 | 172 | /// See: `selinux_snapperd_contexts_path()`. 173 | #[doc(alias = "selinux_snapperd_contexts_path")] 174 | pub fn snapperd_contexts() -> Result<&'static Path> { 175 | let proc_name = "selinux_snapperd_contexts_path()"; 176 | get_static_path(selinux_sys::selinux_snapperd_contexts_path, proc_name) 177 | } 178 | 179 | /// See: `selinux_systemd_contexts_path()`. 180 | #[doc(alias = "selinux_systemd_contexts_path")] 181 | pub fn systemd_contexts() -> Result<&'static Path> { 182 | let proc_name = "selinux_systemd_contexts_path()"; 183 | get_static_path(selinux_sys::selinux_systemd_contexts_path, proc_name) 184 | } 185 | 186 | /// Return the directory containing all of the context configuration files. 187 | /// 188 | /// See: `selinux_contexts_path()`. 189 | #[doc(alias = "selinux_contexts_path")] 190 | pub fn contexts() -> Result<&'static Path> { 191 | let proc_name = "selinux_contexts_path()"; 192 | get_static_path(selinux_sys::selinux_contexts_path, proc_name) 193 | } 194 | 195 | /// Return the defines tty types for newrole securettys. 196 | /// 197 | /// See: `selinux_securetty_types_path()`. 198 | #[doc(alias = "selinux_securetty_types_path")] 199 | pub fn securetty_types() -> Result<&'static Path> { 200 | let proc_name = "selinux_securetty_types_path()"; 201 | get_static_path(selinux_sys::selinux_securetty_types_path, proc_name) 202 | } 203 | 204 | /// See: `selinux_booleans_subs_path()`. 205 | #[doc(alias = "selinux_booleans_subs_path")] 206 | pub fn booleans_subs() -> Result<&'static Path> { 207 | let proc_name = "selinux_booleans_subs_path()"; 208 | get_static_path(selinux_sys::selinux_booleans_subs_path, proc_name) 209 | } 210 | 211 | /// See: `selinux_customizable_types_path()`. 212 | #[doc(alias = "selinux_customizable_types_path")] 213 | pub fn customizable_types() -> Result<&'static Path> { 214 | let proc_name = "selinux_customizable_types_path()"; 215 | get_static_path(selinux_sys::selinux_customizable_types_path, proc_name) 216 | } 217 | 218 | /// Return the file containing mapping between Linux users and SELinux users. 219 | /// 220 | /// See: `selinux_usersconf_path()`. 221 | #[doc(alias = "selinux_usersconf_path")] 222 | pub fn users_conf() -> Result<&'static Path> { 223 | let proc_name = "selinux_usersconf_path()"; 224 | get_static_path(selinux_sys::selinux_usersconf_path, proc_name) 225 | } 226 | 227 | /// See: `selinux_translations_path()`. 228 | #[doc(alias = "selinux_translations_path")] 229 | pub fn translations() -> Result<&'static Path> { 230 | let proc_name = "selinux_translations_path()"; 231 | get_static_path(selinux_sys::selinux_translations_path, proc_name) 232 | } 233 | 234 | /// See: `selinux_colors_path()`. 235 | #[doc(alias = "selinux_colors_path")] 236 | pub fn colors() -> Result<&'static Path> { 237 | get_static_path(selinux_sys::selinux_colors_path, "selinux_colors_path()") 238 | } 239 | 240 | /// Return the default netfilter context. 241 | /// 242 | /// See: `selinux_netfilter_context_path()`. 243 | #[doc(alias = "selinux_netfilter_context_path")] 244 | pub fn netfilter_context() -> Result<&'static Path> { 245 | let proc_name = "selinux_netfilter_context_path()"; 246 | get_static_path(selinux_sys::selinux_netfilter_context_path, proc_name) 247 | } 248 | -------------------------------------------------------------------------------- /selinux/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests; 3 | 4 | use std::ffi::{CStr, CString, OsStr}; 5 | use std::marker::PhantomData; 6 | use std::os::raw::{c_char, c_int, c_uint, c_ulong, c_void}; 7 | use std::path::{Path, PathBuf}; 8 | use std::{io, mem, ptr}; 9 | 10 | use once_cell::sync::OnceCell; 11 | 12 | use crate::errors::{Error, Result}; 13 | 14 | pub(crate) fn str_to_c_string(s: &str) -> Result { 15 | CString::new(s).map_err(|_r| Error::IO1Name { 16 | operation: "CString::new", 17 | name: s.into(), 18 | source: io::ErrorKind::InvalidInput.into(), 19 | }) 20 | } 21 | 22 | #[cfg(unix)] 23 | pub(crate) fn os_str_to_c_string(s: &OsStr) -> Result { 24 | use std::os::unix::ffi::OsStrExt; 25 | 26 | CString::new(s.as_bytes()).map_err(|_r| Error::PathIsInvalid(PathBuf::from(s))) 27 | } 28 | 29 | pub(crate) fn c_str_ptr_to_str<'string>(s: *const c_char) -> Result<&'string str> { 30 | if s.is_null() { 31 | let err = io::ErrorKind::InvalidInput.into(); 32 | Err(Error::from_io("utils::c_str_ptr_to_string()", err)) 33 | } else { 34 | unsafe { CStr::from_ptr(s) }.to_str().map_err(Into::into) 35 | } 36 | } 37 | 38 | #[cfg(unix)] 39 | pub(crate) fn c_str_ptr_to_path<'string>(path_ptr: *const c_char) -> &'string Path { 40 | use std::os::unix::ffi::OsStrExt; 41 | 42 | let c_path = unsafe { CStr::from_ptr(path_ptr) }; 43 | Path::new(OsStr::from_bytes(c_path.to_bytes())) 44 | } 45 | 46 | pub(crate) fn c_str_to_non_null_ptr(s: &CStr) -> ptr::NonNull { 47 | unsafe { ptr::NonNull::new_unchecked(s.as_ptr() as *mut c_char) } 48 | } 49 | 50 | pub(crate) fn get_static_path( 51 | proc: unsafe extern "C" fn() -> *const c_char, 52 | proc_name: &'static str, 53 | ) -> Result<&'static Path> { 54 | let path_ptr = unsafe { proc() }; 55 | if path_ptr.is_null() { 56 | Err(Error::from_io(proc_name, io::ErrorKind::InvalidData.into())) 57 | } else { 58 | Ok(c_str_ptr_to_path(path_ptr)) 59 | } 60 | } 61 | 62 | pub(crate) fn ret_val_to_result(proc_name: &'static str, result: c_int) -> Result<()> { 63 | if result == -1_i32 { 64 | Err(Error::last_io_error(proc_name)) 65 | } else { 66 | Ok(()) 67 | } 68 | } 69 | 70 | pub(crate) fn ret_val_to_result_with_path( 71 | proc_name: &'static str, 72 | result: c_int, 73 | path: &Path, 74 | ) -> Result<()> { 75 | if result == -1_i32 { 76 | let err = io::Error::last_os_error(); 77 | Err(Error::from_io_path(proc_name, path, err)) 78 | } else { 79 | Ok(()) 80 | } 81 | } 82 | 83 | /// An owned block of memory, allocated with [`libc::malloc`]. 84 | /// 85 | /// Dropping this instance calls [`libc::free`] on the managed pointer. 86 | #[derive(Debug)] 87 | pub struct CAllocatedBlock { 88 | pub(crate) pointer: ptr::NonNull, 89 | _phantom_data: PhantomData, 90 | } 91 | 92 | /// # Safety 93 | /// 94 | /// - [`libc::malloc()`]-allocated memory blocks are accessible from any thread. 95 | /// - [`libc::free()`] supports deallocating memory blocks allocated in 96 | /// a different thread. 97 | unsafe impl Send for CAllocatedBlock {} 98 | 99 | impl CAllocatedBlock { 100 | pub(crate) fn new(pointer: *mut T) -> Option { 101 | ptr::NonNull::new(pointer).map(|pointer| Self { 102 | pointer, 103 | _phantom_data: PhantomData, 104 | }) 105 | } 106 | 107 | /// Return the managed raw pointer. 108 | #[must_use] 109 | pub fn as_ptr(&self) -> *const T { 110 | self.pointer.as_ptr() 111 | } 112 | 113 | /// Return the managed raw pointer. 114 | #[must_use] 115 | pub fn as_mut_ptr(&mut self) -> *mut T { 116 | self.pointer.as_ptr() 117 | } 118 | } 119 | 120 | impl CAllocatedBlock { 121 | /// Return the managed null-terminated C string. 122 | #[must_use] 123 | pub fn as_c_str(&self) -> &CStr { 124 | unsafe { CStr::from_ptr(self.pointer.as_ptr()) } 125 | } 126 | } 127 | 128 | impl Drop for CAllocatedBlock { 129 | fn drop(&mut self) { 130 | let pointer = self.pointer.as_ptr(); 131 | self.pointer = ptr::NonNull::dangling(); 132 | unsafe { libc::free(pointer.cast()) }; 133 | } 134 | } 135 | 136 | /// Holds addresses of `libselinux`'s optionally-implemented functions. 137 | #[derive(Debug)] 138 | pub(crate) struct OptionalNativeFunctions { 139 | /// Since version 2.9 140 | pub(crate) security_reject_unknown: unsafe extern "C" fn() -> c_int, 141 | 142 | /// Since version 3.0 143 | pub(crate) selabel_get_digests_all_partial_matches: unsafe extern "C" fn( 144 | rec: *mut selinux_sys::selabel_handle, 145 | key: *const c_char, 146 | calculated_digest: *mut *mut u8, 147 | xattr_digest: *mut *mut u8, 148 | digest_len: *mut usize, 149 | ) -> bool, 150 | 151 | /// Since version 3.0 152 | pub(crate) selabel_hash_all_partial_matches: unsafe extern "C" fn( 153 | rec: *mut selinux_sys::selabel_handle, 154 | key: *const c_char, 155 | digest: *mut u8, 156 | ) -> bool, 157 | 158 | /// Since version 3.0 159 | pub(crate) security_validatetrans: unsafe extern "C" fn( 160 | scon: *const c_char, 161 | tcon: *const c_char, 162 | tclass: selinux_sys::security_class_t, 163 | newcon: *const c_char, 164 | ) -> c_int, 165 | 166 | /// Since version 3.0 167 | pub(crate) security_validatetrans_raw: unsafe extern "C" fn( 168 | scon: *const c_char, 169 | tcon: *const c_char, 170 | tclass: selinux_sys::security_class_t, 171 | newcon: *const c_char, 172 | ) -> c_int, 173 | 174 | /// Since version 3.1 175 | pub(crate) selinux_flush_class_cache: unsafe extern "C" fn(), 176 | 177 | /// Since version 3.4 178 | pub(crate) selinux_restorecon_parallel: unsafe extern "C" fn( 179 | pathname: *const c_char, 180 | restorecon_flags: c_uint, 181 | nthreads: usize, 182 | ) -> c_int, 183 | 184 | /// Since version 3.4 185 | pub(crate) selinux_restorecon_get_skipped_errors: unsafe extern "C" fn() -> c_ulong, 186 | 187 | /// Since version 3.5 188 | pub(crate) getpidprevcon: 189 | unsafe extern "C" fn(pid: selinux_sys::pid_t, con: *mut *mut c_char) -> c_int, 190 | 191 | /// Since version 3.5 192 | pub(crate) getpidprevcon_raw: 193 | unsafe extern "C" fn(pid: selinux_sys::pid_t, con: *mut *mut c_char) -> c_int, 194 | } 195 | 196 | /// Addresses of optionally-implemented functions by libselinux. 197 | pub(crate) static OPT_NATIVE_FN: OnceCell = OnceCell::new(); 198 | 199 | impl Default for OptionalNativeFunctions { 200 | fn default() -> Self { 201 | Self { 202 | security_reject_unknown: Self::not_impl_security_reject_unknown, 203 | selabel_get_digests_all_partial_matches: 204 | Self::not_impl_selabel_get_digests_all_partial_matches, 205 | selabel_hash_all_partial_matches: Self::not_impl_selabel_hash_all_partial_matches, 206 | security_validatetrans: Self::not_impl_security_validatetrans, 207 | security_validatetrans_raw: Self::not_impl_security_validatetrans, 208 | selinux_flush_class_cache: Self::not_impl_selinux_flush_class_cache, 209 | selinux_restorecon_parallel: Self::not_impl_selinux_restorecon_parallel, 210 | selinux_restorecon_get_skipped_errors: 211 | Self::not_impl_selinux_restorecon_get_skipped_errors, 212 | getpidprevcon: Self::not_impl_getpidprevcon, 213 | getpidprevcon_raw: Self::not_impl_getpidprevcon, 214 | } 215 | } 216 | } 217 | 218 | impl OptionalNativeFunctions { 219 | pub(crate) fn get() -> &'static Self { 220 | OPT_NATIVE_FN.get_or_init(Self::initialize) 221 | } 222 | 223 | fn initialize() -> Self { 224 | let mut r = Self::default(); 225 | let lib_handle = Self::get_libselinux_handle(); 226 | if !lib_handle.is_null() { 227 | r.load_functions_addresses(lib_handle); 228 | } 229 | Error::clear_errno(); 230 | r 231 | } 232 | 233 | fn get_libselinux_handle() -> *mut c_void { 234 | // Ensure libselinux is loaded. 235 | unsafe { selinux_sys::is_selinux_enabled() }; 236 | 237 | // Get a handle to the already-loaded libselinux. 238 | let flags = libc::RTLD_NOW | libc::RTLD_GLOBAL | libc::RTLD_NOLOAD | libc::RTLD_NODELETE; 239 | for &lib_name in &[ 240 | "libselinux.so.1\0", 241 | "libselinux.so\0", 242 | "libselinux\0", 243 | "selinux\0", 244 | ] { 245 | let lib_handle = unsafe { libc::dlopen(lib_name.as_ptr().cast(), flags) }; 246 | if !lib_handle.is_null() { 247 | return lib_handle; 248 | } 249 | } 250 | ptr::null_mut() 251 | } 252 | 253 | fn load_functions_addresses(&mut self, lib_handle: *mut c_void) { 254 | let f = unsafe { libc::dlsym(lib_handle, "security_reject_unknown\0".as_ptr().cast()) }; 255 | if !f.is_null() { 256 | self.security_reject_unknown = unsafe { mem::transmute(f) }; 257 | } 258 | 259 | let c_name = "selabel_get_digests_all_partial_matches\0"; 260 | let f = unsafe { libc::dlsym(lib_handle, c_name.as_ptr().cast()) }; 261 | if !f.is_null() { 262 | self.selabel_get_digests_all_partial_matches = unsafe { mem::transmute(f) }; 263 | } 264 | 265 | let c_name = "selabel_hash_all_partial_matches\0"; 266 | let f = unsafe { libc::dlsym(lib_handle, c_name.as_ptr().cast()) }; 267 | if !f.is_null() { 268 | self.selabel_hash_all_partial_matches = unsafe { mem::transmute(f) }; 269 | } 270 | 271 | let f = unsafe { libc::dlsym(lib_handle, "security_validatetrans\0".as_ptr().cast()) }; 272 | if !f.is_null() { 273 | self.security_validatetrans = unsafe { mem::transmute(f) }; 274 | } 275 | 276 | let f = unsafe { libc::dlsym(lib_handle, "security_validatetrans_raw\0".as_ptr().cast()) }; 277 | if !f.is_null() { 278 | self.security_validatetrans_raw = unsafe { mem::transmute(f) }; 279 | } 280 | 281 | let f = unsafe { libc::dlsym(lib_handle, "selinux_flush_class_cache\0".as_ptr().cast()) }; 282 | if !f.is_null() { 283 | self.selinux_flush_class_cache = unsafe { mem::transmute(f) }; 284 | } 285 | 286 | let f = unsafe { libc::dlsym(lib_handle, "selinux_restorecon_parallel\0".as_ptr().cast()) }; 287 | if !f.is_null() { 288 | self.selinux_restorecon_parallel = unsafe { mem::transmute(f) }; 289 | } 290 | 291 | let c_name = "selinux_restorecon_get_skipped_errors\0"; 292 | let f = unsafe { libc::dlsym(lib_handle, c_name.as_ptr().cast()) }; 293 | if !f.is_null() { 294 | self.selinux_restorecon_get_skipped_errors = unsafe { mem::transmute(f) }; 295 | } 296 | 297 | let f = unsafe { libc::dlsym(lib_handle, "getpidprevcon\0".as_ptr().cast()) }; 298 | if !f.is_null() { 299 | self.getpidprevcon = unsafe { mem::transmute(f) }; 300 | } 301 | 302 | let f = unsafe { libc::dlsym(lib_handle, "getpidprevcon_raw\0".as_ptr().cast()) }; 303 | if !f.is_null() { 304 | self.getpidprevcon_raw = unsafe { mem::transmute(f) }; 305 | } 306 | } 307 | 308 | unsafe extern "C" fn not_impl_security_reject_unknown() -> c_int { 309 | Error::set_errno(libc::ENOSYS); 310 | -1_i32 311 | } 312 | 313 | unsafe extern "C" fn not_impl_selabel_get_digests_all_partial_matches( 314 | _rec: *mut selinux_sys::selabel_handle, 315 | _key: *const c_char, 316 | _calculated_digest: *mut *mut u8, 317 | _xattr_digest: *mut *mut u8, 318 | _digest_len: *mut usize, 319 | ) -> bool { 320 | Error::set_errno(libc::ENOSYS); 321 | false 322 | } 323 | 324 | unsafe extern "C" fn not_impl_selabel_hash_all_partial_matches( 325 | _rec: *mut selinux_sys::selabel_handle, 326 | _key: *const c_char, 327 | _digest: *mut u8, 328 | ) -> bool { 329 | Error::set_errno(libc::ENOSYS); 330 | false 331 | } 332 | 333 | unsafe extern "C" fn not_impl_security_validatetrans( 334 | _scon: *const c_char, 335 | _tcon: *const c_char, 336 | _tclass: selinux_sys::security_class_t, 337 | _newcon: *const c_char, 338 | ) -> c_int { 339 | Error::set_errno(libc::ENOSYS); 340 | -1_i32 341 | } 342 | 343 | unsafe extern "C" fn not_impl_selinux_flush_class_cache() { 344 | Error::set_errno(libc::ENOSYS); 345 | } 346 | 347 | unsafe extern "C" fn not_impl_selinux_restorecon_parallel( 348 | _pathname: *const c_char, 349 | _restorecon_flags: c_uint, 350 | _nthreads: usize, 351 | ) -> c_int { 352 | Error::set_errno(libc::ENOSYS); 353 | -1_i32 354 | } 355 | 356 | unsafe extern "C" fn not_impl_selinux_restorecon_get_skipped_errors() -> c_ulong { 357 | 0 358 | } 359 | 360 | unsafe extern "C" fn not_impl_getpidprevcon( 361 | _pid: selinux_sys::pid_t, 362 | _con: *mut *mut c_char, 363 | ) -> c_int { 364 | Error::set_errno(libc::ENOSYS); 365 | -1_i32 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /selinux/src/label/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests; 3 | 4 | use std::ffi::{CStr, CString}; 5 | use std::hash::Hash; 6 | use std::marker::PhantomData; 7 | use std::os::raw::{c_char, c_int, c_void}; 8 | use std::path::Path; 9 | use std::{cmp, io, iter, ptr, slice}; 10 | 11 | use crate::errors::{Error, Result}; 12 | use crate::utils::*; 13 | use crate::{FileAccessMode, SecurityContext}; 14 | 15 | /// Security contexts back-ends. 16 | pub mod back_end; 17 | 18 | use crate::label::back_end::BackEnd; 19 | 20 | /// Labeling handle used for look up operations. 21 | #[derive(Debug)] 22 | pub struct Labeler { 23 | pointer: ptr::NonNull, 24 | _phantom_data1: PhantomData, 25 | _phantom_data2: PhantomData, 26 | is_raw: bool, 27 | } 28 | 29 | impl Labeler { 30 | /// Return `false` if security context translation must be performed. 31 | #[must_use] 32 | pub fn is_raw_format(&self) -> bool { 33 | self.is_raw 34 | } 35 | 36 | /// Return the managed raw pointer to [`selinux_sys::selabel_handle`]. 37 | #[must_use] 38 | pub fn as_ptr(&self) -> *const selinux_sys::selabel_handle { 39 | self.pointer.as_ptr() 40 | } 41 | 42 | /// Return the managed raw pointer to [`selinux_sys::selabel_handle`]. 43 | #[must_use] 44 | pub fn as_mut_ptr(&mut self) -> *mut selinux_sys::selabel_handle { 45 | self.pointer.as_ptr() 46 | } 47 | 48 | /// Initialize a labeling handle to be used for lookup operations. 49 | /// 50 | /// See: `selabel_open()`. 51 | #[doc(alias = "selabel_open")] 52 | pub fn new(options: &[(c_int, *const c_void)], raw_format: bool) -> Result { 53 | let options: Vec = options 54 | .iter() 55 | .map(|&(type_, value)| selinux_sys::selinux_opt { 56 | type_, 57 | value: value.cast(), 58 | }) 59 | .collect(); 60 | 61 | let count = options.len().try_into()?; 62 | let options_ptr = if count == 0 { 63 | ptr::null() 64 | } else { 65 | options.as_ptr() 66 | }; 67 | 68 | let pointer = unsafe { selinux_sys::selabel_open(T::BACK_END, options_ptr, count) }; 69 | ptr::NonNull::new(pointer) 70 | .map(|pointer| Self { 71 | pointer, 72 | _phantom_data1: PhantomData, 73 | _phantom_data2: PhantomData, 74 | is_raw: raw_format, 75 | }) 76 | .ok_or_else(|| Error::last_io_error("selabel_open()")) 77 | } 78 | 79 | /// Obtain SELinux security context from a string label. 80 | /// 81 | /// See: `selabel_lookup()`. 82 | #[doc(alias = "selabel_lookup")] 83 | pub fn look_up(&self, key: &CStr, key_type: c_int) -> Result { 84 | let (proc, proc_name): (unsafe extern "C" fn(_, _, _, _) -> _, _) = if self.is_raw { 85 | (selinux_sys::selabel_lookup_raw, "selabel_lookup_raw()") 86 | } else { 87 | (selinux_sys::selabel_lookup, "selabel_lookup()") 88 | }; 89 | 90 | let handle = self.pointer.as_ptr(); 91 | let mut context: *mut c_char = ptr::null_mut(); 92 | let r = unsafe { proc(handle, &mut context, key.as_ptr(), key_type) }; 93 | SecurityContext::from_result(proc_name, r, context, self.is_raw) 94 | } 95 | 96 | /// Return digest of spec files and list of files used. 97 | /// 98 | /// See: `selabel_digest()`. 99 | #[doc(alias = "selabel_digest")] 100 | pub fn digest(&'_ self) -> Result> { 101 | let mut digest_ptr: *mut u8 = ptr::null_mut(); 102 | let mut digest_size = 0; 103 | let mut spec_files_ptr: *mut *mut c_char = ptr::null_mut(); 104 | let mut num_spec_files = 0; 105 | let r: c_int = unsafe { 106 | selinux_sys::selabel_digest( 107 | self.pointer.as_ptr(), 108 | &mut digest_ptr, 109 | &mut digest_size, 110 | &mut spec_files_ptr, 111 | &mut num_spec_files, 112 | ) 113 | }; 114 | 115 | if r == -1_i32 { 116 | Err(Error::last_io_error("selabel_digest()")) 117 | } else { 118 | Ok(Digest::new( 119 | digest_ptr, 120 | digest_size, 121 | spec_files_ptr.cast(), 122 | num_spec_files, 123 | )) 124 | } 125 | } 126 | 127 | /// Print SELinux labeling statistics. 128 | /// 129 | /// See: `selabel_stats()`. 130 | #[doc(alias = "selabel_stats")] 131 | pub fn log_statistics(&self) { 132 | unsafe { selinux_sys::selabel_stats(self.pointer.as_ptr()) } 133 | } 134 | } 135 | 136 | impl Drop for Labeler { 137 | fn drop(&mut self) { 138 | let pointer = self.pointer.as_ptr(); 139 | self.pointer = ptr::NonNull::dangling(); 140 | unsafe { selinux_sys::selabel_close(pointer) }; 141 | } 142 | } 143 | 144 | impl PartialOrd> for Labeler { 145 | /// Compare this instance to another one. 146 | /// 147 | /// See: `selabel_cmp()`. 148 | #[doc(alias = "selabel_cmp")] 149 | fn partial_cmp(&self, other: &Labeler) -> Option { 150 | let r = unsafe { selinux_sys::selabel_cmp(self.pointer.as_ptr(), other.pointer.as_ptr()) }; 151 | match r { 152 | selinux_sys::selabel_cmp_result::SELABEL_SUBSET => Some(cmp::Ordering::Less), 153 | selinux_sys::selabel_cmp_result::SELABEL_EQUAL => Some(cmp::Ordering::Equal), 154 | selinux_sys::selabel_cmp_result::SELABEL_SUPERSET => Some(cmp::Ordering::Greater), 155 | _ => None, 156 | } 157 | } 158 | } 159 | 160 | impl PartialEq for Labeler { 161 | fn eq(&self, other: &Self) -> bool { 162 | self.partial_cmp(other) == Some(cmp::Ordering::Equal) 163 | } 164 | } 165 | 166 | impl Labeler { 167 | /// Return [`Labeler`] with default parameters for `selinux_restorecon()`. 168 | /// 169 | /// See: `selinux_restorecon_default_handle()`. 170 | #[doc(alias = "selinux_restorecon_default_handle")] 171 | pub fn restorecon_default(raw_format: bool) -> Result { 172 | let pointer = unsafe { selinux_sys::selinux_restorecon_default_handle() }; 173 | ptr::NonNull::new(pointer) 174 | .map(|pointer| Self { 175 | pointer, 176 | _phantom_data1: PhantomData, 177 | _phantom_data2: PhantomData, 178 | is_raw: raw_format, 179 | }) 180 | .ok_or_else(|| Error::last_io_error("selinux_restorecon_default_handle()")) 181 | } 182 | 183 | /// Obtain SELinux security context from a path. 184 | /// 185 | /// See: `selabel_lookup()`. 186 | #[doc(alias = "selabel_lookup")] 187 | pub fn look_up_by_path( 188 | &self, 189 | path: impl AsRef, 190 | mode: Option, 191 | ) -> Result { 192 | let (proc, proc_name): (unsafe extern "C" fn(_, _, _, _) -> _, _) = if self.is_raw { 193 | (selinux_sys::selabel_lookup_raw, "selabel_lookup_raw()") 194 | } else { 195 | (selinux_sys::selabel_lookup, "selabel_lookup()") 196 | }; 197 | 198 | let handle = self.pointer.as_ptr(); 199 | let mut context: *mut c_char = ptr::null_mut(); 200 | let c_path = os_str_to_c_string(path.as_ref().as_os_str())?; 201 | let mode = mode.map_or(0, FileAccessMode::mode) as c_int; 202 | let r = unsafe { proc(handle, &mut context, c_path.as_ptr(), mode) }; 203 | SecurityContext::from_result(proc_name, r, context, self.is_raw) 204 | } 205 | 206 | /// Obtain a best match SELinux security context. 207 | /// 208 | /// See: `selabel_lookup_best_match()`. 209 | #[doc(alias = "selabel_lookup_best_match")] 210 | pub fn look_up_best_match_by_path( 211 | &self, 212 | path: impl AsRef, 213 | alias_paths: &[impl AsRef], 214 | mode: Option, 215 | ) -> Result { 216 | let (proc, proc_name): (unsafe extern "C" fn(_, _, _, _, _) -> _, _) = if self.is_raw { 217 | let proc_name = "selabel_lookup_best_match_raw()"; 218 | (selinux_sys::selabel_lookup_best_match_raw, proc_name) 219 | } else { 220 | let proc_name = "selabel_lookup_best_match()"; 221 | (selinux_sys::selabel_lookup_best_match, proc_name) 222 | }; 223 | 224 | let aliases_storage: Vec; 225 | let mut aliases: Vec<*const c_char>; 226 | 227 | let aliases_ptr = if alias_paths.is_empty() { 228 | ptr::null_mut() 229 | } else { 230 | aliases_storage = alias_paths 231 | .iter() 232 | .map(AsRef::as_ref) 233 | .map(Path::as_os_str) 234 | .map(os_str_to_c_string) 235 | .collect::>>()?; 236 | 237 | aliases = aliases_storage 238 | .iter() 239 | .map(CString::as_c_str) 240 | .map(CStr::as_ptr) 241 | .chain(iter::once(ptr::null())) 242 | .collect(); 243 | 244 | aliases.as_mut_ptr() 245 | }; 246 | 247 | let mut context: *mut c_char = ptr::null_mut(); 248 | let c_path = os_str_to_c_string(path.as_ref().as_os_str())?; 249 | let r = unsafe { 250 | proc( 251 | self.pointer.as_ptr(), 252 | &mut context, 253 | c_path.as_ptr(), 254 | aliases_ptr, 255 | mode.map_or(0, FileAccessMode::mode) as c_int, 256 | ) 257 | }; 258 | SecurityContext::from_result(proc_name, r, context, self.is_raw) 259 | } 260 | 261 | /// Determine whether a direct or partial match is possible on a file path. 262 | /// 263 | /// See: `selabel_partial_match()`. 264 | #[doc(alias = "selabel_partial_match")] 265 | pub fn partial_match_by_path(&self, path: impl AsRef) -> Result { 266 | let c_path = os_str_to_c_string(path.as_ref().as_os_str())?; 267 | Ok(unsafe { selinux_sys::selabel_partial_match(self.pointer.as_ptr(), c_path.as_ptr()) }) 268 | } 269 | 270 | /// Retrieve the partial matches digest and the xattr digest that applies 271 | /// to the supplied path. 272 | /// 273 | /// This function requires `libselinux` version `3.0` or later. 274 | /// 275 | /// See: `selabel_get_digests_all_partial_matches()`. 276 | #[doc(alias = "selabel_get_digests_all_partial_matches")] 277 | pub fn get_digests_all_partial_matches_by_path( 278 | &self, 279 | path: impl AsRef, 280 | ) -> Result { 281 | let c_path = os_str_to_c_string(path.as_ref().as_os_str())?; 282 | let mut calculated_digest_ptr: *mut u8 = ptr::null_mut(); 283 | let mut xattr_digest_ptr: *mut u8 = ptr::null_mut(); 284 | let mut digest_size = 0; 285 | let r = unsafe { 286 | (OptionalNativeFunctions::get().selabel_get_digests_all_partial_matches)( 287 | self.pointer.as_ptr(), 288 | c_path.as_ptr(), 289 | &mut calculated_digest_ptr, 290 | &mut xattr_digest_ptr, 291 | &mut digest_size, 292 | ) 293 | }; 294 | 295 | let match_result = if r { 296 | PartialMatchesResult::Match 297 | } else { 298 | let err = io::Error::last_os_error(); 299 | match err.raw_os_error() { 300 | None | Some(0_i32) => PartialMatchesResult::NoMatchOrMissing, 301 | 302 | _ => { 303 | let proc_name = "selabel_get_digests_all_partial_matches()"; 304 | return Err(Error::from_io_path(proc_name, path.as_ref(), err)); 305 | } 306 | } 307 | }; 308 | 309 | Ok(PartialMatchesDigests { 310 | match_result, 311 | xattr_digest: CAllocatedBlock::new(xattr_digest_ptr), 312 | calculated_digest: CAllocatedBlock::new(calculated_digest_ptr), 313 | digest_size, 314 | }) 315 | } 316 | } 317 | 318 | /// Digest of spec files and list of files used. 319 | /// 320 | /// ⚠️ This instance does **NOT** own the `digest` or the `spec_files`. 321 | #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 322 | pub struct Digest<'list> { 323 | digest: &'list [u8], 324 | spec_files: Vec<&'list Path>, 325 | } 326 | 327 | impl<'list> Digest<'list> { 328 | fn new( 329 | digest: *const u8, 330 | digest_size: usize, 331 | spec_files: *const *const c_char, 332 | num_spec_files: usize, 333 | ) -> Self { 334 | let digest = if digest.is_null() || digest_size == 0 { 335 | &[] 336 | } else { 337 | unsafe { slice::from_raw_parts(digest, digest_size) } 338 | }; 339 | 340 | let spec_files = if spec_files.is_null() || num_spec_files == 0 { 341 | Vec::default() 342 | } else { 343 | unsafe { slice::from_raw_parts(spec_files, num_spec_files) } 344 | .iter() 345 | .take_while(|&&ptr| !ptr.is_null()) 346 | .map(|&ptr| c_str_ptr_to_path(ptr)) 347 | .collect() 348 | }; 349 | 350 | Self { digest, spec_files } 351 | } 352 | 353 | /// Digest of spec files. 354 | #[must_use] 355 | pub fn digest(&self) -> &[u8] { 356 | self.digest 357 | } 358 | 359 | /// List of files used. 360 | #[must_use] 361 | pub fn spec_files(&self) -> &[&'list Path] { 362 | &self.spec_files 363 | } 364 | } 365 | 366 | /// Result of a partial match. 367 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 368 | #[non_exhaustive] 369 | pub enum PartialMatchesResult { 370 | /// Digest matches. 371 | Match, 372 | /// Either digest does not match, or both digests are missing. 373 | NoMatchOrMissing, 374 | } 375 | 376 | /// Result of [`Labeler::get_digests_all_partial_matches_by_path`]. 377 | #[derive(Debug)] 378 | pub struct PartialMatchesDigests { 379 | match_result: PartialMatchesResult, 380 | xattr_digest: Option>, 381 | calculated_digest: Option>, 382 | digest_size: usize, 383 | } 384 | 385 | impl PartialMatchesDigests { 386 | /// Return match result. 387 | #[must_use] 388 | pub fn match_result(&self) -> PartialMatchesResult { 389 | self.match_result 390 | } 391 | 392 | /// Return xattr digest. 393 | #[must_use] 394 | pub fn xattr_digest(&self) -> Option<&[u8]> { 395 | self.xattr_digest 396 | .as_ref() 397 | .map(|block| unsafe { slice::from_raw_parts(block.pointer.as_ptr(), self.digest_size) }) 398 | } 399 | 400 | /// Return calculated digest. 401 | #[must_use] 402 | pub fn calculated_digest(&self) -> Option<&[u8]> { 403 | self.calculated_digest 404 | .as_ref() 405 | .map(|block| unsafe { slice::from_raw_parts(block.pointer.as_ptr(), self.digest_size) }) 406 | } 407 | 408 | /// Return digest length. 409 | #[must_use] 410 | pub fn len(&self) -> usize { 411 | self.digest_size 412 | } 413 | 414 | /// Return `true` if digest is empty. 415 | #[must_use] 416 | pub fn is_empty(&self) -> bool { 417 | self.digest_size == 0 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /selinux/src/context_restore/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests; 3 | 4 | use std::ffi::CStr; 5 | use std::marker::PhantomData; 6 | use std::os::raw::{c_int, c_uint}; 7 | use std::path::Path; 8 | use std::{io, iter, ptr}; 9 | 10 | use crate::errors::{Error, Result}; 11 | use crate::label::Labeler; 12 | use crate::utils::*; 13 | 14 | bitflags! { 15 | /// Flags controlling relabeling operations. 16 | pub struct RestoreFlags: c_uint { 17 | /// Force the checking of labels even if the stored SHA1 digest matches 18 | /// the specfile entries SHA1 digest. 19 | /// 20 | /// The specfile entries digest will be written to the `security.sehash` 21 | /// extended attribute once relabeling has been completed successfully 22 | /// provided the [`NO_CHANGE`] flag has not been set. 23 | /// 24 | /// [`NO_CHANGE`]: Self::NO_CHANGE 25 | const IGNORE_DIGEST = selinux_sys::SELINUX_RESTORECON_IGNORE_DIGEST as c_uint; 26 | 27 | /// Don't change any file labels (passive check) or update the digest in 28 | /// the `security.sehash` extended attribute. 29 | const NO_CHANGE = selinux_sys::SELINUX_RESTORECON_NOCHANGE as c_uint; 30 | 31 | /// If set, reset the files label to match the default spec file context. 32 | /// If not set only reset the files "type" component of the context 33 | /// to match the default spec file context. 34 | const SET_SPEC_FILE_CTX = 35 | selinux_sys::SELINUX_RESTORECON_SET_SPECFILE_CTX as c_uint; 36 | 37 | /// Change file and directory labels recursively (descend directories) 38 | /// and if successful write an SHA1 digest of the spec file entries 39 | /// to an extended attribute. 40 | const RECURSE = selinux_sys::SELINUX_RESTORECON_RECURSE as c_uint; 41 | 42 | /// Log file label changes. 43 | /// 44 | /// Note that if [`VERBOSE`] and [`PROGRESS`] flags are set, 45 | /// then [`PROGRESS`] will take precedence. 46 | /// 47 | /// [`VERBOSE`]: Self::VERBOSE 48 | /// [`PROGRESS`]: Self::PROGRESS 49 | const VERBOSE = selinux_sys::SELINUX_RESTORECON_VERBOSE as c_uint; 50 | 51 | /// Show progress by outputting the number of files in 1k blocks 52 | /// processed to stdout. 53 | /// 54 | /// If the [`MASS_RELABEL`] flag is also set then the approximate 55 | /// percentage complete will be shown. 56 | /// 57 | /// [`MASS_RELABEL`]: Self::MASS_RELABEL 58 | const PROGRESS = selinux_sys::SELINUX_RESTORECON_PROGRESS as c_uint; 59 | 60 | /// Convert passed-in path name to the canonical path name using 61 | /// `realpath()`. 62 | const REAL_PATH = selinux_sys::SELINUX_RESTORECON_REALPATH as c_uint; 63 | 64 | /// Prevent descending into directories that have a different device 65 | /// number than the path name entry from which the descent began. 66 | const XDEV = selinux_sys::SELINUX_RESTORECON_XDEV as c_uint; 67 | 68 | /// Attempt to add an association between an inode and a specification. 69 | /// If there is already an association for the inode and it conflicts 70 | /// with the specification, then use the last matching specification. 71 | const ADD_ASSOC = selinux_sys::SELINUX_RESTORECON_ADD_ASSOC as c_uint; 72 | 73 | /// Abort on errors during the file tree walk. 74 | const ABORT_ON_ERROR = selinux_sys::SELINUX_RESTORECON_ABORT_ON_ERROR as c_uint; 75 | 76 | /// Log any label changes to `syslog()`. 77 | const SYS_LOG_CHANGES = selinux_sys::SELINUX_RESTORECON_SYSLOG_CHANGES as c_uint; 78 | 79 | /// Log what spec file context matched each file. 80 | const LOG_MATCHES = selinux_sys::SELINUX_RESTORECON_LOG_MATCHES as c_uint; 81 | 82 | /// Ignore files that do not exist. 83 | const IGNORE_NO_ENTRY = selinux_sys::SELINUX_RESTORECON_IGNORE_NOENTRY as c_uint; 84 | 85 | /// Do not read `/proc/mounts` to obtain a list of non-seclabel mounts 86 | /// to be excluded from relabeling checks. 87 | /// 88 | /// Setting [`IGNORE_MOUNTS`] is useful where there is a non-seclabel fs 89 | /// mounted with a seclabel fs mounted on a directory below this. 90 | /// 91 | /// [`IGNORE_MOUNTS`]: Self::IGNORE_MOUNTS 92 | const IGNORE_MOUNTS = selinux_sys::SELINUX_RESTORECON_IGNORE_MOUNTS as c_uint; 93 | 94 | /// Generally set when relabeling the entire OS, that will then show 95 | /// the approximate percentage complete. 96 | /// 97 | /// The [`PROGRESS`] flag must also be set. 98 | /// 99 | /// [`PROGRESS`]: Self::PROGRESS 100 | const MASS_RELABEL = selinux_sys::SELINUX_RESTORECON_MASS_RELABEL as c_uint; 101 | 102 | // The rest of the constants were defined after version 2.8, so selinux_sys might not 103 | // export them. We therefore define them manually. 104 | 105 | /// Do not check or update any extended attribute security.sehash entries. 106 | /// 107 | /// This flag is supported only by `libselinux` version `3.0` or later. 108 | const SKIP_DIGEST = 0x08000; 109 | 110 | /// Treat conflicting specifications, such as where two hardlinks for 111 | /// the same inode have different contexts, as errors. 112 | /// 113 | /// This flag is supported only by `libselinux` version `3.1` or later. 114 | const CONFLICT_ERROR = 0x10000; 115 | 116 | /// Count, but otherwise ignore, errors during the file tree walk. 117 | /// 118 | /// This flag requires `libselinux` version `3.4` or later. 119 | const COUNT_ERRORS = 0x20000; 120 | } 121 | } 122 | 123 | bitflags! { 124 | /// Flags of [`ContextRestore::manage_security_sehash_xattr_entries`]. 125 | pub struct XAttrFlags: c_uint { 126 | /// Recursively descend directories. 127 | const RECURSE = selinux_sys::SELINUX_RESTORECON_XATTR_RECURSE as c_uint; 128 | 129 | /// Delete non-matching digests from each directory in path name. 130 | const DELETE_NON_MATCH_DIGESTS = selinux_sys::SELINUX_RESTORECON_XATTR_DELETE_NONMATCH_DIGESTS as c_uint; 131 | 132 | /// Delete all digests from each directory in path name. 133 | const DELETE_ALL_DIGESTS = selinux_sys::SELINUX_RESTORECON_XATTR_DELETE_ALL_DIGESTS as c_uint; 134 | 135 | /// Don't read `/proc/mounts` to obtain a list of non-seclabel mounts 136 | /// to be excluded from the search. 137 | /// 138 | /// Setting [`IGNORE_MOUNTS`] is useful where there is a non-seclabel fs 139 | /// mounted with a seclabel fs mounted on a directory below this. 140 | /// 141 | /// [`IGNORE_MOUNTS`]: Self::IGNORE_MOUNTS 142 | const IGNORE_MOUNTS = selinux_sys::SELINUX_RESTORECON_XATTR_IGNORE_MOUNTS as c_uint; 143 | } 144 | } 145 | 146 | /// Restore file(s) default SELinux security contexts. 147 | #[derive(Debug, Default)] 148 | pub struct ContextRestore<'labeler, T: crate::label::back_end::BackEnd> { 149 | labeler: Option<&'labeler mut Labeler>, 150 | } 151 | 152 | impl<'labeler, T> ContextRestore<'labeler, T> 153 | where 154 | T: crate::label::back_end::BackEnd, 155 | { 156 | /// Set a labeling handle for relabeling. 157 | /// 158 | /// See: `selinux_restorecon_set_sehandle()`. 159 | #[doc(alias = "selinux_restorecon_set_sehandle")] 160 | pub fn with_labeler(labeler: &'labeler mut Labeler) -> Self { 161 | Self { 162 | labeler: Some(labeler), 163 | } 164 | } 165 | 166 | /// Get the labeling handle to be used for relabeling. 167 | #[must_use] 168 | pub fn labeler(&self) -> Option<&&'labeler mut Labeler> { 169 | self.labeler.as_ref() 170 | } 171 | 172 | /// Set an alternate root path for relabeling. 173 | /// 174 | /// See: `selinux_restorecon_set_alt_rootpath()`. 175 | #[doc(alias = "selinux_restorecon_set_alt_rootpath")] 176 | pub fn set_alternative_root_path(&mut self, path: impl AsRef) -> Result<()> { 177 | let c_path = os_str_to_c_string(path.as_ref().as_os_str())?; 178 | let r = unsafe { selinux_sys::selinux_restorecon_set_alt_rootpath(c_path.as_ptr()) }; 179 | ret_val_to_result("selinux_restorecon_set_alt_rootpath()", r) 180 | } 181 | 182 | /// Add to the list of directories to be excluded from relabeling. 183 | /// 184 | /// See: `selinux_restorecon_set_exclude_list()`. 185 | #[doc(alias = "selinux_restorecon_set_exclude_list")] 186 | pub fn add_exclude_list

( 187 | &mut self, 188 | exclusion_patterns: impl IntoIterator, 189 | ) -> Result<()> 190 | where 191 | P: AsRef, 192 | { 193 | let c_list_storage = exclusion_patterns 194 | .into_iter() 195 | .map(|p| os_str_to_c_string(p.as_ref().as_os_str())) 196 | .collect::>>()?; 197 | 198 | if !c_list_storage.is_empty() { 199 | let mut c_ptr_list: Vec<_> = c_list_storage 200 | .iter() 201 | .map(AsRef::as_ref) 202 | .map(CStr::as_ptr) 203 | .chain(iter::once(ptr::null())) 204 | .collect(); 205 | 206 | unsafe { selinux_sys::selinux_restorecon_set_exclude_list(c_ptr_list.as_mut_ptr()) }; 207 | } 208 | Ok(()) 209 | } 210 | 211 | /// Restore file(s) default SELinux security contexts. 212 | /// 213 | /// If `threads_count` is zero, then: 214 | /// - If `selinux_restorecon_parallel()` is supported by `libselinux` (version 3.4 or later), 215 | /// then this operation will use as many threads as the number of online processor 216 | /// cores present. 217 | /// - Otherwise, this operation will run in one thread. 218 | /// 219 | /// When this method succeeds: 220 | /// - If `flags` includes [`RestoreFlags::COUNT_ERRORS`], then this returns `Ok(Some(N))` 221 | /// where `N` is the number of errors that were ignored while walking the file system tree 222 | /// specified by `path`. 223 | /// - Otherwise, `Ok(None)` is returned. 224 | /// 225 | /// See: `selinux_restorecon()`, `selinux_restorecon_parallel()`. 226 | #[doc(alias = "selinux_restorecon")] 227 | #[doc(alias = "selinux_restorecon_parallel")] 228 | pub fn restore_context_of_file_system_entry( 229 | self, 230 | path: impl AsRef, 231 | threads_count: usize, 232 | flags: RestoreFlags, 233 | ) -> Result> { 234 | if let Some(labeler) = self.labeler.map(Labeler::as_mut_ptr) { 235 | unsafe { selinux_sys::selinux_restorecon_set_sehandle(labeler) }; 236 | } 237 | 238 | let c_path = os_str_to_c_string(path.as_ref().as_os_str())?; 239 | match threads_count { 240 | 0 => { 241 | // Call `selinux_restorecon_parallel()` if possible. 242 | Error::clear_errno(); 243 | let r = unsafe { 244 | (OptionalNativeFunctions::get().selinux_restorecon_parallel)( 245 | c_path.as_ptr(), 246 | flags.bits(), 247 | threads_count, 248 | ) 249 | }; 250 | 251 | if r == -1_i32 { 252 | if io::Error::last_os_error().raw_os_error() != Some(libc::ENOSYS) { 253 | return Err(Error::last_io_error("selinux_restorecon_parallel()")); 254 | } 255 | 256 | // `selinux_restorecon_parallel()` is unsupported. 257 | // Call `selinux_restorecon()` instead. 258 | let r = 259 | unsafe { selinux_sys::selinux_restorecon(c_path.as_ptr(), flags.bits()) }; 260 | ret_val_to_result("selinux_restorecon()", r)?; 261 | } 262 | } 263 | 264 | 1 => { 265 | let r = unsafe { selinux_sys::selinux_restorecon(c_path.as_ptr(), flags.bits()) }; 266 | ret_val_to_result("selinux_restorecon()", r)?; 267 | } 268 | 269 | _ => { 270 | let r = unsafe { 271 | (OptionalNativeFunctions::get().selinux_restorecon_parallel)( 272 | c_path.as_ptr(), 273 | flags.bits(), 274 | threads_count, 275 | ) 276 | }; 277 | ret_val_to_result("selinux_restorecon_parallel()", r)?; 278 | } 279 | } 280 | 281 | Ok(flags.contains(RestoreFlags::COUNT_ERRORS).then(|| { 282 | #[allow(clippy::useless_conversion)] 283 | u64::from(unsafe { 284 | (OptionalNativeFunctions::get().selinux_restorecon_get_skipped_errors)() 285 | }) 286 | })) 287 | } 288 | 289 | /// Manage default `security.sehash` extended attribute entries added by 290 | /// `selinux_restorecon()`, `setfiles()` or `restorecon()`. 291 | /// 292 | /// See: `selinux_restorecon_xattr()`. 293 | #[doc(alias = "selinux_restorecon_xattr")] 294 | pub fn manage_security_sehash_xattr_entries( 295 | dir_path: impl AsRef, 296 | flags: XAttrFlags, 297 | ) -> Result { 298 | let mut xattr_list_ptr: *mut *mut selinux_sys::dir_xattr = ptr::null_mut(); 299 | let c_dir_path = os_str_to_c_string(dir_path.as_ref().as_os_str())?; 300 | let r: c_int = unsafe { 301 | selinux_sys::selinux_restorecon_xattr( 302 | c_dir_path.as_ptr(), 303 | flags.bits(), 304 | &mut xattr_list_ptr, 305 | ) 306 | }; 307 | 308 | if r == -1_i32 { 309 | Err(Error::last_io_error("selinux_restorecon_xattr()")) 310 | } else { 311 | let xattr_list = ptr::NonNull::new(xattr_list_ptr).map_or( 312 | ptr::null_mut(), 313 | |mut xattr_list_ptr| unsafe { 314 | let xattr_list = *xattr_list_ptr.as_ref(); 315 | 316 | // Detach the linked list from libselinux, so that we own it from now on. 317 | *xattr_list_ptr.as_mut() = ptr::null_mut(); 318 | 319 | xattr_list 320 | }, 321 | ); 322 | 323 | Ok(DirectoryXAttributesIter(xattr_list)) 324 | } 325 | } 326 | } 327 | 328 | /// Status of a [`DirectoryXAttributes`]. 329 | #[derive(Debug)] 330 | #[non_exhaustive] 331 | pub enum DirectoryDigestResult { 332 | /// Match. 333 | Match { 334 | /// Matching digest deleted from the directory. 335 | deleted: bool, 336 | }, 337 | /// No match. 338 | NoMatch { 339 | /// Non-matching digest deleted from the directory. 340 | deleted: bool, 341 | }, 342 | /// Error. 343 | Error, 344 | /// Unknown status. 345 | Unknown(c_uint), 346 | } 347 | 348 | /// Result of [`ContextRestore::manage_security_sehash_xattr_entries`]. 349 | #[derive(Debug)] 350 | pub struct DirectoryXAttributes { 351 | pointer: ptr::NonNull, 352 | _phantom_data: PhantomData, 353 | } 354 | 355 | impl DirectoryXAttributes { 356 | /// Return the managed raw pointer to [`selinux_sys::dir_xattr`]. 357 | #[must_use] 358 | pub fn as_ptr(&self) -> *const selinux_sys::dir_xattr { 359 | self.pointer.as_ptr() 360 | } 361 | 362 | /// Return the managed raw pointer to [`selinux_sys::dir_xattr`]. 363 | #[must_use] 364 | pub fn as_mut_ptr(&mut self) -> *mut selinux_sys::dir_xattr { 365 | self.pointer.as_ptr() 366 | } 367 | 368 | /// Directory path. 369 | #[must_use] 370 | pub fn directory_path(&self) -> &Path { 371 | c_str_ptr_to_path(unsafe { self.pointer.as_ref().directory }) 372 | } 373 | 374 | /// A hex encoded string that can be printed. 375 | pub fn digest(&self) -> Result<&str> { 376 | c_str_ptr_to_str(unsafe { self.pointer.as_ref().digest }) 377 | } 378 | 379 | /// Status of this entry. 380 | #[must_use] 381 | pub fn digest_result(&self) -> DirectoryDigestResult { 382 | match unsafe { self.pointer.as_ref().result } { 383 | selinux_sys::digest_result::MATCH => DirectoryDigestResult::Match { deleted: false }, 384 | 385 | selinux_sys::digest_result::NOMATCH => { 386 | DirectoryDigestResult::NoMatch { deleted: false } 387 | } 388 | 389 | selinux_sys::digest_result::DELETED_MATCH => { 390 | DirectoryDigestResult::Match { deleted: true } 391 | } 392 | 393 | selinux_sys::digest_result::DELETED_NOMATCH => { 394 | DirectoryDigestResult::NoMatch { deleted: true } 395 | } 396 | 397 | selinux_sys::digest_result::ERROR => DirectoryDigestResult::Error, 398 | 399 | value => DirectoryDigestResult::Unknown(value), 400 | } 401 | } 402 | } 403 | 404 | impl Drop for DirectoryXAttributes { 405 | fn drop(&mut self) { 406 | unsafe { 407 | libc::free(self.pointer.as_ref().directory.cast()); 408 | libc::free(self.pointer.as_ref().digest.cast()); 409 | libc::free(self.pointer.as_ptr().cast()); 410 | } 411 | } 412 | } 413 | 414 | /// Iterator producing [`DirectoryXAttributes`] elements. 415 | #[derive(Debug)] 416 | pub struct DirectoryXAttributesIter(*mut selinux_sys::dir_xattr); 417 | 418 | impl Iterator for DirectoryXAttributesIter { 419 | type Item = DirectoryXAttributes; 420 | 421 | fn next(&mut self) -> Option { 422 | ptr::NonNull::new(self.0).map(|pointer| { 423 | self.0 = unsafe { pointer.as_ref().next }; 424 | DirectoryXAttributes { 425 | pointer, 426 | _phantom_data: PhantomData, 427 | } 428 | }) 429 | } 430 | } 431 | -------------------------------------------------------------------------------- /selinux/src/tests.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(test, target_os = "linux", not(target_env = "kernel")))] 2 | 3 | use std::collections::{HashMap, HashSet}; 4 | use std::convert::TryFrom; 5 | use std::ffi::CStr; 6 | use std::io::Write; 7 | use std::os::raw::{c_char, c_int}; 8 | use std::path::Path; 9 | use std::{fs, io, process, ptr}; 10 | 11 | use assert_matches::assert_matches; 12 | 13 | use crate::utils::*; 14 | 15 | #[test] 16 | fn security_context_from_c_str() { 17 | let ptr: *const c_char = "xyz\0".as_ptr().cast(); 18 | let s = unsafe { CStr::from_ptr(ptr) }; 19 | let mut context = super::SecurityContext::from_c_str(s, false); 20 | assert_eq!(context.as_ptr(), ptr); 21 | assert_eq!(context.as_mut_ptr(), ptr as *mut c_char); 22 | assert_eq!(context.as_bytes().len(), 3); 23 | assert!(!context.is_raw_format()); 24 | 25 | let _ignored = format!("{:?}", &context); 26 | } 27 | 28 | #[test] 29 | fn security_context_from_result() { 30 | super::SecurityContext::from_result("xyz", 0, ptr::null_mut(), false).unwrap_err(); 31 | 32 | crate::errors::Error::set_errno(1); 33 | super::SecurityContext::from_result("xyz", -1, 0x1000 as *mut c_char, false).unwrap_err(); 34 | crate::errors::Error::clear_errno(); 35 | } 36 | 37 | #[test] 38 | fn security_context_from_result_with_name() { 39 | super::SecurityContext::from_result_with_name("xyz", 0, ptr::null_mut(), "abc", false) 40 | .unwrap_err(); 41 | 42 | crate::errors::Error::set_errno(1); 43 | super::SecurityContext::from_result_with_name("xyz", -1, 0x1000 as *mut c_char, "abc", false) 44 | .unwrap_err(); 45 | crate::errors::Error::clear_errno(); 46 | } 47 | 48 | #[test] 49 | fn security_context_from_result_with_pid() { 50 | super::SecurityContext::from_result_with_pid("xyz", 0, ptr::null_mut(), 1, false).unwrap_err(); 51 | 52 | crate::errors::Error::set_errno(1); 53 | super::SecurityContext::from_result_with_pid("xyz", -1, 0x1000 as *mut c_char, 1, false) 54 | .unwrap_err(); 55 | crate::errors::Error::clear_errno(); 56 | } 57 | 58 | #[test] 59 | fn security_context_parse_context_color() { 60 | use super::{LayerColors, SecurityContext, SecurityContextColors, RGB}; 61 | 62 | for &bytes in &[ 63 | b"" as &[u8], 64 | b" ", 65 | b" ", 66 | b"s", 67 | b"s t", 68 | b"s t u", 69 | b"s t u v", 70 | b"s t u v w", 71 | b"s t u v w x", 72 | b"s t u v w x y", 73 | b"s t u v w x y z", 74 | b"# # # # # # # #", 75 | b"#s #t #u #v #w #x #y #z", 76 | b"#0", 77 | b"#0 #0", 78 | b"#0 #0 #0", 79 | b"#0 #0 #0 #0", 80 | b"#0 #0 #0 #0 #0", 81 | b"#0 #0 #0 #0 #0 #0", 82 | b"#0 #0 #0 #0 #0 #0 #0", 83 | b"#-1 #0 #0 #0 #0 #0 #0 #0", 84 | b"#100000000 #0 #0 #0 #0 #0 #0 #0", 85 | b"#1000000 #0 #0 #0 #0 #0 #0 #0", 86 | ] { 87 | SecurityContext::parse_context_color(bytes).unwrap_err(); 88 | } 89 | 90 | let bytes = b"#11 #22 #aa #bb #cc #dd #ee #ff"; 91 | let colors = SecurityContext::parse_context_color(bytes).unwrap(); 92 | let expected_colors = SecurityContextColors::new( 93 | LayerColors::new(RGB::new(0x22, 0, 0), RGB::new(0x11, 0, 0)), 94 | LayerColors::new(RGB::new(0xbb, 0, 0), RGB::new(0xaa, 0, 0)), 95 | LayerColors::new(RGB::new(0xdd, 0, 0), RGB::new(0xcc, 0, 0)), 96 | LayerColors::new(RGB::new(0xff, 0, 0), RGB::new(0xee, 0, 0)), 97 | ); 98 | assert_eq!(colors, expected_colors); 99 | } 100 | 101 | #[test] 102 | fn security_context_color() { 103 | use super::{LayerColors, SecurityContextColors, RGB}; 104 | 105 | let scc = SecurityContextColors::new( 106 | LayerColors::new(RGB::new(0x22, 0, 0), RGB::new(0x11, 0, 0)), 107 | LayerColors::new(RGB::new(0xbb, 0, 0), RGB::new(0xaa, 0, 0)), 108 | LayerColors::new(RGB::new(0xdd, 0, 0), RGB::new(0xcc, 0, 0)), 109 | LayerColors::new(RGB::new(0xff, 0, 0), RGB::new(0xee, 0, 0)), 110 | ); 111 | 112 | let scc_clone = super::SecurityContextColors::clone(&scc); 113 | assert_eq!(scc, scc_clone); 114 | assert!(scc >= scc_clone); 115 | assert!(scc <= scc_clone); 116 | assert_ne!(scc, super::SecurityContextColors::default()); 117 | let _ignored = format!("{:?}", &scc); 118 | let mut ht = HashSet::new(); 119 | ht.insert(scc_clone); 120 | } 121 | 122 | #[test] 123 | fn security_context_current() { 124 | let mut context = super::SecurityContext::current(false).unwrap(); 125 | assert!(!context.as_ptr().is_null()); 126 | assert!(!context.as_mut_ptr().is_null()); 127 | assert!(!context.as_bytes().is_empty()); 128 | 129 | if let Err(err) = context.is_customizable() { 130 | assert_matches!(err, crate::errors::Error::IO { .. }); 131 | if let crate::errors::Error::IO { source, .. } = err { 132 | let errno = source.raw_os_error().unwrap(); 133 | assert!(errno == libc::EINVAL || errno == libc::ENOTDIR); 134 | } 135 | } 136 | 137 | let r = context.check(); 138 | assert!(r.is_none() || r == Some(true)); 139 | 140 | let _canon_context = context.canonicalize().unwrap(); 141 | 142 | let _securetty = context.check_securetty_context(); 143 | 144 | //let _color = context.to_color().unwrap(); 145 | 146 | context.to_translated_format().unwrap_err(); 147 | 148 | let raw_context = context.to_raw_format().unwrap(); 149 | raw_context.to_raw_format().unwrap_err(); 150 | 151 | let _canon_raw_context = raw_context.canonicalize().unwrap(); 152 | 153 | let r = raw_context.check(); 154 | assert!(r.is_none() || r == Some(true)); 155 | 156 | let _context = raw_context.to_translated_format().unwrap(); 157 | 158 | let _raw_context = super::SecurityContext::current(true).unwrap(); 159 | 160 | let _cmp = context.compare_user_insensitive(&raw_context); 161 | } 162 | 163 | #[test] 164 | fn security_context_previous() { 165 | let _context = super::SecurityContext::previous(false).unwrap(); 166 | let _context = super::SecurityContext::previous(true).unwrap(); 167 | } 168 | 169 | #[test] 170 | fn security_context_set_as_current() { 171 | for &raw_format in &[false, true] { 172 | let context = super::SecurityContext::current(raw_format).unwrap(); 173 | context.set_as_current().unwrap(); 174 | } 175 | } 176 | 177 | #[test] 178 | fn security_context_of_next_exec() { 179 | let _context = super::SecurityContext::of_next_exec(false).unwrap(); 180 | let _context = super::SecurityContext::of_next_exec(true).unwrap(); 181 | } 182 | 183 | #[test] 184 | fn security_context_set_default_context_for_next_exec() { 185 | super::SecurityContext::set_default_context_for_next_exec().unwrap(); 186 | } 187 | 188 | #[test] 189 | fn security_context_set_for_next_exec() { 190 | for &raw_format in &[false, true] { 191 | let old_context = super::SecurityContext::of_next_exec(raw_format).unwrap(); 192 | 193 | let context = super::SecurityContext::current(raw_format).unwrap(); 194 | context.set_for_next_exec().unwrap(); 195 | 196 | if let Some(context) = old_context { 197 | context.set_for_next_exec().unwrap(); 198 | } else { 199 | super::SecurityContext::set_default_context_for_next_exec().unwrap(); 200 | } 201 | } 202 | } 203 | 204 | #[test] 205 | fn security_context_of_new_file_system_objects() { 206 | let _context = super::SecurityContext::of_new_file_system_objects(false).unwrap(); 207 | let _context = super::SecurityContext::of_new_file_system_objects(true).unwrap(); 208 | } 209 | 210 | #[test] 211 | fn security_context_set_default_context_for_new_file_system_objects() { 212 | super::SecurityContext::set_default_context_for_new_file_system_objects().unwrap(); 213 | } 214 | 215 | #[test] 216 | fn security_context_set_for_new_file_system_objects() { 217 | for &raw_format in &[false, true] { 218 | let old_context = super::SecurityContext::of_new_file_system_objects(raw_format).unwrap(); 219 | 220 | let context = super::SecurityContext::current(raw_format).unwrap(); 221 | context.set_for_new_file_system_objects(raw_format).unwrap(); 222 | 223 | if let Some(context) = old_context { 224 | context.set_for_new_file_system_objects(raw_format).unwrap(); 225 | } else { 226 | super::SecurityContext::set_default_context_for_new_file_system_objects().unwrap(); 227 | } 228 | } 229 | } 230 | 231 | #[test] 232 | fn security_context_of_new_kernel_key_rings() { 233 | let _context = super::SecurityContext::of_new_kernel_key_rings(false).unwrap(); 234 | let _context = super::SecurityContext::of_new_kernel_key_rings(true).unwrap(); 235 | } 236 | 237 | #[test] 238 | fn security_context_set_default_context_for_new_kernel_key_rings() { 239 | super::SecurityContext::set_default_context_for_new_kernel_key_rings().unwrap(); 240 | } 241 | 242 | #[test] 243 | fn security_context_set_for_new_kernel_key_rings() { 244 | for &raw_format in &[false, true] { 245 | let old_context = super::SecurityContext::of_new_kernel_key_rings(raw_format).unwrap(); 246 | 247 | let context = super::SecurityContext::current(raw_format).unwrap(); 248 | context.set_for_new_kernel_key_rings(raw_format).unwrap(); 249 | 250 | if let Some(context) = old_context { 251 | context.set_for_new_kernel_key_rings(raw_format).unwrap(); 252 | } else { 253 | super::SecurityContext::set_default_context_for_new_kernel_key_rings().unwrap(); 254 | } 255 | } 256 | } 257 | 258 | #[test] 259 | fn security_context_of_new_labeled_sockets() { 260 | let _context = super::SecurityContext::of_new_labeled_sockets(false).unwrap(); 261 | let _context = super::SecurityContext::of_new_labeled_sockets(true).unwrap(); 262 | } 263 | 264 | #[test] 265 | fn security_context_set_default_context_for_new_labeled_sockets() { 266 | super::SecurityContext::set_default_context_for_new_labeled_sockets().unwrap(); 267 | } 268 | 269 | #[test] 270 | fn security_context_set_for_new_labeled_sockets() { 271 | for &raw_format in &[false, true] { 272 | let old_context = super::SecurityContext::of_new_labeled_sockets(raw_format).unwrap(); 273 | 274 | let context = super::SecurityContext::current(raw_format).unwrap(); 275 | context.set_for_new_labeled_sockets(raw_format).unwrap(); 276 | 277 | if let Some(context) = old_context { 278 | context.set_for_new_labeled_sockets(raw_format).unwrap(); 279 | } else { 280 | super::SecurityContext::set_default_context_for_new_labeled_sockets().unwrap(); 281 | } 282 | } 283 | } 284 | 285 | #[test] 286 | fn security_context_of_initial_kernel_context() { 287 | for &raw_format in &[false, true] { 288 | let _context = 289 | super::SecurityContext::of_initial_kernel_context("unlabeled", raw_format).unwrap(); 290 | } 291 | } 292 | 293 | #[test] 294 | fn security_context_of_process() { 295 | let pid = process::id() as c_int; 296 | for &raw_format in &[false, true] { 297 | let _context = super::SecurityContext::of_process(pid, raw_format).unwrap(); 298 | } 299 | } 300 | 301 | #[test] 302 | fn security_context_of_se_user_with_selected_context() { 303 | //let _context = 304 | // super::SecurityContext::of_se_user_with_selected_context("unconfined_u", false).unwrap(); 305 | } 306 | 307 | #[test] 308 | fn security_context_default_for_se_user() { 309 | let _context = 310 | super::SecurityContext::default_for_se_user("unconfined_u", None, None, None, false) 311 | .unwrap(); 312 | 313 | let _context = super::SecurityContext::default_for_se_user( 314 | "unconfined_u", 315 | Some("unconfined_r"), 316 | None, 317 | None, 318 | false, 319 | ) 320 | .unwrap(); 321 | 322 | let _context = 323 | super::SecurityContext::default_for_se_user("unconfined_u", None, Some("low"), None, false) 324 | .unwrap(); 325 | 326 | let _context = super::SecurityContext::default_for_se_user( 327 | "unconfined_u", 328 | Some("unconfined_r"), 329 | Some("low"), 330 | None, 331 | false, 332 | ) 333 | .unwrap(); 334 | 335 | let context = super::SecurityContext::current(false).unwrap(); 336 | let _context = super::SecurityContext::default_for_se_user( 337 | "unconfined_u", 338 | None, 339 | None, 340 | Some(&context), 341 | false, 342 | ) 343 | .unwrap(); 344 | } 345 | 346 | #[test] 347 | fn security_context_of_media_type() { 348 | super::SecurityContext::of_media_type("invalid").unwrap_err(); 349 | //let _context = super::SecurityContext::of_media_type("unlabeled").unwrap(); 350 | } 351 | 352 | #[test] 353 | fn security_context_of_labeling_decision() { 354 | let context = super::SecurityContext::current(false).unwrap(); 355 | let raw_context = super::SecurityContext::current(true).unwrap(); 356 | let target_class = super::SecurityClass::from_name("process").unwrap(); 357 | context 358 | .of_labeling_decision(&raw_context, target_class, "process") 359 | .unwrap_err(); 360 | let _new_context = context 361 | .of_labeling_decision(&context, target_class, "process") 362 | .unwrap(); 363 | let _new_context = raw_context 364 | .of_labeling_decision(&raw_context, target_class, "process") 365 | .unwrap(); 366 | } 367 | 368 | #[test] 369 | fn security_context_of_relabeling_decision() { 370 | let context = super::SecurityContext::current(false).unwrap(); 371 | let raw_context = super::SecurityContext::current(true).unwrap(); 372 | let target_class = super::SecurityClass::from_name("process").unwrap(); 373 | context 374 | .of_relabeling_decision(&raw_context, target_class) 375 | .unwrap_err(); 376 | let _new_context = context 377 | .of_relabeling_decision(&context, target_class) 378 | .unwrap(); 379 | let _new_context = raw_context 380 | .of_relabeling_decision(&raw_context, target_class) 381 | .unwrap(); 382 | } 383 | 384 | #[test] 385 | fn security_context_of_polyinstantiation_member_decision() { 386 | let context = super::SecurityContext::current(false).unwrap(); 387 | let raw_context = super::SecurityContext::current(true).unwrap(); 388 | let target_class = super::SecurityClass::from_name("process").unwrap(); 389 | context 390 | .of_polyinstantiation_member_decision(&raw_context, target_class) 391 | .unwrap_err(); 392 | let _new_context = context 393 | .of_polyinstantiation_member_decision(&context, target_class) 394 | .unwrap(); 395 | let _new_context = raw_context 396 | .of_polyinstantiation_member_decision(&raw_context, target_class) 397 | .unwrap(); 398 | } 399 | 400 | #[test] 401 | fn security_context_validate_transition() { 402 | let context = super::SecurityContext::current(false).unwrap(); 403 | let raw_context = super::SecurityContext::current(true).unwrap(); 404 | let target_class = super::SecurityClass::from_name("process").unwrap(); 405 | context 406 | .validate_transition(&raw_context, target_class, &raw_context) 407 | .unwrap_err(); 408 | 409 | if let Err(r) = context.validate_transition(&context, target_class, &context) { 410 | assert_eq!(r.io_source().unwrap().raw_os_error(), Some(libc::ENOSYS)); 411 | } 412 | 413 | if let Err(r) = raw_context.validate_transition(&raw_context, target_class, &raw_context) { 414 | assert_eq!(r.io_source().unwrap().raw_os_error(), Some(libc::ENOSYS)); 415 | } 416 | } 417 | 418 | #[test] 419 | fn security_context_query_access_decision() { 420 | let context = super::SecurityContext::current(false).unwrap(); 421 | let raw_context = super::SecurityContext::current(true).unwrap(); 422 | let target_class = super::SecurityClass::from_name("process").unwrap(); 423 | context 424 | .query_access_decision(&raw_context, target_class, 0) 425 | .unwrap_err(); 426 | let _new_context = context 427 | .query_access_decision(&context, target_class, 0) 428 | .unwrap(); 429 | let _new_context = raw_context 430 | .query_access_decision(&raw_context, target_class, 0) 431 | .unwrap(); 432 | } 433 | 434 | #[test] 435 | fn security_context_check_access() { 436 | let context = super::SecurityContext::current(false).unwrap(); 437 | let raw_context = super::SecurityContext::current(true).unwrap(); 438 | let _new_context = context 439 | .check_access(&context, "process", "read", ptr::null_mut()) 440 | .unwrap(); 441 | let _new_context = raw_context 442 | .check_access(&raw_context, "process", "read", ptr::null_mut()) 443 | .unwrap(); 444 | } 445 | 446 | #[test] 447 | fn security_context_of_path() { 448 | for &raw_format in &[false, true] { 449 | for &follow_symbolic_links in &[false, true] { 450 | for &path in &["/", "/etc/fstab"] { 451 | let r = super::SecurityContext::of_path(path, follow_symbolic_links, raw_format); 452 | let _context = r.unwrap(); 453 | } 454 | } 455 | } 456 | 457 | let _context = super::SecurityContext::of_path("/non-existent", false, false).unwrap_err(); 458 | } 459 | 460 | #[test] 461 | fn security_context_set_default_for_path() { 462 | super::SecurityContext::set_default_for_path("non-existent").unwrap_err(); 463 | 464 | /* 465 | let file = tempfile::NamedTempFile::new().unwrap(); 466 | super::SecurityContext::set_default_for_path(file.path()).unwrap(); 467 | */ 468 | } 469 | 470 | #[test] 471 | fn security_context_set_for_path() { 472 | use std::os::unix::fs::symlink; 473 | 474 | let context = super::SecurityContext::current(false).unwrap(); 475 | context 476 | .set_for_path(Path::new("/non-existent"), false, false) 477 | .unwrap_err(); 478 | 479 | let context = 480 | unsafe { CStr::from_ptr("unconfined_u:object_r:user_tmp_t:s0\0".as_ptr().cast()) }; 481 | let context = super::SecurityContext::from_c_str(context, false); 482 | 483 | let dir = tempfile::TempDir::new().unwrap(); 484 | let a = dir.path().join("a.txt"); 485 | let la = dir.path().join("la.txt"); 486 | fs::write(&a, "empty file").unwrap(); 487 | symlink(&a, &la).unwrap(); 488 | 489 | for &raw_format in &[false, true] { 490 | for &follow_symbolic_links in &[false, true] { 491 | context 492 | .set_for_path(la.as_path(), follow_symbolic_links, raw_format) 493 | .unwrap(); 494 | } 495 | } 496 | 497 | super::SecurityContext::verify_file_context("/non-existent", None).unwrap_err(); 498 | 499 | /* 500 | super::SecurityContext::verify_file_context( 501 | &la, 502 | super::FileAccessMode::new(libc::S_IFREG | libc::S_IRUSR), 503 | ) 504 | .unwrap(); 505 | */ 506 | } 507 | 508 | #[test] 509 | fn security_context_of_file() { 510 | let mut file = tempfile::tempfile().unwrap(); 511 | writeln!(file, "empty file").unwrap(); 512 | let optional_context = super::SecurityContext::of_file(&file, false).unwrap(); 513 | let optional_raw_context = super::SecurityContext::of_file(&file, true).unwrap(); 514 | 515 | if let Some(context) = optional_context { 516 | context.set_for_file(&file).unwrap(); 517 | } 518 | 519 | if let Some(raw_context) = optional_raw_context { 520 | raw_context.set_for_file(&file).unwrap(); 521 | } 522 | } 523 | 524 | #[test] 525 | fn security_context_of_peer_socket() { 526 | let (s1, s2) = socketpair::socketpair_stream().unwrap(); 527 | 528 | let _context = super::SecurityContext::of_peer_socket(&s1, false).unwrap(); 529 | let _raw_context = super::SecurityContext::of_peer_socket(&s2, true).unwrap(); 530 | } 531 | 532 | #[test] 533 | fn rgb() { 534 | let rgb = super::RGB::new(0x22, 0, 0); 535 | let rgb_clone = super::RGB::clone(&rgb); 536 | assert_eq!(rgb, rgb_clone); 537 | assert!(rgb >= rgb_clone); 538 | assert!(rgb <= rgb_clone); 539 | assert_ne!(rgb, super::RGB::default()); 540 | let _ignored = format!("{:?}", &rgb); 541 | let mut ht = HashSet::new(); 542 | ht.insert(rgb_clone); 543 | } 544 | 545 | #[test] 546 | fn layer_colors() { 547 | let lc = super::LayerColors::new(super::RGB::new(0x22, 0, 0), super::RGB::new(0x11, 0, 0)); 548 | let lc_clone = super::LayerColors::clone(&lc); 549 | assert_eq!(lc, lc_clone); 550 | assert!(lc >= lc_clone); 551 | assert!(lc <= lc_clone); 552 | assert_ne!(lc, super::LayerColors::default()); 553 | let _ignored = format!("{:?}", &lc); 554 | let mut ht = HashSet::new(); 555 | ht.insert(lc_clone); 556 | } 557 | 558 | #[test] 559 | fn file_access_mode() { 560 | assert!(super::FileAccessMode::new(0).is_none()); 561 | 562 | let m = super::FileAccessMode::new(42); 563 | assert_eq!(m, Some(super::FileAccessMode(42))); 564 | assert_eq!(m.unwrap().mode(), 42); 565 | 566 | let _ignored = format!("{:?}", &m); 567 | } 568 | 569 | #[test] 570 | fn security_class_new() { 571 | super::SecurityClass::new(0).unwrap_err(); 572 | 573 | let sc = super::SecurityClass::new(1).unwrap(); 574 | assert_eq!(sc.value(), 1); 575 | 576 | let _ignored = format!("{:?}", &sc); 577 | let _ignored = format!("{}", &sc); 578 | 579 | let _sc = super::SecurityClass::from_name("invalid").unwrap_err(); 580 | 581 | let _sc = super::SecurityClass::try_from( 582 | super::FileAccessMode::new(libc::S_IFREG | libc::S_IRUSR).unwrap(), 583 | ) 584 | .unwrap(); 585 | 586 | super::SecurityClass::try_from(super::FileAccessMode::new(1).unwrap()).unwrap_err(); 587 | 588 | let sc = super::SecurityClass::from_name("process").unwrap(); 589 | let _ignored = format!("{:?}", &sc); 590 | let _ignored = format!("{}", &sc); 591 | 592 | unsafe { sc.access_vector_bit_name(0) }.unwrap_err(); 593 | let _name = unsafe { sc.access_vector_bit_name(1) }.unwrap(); 594 | 595 | let _name = sc.full_access_vector_name(0).unwrap(); 596 | let _name = sc.full_access_vector_name(1).unwrap(); 597 | let _name = sc.full_access_vector_name(u32::MAX).unwrap(); 598 | 599 | sc.access_vector_bit("invalid").unwrap_err(); 600 | 601 | let _av = sc.access_vector_bit("signal").unwrap(); 602 | } 603 | 604 | #[test] 605 | fn opaque_security_context() { 606 | for &context in &[ 607 | "", 608 | "user1", 609 | "user1:role1", 610 | "user1:role1:type1:range1:other1:other2:other3", 611 | ] { 612 | super::OpaqueSecurityContext::new(context).unwrap_err(); 613 | } 614 | 615 | for &context in &[ 616 | "user1:role1:type1", 617 | "user1:role1:type1:range1", 618 | "user1:role1:type1:range1:other1", 619 | "user1:role1:type1:range1:other1:other2", 620 | ] { 621 | let mut osc = super::OpaqueSecurityContext::new(context).unwrap(); 622 | 623 | assert!(!osc.as_ptr().is_null()); 624 | assert!(!osc.as_mut_ptr().is_null()); 625 | 626 | let s = osc.to_c_string().unwrap(); 627 | assert!(!s.as_bytes().is_empty()); 628 | assert_eq!(s.to_string_lossy(), format!("{}", &osc)); 629 | 630 | assert_eq!(osc.user().unwrap().to_str().ok(), Some("user1")); 631 | assert_eq!(osc.role().unwrap().to_str().ok(), Some("role1")); 632 | assert_eq!(osc.the_type().unwrap().to_str().ok(), Some("type1")); 633 | 634 | let expected_range = context.splitn(4, |c| c == ':').nth(3); 635 | if let Ok(range) = osc.range() { 636 | assert_eq!(range.to_str().ok(), expected_range); 637 | } else { 638 | assert!(expected_range.is_none()); 639 | } 640 | 641 | osc.set_user_str("user2").unwrap(); 642 | 643 | assert_eq!(osc.user().unwrap().to_str().ok(), Some("user2")); 644 | assert_eq!(osc.role().unwrap().to_str().ok(), Some("role1")); 645 | assert_eq!(osc.the_type().unwrap().to_str().ok(), Some("type1")); 646 | if let Ok(range) = osc.range() { 647 | assert_eq!(range.to_str().ok(), expected_range); 648 | } else { 649 | assert!(expected_range.is_none()); 650 | } 651 | 652 | osc.set_role_str("role2").unwrap(); 653 | 654 | assert_eq!(osc.user().unwrap().to_str().ok(), Some("user2")); 655 | assert_eq!(osc.role().unwrap().to_str().ok(), Some("role2")); 656 | assert_eq!(osc.the_type().unwrap().to_str().ok(), Some("type1")); 657 | if let Ok(range) = osc.range() { 658 | assert_eq!(range.to_str().ok(), expected_range); 659 | } else { 660 | assert!(expected_range.is_none()); 661 | } 662 | 663 | osc.set_type_str("type2").unwrap(); 664 | 665 | assert_eq!(osc.user().unwrap().to_str().ok(), Some("user2")); 666 | assert_eq!(osc.role().unwrap().to_str().ok(), Some("role2")); 667 | assert_eq!(osc.the_type().unwrap().to_str().ok(), Some("type2")); 668 | if let Ok(range) = osc.range() { 669 | assert_eq!(range.to_str().ok(), expected_range); 670 | } else { 671 | assert!(expected_range.is_none()); 672 | } 673 | 674 | osc.set_range_str("range2").unwrap(); 675 | 676 | assert_eq!(osc.user().unwrap().to_str().ok(), Some("user2")); 677 | assert_eq!(osc.role().unwrap().to_str().ok(), Some("role2")); 678 | assert_eq!(osc.the_type().unwrap().to_str().ok(), Some("type2")); 679 | assert_eq!(osc.range().unwrap().to_str().ok(), Some("range2")); 680 | 681 | let _ignored = format!("{:?}", &osc); 682 | } 683 | } 684 | 685 | #[test] 686 | fn kernel_support() { 687 | let r = super::kernel_support(); 688 | let _ignored = format!("{r:?}"); 689 | } 690 | 691 | #[test] 692 | fn boot_mode() { 693 | if let Err(err) = super::boot_mode() { 694 | assert_matches!(err, crate::errors::Error::IO { .. }); 695 | if let crate::errors::Error::IO { source, .. } = err { 696 | assert_eq!(source.kind(), io::ErrorKind::NotFound); 697 | } 698 | 699 | assert!(fs::symlink_metadata("/etc/selinux/config").is_err()); 700 | } 701 | } 702 | 703 | #[test] 704 | fn current_mode() { 705 | let r = super::current_mode(); 706 | let _ignored = format!("{r:?}"); 707 | } 708 | 709 | #[test] 710 | fn undefined_handling() { 711 | if let Err(err) = super::undefined_handling() { 712 | assert_matches!(err, crate::errors::Error::IO { .. }); 713 | if let crate::errors::Error::IO { source, .. } = err { 714 | assert_eq!(source.kind(), io::ErrorKind::NotFound); 715 | } 716 | } 717 | } 718 | 719 | #[test] 720 | fn protection_checking_mode() { 721 | if let Err(err) = super::protection_checking_mode() { 722 | assert_matches!(err, crate::errors::Error::IO { .. }); 723 | if let crate::errors::Error::IO { source, .. } = err { 724 | assert_eq!(source.kind(), io::ErrorKind::NotFound); 725 | } 726 | } 727 | } 728 | 729 | #[test] 730 | fn dynamic_mapping_into_native_form() { 731 | let mut c_string_storage = HashMap::default(); 732 | 733 | let empty: &[(&str, &[&str]); 0] = &[]; 734 | let c_map = super::dynamic_mapping_into_native_form(empty, &mut c_string_storage).unwrap(); 735 | assert_eq!(c_map.len(), 1 + empty.len()); 736 | assert!(c_map[0].name.is_null()); 737 | 738 | let mapping: &[(&str, &[&str]); 1] = &[("", &[])]; 739 | let c_map = super::dynamic_mapping_into_native_form(mapping, &mut c_string_storage).unwrap(); 740 | assert_eq!(c_map.len(), 1 + mapping.len()); 741 | assert!(c_str_ptr_to_str(c_map[0].name).unwrap().is_empty()); 742 | assert!(c_map[0].perms[0].is_null()); 743 | assert!(c_map[1].name.is_null()); 744 | 745 | let mapping: &[(&str, &[&str]); 1] = &[("", &["", ""])]; 746 | let c_map = super::dynamic_mapping_into_native_form(mapping, &mut c_string_storage).unwrap(); 747 | assert_eq!(c_map.len(), 1 + mapping.len()); 748 | assert!(c_str_ptr_to_str(c_map[0].name).unwrap().is_empty()); 749 | assert!(c_str_ptr_to_str(c_map[0].perms[0]).unwrap().is_empty()); 750 | assert!(c_str_ptr_to_str(c_map[0].perms[1]).unwrap().is_empty()); 751 | assert!(c_map[0].perms[2].is_null()); 752 | assert!(c_map[1].name.is_null()); 753 | 754 | let mapping: &[(&str, &[&str]); 1] = &[("socket", &["bind"])]; 755 | let c_map = super::dynamic_mapping_into_native_form(mapping, &mut c_string_storage).unwrap(); 756 | assert_eq!(c_map.len(), 1 + mapping.len()); 757 | assert_eq!(c_str_ptr_to_str(c_map[0].name).unwrap(), "socket"); 758 | assert_eq!(c_str_ptr_to_str(c_map[0].perms[0]).unwrap(), "bind"); 759 | assert!(c_map[0].perms[1].is_null()); 760 | assert!(c_map[1].name.is_null()); 761 | 762 | let mapping: &[(&str, &[&str]); 2] = &[ 763 | ("socket", &["bind"]), 764 | ("file", &["create", "unlink", "read", "write"]), 765 | ]; 766 | let c_map = super::dynamic_mapping_into_native_form(mapping, &mut c_string_storage).unwrap(); 767 | assert_eq!(c_map.len(), 1 + mapping.len()); 768 | assert_eq!(c_str_ptr_to_str(c_map[0].name).unwrap(), "socket"); 769 | assert_eq!(c_str_ptr_to_str(c_map[0].perms[0]).unwrap(), "bind"); 770 | assert!(c_map[0].perms[1].is_null()); 771 | assert_eq!(c_str_ptr_to_str(c_map[1].name).unwrap(), "file"); 772 | assert_eq!(c_str_ptr_to_str(c_map[1].perms[0]).unwrap(), "create"); 773 | assert_eq!(c_str_ptr_to_str(c_map[1].perms[1]).unwrap(), "unlink"); 774 | assert_eq!(c_str_ptr_to_str(c_map[1].perms[2]).unwrap(), "read"); 775 | assert_eq!(c_str_ptr_to_str(c_map[1].perms[3]).unwrap(), "write"); 776 | assert!(c_map[1].perms[4].is_null()); 777 | assert!(c_map[2].name.is_null()); 778 | } 779 | 780 | #[test] 781 | fn security_context_list_of_se_user() { 782 | let mut se_list = super::SecurityContextList::of_se_user("unconfined_u", None, None).unwrap(); 783 | assert_eq!(se_list.as_ptr(), se_list.as_mut_ptr().cast()); 784 | assert!(!se_list.is_empty()); 785 | assert_ne!(se_list.len(), 0); 786 | 787 | assert!(se_list.get(usize::MAX, false).is_none()); 788 | let _context = se_list.get(0, false).unwrap(); 789 | 790 | let _ignored = format!("{:?}", &se_list); 791 | 792 | super::SecurityContextList::of_se_user("invalid", None, None).unwrap_err(); 793 | 794 | let _se_list = 795 | super::SecurityContextList::of_se_user("unconfined_u", Some("low"), None).unwrap(); 796 | 797 | let context = super::SecurityContext::current(false).unwrap(); 798 | let _se_list = 799 | super::SecurityContextList::of_se_user("unconfined_u", None, Some(&context)).unwrap(); 800 | } 801 | 802 | #[test] 803 | fn set_current_mode() { 804 | super::set_current_mode(super::SELinuxMode::NotRunning).unwrap_err(); 805 | super::set_current_mode(super::SELinuxMode::Permissive).unwrap_err(); 806 | super::set_current_mode(super::SELinuxMode::Enforcing).unwrap_err(); 807 | } 808 | 809 | #[test] 810 | fn se_user_and_level() { 811 | let (se_user, level) = super::se_user_and_level("root", None).unwrap(); 812 | assert!(!se_user.as_c_str().to_bytes().is_empty()); 813 | assert!(!level.as_c_str().to_bytes().is_empty()); 814 | 815 | let (_se_user, _level) = super::se_user_and_level("root", Some("file")).unwrap(); 816 | } 817 | 818 | #[test] 819 | fn reset_config() { 820 | super::reset_config(); 821 | } 822 | 823 | #[test] 824 | fn default_type_for_role() { 825 | //let _type = super::default_type_for_role("unconfined_r").unwrap(); 826 | } 827 | 828 | #[test] 829 | fn set_dynamic_mapping() { 830 | super::set_dynamic_mapping(&[] as &[(&str, &[&str])]).unwrap(); 831 | super::set_dynamic_mapping(&[("file", &["read", "write"] as &[&str])]).unwrap(); 832 | } 833 | --------------------------------------------------------------------------------