priv_sep

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

lib.rs (45751B)


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