priv_sep

Privilege separation library.
git clone https://git.philomathiclife.com/repos/priv_sep
Log | Files | Refs | README

commit 2501b48791b95bf517d1cdbfef25a53afb88be08
parent 210dc8897be1454f42e44d41095a2061f34a5877
Author: Zack Newman <zack@philomathiclife.com>
Date:   Tue, 25 Jul 2023 22:15:18 -0600

add "" promises to pledge.

Diffstat:
MCargo.toml | 2+-
MREADME.md | 19++++++++++++-------
Msrc/lib.rs | 50+++++++++++++++++++++++++++-----------------------
3 files changed, 40 insertions(+), 31 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" name = "priv_sep" readme = "README.md" repository = "https://git.philomathiclife.com/repos/priv_sep/" -version = "0.2.0" +version = "0.3.0" [lib] name = "priv_sep" diff --git a/README.md b/README.md @@ -8,8 +8,7 @@ but in the future may contain functionality for Linux's ## Pledge -It is very rare to use the `execpromises` parameter, so [`pledge`](https://docs.rs/priv_sep/latest/priv_sep/fn.pledge.html) only relies on [`Promise`](https://docs.rs/priv_sep/latest/priv_sep/enum.Promise.html)s. -Additionally this function cannot be used to invoke `pledge(2)` with a `promises` value of `""`. +It is very rare to use the `execpromises` parameter, so [`pledge`](https://docs.rs/priv_sep/latest/priv_sep/fn.pledge.html) only relies on [`Promise`](https://docs.rs/priv_sep/latest/priv_sep/enum.Promise.html)s. ## Unveil @@ -39,12 +38,18 @@ Cloning into 'priv_sep'... laptop$ cd priv_sep/ laptop$ cargo build --release Updating crates.io index - Compiling priv_sep v0.1.0 (/home/zack/projects/priv_sep) - Finished release [optimized] target(s) in 1.35s + Compiling semver v1.0.18 + Compiling libc v0.2.147 + Compiling rustc_version v0.4.0 + Compiling priv_sep v0.3.0 (/home/zack/priv_sep) + Finished release [optimized] target(s) in 1.90s laptop$ cargo t - Compiling priv_sep v0.1.0 (/home/zack/projects/priv_sep) - Finished test [unoptimized + debuginfo] target(s) in 0.33s - Running unittests src/lib.rs (target/debug/deps/priv_sep-2b69b951a87c3632) + Compiling semver v1.0.18 + Compiling libc v0.2.147 + Compiling rustc_version v0.4.0 + Compiling priv_sep v0.3.0 (/home/zack/priv_sep) + Finished test [unoptimized + debuginfo] target(s) in 1.43s + Running unittests src/lib.rs (target/debug/deps/priv_sep-dcb151b099a76f20) running 1 test test tests::test ... ok diff --git a/src/lib.rs b/src/lib.rs @@ -8,8 +8,7 @@ //! //! ## Pledge //! -//! It is very rare to use the `execpromises` parameter, so [`pledge`] only relies on [`Promise`]s. -//! Additionally this function cannot be used to invoke `pledge(2)` with a `promises` value of `""`. +//! It is very rare to use the `execpromises` parameter, so [`pledge`] only relies on [`Promise`]s. //! //! ## Unveil //! @@ -22,6 +21,7 @@ //! Any error returned from the underlying system call is propagated via //! [`c_int`](https://doc.rust-lang.org/core/ffi/type.c_int.html). Note for both `pledge(2)` and `unveil(2)` duplicates //! are ignored, so it is not an error to pass in duplicate values for their corresponding functions in this crate. +#![cfg_attr(all(doc, CHANNEL_NIGHTLY), feature(doc_auto_cfg))] #![deny( unsafe_code, unused, @@ -58,6 +58,7 @@ use Promise::{ Tmppath, Tty, Unix, Unveil, Video, Vminfo, Vmm, Wpath, Wroute, }; #[non_exhaustive] +#[derive(Clone, Copy)] /// A `promise` to [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2). pub enum Promise { /// Consult `pledge(2)`. @@ -134,27 +135,24 @@ pub enum Promise { Wroute, } /// Invokes [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2) always passing in -/// `NULL` for `execpromises`. When no [`Promise`]s are passed, then `NULL` is passed -/// for `promises`. There is no way to pass `""` for `promises`. Like the system call -/// it wraps, duplicates are ignored. +/// `NULL` for `execpromises`. When `None` is passed, then `NULL` is passed for `promises`. When `Some([])` +/// is passed, then `""` is passed for `promises`. Like the system call it wraps, duplicates are ignored. /// /// # Errors /// /// Will return [`c_int`](https://doc.rust-lang.org/stable/core/ffi/type.c_int.html) iff /// `pledge(2)` does. #[inline] -#[allow(unsafe_code, clippy::indexing_slicing, clippy::into_iter_on_ref)] -pub fn pledge<const N: usize>(promises: &[Promise; N]) -> Result<(), c_int> { +#[allow(unsafe_code, clippy::indexing_slicing, clippy::option_if_let_else)] +pub fn pledge<const N: usize>(promises: Option<[Promise; N]>) -> Result<(), c_int> { extern "C" { fn pledge(promises: *const c_char, execpromises: *const c_char) -> c_int; } let arg: CString; - let ptr = if promises.is_empty() { - ptr::null() - } else { + let ptr = if let Some(prom) = promises { let mut p = Vec::new(); - promises.into_iter().fold((), |_, promise| { - p.extend_from_slice(match *promise { + prom.into_iter().fold((), |_, promise| { + p.extend_from_slice(match promise { Audio => b"audio ", Bpf => b"bpf ", Chown => b"chown ", @@ -193,17 +191,23 @@ pub fn pledge<const N: usize>(promises: &[Promise; N]) -> Result<(), c_int> { Wroute => b"wroute ", }); }); - // All promises have a space after them which means - // we must replace the last promise's space with - // 0/nul byte. - let idx = p.len() - 1; - p[idx] = 0; + let idx = p.len(); + if idx == 0 { + p.push(0); + } else { + // All promises have a space after them which means + // we must replace the last promise's space with + // 0/nul byte. + p[idx - 1] = 0; + } // SAFETY: // p was populated above with correct ASCII-encoding of the literal // values all of which do not have 0 bytes. // p ends with a 0/nul byte. arg = unsafe { CString::from_vec_with_nul_unchecked(p) }; arg.as_ptr() + } else { + ptr::null() }; // SAFETY: // pledge is an FFI binding; thus requires unsafe code. @@ -369,7 +373,7 @@ pub fn unveil_all_perms<P: AsRef<Path>>(path: P) -> Result<(), UnveilErr> { ], ) } -#[cfg(test)] +#[cfg(all(test, target_os = "openbsd"))] mod tests { use super::{Permission, Promise}; // We only have one test since we must force the order of pledge/unveil calls. @@ -379,15 +383,15 @@ mod tests { #[test] fn test() { // This tests that a NULL pledge does nothing. - assert!(super::pledge(&[]).is_ok()); + assert!(super::pledge::<0>(None).is_ok()); print!(""); // This tests that duplicate pledges are ignored. - assert!(super::pledge(&[ + assert!(super::pledge(Some([ Promise::Stdio, Promise::Stdio, Promise::Unveil, Promise::Rpath - ]) + ])) .is_ok()); print!(""); // This tests unveil with no permissions. @@ -410,8 +414,8 @@ mod tests { assert!(super::unveil_no_perms("/home/zack/foo.txt").is_err()); assert!(std::fs::metadata("/home/zack/foo.txt").is_ok()); // The below tests that Promises can only be removed and not added. - assert!(super::pledge(&[Promise::Stdio]).is_ok()); - assert!(super::pledge(&[Promise::Rpath]).is_err()); + assert!(super::pledge(Some([Promise::Stdio])).is_ok()); + assert!(super::pledge(Some([Promise::Rpath])).is_err()); // If the below is uncommented, the program should crash since the above // call to pledge no longer allows access to the file system. //_ = std::fs::metadata("/home/zack/foo.txt");