priv_sep

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

commit c698e8b5daba0e89d866d81f94e8889738b43c81
parent e26a313a744dff177da6b17fb99f2d8d5001a480
Author: Zack Newman <zack@philomathiclife.com>
Date:   Wed, 21 Aug 2024 16:02:34 -0600

rewrite promises

Diffstat:
MCargo.toml | 27+++++++++------------------
MLICENSE-MIT | 2+-
MREADME.md | 8++++----
Mbuild.rs | 19++++++++++---------
Msrc/lib.rs | 986+++++++++++++++++++++++++++++++++++++------------------------------------------
5 files changed, 482 insertions(+), 560 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -9,30 +9,21 @@ license = "MIT OR Apache-2.0" name = "priv_sep" readme = "README.md" repository = "https://git.philomathiclife.com/repos/priv_sep/" -version = "1.0.1" +rust-version = "1.77.0" +version = "2.0.0" -[lib] -name = "priv_sep" -path = "src/lib.rs" +[badges] +maintenance = { status = "actively-developed" } + +[package.metadata.docs.rs] +all-features = true [target.'cfg(target_os = "openbsd")'.dependencies] -libc = { version = "0.2.153", default-features = false, features = ["std"], optional = true } +libc = { version = "0.2.158", default-features = false, features = ["std"], optional = true } [build-dependencies] -rustc_version = "0.4.0" +rustc_version = { version = "0.4.0", default-features = false } [features] openbsd = ["dep:libc"] default = ["openbsd"] - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] - -[badges] -maintenance = { status = "actively-developed" } - -[profile.release] -lto = true -panic = 'abort' -strip = true diff --git a/LICENSE-MIT b/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright © 2023 Zack Newman +Copyright © 2024 Zack Newman Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md @@ -2,7 +2,7 @@ `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 +[`unveil(2)`](https://man.openbsd.org/amd64/unveil.2) for OpenBSD, but in the future may contain functionality for Linux's [`seccomp(2)`](https://man7.org/linux/man-pages/man2/seccomp.2.html). @@ -24,8 +24,8 @@ Any error returned from the underlying system call is propagated via [`Error`](h ### Status -This package will be actively maintained to stay in-sync with the latest version of OpenBSD-stable; as a result, +This package will be actively maintained to stay in-sync with the latest version of OpenBSD; as a result, 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. +library. If using -stable, it may be necessary to build the [`rust` port](https://github.com/openbsd/ports/tree/master/lang/rust) +from -current. diff --git a/build.rs b/build.rs @@ -1,12 +1,13 @@ use rustc_version::{version_meta, Channel}; - fn main() { - // Set cfg flags depending on release channel - let channel = match version_meta().unwrap().channel { - Channel::Stable => "CHANNEL_STABLE", - Channel::Beta => "CHANNEL_BETA", - Channel::Nightly => "CHANNEL_NIGHTLY", - Channel::Dev => "CHANNEL_DEV", - }; - println!("cargo:rustc-cfg={}", channel) + println!("cargo::rustc-check-cfg=cfg(channel_dev,channel_nightly,channel_beta,channel_stable)"); + println!( + "cargo::rustc-cfg=channel_{}", + match version_meta().map_or(Channel::Stable, |meta| meta.channel) { + Channel::Dev => "dev", + Channel::Nightly => "nightly", + Channel::Beta => "beta", + Channel::Stable => "stable", + } + ); } diff --git a/src/lib.rs b/src/lib.rs @@ -2,7 +2,7 @@ //! //! `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 +//! [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2) for OpenBSD, but //! in the future may contain functionality for Linux's //! [`seccomp(2)`](https://man7.org/linux/man-pages/man2/seccomp.2.html). //! @@ -19,12 +19,14 @@ //! ## Errors //! //! Any error returned from the underlying system call is propagated via [`io::Error`]. -#![cfg_attr(all(doc, CHANNEL_NIGHTLY), feature(doc_auto_cfg))] +#![cfg_attr(all(doc, channel_nightly), feature(doc_auto_cfg))] #![deny( + unknown_lints, future_incompatible, let_underscore, missing_docs, nonstandard_style, + refining_impl_trait, rust_2018_compatibility, rust_2018_idioms, rust_2021_compatibility, @@ -45,9 +47,12 @@ )] #![allow( clippy::blanket_clippy_restriction_lints, + clippy::doc_markdown, + clippy::exhaustive_structs, clippy::implicit_return, clippy::min_ident_chars, clippy::missing_trait_methods, + clippy::single_call_fn, clippy::single_char_lifetime_names, clippy::unseparated_literal_suffix )] @@ -58,9 +63,8 @@ use core::{ convert::AsRef, ffi::{c_char, c_int}, fmt::{self, Display, Formatter}, - ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Deref, Not}, + ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not}, ptr, - slice::Iter, }; use std::{error, io, os::unix::ffi::OsStrExt, path::Path}; use Promise::{ @@ -145,6 +149,49 @@ pub enum Promise { /// Consult `pledge(2)`. Wroute, } +impl Promise { + /// Returns `self` as a `u64`. + const fn to_u64(self) -> u64 { + match self { + Audio => 0x1, + Bpf => 0x2, + Chown => 0x4, + Cpath => 0x8, + Disklabel => 0x10, + Dns => 0x20, + Dpath => 0x40, + Drm => 0x80, + Error => 0x100, + Exec => 0x200, + Fattr => 0x400, + Flock => 0x800, + Getpw => 0x1000, + Id => 0x2000, + Inet => 0x4000, + Mcast => 0x8000, + Pf => 0x10000, + Proc => 0x20000, + ProtExec => 0x40000, + Ps => 0x80000, + Recvfd => 0x0010_0000, + Route => 0x0020_0000, + Rpath => 0x0040_0000, + Sendfd => 0x0080_0000, + Settime => 0x0100_0000, + Stdio => 0x0200_0000, + Tape => 0x0400_0000, + Tmppath => 0x0800_0000, + Tty => 0x1000_0000, + Unix => 0x2000_0000, + Unveil => 0x4000_0000, + Video => 0x8000_0000, + Vminfo => 0x0001_0000_0000, + Vmm => 0x0002_0000_0000, + Wpath => 0x0004_0000_0000, + Wroute => 0x0008_0000_0000, + } + } +} impl Display for Promise { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { @@ -205,21 +252,14 @@ impl PartialEq<Promise> for &Promise { /// 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. - /// `Promise`s starting from `len` are ones that have been "removed". - proms: [Promise; CAP], - /// The length of `vals`. - len: usize, -} +#[derive(Debug, Eq, PartialEq)] +pub struct Promises(u64); /// Invokes `pledge(2)` always passing `NULL` for `execpromises` and /// `promises` for `promises`. /// /// This function MUST only be called by `Promises::pledge` and /// `pledge_none`. #[allow(unsafe_code)] -#[inline] fn pledge(promises: *const c_char) -> Result<(), io::Error> { extern "C" { fn pledge(promises: *const c_char, execpromises: *const c_char) -> c_int; @@ -235,158 +275,131 @@ fn pledge(promises: *const c_char) -> Result<(), io::Error> { Err(io::Error::last_os_error()) } } -impl<const CAP: usize> Promises<CAP> { +impl Promises { + /// Empty `Promises`. + pub const NONE: Self = Self(0); + /// `Promises` containing all [`Promise`]s. + pub const ALL: Self = Self::NONE + .add(Promise::Audio) + .add(Promise::Bpf) + .add(Promise::Chown) + .add(Promise::Cpath) + .add(Promise::Disklabel) + .add(Promise::Dns) + .add(Promise::Dpath) + .add(Promise::Drm) + .add(Promise::Error) + .add(Promise::Exec) + .add(Promise::Fattr) + .add(Promise::Flock) + .add(Promise::Getpw) + .add(Promise::Id) + .add(Promise::Inet) + .add(Promise::Mcast) + .add(Promise::Pf) + .add(Promise::Proc) + .add(Promise::ProtExec) + .add(Promise::Ps) + .add(Promise::Recvfd) + .add(Promise::Route) + .add(Promise::Rpath) + .add(Promise::Sendfd) + .add(Promise::Settime) + .add(Promise::Stdio) + .add(Promise::Tape) + .add(Promise::Tmppath) + .add(Promise::Tty) + .add(Promise::Unix) + .add(Promise::Unveil) + .add(Promise::Video) + .add(Promise::Vminfo) + .add(Promise::Vmm) + .add(Promise::Wpath) + .add(Promise::Wroute); + /// Returns a `Promises` containing all `Promise`s in `self` and `promise`. + const fn add(self, promise: Promise) -> Self { + Self(self.0 | promise.to_u64()) + } /// Returns a `Promises` containing the unique `Promise`s in `initial`. /// - /// # Example + /// # Examples /// /// ``` - /// use priv_sep::{Promise, Promises}; - /// assert!(Promises::new([Promise::Stdio]).len() == 1); + /// # use priv_sep::{Promise, Promises}; + /// assert_eq!(Promises::new([Promise::Stdio]).len(), 1); /// ``` - #[allow(clippy::arithmetic_side_effects, clippy::indexing_slicing)] #[inline] #[must_use] - pub fn new(mut initial: [Promise; CAP]) -> Self { - // This is the first index where "removed" items begin. - let mut len = CAP; - // We "remove" duplicates by putting them at the end of `initial`. - for i in 0..CAP { - // We don't want to "remove" items that have been "removed" already. - if len > i { - // We start at one past the current index. - // No fear of overflow since the max value of `i` is one less than `CAP`. - let mut start = i + 1; - // We must check all items after `i`. When we find an item that is the same, - // we "remove" it by replacing it with the last active item. This is achieved by - // decrementing `len` (since we know we are removing one). By decrementing `len`, - // `len` becomes the last index of an active item which then allows us to swap it - // with the duplicate item. We keep the index the same in this situation since we need - // to check this item that has been swapped. - loop { - // We don't want to "remove" items that have been "removed" already. - if len > start { - // Check if the item at `start` is a duplicate. - // `i` is less than `CAP`, so indexing `initial` with it won't `panic`. - // `len` is always less than or equal to `CAP`, and `start` is less than it; - // so indexing `initial` with `start` won't `panic`. - if initial[i] == initial[start] { - // We "remove" the item at `start` by swapping it with the last item. - // No fear of underflow since `start` is positive and `len` is greater than it. - len -= 1; - initial.swap(start, len); - } else { - // The item at `start` is not the same as the item at `i`, so we check the next - // item. - // No fear of overflow since `start` is less than `len` which is less than or - // equal to `CAP`. - start += 1; - } - } else { - // We have checked all items after `i`, so we move on to the next. - break; - } - } - } else { - // We have "removed" all duplicate items. - break; - } - } - Self { - proms: initial, - len, - } + pub fn new<P: AsRef<[Promise]>>(initial: P) -> Self { + initial + .as_ref() + .iter() + .fold(Self::NONE, |val, promise| val.add(*promise)) } /// Returns the number of [`Promise`]s. /// - /// # Example + /// # Examples /// /// ``` - /// use priv_sep::{Promise, Promises}; - /// assert!(Promises::new([Promise::Stdio]).len() == 1); + /// # use priv_sep::{Promise, Promises}; + /// assert_eq!(Promises::new([Promise::Stdio]).len(), 1); /// ``` #[inline] #[must_use] - pub const fn len(&self) -> usize { - self.len + pub const fn len(&self) -> u32 { + self.0.count_ones() } /// Returns `true` iff there are no [`Promise`]s. - /// Returns the number of [`Promise`]s. /// - /// # Example + /// # Examples /// /// ``` - /// use priv_sep::Promises; - /// assert!(Promises::new([]).is_empty()); + /// # use priv_sep::Promises; + /// assert!(Promises::NONE.is_empty()); /// ``` #[inline] #[must_use] pub const fn is_empty(&self) -> bool { - self.len == 0 - } - /// Returns the set of `Promise`s. - /// - /// # Example - /// - /// ``` - /// use priv_sep::{Promise, Promises}; - /// assert!(Promises::new([Promise::Stdio]).vals().first().map_or(false, |promise| promise == Promise::Stdio)); - /// ``` - #[inline] - #[must_use] - pub fn vals(&self) -> &[Promise] { - &self.proms[..self.len] + self.len() == 0 } /// Returns `true` iff `self` contains `promise`. /// /// # Examples /// /// ``` - /// use priv_sep::{Promise, Promises}; + /// # use priv_sep::{Promise, Promises}; /// assert!(Promises::new([Promise::Stdio]).contains(Promise::Stdio)); /// assert!(!Promises::new([Promise::Stdio]).contains(Promise::Rpath)); /// ``` #[inline] #[must_use] - pub fn contains(&self, promise: Promise) -> bool { - self.vals().contains(&promise) + pub const fn contains(&self, promise: Promise) -> bool { + let val = promise.to_u64(); + self.0 & val == val } - /// Removes all [`Promise`]s except `promises`. + /// Removes all `Promise`s _not_ in `promises` from `self`. /// - /// Note that the internal order of `Promise`s may no longer be the same. - /// - /// # Example + /// # Examples /// /// ``` - /// use priv_sep::{Promise, Promises}; + /// # use priv_sep::{Promise, Promises}; /// let mut proms = Promises::new([Promise::Rpath, Promise::Stdio]); - /// proms.retain(Promises::new([Promise::Stdio])); - /// assert!(proms.len() == 1 && proms[0] == Promise::Stdio); + /// proms.retain([Promise::Stdio]); + /// assert!(proms.len() == 1 && proms.contains(Promise::Stdio)); /// ``` - #[allow(clippy::arithmetic_side_effects)] #[inline] pub fn retain<P: AsRef<[Promise]>>(&mut self, promises: P) { - let other = promises.as_ref(); - // We "check" if an item is in `promises`. If it is not, we "remove" it by swapping it with the - // the last item. - for i in 0..self.len { - // Since we swap items, we must re-check `i` with the item it was swapped with. - loop { - let Some(prom) = self.vals().get(i) else { - // We have no more items to check, so we are done. - return; - }; - if other.contains(prom) { - // `prom` needs to be retained, so we move to the next item. - break; + self.0 = promises + .as_ref() + .iter() + .fold(Self::NONE, |val, promise| { + if self.contains(*promise) { + val.add(*promise) + } else { + val } - // `prom` needs to be "removed", so we swap it with the last item and do the process again. - // `len` is at least 1; otherwise `self.vals().get(i)` would have returned `None` so this - // won't underflow. - self.len -= 1; - self.proms.swap(i, self.len); - } - } + }) + .0; } /// Same as [`Self::retain`] then [`Self::pledge`]; however this is "atomic" in that if an error occurs from `pledge`, /// `self` is left unchanged. This is useful when one wants to "recover" from an error since there is no @@ -401,7 +414,7 @@ impl<const CAP: usize> Promises<CAP> { /// # Example /// /// ```no_run - /// use priv_sep::{Promise, Promises}; + /// # use priv_sep::{Promise, Promises}; /// assert!(Promises::new([Promise::Rpath, Promise::Stdio]).retain_then_pledge([Promise::Rpath]).is_ok()); /// ``` #[inline] @@ -411,43 +424,33 @@ impl<const CAP: usize> Promises<CAP> { ) -> Result<(), io::Error> { // We opt for the easy way by copying `self`. This should be the fastest for the // typical case since `self` likely has very few `Promise`s, and it makes for less code. - let cur = *self; + let cur = Self(self.0); self.retain(promises); - if self.len < cur.len { + if *self == cur { + Ok(()) + } else { self.pledge().map_err(|err| { *self = cur; err }) - } else { - Ok(()) } } - /// Removes `promises` from `self`. - /// - /// Note that the internal order of `Promise`s may no longer be the same. + /// Removes all `Promise`s in `promises` from `self`. /// - /// # Example + /// # Examples /// /// ``` - /// use priv_sep::{Promise, Promises}; + /// # use priv_sep::{Promise, Promises}; /// let mut proms = Promises::new([Promise::Rpath, Promise::Stdio]); - /// proms.remove_promises(Promises::new([Promise::Stdio])); - /// assert!(proms.len() == 1 && proms[0] == Promise::Rpath); + /// proms.remove_promises([Promise::Stdio]); + /// assert!(proms.len() == 1 && proms.contains(Promise::Rpath)); /// ``` - #[allow(clippy::arithmetic_side_effects, clippy::into_iter_on_ref)] #[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() { - // We must "remove" the item. - if prom == prom2 { - // `len` is at least one; otherwise `self.vals().into_iter()` would have iterated `None`. - self.len -= 1; - self.proms.swap(idx, self.len); - break; - } - } - }); + promises + .as_ref() + .iter() + .fold((), |(), promise| self.remove(*promise)); } /// Same as [`Self::remove_promises`] then [`Self::pledge`]; however this is "atomic" in that if an error occurs from /// `pledge`, `self` is left unchanged. This is useful when one wants to "recover" from an error since there @@ -462,7 +465,7 @@ impl<const CAP: usize> Promises<CAP> { /// # Example /// /// ```no_run - /// use priv_sep::{Promise, Promises}; + /// # use priv_sep::{Promise, Promises}; /// assert!(Promises::new([Promise::Rpath, Promise::Stdio]).remove_promises_then_pledge([Promise::Rpath]).is_ok()); /// ``` #[inline] @@ -472,51 +475,35 @@ impl<const CAP: usize> Promises<CAP> { ) -> Result<(), io::Error> { // We opt for the easy way by copying `self`. This should be the fastest for the // typical case since `self` likely has very few `Promise`s, and it makes for less code. - let cur = *self; + let cur = Self(self.0); self.remove_promises(promises); - if self.len() < cur.len { + if *self == cur { + Ok(()) + } else { self.pledge().map_err(|err| { *self = cur; err }) - } else { - Ok(()) } } - /// Removes `promise` from `self` returning `true` iff `promise` existed. - /// - /// Note that the internal order of `Promise`s may no longer be the same. + /// Removes `promise` from `self`. /// - /// # Example + /// # Examples /// /// ``` - /// use priv_sep::{Promise, Promises}; + /// # use priv_sep::{Promise, Promises}; /// let mut proms = Promises::new([Promise::Rpath, Promise::Stdio]); - /// assert!(proms.remove(Promise::Stdio)); - /// assert!(proms.len() == 1 && proms[0] == Promise::Rpath); + /// proms.remove(Promise::Stdio); + /// assert!(proms.len() == 1 && proms.contains(Promise::Rpath)); /// ``` - #[allow(clippy::arithmetic_side_effects, clippy::into_iter_on_ref)] #[inline] - pub fn remove(&mut self, promise: Promise) -> bool { - for (idx, prom) in self.vals().into_iter().enumerate() { - // We must "remove" `prom`. - if *prom == promise { - // `len` is at least one; otherwise `self.vals().into_iter()` would have iterated `None`. - self.len -= 1; - self.proms.swap(idx, self.len); - // Since `self` contains unique `Promise`s, we don't have to check any more. - return true; - } - } - false + pub fn remove(&mut self, promise: Promise) { + self.0 &= !promise.to_u64(); } /// Same as [`Self::remove`] then [`Self::pledge`]; however this is "atomic" in that if an error occurs from `pledge`, /// `self` is left unchanged. This is useful when one wants to "recover" from an error since there is no /// way to add `Promise`s back forcing one to have to create a second `Promises`. /// - /// The contained `bool` in `Ok` is `true` iff `remove` returned `true`. Note that when `remove` returns - /// `false`, `pledge` is not called. - /// /// # Errors /// /// Returns [`io::Error`] iff `pledge` does. @@ -524,38 +511,24 @@ impl<const CAP: usize> Promises<CAP> { /// # Example /// /// ```no_run - /// use priv_sep::{Promise, Promises}; + /// # use priv_sep::{Promise, Promises}; /// assert!(Promises::new([Promise::Rpath, Promise::Stdio]).remove_then_pledge(Promise::Rpath).is_ok()); /// ``` #[inline] - pub fn remove_then_pledge(&mut self, promise: Promise) -> Result<bool, io::Error> { + pub fn remove_then_pledge(&mut self, promise: Promise) -> Result<(), io::Error> { // We opt for the easy way by copying `self`. This should be the fastest for the // typical case since `self` likely has very few `Promise`s, and it makes for less code. - let cur = *self; - if self.remove(promise) { - self.pledge() - .map_err(|err| { - *self = cur; - err - }) - .map(|()| true) + let cur = Self(self.0); + self.remove(promise); + if *self == cur { + Ok(()) } else { - Ok(false) + self.pledge().map_err(|err| { + *self = cur; + err + }) } } - /// Returns an [`Iterator`] without consuming `self`. - /// - /// # Example - /// - /// ``` - /// use priv_sep::{Promise, Promises}; - /// assert!(Promises::new([Promise::Rpath, Promise::Stdio]).iter().next().map_or(false, |prom| prom == Promise::Rpath)); - /// ``` - #[allow(clippy::into_iter_on_ref)] - #[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`. /// @@ -563,61 +536,130 @@ impl<const CAP: usize> Promises<CAP> { /// /// Returns [`io::Error`] iff `pledge(2)` errors. /// - /// # Example + /// # Examples /// /// ```no_run - /// use priv_sep::{Promise, Promises}; + /// # use priv_sep::{Promise, Promises}; /// assert!(Promises::new([Promise::Stdio]).pledge().is_ok()); /// ``` - #[inline] #[allow( unsafe_code, clippy::arithmetic_side_effects, + clippy::cognitive_complexity, clippy::indexing_slicing, - clippy::into_iter_on_ref + clippy::too_many_lines )] + #[inline] pub fn pledge(&self) -> Result<(), io::Error> { let mut p = Vec::new(); - self.vals().into_iter().fold((), |(), promise| { - p.extend_from_slice(match *promise { - Audio => b"audio ", - Bpf => b"bpf ", - Chown => b"chown ", - Cpath => b"cpath ", - Disklabel => b"disklabel ", - Dns => b"dns ", - Dpath => b"dpath ", - Drm => b"drm ", - Error => b"error ", - Exec => b"exec ", - Fattr => b"fattr ", - Flock => b"flock ", - Getpw => b"getpw ", - Id => b"id ", - Inet => b"inet ", - Mcast => b"mcast ", - Pf => b"pf ", - Proc => b"proc ", - ProtExec => b"prot_exec ", - Ps => b"ps ", - Recvfd => b"recvfd ", - Route => b"route ", - Rpath => b"rpath ", - Sendfd => b"sendfd ", - Settime => b"settime ", - Stdio => b"stdio ", - Tape => b"tape ", - Tmppath => b"tmppath ", - Tty => b"tty ", - Unix => b"unix ", - Unveil => b"unveil ", - Video => b"video ", - Vminfo => b"vminfo ", - Vmm => b"vmm ", - Wpath => b"wpath ", - Wroute => b"wroute ", - }); - }); + if self.contains(Audio) { + p.extend_from_slice(b"audio "); + } + if self.contains(Bpf) { + p.extend_from_slice(b"bpf "); + } + if self.contains(Chown) { + p.extend_from_slice(b"chown "); + } + if self.contains(Cpath) { + p.extend_from_slice(b"cpath "); + } + if self.contains(Disklabel) { + p.extend_from_slice(b"disklabel "); + } + if self.contains(Dns) { + p.extend_from_slice(b"dns "); + } + if self.contains(Dpath) { + p.extend_from_slice(b"dpath "); + } + if self.contains(Drm) { + p.extend_from_slice(b"drm "); + } + if self.contains(Error) { + p.extend_from_slice(b"error "); + } + if self.contains(Exec) { + p.extend_from_slice(b"exec "); + } + if self.contains(Fattr) { + p.extend_from_slice(b"fattr "); + } + if self.contains(Flock) { + p.extend_from_slice(b"flock "); + } + if self.contains(Getpw) { + p.extend_from_slice(b"getpw "); + } + if self.contains(Id) { + p.extend_from_slice(b"id "); + } + if self.contains(Inet) { + p.extend_from_slice(b"inet "); + } + if self.contains(Mcast) { + p.extend_from_slice(b"mcast "); + } + if self.contains(Pf) { + p.extend_from_slice(b"pf "); + } + if self.contains(Proc) { + p.extend_from_slice(b"proc "); + } + if self.contains(ProtExec) { + p.extend_from_slice(b"prot_exec "); + } + if self.contains(Ps) { + p.extend_from_slice(b"ps "); + } + if self.contains(Recvfd) { + p.extend_from_slice(b"recvfd "); + } + if self.contains(Route) { + p.extend_from_slice(b"route "); + } + if self.contains(Rpath) { + p.extend_from_slice(b"rpath "); + } + if self.contains(Sendfd) { + p.extend_from_slice(b"sendfd "); + } + if self.contains(Settime) { + p.extend_from_slice(b"settime "); + } + if self.contains(Stdio) { + p.extend_from_slice(b"stdio "); + } + if self.contains(Tape) { + p.extend_from_slice(b"tape "); + } + if self.contains(Tmppath) { + p.extend_from_slice(b"tmppath "); + } + if self.contains(Tty) { + p.extend_from_slice(b"tty "); + } + if self.contains(Unix) { + p.extend_from_slice(b"unix "); + } + if self.contains(Unveil) { + p.extend_from_slice(b"unveil "); + } + if self.contains(Video) { + p.extend_from_slice(b"video "); + } + if self.contains(Vminfo) { + p.extend_from_slice(b"vminfo "); + } + if self.contains(Vmm) { + p.extend_from_slice(b"vmm "); + } + if self.contains(Wpath) { + p.extend_from_slice(b"wpath "); + } + if self.contains(Wroute) { + p.extend_from_slice(b"wroute "); + } let idx = p.len(); if idx == 0 { p.push(0); @@ -636,95 +678,16 @@ impl<const CAP: usize> Promises<CAP> { pledge(arg.as_ptr()) } } -impl<const CAP: usize> AsRef<[Promise]> for Promises<CAP> { +impl PartialEq<&Self> for Promises { #[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; - #[allow(clippy::arithmetic_side_effects)] - #[inline] - fn next(&mut self) -> Option<Self::Item> { - self.prom.vals().get(self.idx).map(|prom| { - // `idx` must be less than `CAP`, since `get` returned `Some`. - self.idx += 1; - *prom - }) - } -} -impl<const CAP: usize> ExactSizeIterator for PromIter<CAP> { - #[allow(clippy::arithmetic_side_effects)] - #[inline] - fn len(&self) -> usize { - // `idx` is <= `len`. - self.prom.len - self.idx - } -} -impl<const CAP: usize> DoubleEndedIterator for PromIter<CAP> { - #[allow(clippy::arithmetic_side_effects)] - #[inline] - fn next_back(&mut self) -> Option<Self::Item> { - self.prom.proms[self.idx..self.prom.len].last().map(|prom| { - // `len` is at least 1; otherwise the above slice would be empty which means `last` would - // have returned `None`. - 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() + fn eq(&self, other: &&Self) -> bool { + *self == **other } } -impl<const CAP: usize> From<[Promise; CAP]> for Promises<CAP> { +impl PartialEq<Promises> for &Promises { #[inline] - fn from(value: [Promise; CAP]) -> Self { - Self::new(value) + fn eq(&self, other: &Promises) -> bool { + **self == *other } } /// Invokes [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2) with `NULL` for @@ -750,7 +713,6 @@ pub fn pledge_none() -> Result<(), io::Error> { /// 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; @@ -766,62 +728,116 @@ fn unveil(path: *const c_char, permissions: *const c_char) -> Result<(), io::Err Err(io::Error::last_os_error()) } } -/// `permissions` to [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2). -#[allow(clippy::exhaustive_structs, clippy::struct_excessive_bools)] -#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct Permissions { - /// Consult `unveil(2)`. - pub create: bool, - /// Consult `unveil(2)`. - pub execute: bool, - /// Consult `unveil(2)`. - pub read: bool, - /// Consult `unveil(2)`. - pub write: bool, +/// A permission in [`Permissions`]. +#[allow(clippy::exhaustive_enums)] +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum Permission { + /// [c](https://man.openbsd.org/amd64/unveil.2#c). + Create, + /// [x](https://man.openbsd.org/amd64/unveil.2#x). + Execute, + /// [r](https://man.openbsd.org/amd64/unveil.2#r). + Read, + /// [w](https://man.openbsd.org/amd64/unveil.2#w). + Write, +} +impl Permission { + /// Transforms `self` into a `u8`. + const fn to_u8(self) -> u8 { + match self { + Self::Create => 1, + Self::Execute => 2, + Self::Read => 4, + Self::Write => 8, + } + } +} +impl PartialEq<&Self> for Permission { + #[inline] + fn eq(&self, other: &&Self) -> bool { + *self == **other + } +} +impl PartialEq<Permission> for &Permission { + #[inline] + fn eq(&self, other: &Permission) -> bool { + **self == *other + } } +/// `permissions` to [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2). +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Permissions(u8); 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, - }; + /// A `Permissions` with no [`Permission`]s enabled. + pub const NONE: Self = Self(0); + /// A `Permissions` with all [`Permission`]s enabled. + pub const ALL: Self = Self::NONE + .enable_inner(Permission::Create) + .enable_inner(Permission::Execute) + .enable_inner(Permission::Read) + .enable_inner(Permission::Write); + /// A `Permissions` with only [`Permission::Create`] enabled. + pub const CREATE: Self = Self::NONE.enable_inner(Permission::Create); + /// A `Permissions` with only [`Permission::Execute`] enabled. + pub const EXECUTE: Self = Self::NONE.enable_inner(Permission::Execute); + /// A `Permissions` with only [`Permission::Read`] enabled. + pub const READ: Self = Self::NONE.enable_inner(Permission::Read); + /// A `Permissions` with only [`Permission::Write`] enabled. + pub const WRITE: Self = Self::NONE.enable_inner(Permission::Write); + /// Same as [`enable`] but returns a new instance instead of mutating `self`. + const fn enable_inner(self, permission: Permission) -> Self { + Self(self.0 | permission.to_u8()) + } + /// Enables `permission` in `self`. + /// + /// # Examples + /// + /// ``` + /// # use priv_sep::{Permission, Permissions}; + /// let mut perms = Permissions::NONE; + /// perms.enable(Permission::Read); + /// assert_eq!(perms, Permissions::READ); + /// ``` + #[inline] + pub fn enable(&mut self, permission: Permission) { + self.0 |= permission.to_u8(); + } + /// Disables `permission` in `self`. + /// + /// # Examples + /// + /// ``` + /// # use priv_sep::{Permission, Permissions}; + /// let mut perms = Permissions::ALL; + /// perms.disable(Permission::Execute); + /// assert!( + /// perms.is_enabled(Permission::Create) + /// && perms.is_enabled(Permission::Read) + /// && perms.is_enabled(Permission::Write) + /// && !perms.is_enabled(Permission::Execute) + /// ); + /// assert!(perms.is_enabled(Permission::Create) && perms.is_enabled(Permission::Read) && perms.is_enabled(Permission::Write) && !perms.is_enabled(Permission::Execute)); + /// ``` + #[inline] + pub fn disable(&mut self, permission: Permission) { + self.0 &= !permission.to_u8(); + } + /// Returns `true` iff `self` has `permission` enabled. + /// + /// # Examples + /// + /// ``` + /// # use priv_sep::{Permission, Permissions}; + /// let perms = Permissions::CREATE; + /// assert!(perms.is_enabled(Permission::Create)); + /// assert!(!perms.is_enabled(Permission::Write)); + /// ``` + #[inline] + #[must_use] + pub const fn is_enabled(self, permission: Permission) -> bool { + let val = permission.to_u8(); + self.0 & val == val + } /// Invokes [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2) /// passing `path` for `path` and the contained permissions for `permissions`. /// @@ -833,47 +849,41 @@ impl Permissions { /// # Examples /// /// ```no_run - /// use std::io::ErrorKind; - /// use priv_sep::{Permissions, UnveilErr}; + /// # use std::io::ErrorKind; + /// # use priv_sep::{Permissions, UnveilErr}; /// assert!(Permissions::READ.unveil("/path/to/read").is_ok()); /// assert!(Permissions::READ.unveil("/path/does/not/exist").map_or_else( /// |err| match err { /// UnveilErr::Io(e) => e.kind() == ErrorKind::NotFound, /// UnveilErr::Nul(_) => false, /// }, - /// |_| false + /// |()| false /// )); /// ``` + #[allow(unsafe_code, clippy::arithmetic_side_effects)] #[inline] - #[allow( - unsafe_code, - clippy::arithmetic_side_effects, - clippy::as_conversions, - clippy::cast_lossless - )] pub fn unveil<P: AsRef<Path>>(self, path: P) -> Result<(), UnveilErr> { CString::new(path.as_ref().as_os_str().as_bytes()).map_or_else( |e| Err(UnveilErr::Nul(e)), |path_c| { - // `true as usize` is guaranteed to return 1, and `false as usize` is guaranteed to return - // 0. This means the max sum is 5. + // The max sum is 5, so overflow is not possible. let mut vec = Vec::with_capacity( - self.create as usize - + self.execute as usize - + self.read as usize - + self.write as usize + usize::from(self.is_enabled(Permission::Create)) + + usize::from(self.is_enabled(Permission::Execute)) + + usize::from(self.is_enabled(Permission::Read)) + + usize::from(self.is_enabled(Permission::Write)) + 1, ); - if self.create { + if self.is_enabled(Permission::Create) { vec.push(b'c'); } - if self.execute { + if self.is_enabled(Permission::Execute) { vec.push(b'x'); } - if self.read { + if self.is_enabled(Permission::Read) { vec.push(b'r'); } - if self.write { + if self.is_enabled(Permission::Write) { vec.push(b'w'); } vec.push(0); @@ -895,23 +905,34 @@ impl Display for Permissions { 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 { "" }, + if self.is_enabled(Permission::Create) { + "c" + } else { + "" + }, + if self.is_enabled(Permission::Execute) { + "x" + } else { + "" + }, + if self.is_enabled(Permission::Read) { + "r" + } else { + "" + }, + if self.is_enabled(Permission::Write) { + "w" + } else { + "" + }, ) } } -impl BitAnd<Self> for Permissions { +impl BitAnd 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, - } + Self(self.0 & rhs.0) } } impl BitAnd<&Self> for Permissions { @@ -935,13 +956,10 @@ impl<'a> BitAnd<Permissions> for &'a Permissions { *self & rhs } } -impl BitAndAssign<Self> for Permissions { +impl BitAndAssign 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; + self.0 &= rhs.0; } } impl BitAndAssign<&Self> for Permissions { @@ -950,16 +968,11 @@ impl BitAndAssign<&Self> for Permissions { *self &= *rhs; } } -impl BitOr<Self> for Permissions { +impl BitOr 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, - } + Self(self.0 | rhs.0) } } impl BitOr<&Self> for Permissions { @@ -983,13 +996,10 @@ impl<'a> BitOr<Permissions> for &'a Permissions { *self | rhs } } -impl BitOrAssign<Self> for Permissions { +impl BitOrAssign 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; + self.0 |= rhs.0; } } impl BitOrAssign<&Self> for Permissions { @@ -998,16 +1008,11 @@ impl BitOrAssign<&Self> for Permissions { *self |= *rhs; } } -impl BitXor<Self> for Permissions { +impl BitXor 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, - } + Self(self.0 ^ rhs.0) } } impl BitXor<&Self> for Permissions { @@ -1031,13 +1036,10 @@ impl<'a> BitXor<Permissions> for &'a Permissions { *self ^ rhs } } -impl BitXorAssign<Self> for Permissions { +impl BitXorAssign 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; + self.0 ^= rhs.0; } } impl BitXorAssign<&Self> for Permissions { @@ -1050,12 +1052,7 @@ 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, - } + Self(Self::ALL.0 & !self.0) } } impl Not for &Permissions { @@ -1130,11 +1127,15 @@ mod tests { _ = 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()); + drop(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()); + drop( + 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!(""); @@ -1160,82 +1161,8 @@ mod tests { Promise::Exec, ]); vals.retain([Promise::Error, Promise::Chown]); - vals.len() == 2 - && vals.contains(Promise::Chown) - && vals.vals().contains(&Promise::Error) + vals.len() == 2 && vals.contains(Promise::Chown) && 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::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!(Permissions::NONE.unveil(FILE_EXISTS).is_ok()); @@ -1254,13 +1181,16 @@ mod tests { assert!(fs::metadata(FILE_EXISTS).is_ok()); // The below tests that Promises can only be removed and not added. initial_promises.remove_promises([Promise::Unveil]); - assert!(initial_promises.remove(Promise::Rpath)); - assert!(!initial_promises.remove(Promise::Rpath)); + assert_eq!(initial_promises.len(), 2); + initial_promises.remove(Promise::Rpath); + assert_eq!(initial_promises.len(), 1); + initial_promises.remove(Promise::Rpath); + assert_eq!(initial_promises.len(), 1); 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. - //_ = fs::metadata(FILE_EXISTS); + // drop(fs::metadata(FILE_EXISTS)); } }