priv_sep

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

openbsd.rs (51633B)


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