priv_sep

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

lib.rs (45200B)


      1 //! [![git]](https://git.philomathiclife.com/priv_sep/log.html) [![crates-io]](https://crates.io/crates/priv_sep) [![docs-rs]](crate)
      2 //!
      3 //! [git]: https://git.philomathiclife.com/git_badge.svg
      4 //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
      5 //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
      6 //!
      7 //! `priv_sep` is a library that uses the system's libc to perform privilege separation and privilege reduction
      8 //! for Unix-like platforms. The following `target_os` values are supported:
      9 //!
     10 //! * `dragonfly`
     11 //! * `freebsd`
     12 //! * `linux`
     13 //! * `macos`
     14 //! * `netbsd`
     15 //! * `openbsd`
     16 //!
     17 //! ## `priv_sep` in action
     18 //!
     19 //! ```no_run
     20 //! use core::convert::Infallible;
     21 //! use priv_sep::{PrivDropErr, UserInfo};
     22 //! use std::{
     23 //!     io::Error,
     24 //!     net::{Ipv6Addr, SocketAddrV6},
     25 //! };
     26 //! use tokio::net::TcpListener;
     27 //! #[tokio::main(flavor = "current_thread")]
     28 //! async fn main() -> Result<Infallible, PrivDropErr<Error>> {
     29 //!     // Get the user ID and group ID for nobody from `passwd(5)`.
     30 //!     // `chroot(2)` to `/path/chroot/` and `chdir(2)` to `/`.
     31 //!     // Bind to TCP `[::1]:443` as root.
     32 //!     // `setgroups(2)` to drop all supplementary groups.
     33 //!     // `setresgid(2)` to the group ID associated with nobody.
     34 //!     // `setresuid(2)` to the user ID associated with nobody.
     35 //!     let listener =
     36 //!         UserInfo::chroot_then_priv_drop_async(c"nobody", c"/path/chroot/", false, async || {
     37 //!             TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await
     38 //!         })
     39 //!         .await?;
     40 //!     // At this point, the process is running under nobody.
     41 //!     loop {
     42 //!         // Handle TCP connections.
     43 //!         if let Ok((_, ip)) = listener.accept().await {
     44 //!             assert!(ip.is_ipv6());
     45 //!         }
     46 //!     }
     47 //! }
     48 //! ```
     49 //!
     50 //! <details>
     51 //! <summary>Incorporating <a href="https://man.openbsd.org/pledge.2"><code>pledge(2)</code></a> and <a href="https://man.openbsd.org/unveil.2"><code>unveil(2)</code></a> on OpenBSD</summary>
     52 //!
     53 //! ```no_run
     54 //! # #[cfg(target_os = "openbsd")]
     55 //! use core::{convert::Infallible, ffi::CStr};
     56 //! # #[cfg(target_os = "openbsd")]
     57 //! use priv_sep::{Permissions, PrivDropErr, Promise, Promises};
     58 //! # #[cfg(target_os = "openbsd")]
     59 //! use std::{
     60 //!     fs,
     61 //!     io::Error,
     62 //!     net::{Ipv6Addr, SocketAddrV6},
     63 //! };
     64 //! # #[cfg(target_os = "openbsd")]
     65 //! use tokio::net::TcpListener;
     66 //! # #[cfg(not(target_os = "openbsd"))]
     67 //! # fn main() {}
     68 //! # #[cfg(target_os = "openbsd")]
     69 //! #[tokio::main(flavor = "current_thread")]
     70 //! async fn main() -> Result<Infallible, PrivDropErr<Error>> {
     71 //!     /// Config file.
     72 //!     const CONFIG: &CStr = c"config";
     73 //!     /// Config file.
     74 //!     const CONFIG_STR: &str = match CONFIG.to_str() {
     75 //!         Ok(val) => val,
     76 //!         Err(_) => panic!("config is not a valid str"),
     77 //!     };
     78 //!     // Get the user ID and group ID for nobody from `passwd(5)`.
     79 //!     // `chroot(2)` to `/path/chroot/` and `chdir(2)` to `/`.
     80 //!     // `pledge(2)` `id`, `inet`, `rpath`, `stdio`, and `unveil`.
     81 //!     // Bind to TCP `[::1]:443` as root.
     82 //!     // `setgroups(2)` to drop all supplementary groups.
     83 //!     // `setresgid(2)` to the group ID associated with nobody.
     84 //!     // `setresuid(2)` to the user ID associated with nobody.
     85 //!     // Remove `id` from our `pledge(2)`d promises.
     86 //!     let (listener, mut promises) = Promises::new_chroot_then_priv_drop_async(
     87 //!         c"nobody",
     88 //!         c"/path/chroot/",
     89 //!         [Promise::Inet, Promise::Rpath, Promise::Unveil],
     90 //!         false,
     91 //!         async || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await,
     92 //!     )
     93 //!     .await?;
     94 //!     // At this point, the process is running under nobody.
     95 //!     // Only allow file system access to `config` and only allow read access to it.
     96 //!     Permissions::READ.unveil(CONFIG)?;
     97 //!     // Read `config`.
     98 //!     // This will of course fail if the file does not exist or nobody does not
     99 //!     // have read permissions.
    100 //!     let config = fs::read(CONFIG_STR).map_err(PrivDropErr::Other)?;
    101 //!     // Remove file system access.
    102 //!     Permissions::NONE.unveil(CONFIG)?;
    103 //!     // Remove `rpath` and `unveil` from our `pledge(2)`d promises
    104 //!     // (i.e., only have `inet` and `stdio` abilities when we begin accepting TCP connections).
    105 //!     promises.remove_promises_then_pledge([Promise::Rpath, Promise::Unveil])?;
    106 //!     loop {
    107 //!         // Handle TCP connections.
    108 //!         if let Ok((_, ip)) = listener.accept().await {
    109 //!             assert!(ip.is_ipv6());
    110 //!         }
    111 //!     }
    112 //! }
    113 //! ```
    114 //! </details>
    115 #![cfg_attr(docsrs, feature(doc_cfg))]
    116 #![cfg_attr(docsrs, doc(auto_cfg = false))]
    117 #![no_std]
    118 #![cfg(any(
    119     target_os = "dragonfly",
    120     target_os = "freebsd",
    121     target_os = "linux",
    122     target_os = "macos",
    123     target_os = "netbsd",
    124     target_os = "openbsd"
    125 ))]
    126 #![expect(
    127     clippy::pub_use,
    128     reason = "don't want Errno nor openbsd types in a module"
    129 )]
    130 #[cfg(feature = "std")]
    131 extern crate std;
    132 /// C FFI.
    133 mod c;
    134 /// Errno.
    135 mod err;
    136 /// OpenBSD
    137 #[cfg(target_os = "openbsd")]
    138 mod openbsd;
    139 use c::SUCCESS;
    140 use core::{
    141     error::Error as CoreErr,
    142     ffi::{CStr, c_char, c_int},
    143     fmt::{self, Display, Formatter},
    144     mem::MaybeUninit,
    145     ptr,
    146 };
    147 pub use err::Errno;
    148 #[cfg_attr(docsrs, doc(cfg(target_os = "openbsd")))]
    149 #[cfg(target_os = "openbsd")]
    150 pub use openbsd::{Permission, Permissions, Promise, Promises};
    151 /// [`uid_t`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/basedefs/sys_types.h.html).
    152 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    153 #[repr(transparent)]
    154 pub struct Uid(pub u32);
    155 impl Uid {
    156     /// The root user ID (i.e., 0).
    157     pub const ROOT: Self = Self(0);
    158     /// Returns `true` iff `self` is [`Self::ROOT`].
    159     ///
    160     /// # Examples
    161     ///
    162     /// ```no_run
    163     /// # use priv_sep::Uid;
    164     /// assert!(Uid::ROOT.is_root());
    165     /// ```
    166     #[inline]
    167     #[must_use]
    168     pub const fn is_root(self) -> bool {
    169         self.0 == Self::ROOT.0
    170     }
    171     /// [`getuid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/getuid.html).
    172     ///
    173     /// # Examples
    174     ///
    175     /// ```no_run
    176     /// # use priv_sep::Uid;
    177     /// assert_eq!(Uid::getuid(), 1000);
    178     /// ```
    179     #[inline]
    180     #[must_use]
    181     pub fn getuid() -> Self {
    182         Self(c::getuid())
    183     }
    184     /// [`geteuid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/geteuid.html).
    185     ///
    186     /// # Examples
    187     ///
    188     /// ```no_run
    189     /// # use priv_sep::Uid;
    190     /// assert_eq!(Uid::geteuid(), 1000);
    191     /// ```
    192     #[inline]
    193     #[must_use]
    194     pub fn geteuid() -> Self {
    195         Self(c::geteuid())
    196     }
    197     /// Calls [`setresuid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/setresuid.html)
    198     /// passing `self` for the real, effective, and saved user IDs.
    199     ///
    200     /// Note on some platforms `setuid` is called using `self`.
    201     ///
    202     /// # Errors
    203     ///
    204     /// Errors iff `setresuid` does.
    205     ///
    206     /// # Examples
    207     ///
    208     /// ```no_run
    209     /// # use priv_sep::Uid;
    210     /// assert!(Uid(1000).setresuid().is_ok());
    211     /// ```
    212     #[inline]
    213     pub fn setresuid(self) -> Result<(), Errno> {
    214         #[cfg(any(
    215             target_os = "dragonfly",
    216             target_os = "freebsd",
    217             target_os = "linux",
    218             target_os = "openbsd"
    219         ))]
    220         let code = c::setresuid(self.0, self.0, self.0);
    221         #[cfg(any(target_os = "macos", target_os = "netbsd"))]
    222         let code = c::setuid(self.0);
    223         if code == SUCCESS {
    224             Ok(())
    225         } else {
    226             Err(Errno::last())
    227         }
    228     }
    229 }
    230 impl PartialEq<&Self> for Uid {
    231     #[inline]
    232     fn eq(&self, other: &&Self) -> bool {
    233         *self == **other
    234     }
    235 }
    236 impl PartialEq<Uid> for &Uid {
    237     #[inline]
    238     fn eq(&self, other: &Uid) -> bool {
    239         **self == *other
    240     }
    241 }
    242 impl PartialEq<u32> for Uid {
    243     #[inline]
    244     fn eq(&self, other: &u32) -> bool {
    245         self.0 == *other
    246     }
    247 }
    248 impl From<Uid> for u32 {
    249     #[inline]
    250     fn from(value: Uid) -> Self {
    251         value.0
    252     }
    253 }
    254 impl From<u32> for Uid {
    255     #[inline]
    256     fn from(value: u32) -> Self {
    257         Self(value)
    258     }
    259 }
    260 /// [`gid_t`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/basedefs/sys_types.h.html).
    261 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    262 #[repr(transparent)]
    263 pub struct Gid(pub u32);
    264 impl Gid {
    265     /// [`getgid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/getgid.html).
    266     ///
    267     /// # Examples
    268     ///
    269     /// ```no_run
    270     /// # use priv_sep::Gid;
    271     /// assert_eq!(Gid::getgid(), 1000);
    272     /// ```
    273     #[inline]
    274     #[must_use]
    275     pub fn getgid() -> Self {
    276         Self(c::getgid())
    277     }
    278     /// [`getegid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/getegid.html).
    279     ///
    280     /// # Examples
    281     ///
    282     /// ```no_run
    283     /// # use priv_sep::Gid;
    284     /// assert_eq!(Gid::getegid(), 1000);
    285     /// ```
    286     #[inline]
    287     #[must_use]
    288     pub fn getegid() -> Self {
    289         Self(c::getegid())
    290     }
    291     /// Calls [`setresgid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/setresgid.html)
    292     /// passing `self` for the real, effective, and saved group IDs.
    293     ///
    294     /// Note on some platforms `setgid` is called using `self`.
    295     ///
    296     /// # Errors
    297     ///
    298     /// Errors iff `setresgid` does.
    299     ///
    300     /// # Examples
    301     ///
    302     /// ```no_run
    303     /// # use priv_sep::Gid;
    304     /// assert!(Gid(1000).setresgid().is_ok());
    305     /// ```
    306     #[inline]
    307     pub fn setresgid(self) -> Result<(), Errno> {
    308         #[cfg(any(
    309             target_os = "dragonfly",
    310             target_os = "freebsd",
    311             target_os = "linux",
    312             target_os = "openbsd"
    313         ))]
    314         let code = c::setresgid(self.0, self.0, self.0);
    315         #[cfg(any(target_os = "macos", target_os = "netbsd"))]
    316         let code = c::setgid(self.0);
    317         if code == SUCCESS {
    318             Ok(())
    319         } else {
    320             Err(Errno::last())
    321         }
    322     }
    323 }
    324 impl PartialEq<&Self> for Gid {
    325     #[inline]
    326     fn eq(&self, other: &&Self) -> bool {
    327         *self == **other
    328     }
    329 }
    330 impl PartialEq<Gid> for &Gid {
    331     #[inline]
    332     fn eq(&self, other: &Gid) -> bool {
    333         **self == *other
    334     }
    335 }
    336 impl PartialEq<u32> for Gid {
    337     #[inline]
    338     fn eq(&self, other: &u32) -> bool {
    339         self.0 == *other
    340     }
    341 }
    342 impl From<Gid> for u32 {
    343     #[inline]
    344     fn from(value: Gid) -> Self {
    345         value.0
    346     }
    347 }
    348 impl From<u32> for Gid {
    349     #[inline]
    350     fn from(value: u32) -> Self {
    351         Self(value)
    352     }
    353 }
    354 /// [`chroot(2)`](https://manned.org/chroot.2).
    355 ///
    356 /// # Errors
    357 ///
    358 /// Errors iff `chroot(2)` does.
    359 ///
    360 /// # Examples
    361 ///
    362 /// ```no_run
    363 /// assert!(priv_sep::chroot(c"./").is_ok());
    364 /// ```
    365 #[expect(unsafe_code, reason = "chroot(2) takes a pointer")]
    366 #[inline]
    367 pub fn chroot(path: &CStr) -> Result<(), Errno> {
    368     let ptr = path.as_ptr();
    369     // SAFETY:
    370     // `ptr` is valid and not null.
    371     if unsafe { c::chroot(ptr) } == SUCCESS {
    372         Ok(())
    373     } else {
    374         Err(Errno::last())
    375     }
    376 }
    377 /// [`chdir`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/chdir.html).
    378 ///
    379 /// This function MUST only be called by `chdir` and `chroot_then_chdir`.
    380 #[expect(unsafe_code, reason = "chdir(2) takes a pointer")]
    381 fn private_chdir(path: *const c_char) -> Result<(), Errno> {
    382     // SAFETY:
    383     // `path` is valid and not null as can be seen in the only functions that call this function:
    384     // `chdir` and `chroot_then_chdir`.
    385     if unsafe { c::chdir(path) } == SUCCESS {
    386         Ok(())
    387     } else {
    388         Err(Errno::last())
    389     }
    390 }
    391 /// [`chdir`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/chdir.html).
    392 ///
    393 /// # Errors
    394 ///
    395 /// Errors iff `chdir` does.
    396 ///
    397 /// # Examples
    398 ///
    399 /// ```no_run
    400 /// assert!(priv_sep::chdir(c"/").is_ok());
    401 /// ```
    402 #[inline]
    403 pub fn chdir(path: &CStr) -> Result<(), Errno> {
    404     private_chdir(path.as_ptr())
    405 }
    406 /// Calls [`chroot`] on `path` followed by a call to [`chdir`] on `"/"`.
    407 ///
    408 /// # Errors
    409 ///
    410 /// Errors iff `chroot` or `chdir` do.
    411 ///
    412 /// # Examples
    413 ///
    414 /// ```no_run
    415 /// assert!(priv_sep::chroot_then_chdir(c"./").is_ok());
    416 /// ```
    417 #[inline]
    418 pub fn chroot_then_chdir(path: &CStr) -> Result<(), Errno> {
    419     /// Root directory.
    420     const ROOT: *const c_char = c"/".as_ptr();
    421     chroot(path).and_then(|()| private_chdir(ROOT))
    422 }
    423 /// Error returned when dropping privileges.
    424 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    425 pub enum PrivDropErr<E> {
    426     /// Error when an error occurs from a libc call.
    427     Libc(Errno),
    428     /// Error when there is no entry in the user database corresponding to the passed username.
    429     NoPasswdEntry,
    430     /// Error when [`UserInfo::is_root`].
    431     RootEntry,
    432     /// Error returned from the user-provided function that is invoked before calling [`UserInfo::setresid`].
    433     Other(E),
    434 }
    435 impl<E: Display> Display for PrivDropErr<E> {
    436     #[inline]
    437     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
    438         match *self {
    439             Self::Libc(err) => write!(f, "libc error when dropping privileges: {err}"),
    440             Self::NoPasswdEntry => f.write_str("no passwd(5) entry to drop privileges to"),
    441             Self::RootEntry => f.write_str(
    442                 "setresuid(2) is not allowed to be called on uid 0 when dropping privileges",
    443             ),
    444             Self::Other(ref err) => write!(
    445                 f,
    446                 "error calling function before dropping privileges: {err}"
    447             ),
    448         }
    449     }
    450 }
    451 impl<E: CoreErr> CoreErr for PrivDropErr<E> {}
    452 impl<E> From<Errno> for PrivDropErr<E> {
    453     #[inline]
    454     fn from(value: Errno) -> Self {
    455         Self::Libc(value)
    456     }
    457 }
    458 /// Error returned from [`UserInfo::setresid_if_valid`].
    459 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    460 pub enum SetresidErr {
    461     /// Error when an error occurs from a libc call.
    462     Libc(Errno),
    463     /// Error when there is no entry in the user database corresponding to [`UserInfo::uid`].
    464     NoPasswdEntry,
    465     /// Error when the entry in the user database has a different gid than [`UserInfo::gid`].
    466     GidMismatch,
    467 }
    468 impl Display for SetresidErr {
    469     #[inline]
    470     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
    471         match *self {
    472             Self::Libc(err) => write!(f, "libc error when dropping privileges: {err}"),
    473             Self::NoPasswdEntry => f.write_str("no passwd(5) entry to drop privileges to"),
    474             Self::GidMismatch => f.write_str("gid in passwd(5) does match the expected gid"),
    475         }
    476     }
    477 }
    478 impl CoreErr for SetresidErr {}
    479 impl From<Errno> for SetresidErr {
    480     #[inline]
    481     fn from(value: Errno) -> Self {
    482         Self::Libc(value)
    483     }
    484 }
    485 /// Used by [`UserInfo::getpw_entry`].
    486 trait PwEntry {
    487     /// Calling code must uphold the following safety invariants:
    488     /// * `buf` must be a valid, initialized, non-null pointer
    489     /// * `size` must be the length of `buf`
    490     /// * `result` must be a valid, initialized non-null pointer referencing a valid and initialized pointer that
    491     ///   is allowed to be null.
    492     ///
    493     /// Implementors MUST only _write_ to `pwd` and never read from it (i.e., `pwd` is allowed to be unitialized).
    494     #[expect(
    495         unsafe_code,
    496         reason = "getpwnam_r(3) and getpwuid_r(3) take in pointers"
    497     )]
    498     unsafe fn getpw(
    499         self,
    500         pwd: *mut c::Passwd,
    501         buf: *mut c_char,
    502         size: usize,
    503         result: *mut *mut c::Passwd,
    504     ) -> c_int;
    505 }
    506 impl PwEntry for Uid {
    507     #[expect(unsafe_code, reason = "getpwuid_r(3) take in pointers")]
    508     unsafe fn getpw(
    509         self,
    510         pwd: *mut c::Passwd,
    511         buf: *mut c_char,
    512         size: usize,
    513         result: *mut *mut c::Passwd,
    514     ) -> c_int {
    515         // SAFETY:
    516         // Calling code must uphold safety invariants.
    517         // `pwd` is never read from.
    518         unsafe { c::getpwuid_r(self.0, pwd, buf, size, result) }
    519     }
    520 }
    521 /// `newtype` around `CStr`.
    522 #[derive(Clone, Copy)]
    523 struct CStrWrapper<'a>(&'a CStr);
    524 impl PwEntry for CStrWrapper<'_> {
    525     #[expect(unsafe_code, reason = "getpwnam_r(3) takes in pointers")]
    526     unsafe fn getpw(
    527         self,
    528         pwd: *mut c::Passwd,
    529         buf: *mut c_char,
    530         size: usize,
    531         result: *mut *mut c::Passwd,
    532     ) -> c_int {
    533         let ptr = self.0.as_ptr();
    534         // SAFETY:
    535         // Calling code must uphold safety invariants.
    536         // `ptr` is valid, initialized, and not null.
    537         // `pwd` is never read from.
    538         unsafe { c::getpwnam_r(ptr, pwd, buf, size, result) }
    539     }
    540 }
    541 /// User and group ID.
    542 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    543 pub struct UserInfo {
    544     /// The user ID.
    545     pub uid: Uid,
    546     /// The group ID.
    547     pub gid: Gid,
    548 }
    549 impl UserInfo {
    550     /// The buffer size we use to read a `passwd` entry.
    551     const PW_ENT_BUF_LEN: usize = 1024;
    552     /// Returns `true` iff [`Uid::is_root`].
    553     ///
    554     /// # Examples
    555     ///
    556     /// ```no_run
    557     /// # use priv_sep::{Gid, Uid, UserInfo};
    558     /// assert!(UserInfo { uid: Uid::ROOT, gid: Gid(0), }.is_root());
    559     /// ```
    560     #[inline]
    561     #[must_use]
    562     pub const fn is_root(self) -> bool {
    563         self.uid.is_root()
    564     }
    565     /// Helper for [`Self::with_buffer`], [`Self::new`], and [`Self::setresid_if_exists`].
    566     #[expect(
    567         unsafe_code,
    568         reason = "getpwnam_r(3) and getpwuid_r(3) take in pointers"
    569     )]
    570     fn getpw_entry<P: Copy + PwEntry>(u: P, buffer: &mut [c_char]) -> Result<Option<Self>, Errno> {
    571         let mut pwd = MaybeUninit::<c::Passwd>::uninit();
    572         let pwd_ptr = pwd.as_mut_ptr();
    573         let buf_ptr = buffer.as_mut_ptr();
    574         let len = buffer.len();
    575         let mut result = ptr::null_mut();
    576         let res_ptr = &mut result;
    577         // SAFETY:
    578         // `pwd_ptr` is only written to; thus the fact `pwd` is unitialized is fine.
    579         // `buf_ptr` is valid, initialized, and not null.
    580         // `len` is the length of `buf_ptr`.
    581         // `res_ptr` is valid, initialized, and not null.
    582         // `result` is valid, initialized, and allowed to be null.
    583         let code = unsafe { u.getpw(pwd_ptr, buf_ptr, len, res_ptr) };
    584         if code == SUCCESS {
    585             if result.is_null() {
    586                 Ok(None)
    587             } else {
    588                 debug_assert!(
    589                     result.is_aligned(),
    590                     "libc getpwnam_r or getpwuid_r result was not aligned. Something is terribly wrong with your system"
    591                 );
    592                 // SAFETY:
    593                 // Verified above that `result` is not null and aligned. Note while we only verify the pointer
    594                 // is aligned on non-release builds, the situation is so dire that one could argue we are
    595                 // already in "undefined" territory.
    596                 // When `result` is not null, the platform is supposed to have written to `pwd`.
    597                 Ok(Some(unsafe { pwd.assume_init() }.into_user_info()))
    598             }
    599         } else {
    600             Err(Errno::from_raw(code))
    601         }
    602     }
    603     /// [`getpwnam_r`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/getpwnam_r.html).
    604     ///
    605     /// Uses `buffer` to write the user database entry into returning `None` iff there is no entry; otherwise
    606     /// returns `Self`.
    607     ///
    608     /// Note it is the caller's responsibility to ensure `buffer` is large enough; otherwise an [`Errno`] will
    609     /// be returned.
    610     ///
    611     /// # Errors
    612     ///
    613     /// Errors iff `getpwnam_r` does.
    614     ///
    615     /// # Examples
    616     ///
    617     /// ```no_run
    618     /// # use priv_sep::{Uid, UserInfo};
    619     /// assert!(UserInfo::with_buffer(c"root", [0; 128].as_mut_slice())?.map_or(false, |info| info.is_root()));
    620     /// # Ok::<_, priv_sep::Errno>(())
    621     /// ```
    622     #[inline]
    623     pub fn with_buffer(name: &CStr, buffer: &mut [c_char]) -> Result<Option<Self>, Errno> {
    624         Self::getpw_entry(CStrWrapper(name), buffer)
    625     }
    626     /// Same as [`Self::with_buffer`] except a stack-allocated buffer of 1024 bytes is used.
    627     ///
    628     /// # Errors
    629     ///
    630     /// Errors iff [`Self::with_buffer`] does for a 1024-byte buffer.
    631     ///
    632     /// # Examples
    633     ///
    634     /// ```no_run
    635     /// # use priv_sep::UserInfo;
    636     /// assert!(UserInfo::new(c"root")?.map_or(false, |info| info.is_root()));
    637     /// # Ok::<_, priv_sep::Errno>(())
    638     /// ```
    639     #[inline]
    640     pub fn new(name: &CStr) -> Result<Option<Self>, Errno> {
    641         Self::with_buffer(name, &mut [0; Self::PW_ENT_BUF_LEN])
    642     }
    643     /// Calls [`Gid::setresgid`] and [`Uid::setresuid`].
    644     ///
    645     /// # Errors
    646     ///
    647     /// Errors iff `Gid::setresgid` or `Uid::setresuid` error.
    648     ///
    649     /// # Examples
    650     ///
    651     /// ```no_run
    652     /// # use priv_sep::UserInfo;
    653     /// if let Some(user) = UserInfo::new(c"nobody")? {
    654     ///     user.setresid()?;
    655     /// }
    656     /// # Ok::<_, priv_sep::Errno>(())
    657     /// ```
    658     #[inline]
    659     pub fn setresid(self) -> Result<(), Errno> {
    660         self.gid.setresgid().and_then(|()| self.uid.setresuid())
    661     }
    662     /// Same as [`Self::setresid`] except
    663     /// [`getpwuid_r`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/getpwuid_r.html)
    664     /// is used to first confirm the existence of [`Self::uid`] and [`Self::gid`].
    665     ///
    666     /// Note this should rarely be used since most will rely on [`Self::new`], [`Self::with_buffer`],
    667     /// [`Self::priv_drop`], or [`Self::chroot_then_priv_drop`].
    668     ///
    669     /// Like [`Self::new`], this will fail if the buffer needed exceeds 16 KiB.
    670     ///
    671     /// # Errors
    672     ///
    673     /// Errors iff `getpwuid_r` errors for a 16 KiB buffer, [`Self::uid`] and [`Self::gid`] don't exist in the user
    674     /// database, [`Gid::setresgid`] errors, or [`Uid::setresuid`] errors.
    675     ///
    676     /// # Examples
    677     ///
    678     /// ```no_run
    679     /// # use priv_sep::{Gid, Uid, UserInfo};
    680     /// UserInfo { uid: Uid(1000), gid: Gid(1000), }.setresid_if_valid()?;
    681     /// # Ok::<_, priv_sep::SetresidErr>(())
    682     /// ```
    683     #[inline]
    684     pub fn setresid_if_valid(self) -> Result<(), SetresidErr> {
    685         Self::getpw_entry(self.uid, &mut [0; Self::PW_ENT_BUF_LEN])
    686             .map_err(SetresidErr::Libc)
    687             .and_then(|opt| {
    688                 opt.ok_or(SetresidErr::NoPasswdEntry).and_then(|info| {
    689                     if info.gid == self.gid {
    690                         self.setresid().map_err(SetresidErr::Libc)
    691                     } else {
    692                         Err(SetresidErr::GidMismatch)
    693                     }
    694                 })
    695             })
    696     }
    697     /// Helper to unify targets that don't support `setgroups(2)`.
    698     ///
    699     /// No-op.
    700     #[cfg(target_os = "macos")]
    701     #[expect(
    702         clippy::unnecessary_wraps,
    703         reason = "unify with platforms that support `setgroups`"
    704     )]
    705     const fn drop_sup_groups<Never>() -> Result<(), Never> {
    706         Ok(())
    707     }
    708     /// Helper to unify targets that don't support `setgroups(2)`.
    709     #[cfg(any(
    710         target_os = "dragonfly",
    711         target_os = "freebsd",
    712         target_os = "linux",
    713         target_os = "netbsd",
    714         target_os = "openbsd"
    715     ))]
    716     fn drop_sup_groups() -> Result<(), Errno> {
    717         drop_supplementary_groups()
    718     }
    719     /// Calls [`Self::new`], invokes `f`, calls [`drop_supplementary_groups`] (if the platform supports
    720     /// `setgroups(2)`), then calls [`Self::setresid`].
    721     ///
    722     /// Dropping privileges is necessary when needing to perform certain actions as root before no longer needing
    723     /// such abilities; at which point, one calls
    724     /// [`setgroups(2)`](https://www.man7.org/linux/man-pages/man2/setgroups.2.html),
    725     /// [`setresgid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/setresgid.html), and
    726     /// [`setresuid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/setresuid.html)
    727     /// using a lesser privileged gid and uid.
    728     ///
    729     /// # Errors
    730     ///
    731     /// Errors iff [`Self::new`], `f`, [`drop_supplementary_groups`], or [`Self::setresid`] do or there is no entry in the user database
    732     /// corresponding to `name` or the entry has uid 0.
    733     ///
    734     /// # Examples
    735     ///
    736     /// ```no_run
    737     /// # use core::net::{Ipv6Addr, SocketAddrV6};
    738     /// # use priv_sep::{PrivDropErr, UserInfo};
    739     /// # use std::{io::Error, net::TcpListener};
    740     /// let listener = UserInfo::priv_drop(c"nobody", || {
    741     ///     TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0))
    742     /// })?;
    743     /// # Ok::<_, PrivDropErr<Error>>(())
    744     /// ```
    745     #[inline]
    746     pub fn priv_drop<T, E, F: FnOnce() -> Result<T, E>>(
    747         name: &CStr,
    748         f: F,
    749     ) -> Result<T, PrivDropErr<E>> {
    750         Self::new(name).map_err(PrivDropErr::Libc).and_then(|opt| {
    751             opt.ok_or_else(|| PrivDropErr::NoPasswdEntry)
    752                 .and_then(|info| {
    753                     if info.is_root() {
    754                         Err(PrivDropErr::RootEntry)
    755                     } else {
    756                         f().map_err(PrivDropErr::Other).and_then(|res| {
    757                             Self::drop_sup_groups()
    758                                 .and_then(|()| info.setresid().map(|()| res))
    759                                 .map_err(PrivDropErr::Libc)
    760                         })
    761                     }
    762                 })
    763         })
    764     }
    765     /// Same as [`Self::priv_drop`] except `f` is `async`.
    766     ///
    767     /// # Errors
    768     ///
    769     /// Read [`Self::priv_drop`].
    770     ///
    771     /// # Examples
    772     ///
    773     /// ```no_run
    774     /// # use core::net::{Ipv6Addr, SocketAddrV6};
    775     /// # use priv_sep::UserInfo;
    776     /// # use tokio::net::TcpListener;
    777     /// let listener_fut = UserInfo::priv_drop_async(c"nobody", async || {
    778     ///     TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await
    779     /// });
    780     /// ```
    781     #[inline]
    782     pub async fn priv_drop_async<T, E, F: AsyncFnOnce() -> Result<T, E>>(
    783         name: &CStr,
    784         f: F,
    785     ) -> Result<T, PrivDropErr<E>> {
    786         match Self::new(name) {
    787             Ok(opt) => match opt {
    788                 None => Err(PrivDropErr::NoPasswdEntry),
    789                 Some(info) => {
    790                     if info.is_root() {
    791                         Err(PrivDropErr::RootEntry)
    792                     } else {
    793                         f().await.map_err(PrivDropErr::Other).and_then(|res| {
    794                             Self::drop_sup_groups()
    795                                 .and_then(|()| info.setresid().map(|()| res))
    796                                 .map_err(PrivDropErr::Libc)
    797                         })
    798                     }
    799                 }
    800             },
    801             Err(err) => Err(PrivDropErr::Libc(err)),
    802         }
    803     }
    804     /// Same as [`Self::priv_drop`] except [`chroot_then_chdir`] is called before or after invoking `f` based on
    805     /// `chroot_after_f`.
    806     ///
    807     /// # Errors
    808     ///
    809     /// Errors iff [`Self::priv_drop`] or [`chroot_then_chdir`] do.
    810     ///
    811     /// # Examples
    812     ///
    813     /// ```no_run
    814     /// # use core::net::{Ipv6Addr, SocketAddrV6};
    815     /// # use priv_sep::{PrivDropErr, UserInfo};
    816     /// # use std::{io::Error, net::TcpListener};
    817     /// let listener = UserInfo::chroot_then_priv_drop(c"nobody", c"./", false, || {
    818     ///     TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0))
    819     /// })?;
    820     /// # Ok::<_, PrivDropErr<Error>>(())
    821     /// ```
    822     #[inline]
    823     pub fn chroot_then_priv_drop<T, E, F: FnOnce() -> Result<T, E>>(
    824         name: &CStr,
    825         path: &CStr,
    826         chroot_after_f: bool,
    827         f: F,
    828     ) -> Result<T, PrivDropErr<E>> {
    829         Self::new(name).map_err(PrivDropErr::Libc).and_then(|opt| {
    830             opt.ok_or_else(|| PrivDropErr::NoPasswdEntry)
    831                 .and_then(|info| {
    832                     if info.is_root() {
    833                         Err(PrivDropErr::RootEntry)
    834                     } else if chroot_after_f {
    835                         f().map_err(PrivDropErr::Other).and_then(|res| {
    836                             chroot_then_chdir(path)
    837                                 .map_err(PrivDropErr::Libc)
    838                                 .map(|()| res)
    839                         })
    840                     } else {
    841                         chroot_then_chdir(path)
    842                             .map_err(PrivDropErr::Libc)
    843                             .and_then(|()| f().map_err(PrivDropErr::Other))
    844                     }
    845                     .and_then(|res| {
    846                         Self::drop_sup_groups()
    847                             .and_then(|()| info.setresid().map(|()| res))
    848                             .map_err(PrivDropErr::Libc)
    849                     })
    850                 })
    851         })
    852     }
    853     /// Same as [`Self::chroot_then_priv_drop`] except `f` is `async`.
    854     ///
    855     /// # Errors
    856     ///
    857     /// Read [`Self::chroot_then_priv_drop`].
    858     ///
    859     /// # Examples
    860     ///
    861     /// ```no_run
    862     /// # use core::net::{Ipv6Addr, SocketAddrV6};
    863     /// # use priv_sep::UserInfo;
    864     /// # use tokio::net::TcpListener;
    865     /// let listener_fut = UserInfo::chroot_then_priv_drop_async(c"nobody", c"./", false, async || {
    866     ///     TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await
    867     /// });
    868     /// ```
    869     #[inline]
    870     pub async fn chroot_then_priv_drop_async<T, E, F: AsyncFnOnce() -> Result<T, E>>(
    871         name: &CStr,
    872         path: &CStr,
    873         chroot_after_f: bool,
    874         f: F,
    875     ) -> Result<T, PrivDropErr<E>> {
    876         match Self::new(name) {
    877             Ok(opt) => match opt {
    878                 None => Err(PrivDropErr::NoPasswdEntry),
    879                 Some(info) => if info.is_root() {
    880                     Err(PrivDropErr::RootEntry)
    881                 } else if chroot_after_f {
    882                     f().await.map_err(PrivDropErr::Other).and_then(|res| {
    883                         chroot_then_chdir(path)
    884                             .map_err(PrivDropErr::Libc)
    885                             .map(|()| res)
    886                     })
    887                 } else {
    888                     match chroot_then_chdir(path) {
    889                         Ok(()) => f().await.map_err(PrivDropErr::Other),
    890                         Err(err) => Err(PrivDropErr::Libc(err)),
    891                     }
    892                 }
    893                 .and_then(|res| {
    894                     Self::drop_sup_groups()
    895                         .and_then(|()| info.setresid().map(|()| res))
    896                         .map_err(PrivDropErr::Libc)
    897                 }),
    898             },
    899             Err(err) => Err(PrivDropErr::Libc(err)),
    900         }
    901     }
    902 }
    903 impl PartialEq<&Self> for UserInfo {
    904     #[inline]
    905     fn eq(&self, other: &&Self) -> bool {
    906         *self == **other
    907     }
    908 }
    909 impl PartialEq<UserInfo> for &UserInfo {
    910     #[inline]
    911     fn eq(&self, other: &UserInfo) -> bool {
    912         **self == *other
    913     }
    914 }
    915 /// [`setgroups(2)`](https://www.man7.org/linux/man-pages/man2/setgroups.2.html).
    916 ///
    917 /// # Errors
    918 ///
    919 /// Errors iff `setgroups` does.
    920 ///
    921 /// # Examples
    922 ///
    923 /// ```no_run
    924 /// assert!(priv_sep::setgroups(&[]).is_ok());
    925 /// ```
    926 #[cfg_attr(docsrs, doc(cfg(not(target_os = "macos"))))]
    927 #[cfg(not(target_os = "macos"))]
    928 #[expect(unsafe_code, reason = "setgroups(2) takes a pointer")]
    929 #[inline]
    930 pub fn setgroups(groups: &[Gid]) -> Result<(), Errno> {
    931     #[cfg(target_os = "linux")]
    932     let size = groups.len();
    933     #[cfg(any(
    934         target_os = "dragonfly",
    935         target_os = "freebsd",
    936         target_os = "netbsd",
    937         target_os = "openbsd"
    938     ))]
    939     let size = c_int::try_from(groups.len()).map_err(|_e| Errno::EINVAL)?;
    940     let gids = groups.as_ptr().cast();
    941     // SAFETY:
    942     // `size` is the length of `gids`, and `gids` is a valid, non-null, alligned pointer to
    943     // `u32`s. Note that `Gid` is a `newtype` around `u32` with `repr(transparent)`; thus the above
    944     // pointer cast is fine.
    945     let code = unsafe { c::setgroups(size, gids) };
    946     if code == SUCCESS {
    947         Ok(())
    948     } else {
    949         Err(Errno::last())
    950     }
    951 }
    952 /// [`setgroups(2)`](https://www.man7.org/linux/man-pages/man2/setgroups.2.html) passing in
    953 /// 0 and a null pointer.
    954 ///
    955 /// If successful, this drops all supplementary groups.
    956 ///
    957 /// # Errors
    958 ///
    959 /// Errors iff `setgroups` does.
    960 ///
    961 /// # Examples
    962 ///
    963 /// ```no_run
    964 /// assert!(priv_sep::drop_supplementary_groups().is_ok());
    965 /// ```
    966 #[cfg_attr(docsrs, doc(cfg(not(target_os = "macos"))))]
    967 #[cfg(not(target_os = "macos"))]
    968 #[expect(unsafe_code, reason = "setgroups(2) takes a pointer")]
    969 #[inline]
    970 pub fn drop_supplementary_groups() -> Result<(), Errno> {
    971     #[cfg(target_os = "linux")]
    972     let size: usize = 0;
    973     #[cfg(any(
    974         target_os = "dragonfly",
    975         target_os = "freebsd",
    976         target_os = "netbsd",
    977         target_os = "openbsd"
    978     ))]
    979     let size: c_int = 0;
    980     let gids = ptr::null();
    981     // SAFETY:
    982     // Passing in 0 and a null pointer is valid and causes all supplementary groups to be dropped.
    983     let code = unsafe { c::setgroups(size, gids) };
    984     if code == SUCCESS {
    985         Ok(())
    986     } else {
    987         Err(Errno::last())
    988     }
    989 }
    990 #[cfg(test)]
    991 mod tests {
    992     #[cfg(feature = "std")]
    993     use super::{CStr, PrivDropErr};
    994     use super::{Errno, Gid, SetresidErr, Uid, UserInfo};
    995     #[cfg(all(feature = "std", target_os = "openbsd"))]
    996     use super::{Permissions, Promise, Promises};
    997     #[cfg(feature = "std")]
    998     use core::net::{Ipv6Addr, SocketAddrV6};
    999     #[cfg(all(feature = "std", target_os = "openbsd"))]
   1000     extern crate alloc;
   1001     #[cfg(all(feature = "std", target_os = "openbsd"))]
   1002     use alloc::format;
   1003     #[cfg(all(feature = "std", target_os = "openbsd"))]
   1004     use std::io::{self, Write as _};
   1005     #[cfg(feature = "std")]
   1006     use std::{
   1007         fs,
   1008         io::{Error, ErrorKind},
   1009         net::TcpListener,
   1010     };
   1011     use tokio as _;
   1012     #[cfg(feature = "std")]
   1013     const README: &CStr = c"README.md";
   1014     #[cfg(feature = "std")]
   1015     const README_STR: &str = match README.to_str() {
   1016         Ok(val) => val,
   1017         Err(_) => panic!("not possible"),
   1018     };
   1019     #[test]
   1020     fn getuid() {
   1021         _ = Uid::getuid();
   1022     }
   1023     #[test]
   1024     fn geteuid() {
   1025         _ = Uid::geteuid();
   1026     }
   1027     #[test]
   1028     fn getgid() {
   1029         _ = Gid::getgid();
   1030     }
   1031     #[test]
   1032     fn getegid() {
   1033         _ = Gid::getegid();
   1034     }
   1035     #[test]
   1036     fn setresuid() -> Result<(), Errno> {
   1037         Uid::geteuid().setresuid()
   1038     }
   1039     #[test]
   1040     fn setresgid() -> Result<(), Errno> {
   1041         Gid::getegid().setresgid()
   1042     }
   1043     #[test]
   1044     fn user_info_new() {
   1045         assert_eq!(
   1046             UserInfo::new(c"root"),
   1047             Ok(Some(UserInfo {
   1048                 uid: Uid::ROOT,
   1049                 gid: Gid(0),
   1050             }))
   1051         );
   1052     }
   1053     #[test]
   1054     fn user_info_with_buffer() {
   1055         assert_eq!(
   1056             UserInfo::with_buffer(c"root", [0; 512].as_mut_slice()),
   1057             Ok(Some(UserInfo {
   1058                 uid: Uid::ROOT,
   1059                 gid: Gid(0),
   1060             }))
   1061         );
   1062     }
   1063     #[test]
   1064     fn user_info_setresid() -> Result<(), Errno> {
   1065         UserInfo {
   1066             uid: Uid::geteuid(),
   1067             gid: Gid::getegid(),
   1068         }
   1069         .setresid()
   1070     }
   1071     #[test]
   1072     fn user_info_setresid_if_exists() -> Result<(), SetresidErr> {
   1073         UserInfo {
   1074             uid: Uid::geteuid(),
   1075             gid: Gid::getegid(),
   1076         }
   1077         .setresid_if_valid()
   1078     }
   1079     #[test]
   1080     fn user_info_setresid_if_exists_failure() {
   1081         assert_eq!(
   1082             UserInfo {
   1083                 uid: Uid::geteuid(),
   1084                 gid: Gid(u32::MAX),
   1085             }
   1086             .setresid_if_valid(),
   1087             Err(SetresidErr::GidMismatch)
   1088         );
   1089     }
   1090     #[cfg(feature = "std")]
   1091     #[test]
   1092     #[ignore = "primarily useful for root and interferes with chroot_drop_priv"]
   1093     fn priv_drop() {
   1094         if Uid::geteuid().is_root() {
   1095             assert!(
   1096                 UserInfo::priv_drop(c"nobody", || {
   1097                     TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0))
   1098                 })
   1099                 .is_ok_and(|_| true)
   1100             );
   1101             assert!(
   1102                 TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 80, 0, 0)).map_or_else(
   1103                     |e| matches!(e.kind(), ErrorKind::PermissionDenied),
   1104                     |_| false
   1105                 )
   1106             );
   1107         } else {
   1108             assert!(
   1109                 UserInfo::priv_drop(c"root", || Ok::<_, Error>(()))
   1110                     .map_or_else(|e| matches!(e, PrivDropErr::RootEntry), |()| false)
   1111             );
   1112         }
   1113     }
   1114     #[cfg(feature = "std")]
   1115     #[test]
   1116     #[ignore = "primarily useful for root and interferes with priv_drop"]
   1117     fn chroot_drop_priv() {
   1118         if Uid::geteuid().is_root() {
   1119             assert!(
   1120                 UserInfo::chroot_then_priv_drop(c"nobody", c"./", false, || {
   1121                     TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 987, 0, 0))
   1122                 })
   1123                 .is_ok_and(|_| {
   1124                     fs::exists(README_STR).is_ok_and(|exists| {
   1125                         exists
   1126                             && TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 82, 0, 0))
   1127                                 .map_or_else(
   1128                                     |e| matches!(e.kind(), ErrorKind::PermissionDenied),
   1129                                     |_| false,
   1130                                 )
   1131                     })
   1132                 })
   1133             );
   1134         }
   1135     }
   1136     #[expect(
   1137         clippy::panic,
   1138         reason = "reasonable to expect files to not exist that need to in order to test pledge and unveil"
   1139     )]
   1140     #[expect(
   1141         clippy::expect_used,
   1142         reason = "kinda reasonable to expect files to exist that mustn't in order to test pledge and unveil"
   1143     )]
   1144     #[cfg(all(feature = "std", target_os = "openbsd"))]
   1145     #[test]
   1146     #[ignore = "interferes with other tests"]
   1147     fn unveil_pledge() {
   1148         const FILE_EXISTS: &CStr = c"/home/zack/foo.txt";
   1149         const FILE_EXISTS_STR: &str = match FILE_EXISTS.to_str() {
   1150             Ok(val) => val,
   1151             Err(_) => panic!("not possible"),
   1152         };
   1153         const FILE_NOT_EXISTS: &CStr = c"/home/zack/aadkjfasj3s23";
   1154         const FILE_NOT_EXISTS_STR: &str = match FILE_NOT_EXISTS.to_str() {
   1155             Ok(val) => val,
   1156             Err(_) => panic!("not possible"),
   1157         };
   1158         const DIR_NOT_EXISTS: &CStr = c"/home/zack/aadkjfasj3s23/";
   1159         const DIR_NOT_EXISTS_STR: &str = match DIR_NOT_EXISTS.to_str() {
   1160             Ok(val) => val,
   1161             Err(_) => panic!("not possible"),
   1162         };
   1163         _ = fs::metadata(FILE_EXISTS_STR).unwrap_or_else(|_e| {
   1164             panic!("{FILE_EXISTS_STR} does not exist, so unit testing cannot occur")
   1165         });
   1166         drop(fs::metadata(FILE_NOT_EXISTS_STR).expect_err(
   1167             format!("{FILE_NOT_EXISTS_STR} exists, so unit testing cannot occur").as_str(),
   1168         ));
   1169         drop(fs::metadata(DIR_NOT_EXISTS_STR).expect_err(
   1170             format!("{DIR_NOT_EXISTS_STR} exists, so unit testing cannot occur").as_str(),
   1171         ));
   1172         // This tests that a NULL `promise` does nothing.
   1173         assert_eq!(Promises::pledge_none(), Ok(()));
   1174         assert!(writeln!(io::stdout()).is_ok_and(|()| true));
   1175         assert_eq!(Promises::ALL.pledge(), Ok(()));
   1176         // This tests that duplicates are ignored as well as the implementation of PartialEq.
   1177         let mut initial_promises = Promises::new([
   1178             Promise::Stdio,
   1179             Promise::Unveil,
   1180             Promise::Rpath,
   1181             Promise::Stdio,
   1182         ]);
   1183         assert_eq!(initial_promises.len(), 3);
   1184         assert_eq!(
   1185             initial_promises,
   1186             Promises::new([Promise::Rpath, Promise::Stdio, Promise::Unveil])
   1187         );
   1188         // Test retain.
   1189         let mut vals = Promises::new([
   1190             Promise::Audio,
   1191             Promise::Bpf,
   1192             Promise::Chown,
   1193             Promise::Cpath,
   1194             Promise::Error,
   1195             Promise::Exec,
   1196         ]);
   1197         vals.retain([Promise::Error, Promise::Chown]);
   1198         assert_eq!(vals.len(), 2);
   1199         assert!(vals.contains(Promise::Chown));
   1200         assert!(vals.contains(Promise::Error));
   1201         assert_eq!(initial_promises.pledge(), Ok(()));
   1202         // This tests unveil with no permissions.
   1203         assert_eq!(Permissions::NONE.unveil(FILE_EXISTS), Ok(()));
   1204         assert!(fs::metadata(FILE_EXISTS_STR).map_or_else(
   1205             |e| matches!(e.kind(), ErrorKind::PermissionDenied),
   1206             |_| false
   1207         ));
   1208         // This tests unveil with read permissions,
   1209         // and one can unveil more permissions (unlike pledge which can only remove promises).
   1210         assert_eq!(Permissions::READ.unveil(FILE_EXISTS), Ok(()));
   1211         assert!(fs::metadata(FILE_EXISTS_STR).is_ok_and(|_| true));
   1212         // This tests that calls to unveil on missing files don't error.
   1213         assert_eq!(Permissions::NONE.unveil(FILE_NOT_EXISTS), Ok(()));
   1214         // This tests that calls to unveil on missing directories error.
   1215         assert_eq!(Permissions::NONE.unveil(DIR_NOT_EXISTS), Err(Errno::ENOENT));
   1216         // This tests that unveil can no longer be called.
   1217         assert_eq!(Permissions::unveil_no_more(), Ok(()));
   1218         assert_eq!(Permissions::NONE.unveil(FILE_EXISTS), Err(Errno::EPERM));
   1219         assert!(fs::metadata(FILE_EXISTS_STR).is_ok_and(|_| true));
   1220         // The below tests that Promises can only be removed and not added.
   1221         initial_promises.remove_promises([Promise::Unveil]);
   1222         assert_eq!(initial_promises.len(), 2);
   1223         initial_promises.remove(Promise::Rpath);
   1224         assert_eq!(initial_promises.len(), 1);
   1225         initial_promises.remove(Promise::Rpath);
   1226         assert_eq!(initial_promises.len(), 1);
   1227         assert_eq!(initial_promises.pledge(), Ok(()));
   1228         assert!(writeln!(io::stdout()).is_ok_and(|()| true));
   1229         assert_eq!(Promises::new([Promise::Rpath]).pledge(), Err(Errno::EPERM));
   1230         // If the below is uncommented, the program should crash since the above
   1231         // call to pledge no longer allows access to the file system.
   1232         // drop(fs::metadata(FILE_EXISTS_STR));
   1233     }
   1234     #[cfg(all(feature = "std", target_os = "openbsd"))]
   1235     #[test]
   1236     #[ignore = "interferes with other tests when root"]
   1237     fn pledge_inet_drop_priv() {
   1238         if Uid::geteuid().is_root() {
   1239             assert!(
   1240                 Promises::new_priv_drop(
   1241                     c"nobody",
   1242                     [Promise::Inet, Promise::Rpath, Promise::Unveil],
   1243                     false,
   1244                     || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 47, 0, 0)),
   1245                 )
   1246                 .is_ok_and(|(_, _)| {
   1247                     Permissions::unveil_raw(README, c"r").is_ok_and(|()| {
   1248                         fs::exists(README_STR).is_ok_and(|exists| {
   1249                             Permissions::NONE.unveil(README).is_ok_and(|()| {
   1250                                 Promises::pledge_raw(c"inet stdio").is_ok_and(|()| {
   1251                                     exists
   1252                                         && TcpListener::bind(SocketAddrV6::new(
   1253                                             Ipv6Addr::LOCALHOST,
   1254                                             792,
   1255                                             0,
   1256                                             0,
   1257                                         ))
   1258                                         .map_or_else(
   1259                                             |e| matches!(e.kind(), ErrorKind::PermissionDenied),
   1260                                             |_| false,
   1261                                         )
   1262                                 })
   1263                             })
   1264                         })
   1265                     })
   1266                 })
   1267             );
   1268         }
   1269     }
   1270     #[cfg(all(feature = "std", target_os = "openbsd"))]
   1271     #[test]
   1272     #[ignore = "interferes with other tests when root"]
   1273     fn inet_chroot_priv_pledge() {
   1274         if Uid::geteuid().is_root() {
   1275             assert!(
   1276                 Promises::new_chroot_then_priv_drop(
   1277                     c"nobody",
   1278                     c"./",
   1279                     [Promise::Inet, Promise::Rpath, Promise::Unveil],
   1280                     false,
   1281                     || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 382, 0, 0))
   1282                 )
   1283                 .is_ok_and(|(_, mut promises)| Permissions::READ
   1284                     .unveil(README)
   1285                     .is_ok_and(
   1286                         |()| fs::exists(README_STR).is_ok_and(|exists| Permissions::NONE
   1287                             .unveil(README)
   1288                             .is_ok_and(|()| promises
   1289                                 .remove_promises_then_pledge([Promise::Rpath, Promise::Unveil])
   1290                                 .is_ok_and(|()| exists
   1291                                     && TcpListener::bind(SocketAddrV6::new(
   1292                                         Ipv6Addr::LOCALHOST,
   1293                                         588,
   1294                                         0,
   1295                                         0
   1296                                     ))
   1297                                     .map_or_else(
   1298                                         |e| matches!(e.kind(), ErrorKind::PermissionDenied),
   1299                                         |_| false
   1300                                     ))))
   1301                     ))
   1302             );
   1303         }
   1304     }
   1305 }