priv_sep

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

lib.rs (43920B)


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