priv_sep

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

lib.rs (39122B)


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