priv_sep

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

lib.rs (45682B)


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