priv_sep

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

lib.rs (39574B)


      1 //! [![git]](https://git.philomathiclife.com/priv_sep/log.html) [![crates-io]](https://crates.io/crates/priv_sep) [![docs-rs]](crate)
      2 //!
      3 //! [git]: https://git.philomathiclife.com/git_badge.svg
      4 //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
      5 //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
      6 //!
      7 //! `priv_sep` is a library for privilege separation.
      8 //! It is currently designed around [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2) and
      9 //! [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2) for OpenBSD, but
     10 //! in the future may contain functionality for Linux's
     11 //! [`seccomp(2)`](https://man7.org/linux/man-pages/man2/seccomp.2.html).
     12 //!
     13 //! ## Pledge
     14 //!
     15 //! Calls to `pledge(2)` are done via [`Promises::pledge`] and [`pledge_none`].
     16 //! Note that since the use of `execpromises` is quite rare, `NULL` is always
     17 //! used for it.
     18 //!
     19 //! ## Unveil
     20 //!
     21 //! Calls to `unveil(2)` are done via [`Permissions::unveil`] and [`unveil_no_more`].
     22 //!
     23 //! ## Errors
     24 //!
     25 //! Any error returned from the underlying system call is propagated via [`io::Error`].
     26 #![cfg_attr(docsrs, feature(doc_cfg))]
     27 #![deny(
     28     unknown_lints,
     29     future_incompatible,
     30     let_underscore,
     31     missing_docs,
     32     nonstandard_style,
     33     refining_impl_trait,
     34     rust_2018_compatibility,
     35     rust_2018_idioms,
     36     rust_2021_compatibility,
     37     rust_2024_compatibility,
     38     unsafe_code,
     39     unused,
     40     warnings,
     41     clippy::all,
     42     clippy::cargo,
     43     clippy::complexity,
     44     clippy::correctness,
     45     clippy::nursery,
     46     clippy::pedantic,
     47     clippy::perf,
     48     clippy::restriction,
     49     clippy::style,
     50     clippy::suspicious
     51 )]
     52 #![expect(
     53     clippy::blanket_clippy_restriction_lints,
     54     reason = "same reason as below"
     55 )]
     56 #![cfg_attr(docsrs, doc(cfg(feature = "openbsd")))]
     57 #![cfg(feature = "openbsd")]
     58 #![expect(
     59     clippy::arbitrary_source_item_ordering,
     60     clippy::exhaustive_enums,
     61     clippy::implicit_return,
     62     clippy::min_ident_chars,
     63     clippy::missing_trait_methods,
     64     clippy::ref_patterns,
     65     clippy::unseparated_literal_suffix,
     66     reason = "noisy, opinionated, and likely doesn't prevent bugs or improve APIs"
     67 )]
     68 extern crate alloc;
     69 use Promise::{
     70     Audio, Bpf, Chown, Cpath, Disklabel, Dns, Dpath, Drm, Error, Exec, Fattr, Flock, Getpw, Id,
     71     Inet, Mcast, Pf, Proc, ProtExec, Ps, Recvfd, Route, Rpath, Sendfd, Settime, Stdio, Tape,
     72     Tmppath, Tty, Unix, Unveil, Video, Vminfo, Vmm, Wpath, Wroute,
     73 };
     74 use alloc::ffi::{CString, NulError};
     75 use core::{
     76     convert::AsRef,
     77     error,
     78     ffi::{c_char, c_int},
     79     fmt::{self, Display, Formatter},
     80     ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not},
     81     ptr,
     82 };
     83 use std::{io, os::unix::ffi::OsStrExt as _, path::Path};
     84 /// A `promise` to [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2).
     85 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
     86 #[non_exhaustive]
     87 pub enum Promise {
     88     /// Consult `pledge(2)`.
     89     Audio,
     90     /// Consult `pledge(2)`.
     91     Bpf,
     92     /// Consult `pledge(2)`.
     93     Chown,
     94     /// Consult `pledge(2)`.
     95     Cpath,
     96     /// Consult `pledge(2)`.
     97     Disklabel,
     98     /// Consult `pledge(2)`.
     99     Dns,
    100     /// Consult `pledge(2)`.
    101     Dpath,
    102     /// Consult `pledge(2)`.
    103     Drm,
    104     /// Consult `pledge(2)`.
    105     Error,
    106     /// Consult `pledge(2)`.
    107     Exec,
    108     /// Consult `pledge(2)`.
    109     Fattr,
    110     /// Consult `pledge(2)`.
    111     Flock,
    112     /// Consult `pledge(2)`.
    113     Getpw,
    114     /// Consult `pledge(2)`.
    115     Id,
    116     /// Consult `pledge(2)`.
    117     Inet,
    118     /// Consult `pledge(2)`.
    119     Mcast,
    120     /// Consult `pledge(2)`.
    121     Pf,
    122     /// Consult `pledge(2)`.
    123     Proc,
    124     /// Consult `pledge(2)`.
    125     ProtExec,
    126     /// Consult `pledge(2)`.
    127     Ps,
    128     /// Consult `pledge(2)`.
    129     Recvfd,
    130     /// Consult `pledge(2)`.
    131     Route,
    132     /// Consult `pledge(2)`.
    133     Rpath,
    134     /// Consult `pledge(2)`.
    135     Sendfd,
    136     /// Consult `pledge(2)`.
    137     Settime,
    138     /// Consult `pledge(2)`.
    139     Stdio,
    140     /// Consult `pledge(2)`.
    141     Tape,
    142     /// Consult `pledge(2)`.
    143     Tmppath,
    144     /// Consult `pledge(2)`.
    145     Tty,
    146     /// Consult `pledge(2)`.
    147     Unix,
    148     /// Consult `pledge(2)`.
    149     Unveil,
    150     /// Consult `pledge(2)`.
    151     Video,
    152     /// Consult `pledge(2)`.
    153     Vminfo,
    154     /// Consult `pledge(2)`.
    155     Vmm,
    156     /// Consult `pledge(2)`.
    157     Wpath,
    158     /// Consult `pledge(2)`.
    159     Wroute,
    160 }
    161 impl Promise {
    162     /// Returns `self` as a `u64`.
    163     const fn to_u64(self) -> u64 {
    164         match self {
    165             Audio => 0x1,
    166             Bpf => 0x2,
    167             Chown => 0x4,
    168             Cpath => 0x8,
    169             Disklabel => 0x10,
    170             Dns => 0x20,
    171             Dpath => 0x40,
    172             Drm => 0x80,
    173             Error => 0x100,
    174             Exec => 0x200,
    175             Fattr => 0x400,
    176             Flock => 0x800,
    177             Getpw => 0x1000,
    178             Id => 0x2000,
    179             Inet => 0x4000,
    180             Mcast => 0x8000,
    181             Pf => 0x10000,
    182             Proc => 0x20000,
    183             ProtExec => 0x40000,
    184             Ps => 0x80000,
    185             Recvfd => 0x0010_0000,
    186             Route => 0x0020_0000,
    187             Rpath => 0x0040_0000,
    188             Sendfd => 0x0080_0000,
    189             Settime => 0x0100_0000,
    190             Stdio => 0x0200_0000,
    191             Tape => 0x0400_0000,
    192             Tmppath => 0x0800_0000,
    193             Tty => 0x1000_0000,
    194             Unix => 0x2000_0000,
    195             Unveil => 0x4000_0000,
    196             Video => 0x8000_0000,
    197             Vminfo => 0x0001_0000_0000,
    198             Vmm => 0x0002_0000_0000,
    199             Wpath => 0x0004_0000_0000,
    200             Wroute => 0x0008_0000_0000,
    201         }
    202     }
    203 }
    204 impl Display for Promise {
    205     #[inline]
    206     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
    207         match *self {
    208             Audio => f.write_str("pledge(2) 'audio' promise"),
    209             Bpf => f.write_str("pledge(2) 'bpf' promise"),
    210             Chown => f.write_str("pledge(2) 'chown' promise"),
    211             Cpath => f.write_str("pledge(2) 'cpath' promise"),
    212             Disklabel => f.write_str("pledge(2) 'disklabel' promise"),
    213             Dns => f.write_str("pledge(2) 'dns' promise"),
    214             Dpath => f.write_str("pledge(2) 'dpath' promise"),
    215             Drm => f.write_str("pledge(2) 'drm' promise"),
    216             Error => f.write_str("pledge(2) 'error' promise"),
    217             Exec => f.write_str("pledge(2) 'exec' promise"),
    218             Fattr => f.write_str("pledge(2) 'fattr' promise"),
    219             Flock => f.write_str("pledge(2) 'flock' promise"),
    220             Getpw => f.write_str("pledge(2) 'getpw' promise"),
    221             Id => f.write_str("pledge(2) 'id' promise"),
    222             Inet => f.write_str("pledge(2) 'inet' promise"),
    223             Mcast => f.write_str("pledge(2) 'mcast' promise"),
    224             Pf => f.write_str("pledge(2) 'pf' promise"),
    225             Proc => f.write_str("pledge(2) 'proc' promise"),
    226             ProtExec => f.write_str("pledge(2) 'prot_exec' promise"),
    227             Ps => f.write_str("pledge(2) 'ps' promise"),
    228             Recvfd => f.write_str("pledge(2) 'recvfd' promise"),
    229             Route => f.write_str("pledge(2) 'route' promise"),
    230             Rpath => f.write_str("pledge(2) 'rpath' promise"),
    231             Sendfd => f.write_str("pledge(2) 'sendfd' promise"),
    232             Settime => f.write_str("pledge(2) 'settime' promise"),
    233             Stdio => f.write_str("pledge(2) 'stdio' promise"),
    234             Tape => f.write_str("pledge(2) 'tape' promise"),
    235             Tmppath => f.write_str("pledge(2) 'tmppath' promise"),
    236             Tty => f.write_str("pledge(2) 'tty' promise"),
    237             Unix => f.write_str("pledge(2) 'unix' promise"),
    238             Unveil => f.write_str("pledge(2) 'unveil' promise"),
    239             Video => f.write_str("pledge(2) 'video' promise"),
    240             Vminfo => f.write_str("pledge(2) 'vminfo' promise"),
    241             Vmm => f.write_str("pledge(2) 'vmm' promise"),
    242             Wpath => f.write_str("pledge(2) 'wpath' promise"),
    243             Wroute => f.write_str("pledge(2) 'wroute' promise"),
    244         }
    245     }
    246 }
    247 impl PartialEq<&Self> for Promise {
    248     #[inline]
    249     fn eq(&self, other: &&Self) -> bool {
    250         *self == **other
    251     }
    252 }
    253 impl PartialEq<Promise> for &Promise {
    254     #[inline]
    255     fn eq(&self, other: &Promise) -> bool {
    256         **self == *other
    257     }
    258 }
    259 /// A set of [`Promise`]s that can only have `Promise`s removed after creation.
    260 ///
    261 /// Once a set of `promises` has been [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2)d,
    262 /// only a subset of those `promises` can be `pledge(2)`d again, so this type can be used
    263 /// to ensure that `Promise`s are never added but only removed from an initial set.
    264 #[derive(Debug, Eq, PartialEq)]
    265 pub struct Promises(u64);
    266 /// Invokes `pledge(2)` always passing `NULL` for `execpromises` and
    267 /// `promises` for `promises`.
    268 ///
    269 /// This function MUST only be called by `Promises::pledge` and
    270 /// `pledge_none`.
    271 #[expect(unsafe_code, reason = "FFI requires unsafe code")]
    272 fn pledge(promises: *const c_char) -> Result<(), io::Error> {
    273     unsafe extern "C" {
    274         fn pledge(promises: *const c_char, execpromises: *const c_char) -> c_int;
    275     }
    276     // SAFETY:
    277     // `pledge` is an FFI binding; thus requires unsafe code.
    278     // `NULL` is always valid for `execpromises`, and `promises` meets the requirements of the `pledge(2)` call
    279     // as can be verified in the only two functions that call this function:
    280     // `Promises::pledge` and `pledge_none`.
    281     if unsafe { pledge(promises, ptr::null()) } == 0i32 {
    282         Ok(())
    283     } else {
    284         Err(io::Error::last_os_error())
    285     }
    286 }
    287 impl Promises {
    288     /// Empty `Promises`.
    289     pub const NONE: Self = Self(0);
    290     /// `Promises` containing all [`Promise`]s.
    291     pub const ALL: Self = Self::NONE
    292         .add(Promise::Audio)
    293         .add(Promise::Bpf)
    294         .add(Promise::Chown)
    295         .add(Promise::Cpath)
    296         .add(Promise::Disklabel)
    297         .add(Promise::Dns)
    298         .add(Promise::Dpath)
    299         .add(Promise::Drm)
    300         .add(Promise::Error)
    301         .add(Promise::Exec)
    302         .add(Promise::Fattr)
    303         .add(Promise::Flock)
    304         .add(Promise::Getpw)
    305         .add(Promise::Id)
    306         .add(Promise::Inet)
    307         .add(Promise::Mcast)
    308         .add(Promise::Pf)
    309         .add(Promise::Proc)
    310         .add(Promise::ProtExec)
    311         .add(Promise::Ps)
    312         .add(Promise::Recvfd)
    313         .add(Promise::Route)
    314         .add(Promise::Rpath)
    315         .add(Promise::Sendfd)
    316         .add(Promise::Settime)
    317         .add(Promise::Stdio)
    318         .add(Promise::Tape)
    319         .add(Promise::Tmppath)
    320         .add(Promise::Tty)
    321         .add(Promise::Unix)
    322         .add(Promise::Unveil)
    323         .add(Promise::Video)
    324         .add(Promise::Vminfo)
    325         .add(Promise::Vmm)
    326         .add(Promise::Wpath)
    327         .add(Promise::Wroute);
    328     /// Returns a `Promises` containing all `Promise`s in `self` and `promise`.
    329     const fn add(self, promise: Promise) -> Self {
    330         Self(self.0 | promise.to_u64())
    331     }
    332     /// Returns a `Promises` containing the unique `Promise`s in `initial`.
    333     ///
    334     /// # Examples
    335     ///
    336     /// ```
    337     /// # use priv_sep::{Promise, Promises};
    338     /// assert_eq!(Promises::new([Promise::Stdio]).len(), 1);
    339     /// ```
    340     #[inline]
    341     #[must_use]
    342     pub fn new<P: AsRef<[Promise]>>(initial: P) -> Self {
    343         initial
    344             .as_ref()
    345             .iter()
    346             .fold(Self::NONE, |val, promise| val.add(*promise))
    347     }
    348     /// Returns the number of [`Promise`]s.
    349     ///
    350     /// # Examples
    351     ///
    352     /// ```
    353     /// # use priv_sep::{Promise, Promises};
    354     /// assert_eq!(Promises::new([Promise::Stdio]).len(), 1);
    355     /// ```
    356     #[inline]
    357     #[must_use]
    358     pub const fn len(&self) -> u32 {
    359         self.0.count_ones()
    360     }
    361     /// Returns `true` iff there are no [`Promise`]s.
    362     ///
    363     /// # Examples
    364     ///
    365     /// ```
    366     /// # use priv_sep::Promises;
    367     /// assert!(Promises::NONE.is_empty());
    368     /// ```
    369     #[inline]
    370     #[must_use]
    371     pub const fn is_empty(&self) -> bool {
    372         self.len() == 0
    373     }
    374     /// Returns `true` iff `self` contains `promise`.
    375     ///
    376     /// # Examples
    377     ///
    378     /// ```
    379     /// # use priv_sep::{Promise, Promises};
    380     /// assert!(Promises::new([Promise::Stdio]).contains(Promise::Stdio));
    381     /// assert!(!Promises::new([Promise::Stdio]).contains(Promise::Rpath));
    382     /// ```
    383     #[inline]
    384     #[must_use]
    385     pub const fn contains(&self, promise: Promise) -> bool {
    386         let val = promise.to_u64();
    387         self.0 & val == val
    388     }
    389     /// Removes all `Promise`s _not_ in `promises` from `self`.
    390     ///
    391     /// # Examples
    392     ///
    393     /// ```
    394     /// # use priv_sep::{Promise, Promises};
    395     /// let mut proms = Promises::new([Promise::Rpath, Promise::Stdio]);
    396     /// proms.retain([Promise::Stdio]);
    397     /// assert!(proms.len() == 1 && proms.contains(Promise::Stdio));
    398     /// ```
    399     #[inline]
    400     pub fn retain<P: AsRef<[Promise]>>(&mut self, promises: P) {
    401         self.0 = promises
    402             .as_ref()
    403             .iter()
    404             .fold(Self::NONE, |val, promise| {
    405                 if self.contains(*promise) {
    406                     val.add(*promise)
    407                 } else {
    408                     val
    409                 }
    410             })
    411             .0;
    412     }
    413     /// Same as [`Self::retain`] then [`Self::pledge`]; however this is "atomic" in that if an error occurs from `pledge`,
    414     /// `self` is left unchanged. This is useful when one wants to "recover" from an error since there is no
    415     /// way to add `Promise`s back forcing one to have to create a second `Promises`.
    416     ///
    417     /// Note that when `retain` doesn't change `self`, `pledge` is not called.
    418     ///
    419     /// # Errors
    420     ///
    421     /// Returns [`io::Error`] iff `pledge` does.
    422     ///
    423     /// # Example
    424     ///
    425     /// ```no_run
    426     /// # use priv_sep::{Promise, Promises};
    427     /// assert!(Promises::new([Promise::Rpath, Promise::Stdio]).retain_then_pledge([Promise::Rpath]).is_ok());
    428     /// ```
    429     #[inline]
    430     pub fn retain_then_pledge<P: AsRef<[Promise]>>(
    431         &mut self,
    432         promises: P,
    433     ) -> Result<(), io::Error> {
    434         // We opt for the easy way by copying `self`. This should be the fastest for the
    435         // typical case since `self` likely has very few `Promise`s, and it makes for less code.
    436         let cur = Self(self.0);
    437         self.retain(promises);
    438         if *self == cur {
    439             Ok(())
    440         } else {
    441             self.pledge().inspect_err(|_| *self = cur)
    442         }
    443     }
    444     /// Removes all `Promise`s in `promises` from `self`.
    445     ///
    446     /// # Examples
    447     ///
    448     /// ```
    449     /// # use priv_sep::{Promise, Promises};
    450     /// let mut proms = Promises::new([Promise::Rpath, Promise::Stdio]);
    451     /// proms.remove_promises([Promise::Stdio]);
    452     /// assert!(proms.len() == 1 && proms.contains(Promise::Rpath));
    453     /// ```
    454     #[inline]
    455     pub fn remove_promises<P: AsRef<[Promise]>>(&mut self, promises: P) {
    456         promises
    457             .as_ref()
    458             .iter()
    459             .fold((), |(), promise| self.remove(*promise));
    460     }
    461     /// Same as [`Self::remove_promises`] then [`Self::pledge`]; however this is "atomic" in that if an error occurs from
    462     /// `pledge`, `self` is left unchanged. This is useful when one wants to "recover" from an error since there
    463     /// is no way to add `Promise`s back forcing one to have to create a second `Promises`.
    464     ///
    465     /// Note that when `remove_promises` doesn't remove any, `pledge` is not called.
    466     ///
    467     /// # Errors
    468     ///
    469     /// Returns [`io::Error`] iff `pledge` does.
    470     ///
    471     /// # Example
    472     ///
    473     /// ```no_run
    474     /// # use priv_sep::{Promise, Promises};
    475     /// assert!(Promises::new([Promise::Rpath, Promise::Stdio]).remove_promises_then_pledge([Promise::Rpath]).is_ok());
    476     /// ```
    477     #[inline]
    478     pub fn remove_promises_then_pledge<P: AsRef<[Promise]>>(
    479         &mut self,
    480         promises: P,
    481     ) -> Result<(), io::Error> {
    482         // We opt for the easy way by copying `self`. This should be the fastest for the
    483         // typical case since `self` likely has very few `Promise`s, and it makes for less code.
    484         let cur = Self(self.0);
    485         self.remove_promises(promises);
    486         if *self == cur {
    487             Ok(())
    488         } else {
    489             self.pledge().inspect_err(|_| *self = cur)
    490         }
    491     }
    492     /// Removes `promise` from `self`.
    493     ///
    494     /// # Examples
    495     ///
    496     /// ```
    497     /// # use priv_sep::{Promise, Promises};
    498     /// let mut proms = Promises::new([Promise::Rpath, Promise::Stdio]);
    499     /// proms.remove(Promise::Stdio);
    500     /// assert!(proms.len() == 1 && proms.contains(Promise::Rpath));
    501     /// ```
    502     #[inline]
    503     pub const fn remove(&mut self, promise: Promise) {
    504         self.0 &= !promise.to_u64();
    505     }
    506     /// Same as [`Self::remove`] then [`Self::pledge`]; however this is "atomic" in that if an error occurs from `pledge`,
    507     /// `self` is left unchanged. This is useful when one wants to "recover" from an error since there is no
    508     /// way to add `Promise`s back forcing one to have to create a second `Promises`.
    509     ///
    510     /// # Errors
    511     ///
    512     /// Returns [`io::Error`] iff `pledge` does.
    513     ///
    514     /// # Example
    515     ///
    516     /// ```no_run
    517     /// # use priv_sep::{Promise, Promises};
    518     /// assert!(Promises::new([Promise::Rpath, Promise::Stdio]).remove_then_pledge(Promise::Rpath).is_ok());
    519     /// ```
    520     #[inline]
    521     pub fn remove_then_pledge(&mut self, promise: Promise) -> Result<(), io::Error> {
    522         // We opt for the easy way by copying `self`. This should be the fastest for the
    523         // typical case since `self` likely has very few `Promise`s, and it makes for less code.
    524         let cur = Self(self.0);
    525         self.remove(promise);
    526         if *self == cur {
    527             Ok(())
    528         } else {
    529             self.pledge().inspect_err(|_| *self = cur)
    530         }
    531     }
    532     /// Invokes [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2) always passing in
    533     /// `NULL` for `execpromises` and its contained [`Promise`]s for `promises`.
    534     ///
    535     /// # Errors
    536     ///
    537     /// Returns [`io::Error`] iff `pledge(2)` errors.
    538     ///
    539     /// # Examples
    540     ///
    541     /// ```no_run
    542     /// # use priv_sep::{Promise, Promises};
    543     /// assert!(Promises::new([Promise::Stdio]).pledge().is_ok());
    544     /// ```
    545     #[expect(
    546         unsafe_code,
    547         reason = "we manually construct a valid CString and ensure its safety"
    548     )]
    549     #[expect(
    550         clippy::arithmetic_side_effects,
    551         clippy::indexing_slicing,
    552         reason = "we replace a trailing space with 0 to ensure CString is correctly constructed"
    553     )]
    554     #[expect(
    555         clippy::cognitive_complexity,
    556         clippy::too_many_lines,
    557         reason = "many if blocks to handle each Promise"
    558     )]
    559     #[inline]
    560     pub fn pledge(&self) -> Result<(), io::Error> {
    561         let mut p = Vec::new();
    562         if self.contains(Audio) {
    563             p.extend_from_slice(b"audio ");
    564         }
    565         if self.contains(Bpf) {
    566             p.extend_from_slice(b"bpf ");
    567         }
    568         if self.contains(Chown) {
    569             p.extend_from_slice(b"chown ");
    570         }
    571         if self.contains(Cpath) {
    572             p.extend_from_slice(b"cpath ");
    573         }
    574         if self.contains(Disklabel) {
    575             p.extend_from_slice(b"disklabel ");
    576         }
    577         if self.contains(Dns) {
    578             p.extend_from_slice(b"dns ");
    579         }
    580         if self.contains(Dpath) {
    581             p.extend_from_slice(b"dpath ");
    582         }
    583         if self.contains(Drm) {
    584             p.extend_from_slice(b"drm ");
    585         }
    586         if self.contains(Error) {
    587             p.extend_from_slice(b"error ");
    588         }
    589         if self.contains(Exec) {
    590             p.extend_from_slice(b"exec ");
    591         }
    592         if self.contains(Fattr) {
    593             p.extend_from_slice(b"fattr ");
    594         }
    595         if self.contains(Flock) {
    596             p.extend_from_slice(b"flock ");
    597         }
    598         if self.contains(Getpw) {
    599             p.extend_from_slice(b"getpw ");
    600         }
    601         if self.contains(Id) {
    602             p.extend_from_slice(b"id ");
    603         }
    604         if self.contains(Inet) {
    605             p.extend_from_slice(b"inet ");
    606         }
    607         if self.contains(Mcast) {
    608             p.extend_from_slice(b"mcast ");
    609         }
    610         if self.contains(Pf) {
    611             p.extend_from_slice(b"pf ");
    612         }
    613         if self.contains(Proc) {
    614             p.extend_from_slice(b"proc ");
    615         }
    616         if self.contains(ProtExec) {
    617             p.extend_from_slice(b"prot_exec ");
    618         }
    619         if self.contains(Ps) {
    620             p.extend_from_slice(b"ps ");
    621         }
    622         if self.contains(Recvfd) {
    623             p.extend_from_slice(b"recvfd ");
    624         }
    625         if self.contains(Route) {
    626             p.extend_from_slice(b"route ");
    627         }
    628         if self.contains(Rpath) {
    629             p.extend_from_slice(b"rpath ");
    630         }
    631         if self.contains(Sendfd) {
    632             p.extend_from_slice(b"sendfd ");
    633         }
    634         if self.contains(Settime) {
    635             p.extend_from_slice(b"settime ");
    636         }
    637         if self.contains(Stdio) {
    638             p.extend_from_slice(b"stdio ");
    639         }
    640         if self.contains(Tape) {
    641             p.extend_from_slice(b"tape ");
    642         }
    643         if self.contains(Tmppath) {
    644             p.extend_from_slice(b"tmppath ");
    645         }
    646         if self.contains(Tty) {
    647             p.extend_from_slice(b"tty ");
    648         }
    649         if self.contains(Unix) {
    650             p.extend_from_slice(b"unix ");
    651         }
    652         if self.contains(Unveil) {
    653             p.extend_from_slice(b"unveil ");
    654         }
    655         if self.contains(Video) {
    656             p.extend_from_slice(b"video ");
    657         }
    658         if self.contains(Vminfo) {
    659             p.extend_from_slice(b"vminfo ");
    660         }
    661         if self.contains(Vmm) {
    662             p.extend_from_slice(b"vmm ");
    663         }
    664         if self.contains(Wpath) {
    665             p.extend_from_slice(b"wpath ");
    666         }
    667         if self.contains(Wroute) {
    668             p.extend_from_slice(b"wroute ");
    669         }
    670         let idx = p.len();
    671         if idx == 0 {
    672             p.push(0);
    673         } else {
    674             // All promises have a space after them which means
    675             // we must replace the last promise's space with
    676             // 0/nul byte.
    677             // `idx` is at least 1 based on the above check.
    678             p[idx - 1] = 0;
    679         }
    680         // SAFETY:
    681         // `p` was populated above with correct ASCII-encoding of the literal
    682         // values all of which do not have 0 bytes.
    683         // `p` ends with a 0/nul byte.
    684         let arg = unsafe { CString::from_vec_with_nul_unchecked(p) };
    685         pledge(arg.as_ptr())
    686     }
    687 }
    688 impl PartialEq<&Self> for Promises {
    689     #[inline]
    690     fn eq(&self, other: &&Self) -> bool {
    691         *self == **other
    692     }
    693 }
    694 impl PartialEq<Promises> for &Promises {
    695     #[inline]
    696     fn eq(&self, other: &Promises) -> bool {
    697         **self == *other
    698     }
    699 }
    700 /// Invokes [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2) with `NULL` for
    701 /// both `promises` and `execpromises`.
    702 ///
    703 /// # Errors
    704 ///
    705 /// Returns [`io::Error`] iff `pledge(2)` errors.
    706 ///
    707 /// # Example
    708 ///
    709 /// ```no_run
    710 /// use priv_sep;
    711 /// assert!(priv_sep::pledge_none().is_ok());
    712 /// ```
    713 #[inline]
    714 pub fn pledge_none() -> Result<(), io::Error> {
    715     // `NULL` is always valid for `promises`.
    716     pledge(ptr::null())
    717 }
    718 /// Invokes `unveil(2)` passing `path` for `path` and `permissions` for `permissions`.
    719 ///
    720 /// This function MUST only be called by the functions `Permissions::unveil` and
    721 /// `unveil_no_more`.
    722 #[expect(unsafe_code, reason = "FFI requires unsafe code")]
    723 fn unveil(path: *const c_char, permissions: *const c_char) -> Result<(), io::Error> {
    724     unsafe extern "C" {
    725         fn unveil(path: *const c_char, permissions: *const c_char) -> c_int;
    726     }
    727     // SAFETY:
    728     // `unveil` is an FFI binding; thus requires unsafe code.
    729     // `path` and `permissions` meet the requirements of the `unveil(2)` call
    730     // as can be seen in the only functions that call this function:
    731     // `Permissions::unveil` and `unveil_no_more`.
    732     if unsafe { unveil(path, permissions) } == 0i32 {
    733         Ok(())
    734     } else {
    735         Err(io::Error::last_os_error())
    736     }
    737 }
    738 /// A permission in [`Permissions`].
    739 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
    740 pub enum Permission {
    741     /// [c](https://man.openbsd.org/amd64/unveil.2#c).
    742     Create,
    743     /// [x](https://man.openbsd.org/amd64/unveil.2#x).
    744     Execute,
    745     /// [r](https://man.openbsd.org/amd64/unveil.2#r).
    746     Read,
    747     /// [w](https://man.openbsd.org/amd64/unveil.2#w).
    748     Write,
    749 }
    750 impl Permission {
    751     /// Transforms `self` into a `u8`.
    752     const fn to_u8(self) -> u8 {
    753         match self {
    754             Self::Create => 1,
    755             Self::Execute => 2,
    756             Self::Read => 4,
    757             Self::Write => 8,
    758         }
    759     }
    760 }
    761 impl PartialEq<&Self> for Permission {
    762     #[inline]
    763     fn eq(&self, other: &&Self) -> bool {
    764         *self == **other
    765     }
    766 }
    767 impl PartialEq<Permission> for &Permission {
    768     #[inline]
    769     fn eq(&self, other: &Permission) -> bool {
    770         **self == *other
    771     }
    772 }
    773 /// `permissions` to [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2).
    774 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
    775 pub struct Permissions(u8);
    776 impl Permissions {
    777     /// A `Permissions` with no [`Permission`]s enabled.
    778     pub const NONE: Self = Self(0);
    779     /// A `Permissions` with all [`Permission`]s enabled.
    780     pub const ALL: Self = Self::NONE
    781         .enable_inner(Permission::Create)
    782         .enable_inner(Permission::Execute)
    783         .enable_inner(Permission::Read)
    784         .enable_inner(Permission::Write);
    785     /// A `Permissions` with only [`Permission::Create`] enabled.
    786     pub const CREATE: Self = Self::NONE.enable_inner(Permission::Create);
    787     /// A `Permissions` with only [`Permission::Execute`] enabled.
    788     pub const EXECUTE: Self = Self::NONE.enable_inner(Permission::Execute);
    789     /// A `Permissions` with only [`Permission::Read`] enabled.
    790     pub const READ: Self = Self::NONE.enable_inner(Permission::Read);
    791     /// A `Permissions` with only [`Permission::Write`] enabled.
    792     pub const WRITE: Self = Self::NONE.enable_inner(Permission::Write);
    793     /// Same as [`enable`] but returns a new instance instead of mutating `self`.
    794     const fn enable_inner(self, permission: Permission) -> Self {
    795         Self(self.0 | permission.to_u8())
    796     }
    797     /// Enables `permission` in `self`.
    798     ///
    799     /// # Examples
    800     ///
    801     /// ```
    802     /// # use priv_sep::{Permission, Permissions};
    803     /// let mut perms = Permissions::NONE;
    804     /// perms.enable(Permission::Read);
    805     /// assert_eq!(perms, Permissions::READ);
    806     /// ```
    807     #[inline]
    808     pub const fn enable(&mut self, permission: Permission) {
    809         self.0 |= permission.to_u8();
    810     }
    811     /// Disables `permission` in `self`.
    812     ///
    813     /// # Examples
    814     ///
    815     /// ```
    816     /// # use priv_sep::{Permission, Permissions};
    817     /// let mut perms = Permissions::ALL;
    818     /// perms.disable(Permission::Execute);
    819     /// assert!(
    820     ///     perms.is_enabled(Permission::Create)
    821     ///         && perms.is_enabled(Permission::Read)
    822     ///         && perms.is_enabled(Permission::Write)
    823     ///         && !perms.is_enabled(Permission::Execute)
    824     /// );
    825     /// assert!(perms.is_enabled(Permission::Create) && perms.is_enabled(Permission::Read) && perms.is_enabled(Permission::Write) && !perms.is_enabled(Permission::Execute));
    826     /// ```
    827     #[inline]
    828     pub const fn disable(&mut self, permission: Permission) {
    829         self.0 &= !permission.to_u8();
    830     }
    831     /// Returns `true` iff `self` has `permission` enabled.
    832     ///
    833     /// # Examples
    834     ///
    835     /// ```
    836     /// # use priv_sep::{Permission, Permissions};
    837     /// let perms = Permissions::CREATE;
    838     /// assert!(perms.is_enabled(Permission::Create));
    839     /// assert!(!perms.is_enabled(Permission::Write));
    840     /// ```
    841     #[inline]
    842     #[must_use]
    843     pub const fn is_enabled(self, permission: Permission) -> bool {
    844         let val = permission.to_u8();
    845         self.0 & val == val
    846     }
    847     /// Invokes [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2)
    848     /// passing `path` for `path` and the contained permissions for `permissions`.
    849     ///
    850     /// # Errors
    851     ///
    852     /// Returns [`NulError`] iff [`CString::new`] does.
    853     /// Returns [`io::Error`] iff `unveil(2)` errors.
    854     ///
    855     /// # Examples
    856     ///
    857     /// ```no_run
    858     /// # use std::io::ErrorKind;
    859     /// # use priv_sep::{Permissions, UnveilErr};
    860     /// assert!(Permissions::READ.unveil("/path/to/read").is_ok());
    861     /// assert!(Permissions::READ.unveil("/path/does/not/exist").map_or_else(
    862     ///     |err| match err {
    863     ///         UnveilErr::Io(e) => e.kind() == ErrorKind::NotFound,
    864     ///         UnveilErr::Nul(_) => false,
    865     ///     },
    866     ///     |()| false
    867     /// ));
    868     /// ```
    869     #[expect(
    870         unsafe_code,
    871         reason = "we manually construct a valid CString and ensure its safety"
    872     )]
    873     #[expect(
    874         clippy::arithmetic_side_effects,
    875         reason = "we pre-allocate a Vec with the exact capacity which is capped at 5"
    876     )]
    877     #[inline]
    878     pub fn unveil<P: AsRef<Path>>(self, path: P) -> Result<(), UnveilErr> {
    879         CString::new(path.as_ref().as_os_str().as_bytes()).map_or_else(
    880             |e| Err(UnveilErr::Nul(e)),
    881             |path_c| {
    882                 // The max sum is 5, so overflow is not possible.
    883                 let mut vec = Vec::with_capacity(
    884                     usize::from(self.is_enabled(Permission::Create))
    885                         + usize::from(self.is_enabled(Permission::Execute))
    886                         + usize::from(self.is_enabled(Permission::Read))
    887                         + usize::from(self.is_enabled(Permission::Write))
    888                         + 1,
    889                 );
    890                 if self.is_enabled(Permission::Create) {
    891                     vec.push(b'c');
    892                 }
    893                 if self.is_enabled(Permission::Execute) {
    894                     vec.push(b'x');
    895                 }
    896                 if self.is_enabled(Permission::Read) {
    897                     vec.push(b'r');
    898                 }
    899                 if self.is_enabled(Permission::Write) {
    900                     vec.push(b'w');
    901                 }
    902                 vec.push(0);
    903                 // SAFETY:
    904                 // `vec` was populated above with correct ASCII-encoding of the literal
    905                 // values all of which do not have 0 bytes.
    906                 // `vec` ends with a 0/nul byte.
    907                 let perm_c = unsafe { CString::from_vec_with_nul_unchecked(vec) };
    908                 let path_ptr = path_c.as_ptr();
    909                 let perm_ptr = perm_c.as_ptr();
    910                 unveil(path_ptr, perm_ptr).map_err(UnveilErr::Io)
    911             },
    912         )
    913     }
    914 }
    915 impl Display for Permissions {
    916     #[inline]
    917     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
    918         write!(
    919             f,
    920             "unveil(2) '{}{}{}{}' permissions",
    921             if self.is_enabled(Permission::Create) {
    922                 "c"
    923             } else {
    924                 ""
    925             },
    926             if self.is_enabled(Permission::Execute) {
    927                 "x"
    928             } else {
    929                 ""
    930             },
    931             if self.is_enabled(Permission::Read) {
    932                 "r"
    933             } else {
    934                 ""
    935             },
    936             if self.is_enabled(Permission::Write) {
    937                 "w"
    938             } else {
    939                 ""
    940             },
    941         )
    942     }
    943 }
    944 impl BitAnd for Permissions {
    945     type Output = Self;
    946     #[inline]
    947     fn bitand(self, rhs: Self) -> Self::Output {
    948         Self(self.0 & rhs.0)
    949     }
    950 }
    951 impl BitAnd<&Self> for Permissions {
    952     type Output = Self;
    953     #[inline]
    954     fn bitand(self, rhs: &Self) -> Self::Output {
    955         self & *rhs
    956     }
    957 }
    958 impl BitAnd<&Permissions> for &Permissions {
    959     type Output = Permissions;
    960     #[inline]
    961     fn bitand(self, rhs: &Permissions) -> Self::Output {
    962         *self & *rhs
    963     }
    964 }
    965 impl BitAnd<Permissions> for &Permissions {
    966     type Output = Permissions;
    967     #[inline]
    968     fn bitand(self, rhs: Permissions) -> Self::Output {
    969         *self & rhs
    970     }
    971 }
    972 impl BitAndAssign for Permissions {
    973     #[inline]
    974     fn bitand_assign(&mut self, rhs: Self) {
    975         self.0 &= rhs.0;
    976     }
    977 }
    978 impl BitAndAssign<&Self> for Permissions {
    979     #[inline]
    980     fn bitand_assign(&mut self, rhs: &Self) {
    981         *self &= *rhs;
    982     }
    983 }
    984 impl BitOr for Permissions {
    985     type Output = Self;
    986     #[inline]
    987     fn bitor(self, rhs: Self) -> Self::Output {
    988         Self(self.0 | rhs.0)
    989     }
    990 }
    991 impl BitOr<&Self> for Permissions {
    992     type Output = Self;
    993     #[inline]
    994     fn bitor(self, rhs: &Self) -> Self::Output {
    995         self | *rhs
    996     }
    997 }
    998 impl BitOr<&Permissions> for &Permissions {
    999     type Output = Permissions;
   1000     #[inline]
   1001     fn bitor(self, rhs: &Permissions) -> Self::Output {
   1002         *self | *rhs
   1003     }
   1004 }
   1005 impl BitOr<Permissions> for &Permissions {
   1006     type Output = Permissions;
   1007     #[inline]
   1008     fn bitor(self, rhs: Permissions) -> Self::Output {
   1009         *self | rhs
   1010     }
   1011 }
   1012 impl BitOrAssign for Permissions {
   1013     #[inline]
   1014     fn bitor_assign(&mut self, rhs: Self) {
   1015         self.0 |= rhs.0;
   1016     }
   1017 }
   1018 impl BitOrAssign<&Self> for Permissions {
   1019     #[inline]
   1020     fn bitor_assign(&mut self, rhs: &Self) {
   1021         *self |= *rhs;
   1022     }
   1023 }
   1024 impl BitXor for Permissions {
   1025     type Output = Self;
   1026     #[inline]
   1027     fn bitxor(self, rhs: Self) -> Self::Output {
   1028         Self(self.0 ^ rhs.0)
   1029     }
   1030 }
   1031 impl BitXor<&Self> for Permissions {
   1032     type Output = Self;
   1033     #[inline]
   1034     fn bitxor(self, rhs: &Self) -> Self::Output {
   1035         self ^ *rhs
   1036     }
   1037 }
   1038 impl BitXor<&Permissions> for &Permissions {
   1039     type Output = Permissions;
   1040     #[inline]
   1041     fn bitxor(self, rhs: &Permissions) -> Self::Output {
   1042         *self ^ *rhs
   1043     }
   1044 }
   1045 impl BitXor<Permissions> for &Permissions {
   1046     type Output = Permissions;
   1047     #[inline]
   1048     fn bitxor(self, rhs: Permissions) -> Self::Output {
   1049         *self ^ rhs
   1050     }
   1051 }
   1052 impl BitXorAssign for Permissions {
   1053     #[inline]
   1054     fn bitxor_assign(&mut self, rhs: Self) {
   1055         self.0 ^= rhs.0;
   1056     }
   1057 }
   1058 impl BitXorAssign<&Self> for Permissions {
   1059     #[inline]
   1060     fn bitxor_assign(&mut self, rhs: &Self) {
   1061         *self ^= *rhs;
   1062     }
   1063 }
   1064 impl Not for Permissions {
   1065     type Output = Self;
   1066     #[inline]
   1067     fn not(self) -> Self::Output {
   1068         Self(Self::ALL.0 & !self.0)
   1069     }
   1070 }
   1071 impl Not for &Permissions {
   1072     type Output = Permissions;
   1073     #[inline]
   1074     fn not(self) -> Self::Output {
   1075         !*self
   1076     }
   1077 }
   1078 impl PartialEq<&Self> for Permissions {
   1079     #[inline]
   1080     fn eq(&self, other: &&Self) -> bool {
   1081         *self == **other
   1082     }
   1083 }
   1084 impl PartialEq<Permissions> for &Permissions {
   1085     #[inline]
   1086     fn eq(&self, other: &Permissions) -> bool {
   1087         **self == *other
   1088     }
   1089 }
   1090 /// Error returned by [`Permissions::unveil`].
   1091 #[derive(Debug)]
   1092 pub enum UnveilErr {
   1093     /// Error propagated from [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2).
   1094     Io(io::Error),
   1095     /// Error when a path cannot be converted into a
   1096     /// [`CString`].
   1097     Nul(NulError),
   1098 }
   1099 impl Display for UnveilErr {
   1100     #[inline]
   1101     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
   1102         match *self {
   1103             Self::Io(ref err) => err.fmt(f),
   1104             Self::Nul(ref e) => write!(
   1105                 f,
   1106                 "The path passed to 'unveil(2)' was unable to be converted to a CString: {e}"
   1107             ),
   1108         }
   1109     }
   1110 }
   1111 impl error::Error for UnveilErr {}
   1112 /// Invokes [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2) by passing `NULL` for both `path` and `permissions`.
   1113 ///
   1114 /// # Errors
   1115 ///
   1116 /// Returns [`io::Error`] when a problem occurs.
   1117 ///
   1118 /// # Example
   1119 ///
   1120 /// ```no_run
   1121 /// use priv_sep;
   1122 /// assert!(priv_sep::unveil_no_more().is_ok());
   1123 /// ```
   1124 #[inline]
   1125 pub fn unveil_no_more() -> Result<(), io::Error> {
   1126     // `NULL` is valid for both `path` and `permissions`.
   1127     unveil(ptr::null(), ptr::null())
   1128 }
   1129 #[cfg(all(test, target_os = "openbsd"))]
   1130 mod tests {
   1131     use crate::{Permissions, Promise, Promises};
   1132     use std::fs;
   1133     // We only have one test since we must force the order of pledge/unveil calls.
   1134     #[test]
   1135     #[ignore]
   1136     fn test() {
   1137         const FILE_EXISTS: &str = "/home/zack/foo.txt";
   1138         _ = fs::metadata(FILE_EXISTS)
   1139             .expect(format!("{FILE_EXISTS} does not exist, so unit testing cannot occur").as_str());
   1140         const FILE_NOT_EXISTS: &str = "/home/zack/aadkjfasj3s23";
   1141         drop(fs::metadata(FILE_NOT_EXISTS).expect_err(
   1142             format!("{FILE_NOT_EXISTS} exists, so unit testing cannot occur").as_str(),
   1143         ));
   1144         const DIR_NOT_EXISTS: &str = "/home/zack/aadkjfasj3s23/";
   1145         drop(
   1146             fs::metadata(DIR_NOT_EXISTS).expect_err(
   1147                 format!("{DIR_NOT_EXISTS} exists, so unit testing cannot occur").as_str(),
   1148             ),
   1149         );
   1150         // This tests that a NULL `promise` does nothing.
   1151         assert!(crate::pledge_none().is_ok());
   1152         print!("");
   1153         assert!(Promises::ALL.pledge().is_ok());
   1154         // This tests that duplicates are ignored as well as the implementation of PartialEq.
   1155         let mut initial_promises = Promises::new([
   1156             Promise::Stdio,
   1157             Promise::Unveil,
   1158             Promise::Rpath,
   1159             Promise::Stdio,
   1160         ]);
   1161         assert!(initial_promises.len() == 3);
   1162         assert!(
   1163             initial_promises == Promises::new([Promise::Rpath, Promise::Stdio, Promise::Unveil])
   1164         );
   1165         // Test retain.
   1166         assert!({
   1167             let mut vals = Promises::new([
   1168                 Promise::Audio,
   1169                 Promise::Bpf,
   1170                 Promise::Chown,
   1171                 Promise::Cpath,
   1172                 Promise::Error,
   1173                 Promise::Exec,
   1174             ]);
   1175             vals.retain([Promise::Error, Promise::Chown]);
   1176             vals.len() == 2 && vals.contains(Promise::Chown) && vals.contains(Promise::Error)
   1177         });
   1178         assert!(initial_promises.pledge().is_ok());
   1179         // This tests unveil with no permissions.
   1180         assert!(Permissions::NONE.unveil(FILE_EXISTS).is_ok());
   1181         assert!(fs::metadata(FILE_EXISTS).is_err());
   1182         // This tests unveil with read permissions,
   1183         // and one can unveil more permissions (unlike pledge which can only remove promises).
   1184         assert!(Permissions::READ.unveil(FILE_EXISTS).is_ok());
   1185         assert!(fs::metadata(FILE_EXISTS).is_ok());
   1186         // This tests that calls to unveil on missing files don't error.
   1187         assert!(Permissions::NONE.unveil(FILE_NOT_EXISTS).is_ok());
   1188         // This tests that calls to unveil on missing directories error.
   1189         assert!(Permissions::NONE.unveil(DIR_NOT_EXISTS).is_err());
   1190         // This tests that unveil can no longer be called.
   1191         assert!(crate::unveil_no_more().is_ok());
   1192         assert!(Permissions::NONE.unveil(FILE_EXISTS).is_err());
   1193         assert!(fs::metadata(FILE_EXISTS).is_ok());
   1194         // The below tests that Promises can only be removed and not added.
   1195         initial_promises.remove_promises([Promise::Unveil]);
   1196         assert_eq!(initial_promises.len(), 2);
   1197         initial_promises.remove(Promise::Rpath);
   1198         assert_eq!(initial_promises.len(), 1);
   1199         initial_promises.remove(Promise::Rpath);
   1200         assert_eq!(initial_promises.len(), 1);
   1201         assert!(initial_promises.pledge().is_ok());
   1202         print!("");
   1203         assert!(Promises::new([Promise::Rpath]).pledge().is_err());
   1204         // If the below is uncommented, the program should crash since the above
   1205         // call to pledge no longer allows access to the file system.
   1206         // drop(fs::metadata(FILE_EXISTS));
   1207     }
   1208 }