priv_sep

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

lib.rs (54384B)


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