commit 1d851f90648606c63a8c956a75f7dbb4dbda6920
parent 4735b96619548aa1d2e931b787e3bf072a2fe286
Author: Zack Newman <zack@philomathiclife.com>
Date: Wed, 25 Oct 2023 12:27:22 -0600
complete refactor
Diffstat:
M | Cargo.toml | | | 2 | +- |
M | README.md | | | 53 | +++++++++-------------------------------------------- |
M | src/lib.rs | | | 891 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------ |
3 files changed, 699 insertions(+), 247 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.7.0"
+version = "0.8.0"
[lib]
name = "priv_sep"
diff --git a/README.md b/README.md
@@ -1,6 +1,6 @@
# `priv_sep`
-[`priv_sep`](https://docs.rs/priv_sep/latest/priv_sep) is a library for privilege separation.
+`priv_sep` is a library for privilege separation.
It is currently designed around [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2) and
[`unveil(2)`](https://man.openbsd.org/amd64/unveil.2) for OpenBSD-stable—that is correct, -stable not -current—but
in the future may contain functionality for Linux's
@@ -8,20 +8,19 @@ 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.
+Calls to `pledge(2)` are done via [`Promises::pledge`](https://docs.rs/priv_sep/latest/priv_sep/struct.Promises.html#method.pledge)
+and [`pledge_none`](https://docs.rs/priv_sep/latest/priv_sep/fn.pledge_none.html).
+Note that since the use of `execpromises` is quite rare, `NULL` is always
+used for it.
## Unveil
-Unlike `pledge(2)` which allows a large quantity of duplicate `promises` to be provided, `unveil(2)` allows a maximum
-of four `permissions` to be passed. For this reason, there are dedicated functions for each quantity of
-[`Permission`](https://docs.rs/priv_sep/latest/priv_sep/enum.Permission.html)s.
+Calls to `unveil(2)` are done via [`Permissions::unveil`](https://docs.rs/priv_sep/latest/priv_sep/struct.Permissions.html#method.unveil)
+and [`unveil_no_more`](https://docs.rs/priv_sep/latest/priv_sep/fn.unveil_no_more.html).
## Errors
-Any error returned from the underlying system call is propagated via
-[`Error`](https://doc.rust-lang.org/std/io/struct.Error.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.
+Any error returned from the underlying system call is propagated via [`Error`](https://doc.rust-lang.org/std/io/struct.Error.html).
### Status
@@ -29,38 +28,4 @@ This package will be actively maintained to stay in-sync with the latest version
the crate is only tested on the `x86_64-unknown-openbsd` target. While OpenBSD supports both the most recent
-release/-stable release as well as the previous version, only the most recent version will be supported by this
library. For that reason any removal of `promises` in subsequent releases of `pledge(2)` will lead to breaking
-changes in this library as the corresponding `Promise` variant will be removed.
-
-### Building and testing
-
-```bash
-laptop$ git clone https://git.philomathiclife.com/repos/priv_sep
-Cloning into 'priv_sep'...
-laptop$ cd priv_sep/
-laptop$ cargo build --release
- Updating crates.io index
- Compiling semver v1.0.18
- Compiling libc v0.2.149
- Compiling rustc_version v0.4.0
- Compiling priv_sep v0.7.0 (/home/zack/priv_sep)
- Finished release [optimized] target(s) in 1.90s
-laptop$ touch /home/zack/foo.txt && cargo t && rm /home/zack/foo.txt
- Compiling semver v1.0.18
- Compiling libc v0.2.149
- Compiling rustc_version v0.4.0
- Compiling priv_sep v0.7.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
-
-test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
- Doc-tests priv_sep
-
-running 0 tests
-
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
-```
+changes in this library as the corresponding `Promise` variant will be removed.
diff --git a/src/lib.rs b/src/lib.rs
@@ -1,26 +1,24 @@
-//! # `priv_sep`
-//!
+//! # `priv_sep`
+//!
//! `priv_sep` is a library for privilege separation.
//! It is currently designed around [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2) and
//! [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2) for OpenBSD-stable—that is correct, -stable not -current—but
//! in the future may contain functionality for Linux's
-//! [`seccomp(2)`](https://man7.org/linux/man-pages/man2/seccomp.2.html).
-//!
-//! ## Pledge
-//!
-//! It is very rare to use the `execpromises` parameter, so [`pledge`] only relies on [`Promise`]s.
-//!
-//! ## Unveil
-//!
-//! Unlike `pledge(2)` which allows a large quantity of duplicate `promises` to be provided, `unveil(2)` allows a maximum
-//! of four `permissions` to be passed. For this reason, there are dedicated functions for each quantity of
-//! [`Permission`]s.
-//!
-//! ## Errors
-//!
-//! Any error returned from the underlying system call is propagated via
-//! [`io::Error`]. 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.
+//! [`seccomp(2)`](https://man7.org/linux/man-pages/man2/seccomp.2.html).
+//!
+//! ## Pledge
+//!
+//! Calls to `pledge(2)` are done via [`Promises::pledge`] and [`pledge_none`].
+//! Note that since the use of `execpromises` is quite rare, `NULL` is always
+//! used for it.
+//!
+//! ## Unveil
+//!
+//! Calls to `unveil(2)` are done via [`Permissions::unveil`] and [`unveil_no_more`].
+//!
+//! ## Errors
+//!
+//! Any error returned from the underlying system call is propagated via [`io::Error`].
#![cfg_attr(all(doc, CHANNEL_NIGHTLY), feature(doc_auto_cfg))]
#![deny(
unsafe_code,
@@ -40,30 +38,35 @@
#![allow(
clippy::arithmetic_side_effects,
clippy::blanket_clippy_restriction_lints,
+ clippy::exhaustive_structs,
clippy::implicit_return,
clippy::into_iter_on_ref,
clippy::min_ident_chars,
clippy::missing_trait_methods,
+ clippy::single_char_lifetime_names,
clippy::unseparated_literal_suffix
)]
#![cfg(feature = "openbsd")]
extern crate alloc;
use alloc::ffi::{CString, NulError};
+use core::convert::AsRef;
use core::ffi::{c_char, c_int};
use core::fmt::{self, Display, Formatter};
+use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Deref, Not};
use core::ptr;
+use core::slice::Iter;
use std::error;
use std::io;
use std::os::unix::ffi::OsStrExt;
-use std::path::{Path, PathBuf};
+use std::path::Path;
use Promise::{
Audio, Bpf, Chown, Cpath, Disklabel, Dns, Dpath, Drm, Error, Exec, Fattr, Flock, Getpw, Id,
Inet, Mcast, Pf, Proc, ProtExec, Ps, Recvfd, Route, Rpath, Sendfd, Settime, Stdio, Tape,
Tmppath, Tty, Unix, Unveil, Video, Vminfo, Vmm, Wpath, Wroute,
};
-#[non_exhaustive]
-#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
/// A `promise` to [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2).
+#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
+#[non_exhaustive]
pub enum Promise {
/// Consult `pledge(2)`.
Audio,
@@ -181,23 +184,167 @@ impl Display for Promise {
}
}
}
-/// Invokes [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2) always passing in
-/// `NULL` for `execpromises`. When `None` is passed, then `NULL` is passed for `promises`. When `Some([].as_slice())`
-/// is passed, then `""` is passed for `promises`. Like the system call it wraps, duplicates are ignored.
+/// A set of [`Promise`]s that can only have `Promise`s removed after creation.
///
-/// # Errors
+/// Once a set of `promises` has been [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2)d,
+/// only a subset of those `promises` can be `pledge(2)`d again, so this type can be used
+/// to ensure that `Promise`s are never added but only removed from an initial set.
+#[derive(Clone, Copy, Debug)]
+pub struct Promises<const CAP: usize> {
+ /// The initial set of `Promise`s.
+ proms: [Promise; CAP],
+ /// The length of `vals`.
+ len: usize,
+}
+/// Invokes `pledge(2)` always passing `NULL` for `execpromises` and
+/// `promises` for `promises`.
///
-/// Returns [`io::Error`] iff `pledge(2)` errors.
+/// This function MUST only be called by `Promises::pledge` and
+/// `pledge_none`.
+#[allow(unsafe_code)]
#[inline]
-#[allow(unsafe_code, clippy::indexing_slicing, clippy::option_if_let_else)]
-pub fn pledge(promises: Option<&[Promise]>) -> Result<(), io::Error> {
+fn pledge(promises: *const c_char) -> Result<(), io::Error> {
extern "C" {
fn pledge(promises: *const c_char, execpromises: *const c_char) -> c_int;
}
- let arg: CString;
- let ptr = if let Some(prom) = promises {
+ // SAFETY:
+ // `pledge` is an FFI binding; thus requires unsafe code.
+ // `NULL` is always valid for `execpromises`, and `promises` meets the requirements of the `pledge(2)` call
+ // as can be verified in the only two functions that call this function:
+ // `Promises::pledge` and `pledge_none`.
+ if unsafe { pledge(promises, ptr::null()) } == 0i32 {
+ Ok(())
+ } else {
+ Err(io::Error::last_os_error())
+ }
+}
+impl<const CAP: usize> Promises<CAP> {
+ /// Returns a `Promises` containing the unique
+ /// `Promise`s in `initial`.
+ ///
+ /// Note that the order of `initial` may
+ /// not be retained.
+ #[allow(clippy::indexing_slicing)]
+ #[inline]
+ #[must_use]
+ pub fn new(mut initial: [Promise; CAP]) -> Self {
+ let mut len = CAP;
+ for i in 0..CAP {
+ if len > i {
+ let mut start = i + 1;
+ loop {
+ if len > start {
+ if initial[i] == initial[start] {
+ len -= 1;
+ initial.swap(start, len);
+ } else {
+ start += 1;
+ }
+ } else {
+ break;
+ }
+ }
+ } else {
+ break;
+ }
+ }
+ Self {
+ proms: initial,
+ len,
+ }
+ }
+ /// Returns the number of [`Promise`]s.
+ #[inline]
+ #[must_use]
+ pub const fn len(&self) -> usize {
+ self.len
+ }
+ /// Returns `true` iff there are no [`Promise`]s.
+ #[inline]
+ #[must_use]
+ pub const fn is_empty(&self) -> bool {
+ self.len == 0
+ }
+ /// Returns the set of `Promise`s.
+ #[inline]
+ #[must_use]
+ pub fn vals(&self) -> &[Promise] {
+ &self.proms[..self.len]
+ }
+ /// Returns `true` iff `self` contains `promise`.
+ #[inline]
+ #[must_use]
+ pub fn contains(&self, promise: Promise) -> bool {
+ self.vals().contains(&promise)
+ }
+ /// Removes all [`Promise`]s except `promises`.
+ ///
+ /// Note that the internal order of `Promise`s
+ /// may no longer be the same.
+ #[inline]
+ pub fn retain<P: AsRef<[Promise]>>(&mut self, promises: P) {
+ let other = promises.as_ref();
+ for i in 0..self.len {
+ loop {
+ let Some(prom) = self.vals().get(i) else {
+ return;
+ };
+ if other.contains(prom) {
+ break;
+ }
+ self.len -= 1;
+ self.proms.swap(i, self.len);
+ }
+ }
+ }
+ /// Removes `promises` from `self`.
+ ///
+ /// Note that the internal order of `Promise`s
+ /// may no longer be the same.
+ #[inline]
+ pub fn remove_promises<P: AsRef<[Promise]>>(&mut self, promises: P) {
+ promises.as_ref().into_iter().fold((), |(), prom| {
+ for (idx, prom2) in self.vals().into_iter().enumerate() {
+ if prom == prom2 {
+ self.len -= 1;
+ self.proms.swap(idx, self.len);
+ break;
+ }
+ }
+ });
+ }
+ /// Removes `promise` from `self` returning `true` iff
+ /// `promise` existed.
+ ///
+ /// Note that the internal order of `Promise`s
+ /// may no longer be the same.
+ #[inline]
+ pub fn remove(&mut self, promise: Promise) -> bool {
+ for (idx, prom) in self.vals().into_iter().enumerate() {
+ if *prom == promise {
+ self.len -= 1;
+ self.proms.swap(idx, self.len);
+ return true;
+ }
+ }
+ false
+ }
+ /// Returns an [`Iterator`] without consuming `self`.
+ #[inline]
+ pub fn iter(&self) -> Iter<'_, Promise> {
+ self.vals().into_iter()
+ }
+ /// Invokes [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2) always passing in
+ /// `NULL` for `execpromises` and its contained [`Promise`]s for `promises`.
+ ///
+ /// # Errors
+ ///
+ /// Returns [`io::Error`] iff `pledge(2)` errors.
+ #[inline]
+ #[allow(unsafe_code, clippy::indexing_slicing)]
+ pub fn pledge(&self) -> Result<(), io::Error> {
let mut p = Vec::new();
- prom.into_iter().fold((), |(), promise| {
+ self.vals().into_iter().fold((), |(), promise| {
p.extend_from_slice(match *promise {
Audio => b"audio ",
Bpf => b"bpf ",
@@ -247,47 +394,401 @@ pub fn pledge(promises: Option<&[Promise]>) -> Result<(), io::Error> {
p[idx - 1] = 0;
}
// SAFETY:
- // p was populated above with correct ASCII-encoding of the literal
+ // `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()
- };
+ // `p` ends with a 0/nul byte.
+ let arg = unsafe { CString::from_vec_with_nul_unchecked(p) };
+ pledge(arg.as_ptr())
+ }
+}
+impl<const CAP: usize> AsRef<[Promise]> for Promises<CAP> {
+ #[inline]
+ fn as_ref(&self) -> &[Promise] {
+ self.vals()
+ }
+}
+impl<const CAP: usize> Deref for Promises<CAP> {
+ type Target = [Promise];
+ #[inline]
+ fn deref(&self) -> &[Promise] {
+ self.vals()
+ }
+}
+impl<const CAP: usize, const CAP2: usize> PartialEq<Promises<CAP2>> for Promises<CAP> {
+ #[inline]
+ fn eq(&self, other: &Promises<CAP2>) -> bool {
+ self.len == other.len
+ && self
+ .iter()
+ .try_fold((), |(), prom| {
+ if other.contains(*prom) {
+ Ok(())
+ } else {
+ Err(())
+ }
+ })
+ .map_or(false, |()| true)
+ }
+}
+impl<const CAP: usize> Eq for Promises<CAP> {}
+/// An [`Iterator`] of [`Promise`]s
+pub struct PromIter<const CAP: usize> {
+ /// Source of `Promise`s.
+ prom: Promises<CAP>,
+ /// Current index.
+ idx: usize,
+}
+impl<const CAP: usize> Iterator for PromIter<CAP> {
+ type Item = Promise;
+ #[inline]
+ fn next(&mut self) -> Option<Self::Item> {
+ self.prom.vals().get(self.idx).map(|prom| {
+ self.idx += 1;
+ *prom
+ })
+ }
+}
+impl<const CAP: usize> ExactSizeIterator for PromIter<CAP> {
+ #[inline]
+ fn len(&self) -> usize {
+ self.prom.len - self.idx
+ }
+}
+impl<const CAP: usize> DoubleEndedIterator for PromIter<CAP> {
+ #[inline]
+ fn next_back(&mut self) -> Option<Self::Item> {
+ self.prom.proms[self.idx..self.prom.len].last().map(|prom| {
+ self.prom.len -= 1;
+ *prom
+ })
+ }
+}
+impl<const CAP: usize> IntoIterator for Promises<CAP> {
+ type Item = Promise;
+ type IntoIter = PromIter<CAP>;
+ #[inline]
+ fn into_iter(self) -> Self::IntoIter {
+ PromIter { prom: self, idx: 0 }
+ }
+}
+impl<'a, const CAP: usize> IntoIterator for &'a Promises<CAP> {
+ type Item = &'a Promise;
+ type IntoIter = Iter<'a, Promise>;
+ #[inline]
+ fn into_iter(self) -> Self::IntoIter {
+ self.iter()
+ }
+}
+impl<const CAP: usize> From<[Promise; CAP]> for Promises<CAP> {
+ #[inline]
+ fn from(value: [Promise; CAP]) -> Self {
+ Self::new(value)
+ }
+}
+/// Invokes [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2) with `NULL` for
+/// both `promises` and `execpromises`.
+///
+/// # Errors
+///
+/// Returns [`io::Error`] iff `pledge(2)` errors.
+#[inline]
+pub fn pledge_none() -> Result<(), io::Error> {
+ // `NULL` is always valid for `promises`.
+ pledge(ptr::null())
+}
+/// Invokes `unveil(2)` passing `path` for `path` and `permissions` for `permissions`.
+///
+/// This function MUST only be called by the functions `Permissions::unveil` and
+/// `unveil_no_more`.
+#[allow(unsafe_code)]
+#[inline]
+fn unveil(path: *const c_char, permissions: *const c_char) -> Result<(), io::Error> {
+ extern "C" {
+ fn unveil(path: *const c_char, permissions: *const c_char) -> c_int;
+ }
// SAFETY:
- // pledge is an FFI binding; thus requires unsafe code.
- // ptr meets the requirements of the pledge(2) call.
- match unsafe { pledge(ptr, ptr::null()) } {
- 0i32 => Ok(()),
- _ => Err(io::Error::last_os_error()),
+ // `unveil` is an FFI binding; thus requires unsafe code.
+ // `path` and `permissions` meet the requirements of the `unveil(2)` call
+ // as can be seen in the only functions that call this function:
+ // `Permissions::unveil` and `unveil_no_more`.
+ if unsafe { unveil(path, permissions) } == 0i32 {
+ Ok(())
+ } else {
+ Err(io::Error::last_os_error())
}
}
-/// A `permission` to [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2).
-#[allow(clippy::exhaustive_enums)]
-#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
-pub enum Permission {
+/// `permissions` to [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2).
+#[allow(clippy::struct_excessive_bools)]
+#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub struct Permissions {
/// Consult `unveil(2)`.
- Create,
+ pub create: bool,
/// Consult `unveil(2)`.
- Execute,
+ pub execute: bool,
/// Consult `unveil(2)`.
- Read,
+ pub read: bool,
/// Consult `unveil(2)`.
- Write,
+ pub write: bool,
}
-impl Display for Permission {
+impl Permissions {
+ /// Contains all fields as `true`.
+ pub const ALL: Self = Self {
+ create: true,
+ execute: true,
+ read: true,
+ write: true,
+ };
+ /// Contains all fields as `false`.
+ pub const NONE: Self = Self {
+ create: false,
+ execute: false,
+ read: false,
+ write: false,
+ };
+ /// A `Permissions` with only [`Permissions::create`] set to `true`.
+ pub const CREATE: Self = Self {
+ create: true,
+ execute: false,
+ read: false,
+ write: false,
+ };
+ /// A `Permissions` with only [`Permissions::execute`] set to `true`.
+ pub const EXECUTE: Self = Self {
+ create: false,
+ execute: true,
+ read: false,
+ write: false,
+ };
+ /// A `Permissions` with only [`Permissions::read`] set to `true`.
+ pub const READ: Self = Self {
+ create: false,
+ execute: false,
+ read: true,
+ write: false,
+ };
+ /// A `Permissions` with only [`Permissions::write`] set to `true`.
+ pub const WRITE: Self = Self {
+ create: false,
+ execute: false,
+ read: false,
+ write: true,
+ };
+ /// Invokes [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2)
+ /// passing `path` for `path` and the contained permissions for `permissions`.
+ ///
+ /// # Errors
+ ///
+ /// Returns [`NulError`] iff [`CString::new`] does.
+ /// Returns [`io::Error`] iff `unveil(2)` errors.
+ #[inline]
+ #[allow(unsafe_code)]
+ pub fn unveil<P: AsRef<Path>>(self, path: P) -> Result<(), UnveilErr> {
+ let mut vec = Vec::new();
+ if self.create {
+ vec.push(b'c');
+ }
+ if self.execute {
+ vec.push(b'x');
+ }
+ if self.read {
+ vec.push(b'r');
+ }
+ if self.write {
+ vec.push(b'w');
+ }
+ vec.push(0);
+ CString::new(path.as_ref().as_os_str().as_bytes()).map_or_else(
+ |e| Err(UnveilErr::Nul(e)),
+ |path_c| {
+ // SAFETY:
+ // `vec` was populated above with correct ASCII-encoding of the literal
+ // values all of which do not have 0 bytes.
+ // `vec` ends with a 0/nul byte.
+ let perm_c = unsafe { CString::from_vec_with_nul_unchecked(vec) };
+ let path_ptr = path_c.as_ptr();
+ let perm_ptr = perm_c.as_ptr();
+ unveil(path_ptr, perm_ptr).map_err(UnveilErr::Io)
+ },
+ )
+ }
+}
+impl Display for Permissions {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
- match *self {
- Self::Create => f.write_str("unveil(2) 'c' permission"),
- Self::Execute => f.write_str("unveil(2) 'x' permission"),
- Self::Read => f.write_str("unveil(2) 'r' permission"),
- Self::Write => f.write_str("unveil(2) 'w' permission"),
+ write!(
+ f,
+ "unveil(2) '{}{}{}{}' permissions",
+ if self.create { "c" } else { "" },
+ if self.execute { "x" } else { "" },
+ if self.read { "r" } else { "" },
+ if self.write { "w" } else { "" },
+ )
+ }
+}
+impl BitAnd<Self> for Permissions {
+ type Output = Self;
+ #[inline]
+ fn bitand(self, rhs: Self) -> Self::Output {
+ Self {
+ create: self.create & rhs.create,
+ execute: self.execute & rhs.execute,
+ read: self.read & rhs.read,
+ write: self.write & rhs.write,
+ }
+ }
+}
+impl BitAnd<&Self> for Permissions {
+ type Output = Self;
+ #[inline]
+ fn bitand(self, rhs: &Self) -> Self::Output {
+ self & *rhs
+ }
+}
+impl BitAnd<&Permissions> for &Permissions {
+ type Output = Permissions;
+ #[inline]
+ fn bitand(self, rhs: &Permissions) -> Self::Output {
+ *self & *rhs
+ }
+}
+impl<'a> BitAnd<Permissions> for &'a Permissions {
+ type Output = Permissions;
+ #[inline]
+ fn bitand(self, rhs: Permissions) -> Self::Output {
+ *self & rhs
+ }
+}
+impl BitAndAssign<Self> for Permissions {
+ #[inline]
+ fn bitand_assign(&mut self, rhs: Self) {
+ self.create &= rhs.create;
+ self.execute &= rhs.execute;
+ self.read &= rhs.read;
+ self.write &= rhs.write;
+ }
+}
+impl BitAndAssign<&Self> for Permissions {
+ #[inline]
+ fn bitand_assign(&mut self, rhs: &Self) {
+ *self &= *rhs;
+ }
+}
+impl BitOr<Self> for Permissions {
+ type Output = Self;
+ #[inline]
+ fn bitor(self, rhs: Self) -> Self::Output {
+ Self {
+ create: self.create | rhs.create,
+ execute: self.execute | rhs.execute,
+ read: self.read | rhs.read,
+ write: self.write | rhs.write,
}
}
}
-/// Error returned by the `unveil` functions.
+impl BitOr<&Self> for Permissions {
+ type Output = Self;
+ #[inline]
+ fn bitor(self, rhs: &Self) -> Self::Output {
+ self | *rhs
+ }
+}
+impl BitOr<&Permissions> for &Permissions {
+ type Output = Permissions;
+ #[inline]
+ fn bitor(self, rhs: &Permissions) -> Self::Output {
+ *self | *rhs
+ }
+}
+impl<'a> BitOr<Permissions> for &'a Permissions {
+ type Output = Permissions;
+ #[inline]
+ fn bitor(self, rhs: Permissions) -> Self::Output {
+ *self | rhs
+ }
+}
+impl BitOrAssign<Self> for Permissions {
+ #[inline]
+ fn bitor_assign(&mut self, rhs: Self) {
+ self.create |= rhs.create;
+ self.execute |= rhs.execute;
+ self.read |= rhs.read;
+ self.write |= rhs.write;
+ }
+}
+impl BitOrAssign<&Self> for Permissions {
+ #[inline]
+ fn bitor_assign(&mut self, rhs: &Self) {
+ *self |= *rhs;
+ }
+}
+impl BitXor<Self> for Permissions {
+ type Output = Self;
+ #[inline]
+ fn bitxor(self, rhs: Self) -> Self::Output {
+ Self {
+ create: self.create ^ rhs.create,
+ execute: self.execute ^ rhs.execute,
+ read: self.read ^ rhs.read,
+ write: self.write ^ rhs.write,
+ }
+ }
+}
+impl BitXor<&Self> for Permissions {
+ type Output = Self;
+ #[inline]
+ fn bitxor(self, rhs: &Self) -> Self::Output {
+ self ^ *rhs
+ }
+}
+impl BitXor<&Permissions> for &Permissions {
+ type Output = Permissions;
+ #[inline]
+ fn bitxor(self, rhs: &Permissions) -> Self::Output {
+ *self ^ *rhs
+ }
+}
+impl<'a> BitXor<Permissions> for &'a Permissions {
+ type Output = Permissions;
+ #[inline]
+ fn bitxor(self, rhs: Permissions) -> Self::Output {
+ *self ^ rhs
+ }
+}
+impl BitXorAssign<Self> for Permissions {
+ #[inline]
+ fn bitxor_assign(&mut self, rhs: Self) {
+ self.create ^= rhs.create;
+ self.execute ^= rhs.execute;
+ self.read ^= rhs.read;
+ self.write ^= rhs.write;
+ }
+}
+impl BitXorAssign<&Self> for Permissions {
+ #[inline]
+ fn bitxor_assign(&mut self, rhs: &Self) {
+ *self ^= *rhs;
+ }
+}
+impl Not for Permissions {
+ type Output = Self;
+ #[inline]
+ fn not(self) -> Self::Output {
+ Self {
+ create: !self.create,
+ execute: !self.execute,
+ read: !self.read,
+ write: !self.write,
+ }
+ }
+}
+impl Not for &Permissions {
+ type Output = Permissions;
+ #[inline]
+ fn not(self) -> Self::Output {
+ !*self
+ }
+}
+/// Error returned by [`Permissions::unveil`].
#[allow(clippy::exhaustive_enums)]
#[derive(Debug)]
pub enum UnveilErr {
@@ -311,173 +812,159 @@ impl Display for UnveilErr {
}
}
impl error::Error for UnveilErr {}
-/// Invokes [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2).
-///
-/// # Errors
-///
-/// Returns `NulError` iff `CString::new` does.
-/// Returns `Error` iff `unveil(2)` errors.
-/// This is a private function and uses `Option` for the path to indicate calling `unveil(2)` with
-/// two `NULL` arguments.
-#[inline]
-#[allow(unsafe_code, clippy::indexing_slicing)]
-fn unveil<P: AsRef<Path>, const N: usize>(
- path: Option<P>,
- permissions: [Permission; N],
-) -> Result<(), UnveilErr> {
- extern "C" {
- fn unveil(path: *const c_char, permissions: *const c_char) -> c_int;
- }
- let path_c: CString;
- let perm_c: CString;
- let (fst, snd) = if let Some(p) = path {
- let mut v = Vec::new();
- permissions.into_iter().fold((), |(), perm| {
- v.push(match perm {
- Permission::Create => b'c',
- Permission::Execute => b'x',
- Permission::Read => b'r',
- Permission::Write => b'w',
- });
- });
- v.push(0);
- match CString::new(p.as_ref().as_os_str().as_bytes()) {
- Ok(s) => {
- path_c = s;
- // SAFETY:
- // v was populated above with correct ASCII-encoding of the literal
- // values all of which do not have 0 bytes.
- // v ends with a 0/nul byte.
- perm_c = unsafe { CString::from_vec_with_nul_unchecked(v) };
- (path_c.as_ptr(), perm_c.as_ptr())
- }
- Err(e) => return Err(UnveilErr::Nul(e)),
- }
- } else {
- (ptr::null(), ptr::null())
- };
- // SAFETY:
- // unveil is an FFI binding; thus requires unsafe code.
- // fst and snd meet the requirements of the unveil(2) call.
- match unsafe { unveil(fst, snd) } {
- 0i32 => Ok(()),
- _ => Err(UnveilErr::Io(io::Error::last_os_error())),
- }
-}
/// Invokes [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2) by passing `NULL` for both `path` and `permissions`.
///
/// # Errors
///
/// Returns [`io::Error`] when a problem occurs.
-#[allow(clippy::unreachable)]
#[inline]
pub fn unveil_no_more() -> Result<(), io::Error> {
- unveil::<PathBuf, 0>(None, []).map_err(|e| match e {
- UnveilErr::Io(err) => err,
- UnveilErr::Nul(_) => unreachable!("There is a bug in unveil."),
- })
-}
-/// Invokes [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2) by passing `path` for `path` and `""` for `permissions`.
-///
-/// # Errors
-///
-/// Returns [`UnveilErr`] when a problem occurs.
-#[inline]
-pub fn unveil_no_perms<P: AsRef<Path>>(path: P) -> Result<(), UnveilErr> {
- unveil(Some(path), [])
-}
-/// Invokes [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2) by passing `path` for `path` and `perm` for `permissions`.
-///
-/// # Errors
-///
-/// Returns [`UnveilErr`] when a problem occurs.
-#[inline]
-pub fn unveil_one_perm<P: AsRef<Path>>(path: P, perm: Permission) -> Result<(), UnveilErr> {
- unveil(Some(path), [perm])
-}
-/// Invokes [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2) by passing `path` for `path` and `perms` for `permissions`.
-///
-/// # Errors
-///
-/// Returns [`UnveilErr`] when a problem occurs.
-#[inline]
-pub fn unveil_two_perms<P: AsRef<Path>>(path: P, perms: [Permission; 2]) -> Result<(), UnveilErr> {
- unveil(Some(path), perms)
-}
-/// Invokes [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2) by passing `path` for `path` and `perms` for `permissions`.
-///
-/// # Errors
-///
-/// Returns [`UnveilErr`] when a problem occurs.
-#[inline]
-pub fn unveil_three_perms<P: AsRef<Path>>(
- path: P,
- perms: [Permission; 3],
-) -> Result<(), UnveilErr> {
- unveil(Some(path), perms)
-}
-/// Invokes [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2) by passing `path` for `path` and `"cxrw"` for `permissions`.
-///
-/// # Errors
-///
-/// Returns [`UnveilErr`] when a problem occurs.
-#[inline]
-pub fn unveil_all_perms<P: AsRef<Path>>(path: P) -> Result<(), UnveilErr> {
- unveil(
- Some(path),
- [
- Permission::Create,
- Permission::Execute,
- Permission::Read,
- Permission::Write,
- ],
- )
+ // `NULL` is valid for both `path` and `permissions`.
+ unveil(ptr::null(), ptr::null())
}
#[cfg(all(test, target_os = "openbsd"))]
mod tests {
- use super::{Permission, Promise};
+ use crate::{Permissions, Promise, Promises};
+ use std::fs;
// We only have one test since we must force the order of pledge/unveil calls.
- // The file /home/zack/foo.txt must exist before calling.
- // The file /home/zack/aadkjfasj3s23.2lk3h and the directory /home/zack/aadkjfasj3s23/2lk3h
- // must not exist before calling.
#[test]
+ #[ignore]
fn test() {
- // This tests that a NULL pledge does nothing.
- assert!(super::pledge::<0>(None).is_ok());
+ const FILE_EXISTS: &str = "/home/zack/foo.txt";
+ _ = fs::metadata(FILE_EXISTS)
+ .expect(format!("{FILE_EXISTS} does not exist, so unit testing cannot occur").as_str());
+ const FILE_NOT_EXISTS: &str = "/home/zack/aadkjfasj3s23";
+ _ = fs::metadata(FILE_NOT_EXISTS)
+ .expect_err(format!("{FILE_NOT_EXISTS} exists, so unit testing cannot occur").as_str());
+ const DIR_NOT_EXISTS: &str = "/home/zack/aadkjfasj3s23/";
+ _ = fs::metadata(DIR_NOT_EXISTS)
+ .expect_err(format!("{DIR_NOT_EXISTS} exists, so unit testing cannot occur").as_str());
+ // This tests that a NULL `promise` does nothing.
+ assert!(crate::pledge_none().is_ok());
print!("");
- // This tests that duplicate pledges are ignored.
- assert!(super::pledge(Some([
+ // This tests that duplicates are ignored as well as the implementation of PartialEq.
+ let mut initial_promises = Promises::new([
Promise::Stdio,
+ Promise::Unveil,
+ Promise::Rpath,
Promise::Stdio,
+ ]);
+ assert!(initial_promises.len() == 3);
+ assert!(
+ initial_promises == Promises::new([Promise::Rpath, Promise::Stdio, Promise::Unveil])
+ );
+ // Test retain.
+ assert!({
+ let mut vals = Promises::new([
+ Promise::Audio,
+ Promise::Bpf,
+ Promise::Chown,
+ Promise::Cpath,
+ Promise::Error,
+ Promise::Exec,
+ ]);
+ vals.retain([Promise::Error, Promise::Chown]);
+ vals.len() == 2
+ && vals.contains(Promise::Chown)
+ && vals.vals().contains(&Promise::Error)
+ });
+ // This tests iteration.
+ // After "removal" of duplicates,
+ // this should be [Stdio, Unveil, Error, Rpath] in
+ // some order.
+ let mut proms = Promises::new([
Promise::Unveil,
- Promise::Rpath
- ]))
- .is_ok());
- print!("");
+ Promise::Unveil,
+ Promise::Stdio,
+ Promise::Unveil,
+ Promise::Unveil,
+ Promise::Rpath,
+ Promise::Error,
+ Promise::Stdio,
+ Promise::Rpath,
+ Promise::Error,
+ ])
+ .into_iter();
+ assert!(proms.len() == 4);
+ let mut unveil = false;
+ let mut stdio = false;
+ let mut rpath = false;
+ let mut error = false;
+ let mut closure = |prom: Promise| -> bool {
+ match prom {
+ Promise::Unveil => {
+ if unveil {
+ return false;
+ } else {
+ unveil = true;
+ true
+ }
+ }
+ Promise::Rpath => {
+ if rpath {
+ return false;
+ } else {
+ rpath = true;
+ true
+ }
+ }
+ Promise::Error => {
+ if error {
+ return false;
+ } else {
+ error = true;
+ true
+ }
+ }
+ Promise::Stdio => {
+ if stdio {
+ return false;
+ } else {
+ stdio = true;
+ true
+ }
+ }
+ _ => false,
+ }
+ };
+ assert!(proms.next().map_or(false, &mut closure));
+ assert!(proms.len() == 3);
+ assert!(proms.next_back().map_or(false, &mut closure));
+ assert!(proms.len() == 2);
+ assert!(proms.next_back().map_or(false, &mut closure));
+ assert!(proms.len() == 1);
+ assert!(proms.next().map_or(false, &mut closure));
+ assert!(proms.len() == 0);
+ assert!(unveil && stdio && error && rpath);
+ assert!(proms.next().is_none());
+ assert!(proms.next_back().is_none());
+ assert!(proms.next().is_none());
+ assert!(proms.next_back().is_none());
+ assert!(initial_promises.pledge().is_ok());
// This tests unveil with no permissions.
- assert!(super::unveil_no_perms("/home/zack/foo.txt").is_ok());
- assert!(std::fs::metadata("/home/zack/foo.txt").is_err());
- // This tests unveil with read permissions, duplicates are ignored,
+ assert!(Permissions::NONE.unveil(FILE_EXISTS).is_ok());
+ assert!(fs::metadata(FILE_EXISTS).is_err());
+ // This tests unveil with read permissions,
// and one can unveil more permissions (unlike pledge which can only remove promises).
- assert!(super::unveil_two_perms(
- "/home/zack/foo.txt",
- [Permission::Read, Permission::Read]
- )
- .is_ok());
- assert!(std::fs::metadata("/home/zack/foo.txt").is_ok());
+ assert!(Permissions::READ.unveil(FILE_EXISTS).is_ok());
+ assert!(fs::metadata(FILE_EXISTS).is_ok());
// This tests that calls to unveil on missing files don't error.
- assert!(super::unveil_no_perms("/home/zack/aadkjfasj3s23.2lk3h").is_ok());
+ assert!(Permissions::NONE.unveil(FILE_NOT_EXISTS).is_ok());
// This tests that calls to unveil on missing directories error.
- assert!(super::unveil_no_perms("/home/zack/aadkjfasj3s23/2lk3h").is_err());
+ assert!(Permissions::NONE.unveil(DIR_NOT_EXISTS).is_err());
// This tests that unveil can no longer be called.
- assert!(super::unveil_no_more().is_ok());
- assert!(super::unveil_no_perms("/home/zack/foo.txt").is_err());
- assert!(std::fs::metadata("/home/zack/foo.txt").is_ok());
+ assert!(crate::unveil_no_more().is_ok());
+ assert!(Permissions::NONE.unveil(FILE_EXISTS).is_err());
+ assert!(fs::metadata(FILE_EXISTS).is_ok());
// The below tests that Promises can only be removed and not added.
- assert!(super::pledge(Some([Promise::Stdio])).is_ok());
- assert!(super::pledge(Some([Promise::Rpath])).is_err());
+ initial_promises.remove_promises([Promise::Unveil]);
+ assert!(initial_promises.remove(Promise::Rpath));
+ assert!(!initial_promises.remove(Promise::Rpath));
+ assert!(initial_promises.pledge().is_ok());
+ print!("");
+ assert!(Promises::new([Promise::Rpath]).pledge().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");
+ //_ = fs::metadata(FILE_EXISTS);
}
}