priv_sep

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

lib.rs (43452B)


      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`](https://doc.rust-lang.org/stable/alloc/) support. While "typical" use of `priv_sep`
    116 //! should work without `alloc`, there are cases where one may desire heap allocation. For example if a
    117 //! database entry associated with a user requires more than 1 KiB of space, [`UserInfo::new`] will error
    118 //! with [`Errno::ERANGE`] when `alloc` is not enabled.
    119 //!
    120 //! Additional [`CStrHelper`] `impl`s are exposed as well (e.g.,
    121 //! [`String`](./trait.CStrHelper.html#impl-CStrHelper-for-String)).
    122 //!
    123 //! ### `std`
    124 //!
    125 //! Enables [`std`](https://doc.rust-lang.org/stable/std/) support. This is useful for additional [`CStrHelper`]
    126 //! `impl`s (e.g., [`OsStr`](./trait.CStrHelper.html#impl-CStrHelper-for-OsStr)) as well as
    127 //! [`TryFrom<Error>`](./enum.Errno.html#impl-TryFrom%3CError%3E-for-Errno) and
    128 //! [`From<Errno>`](./enum.Errno.html#impl-From%3CErrno%3E-for-Error).
    129 //!
    130 //! This feature implies [`alloc`](#alloc) and is enabled by default via the `default` feature.
    131 #![expect(
    132     clippy::doc_paragraphs_missing_punctuation,
    133     reason = "false positive for crate documentation having image links"
    134 )]
    135 #![cfg_attr(docsrs, feature(doc_cfg))]
    136 #![cfg_attr(docsrs, doc(auto_cfg = false))]
    137 #![no_std]
    138 #![cfg(any(
    139     target_os = "dragonfly",
    140     target_os = "freebsd",
    141     target_os = "linux",
    142     target_os = "macos",
    143     target_os = "netbsd",
    144     target_os = "openbsd"
    145 ))]
    146 #![expect(
    147     clippy::pub_use,
    148     reason = "don't want Errno nor openbsd types in a module"
    149 )]
    150 #[cfg(feature = "alloc")]
    151 extern crate alloc;
    152 #[cfg(feature = "std")]
    153 extern crate std;
    154 /// C FFI.
    155 mod c;
    156 /// Errno.
    157 mod err;
    158 /// OpenBSD
    159 #[cfg(any(doc, target_os = "openbsd"))]
    160 mod openbsd;
    161 /// Unit tests.
    162 #[cfg(test)]
    163 mod tests;
    164 #[cfg(feature = "std")]
    165 use alloc::borrow::Cow;
    166 #[cfg(feature = "alloc")]
    167 use alloc::{ffi::CString, string::String, vec};
    168 use c::SUCCESS;
    169 use core::{
    170     error::Error as CoreErr,
    171     ffi::{CStr, c_char, c_int},
    172     fmt::{self, Display, Formatter},
    173     mem::MaybeUninit,
    174     ptr, slice,
    175 };
    176 pub use err::Errno;
    177 #[cfg_attr(docsrs, doc(cfg(target_os = "openbsd")))]
    178 #[cfg(any(doc, target_os = "openbsd"))]
    179 pub use openbsd::{Permission, Permissions, Promise, Promises};
    180 #[cfg(feature = "std")]
    181 use std::{
    182     ffi::{OsStr, OsString},
    183     path::{Component, Components, Iter, Path, PathBuf},
    184 };
    185 /// [`uid_t`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/basedefs/sys_types.h.html).
    186 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    187 #[repr(transparent)]
    188 pub struct Uid(pub u32);
    189 impl Uid {
    190     /// The root user ID (i.e., 0).
    191     pub const ROOT: Self = Self(0);
    192     /// Returns `true` iff `self` is [`Self::ROOT`].
    193     ///
    194     /// # Examples
    195     ///
    196     /// ```no_run
    197     /// # use priv_sep::Uid;
    198     /// assert!(Uid::ROOT.is_root());
    199     /// ```
    200     #[inline]
    201     #[must_use]
    202     pub const fn is_root(self) -> bool {
    203         self.0 == Self::ROOT.0
    204     }
    205     /// [`getuid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/getuid.html).
    206     ///
    207     /// # Examples
    208     ///
    209     /// ```no_run
    210     /// # use priv_sep::Uid;
    211     /// assert_eq!(Uid::getuid(), 1000);
    212     /// ```
    213     #[inline]
    214     #[must_use]
    215     pub fn getuid() -> Self {
    216         Self(c::getuid())
    217     }
    218     /// [`geteuid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/geteuid.html).
    219     ///
    220     /// # Examples
    221     ///
    222     /// ```no_run
    223     /// # use priv_sep::Uid;
    224     /// assert_eq!(Uid::geteuid(), 1000);
    225     /// ```
    226     #[inline]
    227     #[must_use]
    228     pub fn geteuid() -> Self {
    229         Self(c::geteuid())
    230     }
    231     /// Calls [`setresuid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/setresuid.html)
    232     /// passing `self` for the real, effective, and saved user IDs.
    233     ///
    234     /// Note on some platforms `setuid` is called using `self`.
    235     ///
    236     /// # Errors
    237     ///
    238     /// Errors iff `setresuid` does.
    239     ///
    240     /// # Examples
    241     ///
    242     /// ```no_run
    243     /// # use priv_sep::Uid;
    244     /// assert!(Uid(1000).setresuid().is_ok());
    245     /// ```
    246     #[inline]
    247     pub fn setresuid(self) -> Result<(), Errno> {
    248         #[cfg(any(
    249             target_os = "dragonfly",
    250             target_os = "freebsd",
    251             target_os = "linux",
    252             target_os = "openbsd"
    253         ))]
    254         let code = c::setresuid(self.0, self.0, self.0);
    255         #[cfg(any(target_os = "macos", target_os = "netbsd"))]
    256         let code = c::setuid(self.0);
    257         if code == SUCCESS {
    258             Ok(())
    259         } else {
    260             Err(Errno::last())
    261         }
    262     }
    263 }
    264 impl PartialEq<&Self> for Uid {
    265     #[inline]
    266     fn eq(&self, other: &&Self) -> bool {
    267         *self == **other
    268     }
    269 }
    270 impl PartialEq<Uid> for &Uid {
    271     #[inline]
    272     fn eq(&self, other: &Uid) -> bool {
    273         **self == *other
    274     }
    275 }
    276 impl PartialEq<u32> for Uid {
    277     #[inline]
    278     fn eq(&self, other: &u32) -> bool {
    279         self.0 == *other
    280     }
    281 }
    282 impl From<Uid> for u32 {
    283     #[inline]
    284     fn from(value: Uid) -> Self {
    285         value.0
    286     }
    287 }
    288 impl From<u32> for Uid {
    289     #[inline]
    290     fn from(value: u32) -> Self {
    291         Self(value)
    292     }
    293 }
    294 /// [`gid_t`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/basedefs/sys_types.h.html).
    295 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    296 #[repr(transparent)]
    297 pub struct Gid(pub u32);
    298 impl Gid {
    299     /// [`getgid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/getgid.html).
    300     ///
    301     /// # Examples
    302     ///
    303     /// ```no_run
    304     /// # use priv_sep::Gid;
    305     /// assert_eq!(Gid::getgid(), 1000);
    306     /// ```
    307     #[inline]
    308     #[must_use]
    309     pub fn getgid() -> Self {
    310         Self(c::getgid())
    311     }
    312     /// [`getegid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/getegid.html).
    313     ///
    314     /// # Examples
    315     ///
    316     /// ```no_run
    317     /// # use priv_sep::Gid;
    318     /// assert_eq!(Gid::getegid(), 1000);
    319     /// ```
    320     #[inline]
    321     #[must_use]
    322     pub fn getegid() -> Self {
    323         Self(c::getegid())
    324     }
    325     /// Calls [`setresgid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/setresgid.html)
    326     /// passing `self` for the real, effective, and saved group IDs.
    327     ///
    328     /// Note on some platforms `setgid` is called using `self`.
    329     ///
    330     /// # Errors
    331     ///
    332     /// Errors iff `setresgid` does.
    333     ///
    334     /// # Examples
    335     ///
    336     /// ```no_run
    337     /// # use priv_sep::Gid;
    338     /// assert!(Gid(1000).setresgid().is_ok());
    339     /// ```
    340     #[inline]
    341     pub fn setresgid(self) -> Result<(), Errno> {
    342         #[cfg(any(
    343             target_os = "dragonfly",
    344             target_os = "freebsd",
    345             target_os = "linux",
    346             target_os = "openbsd"
    347         ))]
    348         let code = c::setresgid(self.0, self.0, self.0);
    349         #[cfg(any(target_os = "macos", target_os = "netbsd"))]
    350         let code = c::setgid(self.0);
    351         if code == SUCCESS {
    352             Ok(())
    353         } else {
    354             Err(Errno::last())
    355         }
    356     }
    357 }
    358 impl PartialEq<&Self> for Gid {
    359     #[inline]
    360     fn eq(&self, other: &&Self) -> bool {
    361         *self == **other
    362     }
    363 }
    364 impl PartialEq<Gid> for &Gid {
    365     #[inline]
    366     fn eq(&self, other: &Gid) -> bool {
    367         **self == *other
    368     }
    369 }
    370 impl PartialEq<u32> for Gid {
    371     #[inline]
    372     fn eq(&self, other: &u32) -> bool {
    373         self.0 == *other
    374     }
    375 }
    376 impl From<Gid> for u32 {
    377     #[inline]
    378     fn from(value: Gid) -> Self {
    379         value.0
    380     }
    381 }
    382 impl From<u32> for Gid {
    383     #[inline]
    384     fn from(value: u32) -> Self {
    385         Self(value)
    386     }
    387 }
    388 /// Primarily an internal `trait` that allows one to use a variety of types in lieu of
    389 /// [`CStr`](https://doc.rust-lang.org/stable/core/ffi/struct.CStr.html).
    390 pub trait CStrHelper {
    391     /// First converts `self` into a
    392     /// [`CStr`](https://doc.rust-lang.org/stable/core/ffi/struct.CStr.html)
    393     /// before passing it into `f`.
    394     ///
    395     /// # Errors
    396     ///
    397     /// Errors whenever necessary.
    398     ///
    399     /// If an error occurs due to insufficient buffer space (e.g., when [`alloc`](./index.html#alloc) is not
    400     /// enabled and a `str` is used with length greater than 1023), then [`Errno::ERANGE`] must be returned.
    401     ///
    402     /// If an error occurs from converting `self` into a `CStr` due to nul bytes, then [`Errno::EINVAL`]
    403     /// must be returned.
    404     fn convert_then_apply<T, F: FnOnce(&CStr) -> Result<T, Errno>>(&self, f: F)
    405     -> Result<T, Errno>;
    406 }
    407 impl CStrHelper for CStr {
    408     #[inline]
    409     fn convert_then_apply<T, F: FnOnce(&Self) -> Result<T, Errno>>(
    410         &self,
    411         f: F,
    412     ) -> Result<T, Errno> {
    413         f(self)
    414     }
    415 }
    416 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
    417 #[cfg(feature = "alloc")]
    418 impl CStrHelper for CString {
    419     #[inline]
    420     fn convert_then_apply<T, F: FnOnce(&CStr) -> Result<T, Errno>>(
    421         &self,
    422         f: F,
    423     ) -> Result<T, Errno> {
    424         self.as_c_str().convert_then_apply(f)
    425     }
    426 }
    427 /// Converts `val` into a `CStr` via heap allocation before applying `f` to it.
    428 #[cold]
    429 #[inline(never)]
    430 #[cfg(feature = "alloc")]
    431 fn c_str_allocating<T, F: FnOnce(&CStr) -> Result<T, Errno>>(
    432     bytes: &[u8],
    433     f: F,
    434 ) -> Result<T, Errno> {
    435     CString::new(bytes)
    436         .map_err(|_e| Errno::EINVAL)
    437         .and_then(|c| f(&c))
    438 }
    439 impl CStrHelper for [u8] {
    440     #[expect(unsafe_code, reason = "comments justify correctness")]
    441     #[expect(
    442         clippy::arithmetic_side_effects,
    443         reason = "comment justifies correctness"
    444     )]
    445     #[inline]
    446     fn convert_then_apply<T, F: FnOnce(&CStr) -> Result<T, Errno>>(
    447         &self,
    448         f: F,
    449     ) -> Result<T, Errno> {
    450         /// Maximum stack allocation for `CStr` conversion.
    451         const C_STR_MAX_STACK_ALLOCATION: usize = 0x400;
    452         let len = self.len();
    453         if len < C_STR_MAX_STACK_ALLOCATION {
    454             let mut buf = MaybeUninit::<[u8; C_STR_MAX_STACK_ALLOCATION]>::uninit();
    455             let buf_ptr = buf.as_mut_ptr().cast();
    456             let slice_ptr = self.as_ptr();
    457             // SAFETY:
    458             // `slice_ptr` was created from `self` which has length `len` thus is valid for `len` bytes.
    459             // `buf_ptr` was created from `buf` which has length `C_STR_MAX_STACK_ALLOCATION > len`; thus
    460             // it too is valid for `len` bytes.
    461             // `buf`, while unitialized, is only written to before ever being read from.
    462             // `slice_ptr` and `buf_ptr` are properly aligned.
    463             // `slice_ptr` and `buf_ptr` point to completely separate allocations.
    464             unsafe { ptr::copy_nonoverlapping(slice_ptr, buf_ptr, len) };
    465             // SAFETY:
    466             // We just wrote `len` bytes into `buf` (which `buf_ptr` points to).
    467             let nul_pos = unsafe { buf_ptr.add(len) };
    468             // SAFETY:
    469             // `buf.len() > len`; thus we know we have at least one byte of space to write `0` to.
    470             unsafe { nul_pos.write(0) };
    471             // `len <= isize::MAX`; thus this cannot overflow `usize::MAX`.
    472             let final_len = len + 1;
    473             // SAFETY:
    474             // The first `final_len` bytes of `buf` (which `buf_ptr` points to) is initialized, aligned, valid, and
    475             // not null.
    476             // `CStr::from_bytes_with_nul` doesn't mutate `raw_slice`.
    477             let raw_slice = unsafe { slice::from_raw_parts(buf_ptr, final_len) };
    478             CStr::from_bytes_with_nul(raw_slice)
    479                 .map_err(|_e| Errno::EINVAL)
    480                 .and_then(f)
    481         } else {
    482             #[cfg(not(feature = "alloc"))]
    483             let res = Err(Errno::ERANGE);
    484             #[cfg(feature = "alloc")]
    485             let res = c_str_allocating(self, f);
    486             res
    487         }
    488     }
    489 }
    490 impl CStrHelper for str {
    491     #[inline]
    492     fn convert_then_apply<T, F: FnOnce(&CStr) -> Result<T, Errno>>(
    493         &self,
    494         f: F,
    495     ) -> Result<T, Errno> {
    496         self.as_bytes().convert_then_apply(f)
    497     }
    498 }
    499 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
    500 #[cfg(feature = "alloc")]
    501 impl CStrHelper for String {
    502     #[inline]
    503     fn convert_then_apply<T, F: FnOnce(&CStr) -> Result<T, Errno>>(
    504         &self,
    505         f: F,
    506     ) -> Result<T, Errno> {
    507         self.as_str().convert_then_apply(f)
    508     }
    509 }
    510 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
    511 #[cfg(feature = "std")]
    512 impl CStrHelper for OsStr {
    513     #[inline]
    514     fn convert_then_apply<T, F: FnOnce(&CStr) -> Result<T, Errno>>(
    515         &self,
    516         f: F,
    517     ) -> Result<T, Errno> {
    518         self.as_encoded_bytes().convert_then_apply(f)
    519     }
    520 }
    521 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
    522 #[cfg(feature = "std")]
    523 impl CStrHelper for OsString {
    524     #[inline]
    525     fn convert_then_apply<T, F: FnOnce(&CStr) -> Result<T, Errno>>(
    526         &self,
    527         f: F,
    528     ) -> Result<T, Errno> {
    529         self.as_os_str().convert_then_apply(f)
    530     }
    531 }
    532 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
    533 #[cfg(feature = "std")]
    534 impl CStrHelper for Cow<'_, OsStr> {
    535     #[inline]
    536     fn convert_then_apply<T, F: FnOnce(&CStr) -> Result<T, Errno>>(
    537         &self,
    538         f: F,
    539     ) -> Result<T, Errno> {
    540         (**self).convert_then_apply(f)
    541     }
    542 }
    543 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
    544 #[cfg(feature = "std")]
    545 impl CStrHelper for Path {
    546     #[inline]
    547     fn convert_then_apply<T, F: FnOnce(&CStr) -> Result<T, Errno>>(
    548         &self,
    549         f: F,
    550     ) -> Result<T, Errno> {
    551         self.as_os_str().convert_then_apply(f)
    552     }
    553 }
    554 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
    555 #[cfg(feature = "std")]
    556 impl CStrHelper for PathBuf {
    557     #[inline]
    558     fn convert_then_apply<T, F: FnOnce(&CStr) -> Result<T, Errno>>(
    559         &self,
    560         f: F,
    561     ) -> Result<T, Errno> {
    562         self.as_os_str().convert_then_apply(f)
    563     }
    564 }
    565 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
    566 #[cfg(feature = "std")]
    567 impl CStrHelper for Component<'_> {
    568     #[inline]
    569     fn convert_then_apply<T, F: FnOnce(&CStr) -> Result<T, Errno>>(
    570         &self,
    571         f: F,
    572     ) -> Result<T, Errno> {
    573         self.as_os_str().convert_then_apply(f)
    574     }
    575 }
    576 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
    577 #[cfg(feature = "std")]
    578 impl CStrHelper for Components<'_> {
    579     #[inline]
    580     fn convert_then_apply<T, F: FnOnce(&CStr) -> Result<T, Errno>>(
    581         &self,
    582         f: F,
    583     ) -> Result<T, Errno> {
    584         self.as_path().convert_then_apply(f)
    585     }
    586 }
    587 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
    588 #[cfg(feature = "std")]
    589 impl CStrHelper for Iter<'_> {
    590     #[inline]
    591     fn convert_then_apply<T, F: FnOnce(&CStr) -> Result<T, Errno>>(
    592         &self,
    593         f: F,
    594     ) -> Result<T, Errno> {
    595         self.as_path().convert_then_apply(f)
    596     }
    597 }
    598 impl<C: CStrHelper + ?Sized> CStrHelper for &C {
    599     #[inline]
    600     fn convert_then_apply<T, F: FnOnce(&CStr) -> Result<T, Errno>>(
    601         &self,
    602         f: F,
    603     ) -> Result<T, Errno> {
    604         (**self).convert_then_apply(f)
    605     }
    606 }
    607 impl<C: CStrHelper + ?Sized> CStrHelper for &mut C {
    608     #[inline]
    609     fn convert_then_apply<T, F: FnOnce(&CStr) -> Result<T, Errno>>(
    610         &self,
    611         f: F,
    612     ) -> Result<T, Errno> {
    613         (**self).convert_then_apply(f)
    614     }
    615 }
    616 /// [`chroot(2)`](https://manned.org/chroot.2).
    617 ///
    618 /// # Errors
    619 ///
    620 /// Errors iff `chroot(2)` does.
    621 ///
    622 /// # Examples
    623 ///
    624 /// ```no_run
    625 /// assert!(priv_sep::chroot(c"./").is_ok());
    626 /// ```
    627 #[expect(unsafe_code, reason = "chroot(2) takes a pointer")]
    628 #[inline]
    629 pub fn chroot<P: CStrHelper>(path: P) -> Result<(), Errno> {
    630     fn f(path: &CStr) -> Result<(), Errno> {
    631         let ptr = path.as_ptr();
    632         // SAFETY:
    633         // `ptr` is valid and not null.
    634         if unsafe { c::chroot(ptr) } == SUCCESS {
    635             Ok(())
    636         } else {
    637             Err(Errno::last())
    638         }
    639     }
    640     path.convert_then_apply(f)
    641 }
    642 /// [`chdir`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/chdir.html).
    643 ///
    644 /// This function MUST only be called by `chdir` and `chroot_then_chdir`.
    645 #[expect(unsafe_code, reason = "chdir(2) takes a pointer")]
    646 fn private_chdir(path: *const c_char) -> Result<(), Errno> {
    647     // SAFETY:
    648     // `path` is valid and not null as can be seen in the only functions that call this function:
    649     // `chdir` and `chroot_then_chdir`.
    650     if unsafe { c::chdir(path) } == SUCCESS {
    651         Ok(())
    652     } else {
    653         Err(Errno::last())
    654     }
    655 }
    656 /// [`chdir`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/chdir.html).
    657 ///
    658 /// # Errors
    659 ///
    660 /// Errors iff `chdir` does.
    661 ///
    662 /// # Examples
    663 ///
    664 /// ```no_run
    665 /// assert!(priv_sep::chdir(c"/").is_ok());
    666 /// ```
    667 #[inline]
    668 pub fn chdir<P: CStrHelper>(path: P) -> Result<(), Errno> {
    669     fn f(path: &CStr) -> Result<(), Errno> {
    670         private_chdir(path.as_ptr())
    671     }
    672     path.convert_then_apply(f)
    673 }
    674 /// Calls [`chroot`] on `path` followed by a call to [`chdir`] on `"/"`.
    675 ///
    676 /// # Errors
    677 ///
    678 /// Errors iff `chroot` or `chdir` do.
    679 ///
    680 /// # Examples
    681 ///
    682 /// ```no_run
    683 /// assert!(priv_sep::chroot_then_chdir(c"./").is_ok());
    684 /// ```
    685 #[inline]
    686 pub fn chroot_then_chdir<P: CStrHelper>(path: P) -> Result<(), Errno> {
    687     /// Root directory.
    688     const ROOT: *const c_char = c"/".as_ptr();
    689     chroot(path).and_then(|()| private_chdir(ROOT))
    690 }
    691 /// Error returned when dropping privileges.
    692 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    693 pub enum PrivDropErr<E> {
    694     /// Error when an error occurs from a libc call.
    695     Libc(Errno),
    696     /// Error when there is no entry in the user database corresponding to the passed username.
    697     NoPasswdEntry,
    698     /// Error when [`UserInfo::is_root`].
    699     RootEntry,
    700     /// Error returned from the user-provided function that is invoked before calling [`UserInfo::setresid`].
    701     Other(E),
    702 }
    703 impl<E: Display> Display for PrivDropErr<E> {
    704     #[inline]
    705     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
    706         match *self {
    707             Self::Libc(err) => write!(f, "libc error when dropping privileges: {err}"),
    708             Self::NoPasswdEntry => f.write_str("no passwd(5) entry to drop privileges to"),
    709             Self::RootEntry => f.write_str(
    710                 "setresuid(2) is not allowed to be called on uid 0 when dropping privileges",
    711             ),
    712             Self::Other(ref err) => write!(
    713                 f,
    714                 "error calling function before dropping privileges: {err}"
    715             ),
    716         }
    717     }
    718 }
    719 impl<E: CoreErr> CoreErr for PrivDropErr<E> {}
    720 impl<E> From<Errno> for PrivDropErr<E> {
    721     #[inline]
    722     fn from(value: Errno) -> Self {
    723         Self::Libc(value)
    724     }
    725 }
    726 /// Error returned from [`UserInfo::setresid_if_valid`].
    727 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    728 pub enum SetresidErr {
    729     /// Error when an error occurs from a libc call.
    730     Libc(Errno),
    731     /// Error when there is no entry in the user database corresponding to [`UserInfo::uid`].
    732     NoPasswdEntry,
    733     /// Error when the entry in the user database has a different gid than [`UserInfo::gid`].
    734     GidMismatch,
    735 }
    736 impl Display for SetresidErr {
    737     #[inline]
    738     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
    739         match *self {
    740             Self::Libc(err) => write!(f, "libc error when dropping privileges: {err}"),
    741             Self::NoPasswdEntry => f.write_str("no passwd(5) entry to drop privileges to"),
    742             Self::GidMismatch => f.write_str("gid in passwd(5) does match the expected gid"),
    743         }
    744     }
    745 }
    746 impl CoreErr for SetresidErr {}
    747 impl From<Errno> for SetresidErr {
    748     #[inline]
    749     fn from(value: Errno) -> Self {
    750         Self::Libc(value)
    751     }
    752 }
    753 /// Used by [`UserInfo::getpw_entry`].
    754 trait PwEntry {
    755     /// Calling code must uphold the following safety invariants:
    756     /// * `buf` must be a valid, initialized, non-null pointer
    757     /// * `size` must be the length of `buf`
    758     /// * `result` must be a valid, initialized non-null pointer referencing a valid and initialized pointer that
    759     ///   is allowed to be null.
    760     ///
    761     /// Implementors MUST only _write_ to `pwd` and never read from it (i.e., `pwd` is allowed to be unitialized).
    762     #[expect(
    763         unsafe_code,
    764         reason = "getpwnam_r(3) and getpwuid_r(3) take in pointers"
    765     )]
    766     unsafe fn getpw(
    767         self,
    768         pwd: *mut c::Passwd,
    769         buf: *mut c_char,
    770         size: usize,
    771         result: *mut *mut c::Passwd,
    772     ) -> c_int;
    773 }
    774 impl PwEntry for Uid {
    775     #[expect(unsafe_code, reason = "getpwuid_r(3) take in pointers")]
    776     unsafe fn getpw(
    777         self,
    778         pwd: *mut c::Passwd,
    779         buf: *mut c_char,
    780         size: usize,
    781         result: *mut *mut c::Passwd,
    782     ) -> c_int {
    783         // SAFETY:
    784         // Calling code must uphold safety invariants.
    785         // `pwd` is never read from.
    786         unsafe { c::getpwuid_r(self.0, pwd, buf, size, result) }
    787     }
    788 }
    789 /// `newtype` around `CStr`.
    790 #[derive(Clone, Copy)]
    791 struct CStrWrapper<'a>(&'a CStr);
    792 impl PwEntry for CStrWrapper<'_> {
    793     #[expect(unsafe_code, reason = "getpwnam_r(3) takes in pointers")]
    794     unsafe fn getpw(
    795         self,
    796         pwd: *mut c::Passwd,
    797         buf: *mut c_char,
    798         size: usize,
    799         result: *mut *mut c::Passwd,
    800     ) -> c_int {
    801         let ptr = self.0.as_ptr();
    802         // SAFETY:
    803         // Calling code must uphold safety invariants.
    804         // `ptr` is valid, initialized, and not null.
    805         // `pwd` is never read from.
    806         unsafe { c::getpwnam_r(ptr, pwd, buf, size, result) }
    807     }
    808 }
    809 /// User and group ID.
    810 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    811 pub struct UserInfo {
    812     /// The user ID.
    813     pub uid: Uid,
    814     /// The group ID.
    815     pub gid: Gid,
    816 }
    817 impl UserInfo {
    818     /// Returns `true` iff [`Uid::is_root`].
    819     ///
    820     /// # Examples
    821     ///
    822     /// ```no_run
    823     /// # use priv_sep::{Gid, Uid, UserInfo};
    824     /// assert!(UserInfo { uid: Uid::ROOT, gid: Gid(0), }.is_root());
    825     /// ```
    826     #[inline]
    827     #[must_use]
    828     pub const fn is_root(self) -> bool {
    829         self.uid.is_root()
    830     }
    831     /// Helper for [`Self::new`] and [`Self::setresid_if_valid`].
    832     #[expect(
    833         unsafe_code,
    834         reason = "getpwnam_r(3) and getpwuid_r(3) take in pointers"
    835     )]
    836     fn getpw_entry<P: Copy + PwEntry>(u: P, buffer: &mut [c_char]) -> Result<Option<Self>, Errno> {
    837         let mut pwd = MaybeUninit::<c::Passwd>::uninit();
    838         let pwd_ptr = pwd.as_mut_ptr();
    839         let buf_ptr = buffer.as_mut_ptr();
    840         let len = buffer.len();
    841         let mut result = ptr::null_mut();
    842         let res_ptr = &mut result;
    843         // SAFETY:
    844         // `pwd_ptr` is only written to; thus the fact `pwd` is unitialized is fine.
    845         // `buf_ptr` is valid, initialized, and not null.
    846         // `len` is the length of `buf_ptr`.
    847         // `res_ptr` is valid, initialized, and not null.
    848         // `result` is valid, initialized, and allowed to be null.
    849         let code = unsafe { u.getpw(pwd_ptr, buf_ptr, len, res_ptr) };
    850         if code == SUCCESS {
    851             if result.is_null() {
    852                 Ok(None)
    853             } else {
    854                 debug_assert!(
    855                     result.is_aligned(),
    856                     "libc getpwnam_r or getpwuid_r result was not aligned. Something is terribly wrong with your system"
    857                 );
    858                 // SAFETY:
    859                 // Verified above that `result` is not null and aligned. Note while we only verify the pointer
    860                 // is aligned on non-release builds, the situation is so dire that one could argue we are
    861                 // already in "undefined" territory.
    862                 // When `result` is not null, the platform is supposed to have written to `pwd`.
    863                 Ok(Some(unsafe { pwd.assume_init() }.into_user_info()))
    864             }
    865         } else {
    866             Err(Errno::from_raw(code))
    867         }
    868     }
    869     /// [`getpwnam_r`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/getpwnam_r.html).
    870     ///
    871     /// Obtains the user database entry returning `None` iff there is no entry; otherwise returns `Self`.
    872     ///
    873     /// A 1 KiB stack-allocated buffer is used to write the database entry into. If [`Errno::ERANGE`]
    874     /// is returned, then the following will occur:
    875     ///
    876     /// * If [`alloc`](./index.html#alloc) is not enabled, then the error is returned.
    877     /// * If [`alloc`](./index.html#alloc) is enabled and a 16-bit architecture is used, then a heap-allocated
    878     ///   buffer of 16 KiB is used. If this errors, then the error is returned.
    879     /// * If [`alloc`](./index.html#alloc) is enabled and a non-16-bit architecture is used, then a heap-allocated
    880     ///   buffer of 1 MiB is used. If this errors, then the error is returned.
    881     ///
    882     /// # Errors
    883     ///
    884     /// Errors iff `getpwnam_r` does.
    885     ///
    886     /// # Examples
    887     ///
    888     /// ```no_run
    889     /// # use priv_sep::UserInfo;
    890     /// assert!(UserInfo::new(c"root")?.is_some_and(|info| info.is_root()));
    891     /// # Ok::<_, priv_sep::Errno>(())
    892     /// ```
    893     #[inline]
    894     pub fn new<C: CStrHelper>(name: C) -> Result<Option<Self>, Errno> {
    895         fn f(name: &CStr) -> Result<Option<UserInfo>, Errno> {
    896             let wrapper = CStrWrapper(name);
    897             let res = UserInfo::getpw_entry(wrapper, &mut [0; 0x400]);
    898             #[cfg(not(feature = "alloc"))]
    899             let res_final = res;
    900             #[cfg(all(target_pointer_width = "16", feature = "alloc"))]
    901             let res_final = res.or_else(|e| {
    902                 if matches!(e, Errno::ERANGE) {
    903                     Self::getpw_entry(wrapper, vec![0; 0x4000].as_mut_slice())
    904                 } else {
    905                     Err(e)
    906                 }
    907             });
    908             #[cfg(all(not(target_pointer_width = "16"), feature = "alloc"))]
    909             let res_final = res.or_else(|e| {
    910                 if matches!(e, Errno::ERANGE) {
    911                     UserInfo::getpw_entry(wrapper, vec![0; 0x10_0000].as_mut_slice())
    912                 } else {
    913                     Err(e)
    914                 }
    915             });
    916             res_final
    917         }
    918         name.convert_then_apply(f)
    919     }
    920     /// Calls [`Gid::setresgid`] and [`Uid::setresuid`].
    921     ///
    922     /// # Errors
    923     ///
    924     /// Errors iff `Gid::setresgid` or `Uid::setresuid` error.
    925     ///
    926     /// # Examples
    927     ///
    928     /// ```no_run
    929     /// # use priv_sep::UserInfo;
    930     /// if let Some(user) = UserInfo::new(c"nobody")? {
    931     ///     user.setresid()?;
    932     /// }
    933     /// # Ok::<_, priv_sep::Errno>(())
    934     /// ```
    935     #[inline]
    936     pub fn setresid(self) -> Result<(), Errno> {
    937         self.gid.setresgid().and_then(|()| self.uid.setresuid())
    938     }
    939     /// Same as [`Self::setresid`] except
    940     /// [`getpwuid_r`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/getpwuid_r.html)
    941     /// is used to first confirm the existence of [`Self::uid`] and [`Self::gid`].
    942     ///
    943     /// Note this should rarely be used since most will rely on [`Self::new`], [`Self::priv_drop`], or
    944     /// [`Self::chroot_then_priv_drop`].
    945     ///
    946     /// Read [`Self::new`] for more information on the buffering strategy.
    947     ///
    948     /// # Errors
    949     ///
    950     /// Errors iff `getpwuid_r` errors, [`Self::uid`] and [`Self::gid`] don't exist in the user
    951     /// database, [`Gid::setresgid`] errors, or [`Uid::setresuid`] errors.
    952     ///
    953     /// # Examples
    954     ///
    955     /// ```no_run
    956     /// # use priv_sep::{Gid, Uid, UserInfo};
    957     /// UserInfo { uid: Uid(1000), gid: Gid(1000), }.setresid_if_valid()?;
    958     /// # Ok::<_, priv_sep::SetresidErr>(())
    959     /// ```
    960     #[inline]
    961     pub fn setresid_if_valid(self) -> Result<(), SetresidErr> {
    962         let res = Self::getpw_entry(self.uid, &mut [0; 0x400]);
    963         #[cfg(not(feature = "alloc"))]
    964         let res_final = res;
    965         #[cfg(all(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; 0x4000].as_mut_slice())
    969             } else {
    970                 Err(e)
    971             }
    972         });
    973         #[cfg(all(not(target_pointer_width = "16"), feature = "alloc"))]
    974         let res_final = res.or_else(|e| {
    975             if matches!(e, Errno::ERANGE) {
    976                 Self::getpw_entry(self.uid, vec![0; 0x10_0000].as_mut_slice())
    977             } else {
    978                 Err(e)
    979             }
    980         });
    981         res_final.map_err(SetresidErr::Libc).and_then(|opt| {
    982             opt.ok_or(SetresidErr::NoPasswdEntry).and_then(|info| {
    983                 if info.gid == self.gid {
    984                     self.setresid().map_err(SetresidErr::Libc)
    985                 } else {
    986                     Err(SetresidErr::GidMismatch)
    987                 }
    988             })
    989         })
    990     }
    991     /// Helper to unify targets that don't support `setgroups(2)`.
    992     ///
    993     /// No-op.
    994     #[cfg(target_os = "macos")]
    995     #[expect(
    996         clippy::unnecessary_wraps,
    997         reason = "unify with platforms that support `setgroups`"
    998     )]
    999     const fn drop_sup_groups<Never>() -> Result<(), Never> {
   1000         Ok(())
   1001     }
   1002     /// Helper to unify targets that don't support `setgroups(2)`.
   1003     #[cfg(any(
   1004         target_os = "dragonfly",
   1005         target_os = "freebsd",
   1006         target_os = "linux",
   1007         target_os = "netbsd",
   1008         target_os = "openbsd"
   1009     ))]
   1010     fn drop_sup_groups() -> Result<(), Errno> {
   1011         drop_supplementary_groups()
   1012     }
   1013     /// Calls [`Self::new`], invokes `f`, calls [`drop_supplementary_groups`] (if the platform supports
   1014     /// `setgroups(2)`), then calls [`Self::setresid`].
   1015     ///
   1016     /// Dropping privileges is necessary when needing to perform certain actions as root before no longer needing
   1017     /// such abilities; at which point, one calls
   1018     /// [`setgroups(2)`](https://www.man7.org/linux/man-pages/man2/setgroups.2.html),
   1019     /// [`setresgid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/setresgid.html), and
   1020     /// [`setresuid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/setresuid.html)
   1021     /// using a lesser privileged gid and uid.
   1022     ///
   1023     /// # Errors
   1024     ///
   1025     /// Errors iff [`Self::new`], `f`, [`drop_supplementary_groups`], or [`Self::setresid`] do or there is no entry in the user database
   1026     /// corresponding to `name` or the entry has uid 0.
   1027     ///
   1028     /// # Examples
   1029     ///
   1030     /// ```no_run
   1031     /// # use core::net::{Ipv6Addr, SocketAddrV6};
   1032     /// # use priv_sep::{PrivDropErr, UserInfo};
   1033     /// # use std::{io::Error, net::TcpListener};
   1034     /// let listener = UserInfo::priv_drop(c"nobody", || {
   1035     ///     TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0))
   1036     /// })?;
   1037     /// # Ok::<_, PrivDropErr<Error>>(())
   1038     /// ```
   1039     #[inline]
   1040     pub fn priv_drop<C: CStrHelper, T, E, F: FnOnce() -> Result<T, E>>(
   1041         name: C,
   1042         f: F,
   1043     ) -> Result<T, PrivDropErr<E>> {
   1044         Self::new(name).map_err(PrivDropErr::Libc).and_then(|opt| {
   1045             opt.ok_or_else(|| PrivDropErr::NoPasswdEntry)
   1046                 .and_then(|info| {
   1047                     if info.is_root() {
   1048                         Err(PrivDropErr::RootEntry)
   1049                     } else {
   1050                         f().map_err(PrivDropErr::Other).and_then(|res| {
   1051                             Self::drop_sup_groups()
   1052                                 .and_then(|()| info.setresid().map(|()| res))
   1053                                 .map_err(PrivDropErr::Libc)
   1054                         })
   1055                     }
   1056                 })
   1057         })
   1058     }
   1059     /// Same as [`Self::priv_drop`] except `f` is `async`.
   1060     ///
   1061     /// # Errors
   1062     ///
   1063     /// Read [`Self::priv_drop`].
   1064     ///
   1065     /// # Examples
   1066     ///
   1067     /// ```no_run
   1068     /// # use core::net::{Ipv6Addr, SocketAddrV6};
   1069     /// # use priv_sep::UserInfo;
   1070     /// # use tokio::net::TcpListener;
   1071     /// let listener_fut = UserInfo::priv_drop_async(c"nobody", async || {
   1072     ///     TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await
   1073     /// });
   1074     /// ```
   1075     #[inline]
   1076     pub async fn priv_drop_async<C: CStrHelper, T, E, F: AsyncFnOnce() -> Result<T, E>>(
   1077         name: C,
   1078         f: F,
   1079     ) -> Result<T, PrivDropErr<E>> {
   1080         match Self::new(name) {
   1081             Ok(opt) => match opt {
   1082                 None => Err(PrivDropErr::NoPasswdEntry),
   1083                 Some(info) => {
   1084                     if info.is_root() {
   1085                         Err(PrivDropErr::RootEntry)
   1086                     } else {
   1087                         f().await.map_err(PrivDropErr::Other).and_then(|res| {
   1088                             Self::drop_sup_groups()
   1089                                 .and_then(|()| info.setresid().map(|()| res))
   1090                                 .map_err(PrivDropErr::Libc)
   1091                         })
   1092                     }
   1093                 }
   1094             },
   1095             Err(err) => Err(PrivDropErr::Libc(err)),
   1096         }
   1097     }
   1098     /// Same as [`Self::priv_drop`] except [`chroot_then_chdir`] is called before or after invoking `f` based on
   1099     /// `chroot_after_f`.
   1100     ///
   1101     /// # Errors
   1102     ///
   1103     /// Errors iff [`Self::priv_drop`] or [`chroot_then_chdir`] do.
   1104     ///
   1105     /// # Examples
   1106     ///
   1107     /// ```no_run
   1108     /// # use core::net::{Ipv6Addr, SocketAddrV6};
   1109     /// # use priv_sep::{PrivDropErr, UserInfo};
   1110     /// # use std::{io::Error, net::TcpListener};
   1111     /// let listener = UserInfo::chroot_then_priv_drop(c"nobody", c"./", false, || {
   1112     ///     TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0))
   1113     /// })?;
   1114     /// # Ok::<_, PrivDropErr<Error>>(())
   1115     /// ```
   1116     #[inline]
   1117     pub fn chroot_then_priv_drop<
   1118         N: CStrHelper,
   1119         P: CStrHelper,
   1120         T,
   1121         E,
   1122         F: FnOnce() -> Result<T, E>,
   1123     >(
   1124         name: N,
   1125         path: P,
   1126         chroot_after_f: bool,
   1127         f: F,
   1128     ) -> Result<T, PrivDropErr<E>> {
   1129         Self::new(name).map_err(PrivDropErr::Libc).and_then(|opt| {
   1130             opt.ok_or_else(|| PrivDropErr::NoPasswdEntry)
   1131                 .and_then(|info| {
   1132                     if info.is_root() {
   1133                         Err(PrivDropErr::RootEntry)
   1134                     } else if chroot_after_f {
   1135                         f().map_err(PrivDropErr::Other).and_then(|res| {
   1136                             chroot_then_chdir(path)
   1137                                 .map_err(PrivDropErr::Libc)
   1138                                 .map(|()| res)
   1139                         })
   1140                     } else {
   1141                         chroot_then_chdir(path)
   1142                             .map_err(PrivDropErr::Libc)
   1143                             .and_then(|()| f().map_err(PrivDropErr::Other))
   1144                     }
   1145                     .and_then(|res| {
   1146                         Self::drop_sup_groups()
   1147                             .and_then(|()| info.setresid().map(|()| res))
   1148                             .map_err(PrivDropErr::Libc)
   1149                     })
   1150                 })
   1151         })
   1152     }
   1153     /// Same as [`Self::chroot_then_priv_drop`] except `f` is `async`.
   1154     ///
   1155     /// # Errors
   1156     ///
   1157     /// Read [`Self::chroot_then_priv_drop`].
   1158     ///
   1159     /// # Examples
   1160     ///
   1161     /// ```no_run
   1162     /// # use core::net::{Ipv6Addr, SocketAddrV6};
   1163     /// # use priv_sep::UserInfo;
   1164     /// # use tokio::net::TcpListener;
   1165     /// let listener_fut = UserInfo::chroot_then_priv_drop_async(c"nobody", c"./", false, async || {
   1166     ///     TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await
   1167     /// });
   1168     /// ```
   1169     #[inline]
   1170     pub async fn chroot_then_priv_drop_async<
   1171         N: CStrHelper,
   1172         P: CStrHelper,
   1173         T,
   1174         E,
   1175         F: AsyncFnOnce() -> Result<T, E>,
   1176     >(
   1177         name: N,
   1178         path: P,
   1179         chroot_after_f: bool,
   1180         f: F,
   1181     ) -> Result<T, PrivDropErr<E>> {
   1182         match Self::new(name) {
   1183             Ok(opt) => match opt {
   1184                 None => Err(PrivDropErr::NoPasswdEntry),
   1185                 Some(info) => if info.is_root() {
   1186                     Err(PrivDropErr::RootEntry)
   1187                 } else if chroot_after_f {
   1188                     f().await.map_err(PrivDropErr::Other).and_then(|res| {
   1189                         chroot_then_chdir(path)
   1190                             .map_err(PrivDropErr::Libc)
   1191                             .map(|()| res)
   1192                     })
   1193                 } else {
   1194                     match chroot_then_chdir(path) {
   1195                         Ok(()) => f().await.map_err(PrivDropErr::Other),
   1196                         Err(err) => Err(PrivDropErr::Libc(err)),
   1197                     }
   1198                 }
   1199                 .and_then(|res| {
   1200                     Self::drop_sup_groups()
   1201                         .and_then(|()| info.setresid().map(|()| res))
   1202                         .map_err(PrivDropErr::Libc)
   1203                 }),
   1204             },
   1205             Err(err) => Err(PrivDropErr::Libc(err)),
   1206         }
   1207     }
   1208 }
   1209 impl PartialEq<&Self> for UserInfo {
   1210     #[inline]
   1211     fn eq(&self, other: &&Self) -> bool {
   1212         *self == **other
   1213     }
   1214 }
   1215 impl PartialEq<UserInfo> for &UserInfo {
   1216     #[inline]
   1217     fn eq(&self, other: &UserInfo) -> bool {
   1218         **self == *other
   1219     }
   1220 }
   1221 /// [`setgroups(2)`](https://www.man7.org/linux/man-pages/man2/setgroups.2.html).
   1222 ///
   1223 /// # Errors
   1224 ///
   1225 /// Errors iff `setgroups` does.
   1226 ///
   1227 /// # Examples
   1228 ///
   1229 /// ```no_run
   1230 /// assert!(priv_sep::setgroups(&[]).is_ok());
   1231 /// ```
   1232 #[cfg_attr(docsrs, doc(cfg(not(target_os = "macos"))))]
   1233 #[cfg(any(doc, not(target_os = "macos")))]
   1234 #[expect(unsafe_code, reason = "setgroups(2) takes a pointer")]
   1235 #[inline]
   1236 pub fn setgroups(groups: &[Gid]) -> Result<(), Errno> {
   1237     #[cfg(target_os = "linux")]
   1238     let size = groups.len();
   1239     #[cfg(any(
   1240         target_os = "dragonfly",
   1241         target_os = "freebsd",
   1242         target_os = "netbsd",
   1243         target_os = "openbsd"
   1244     ))]
   1245     let size = c_int::try_from(groups.len()).map_err(|_e| Errno::EINVAL)?;
   1246     let gids = groups.as_ptr().cast();
   1247     // SAFETY:
   1248     // `size` is the length of `gids`, and `gids` is a valid, non-null, aligned pointer to
   1249     // `u32`s. Note that `Gid` is a `newtype` around `u32` with `repr(transparent)`; thus the above
   1250     // pointer cast is fine.
   1251     let code = unsafe { c::setgroups(size, gids) };
   1252     if code == SUCCESS {
   1253         Ok(())
   1254     } else {
   1255         Err(Errno::last())
   1256     }
   1257 }
   1258 /// [`setgroups(2)`](https://www.man7.org/linux/man-pages/man2/setgroups.2.html) passing in
   1259 /// 0 and a null pointer.
   1260 ///
   1261 /// If successful, this drops all supplementary groups.
   1262 ///
   1263 /// # Errors
   1264 ///
   1265 /// Errors iff `setgroups` does.
   1266 ///
   1267 /// # Examples
   1268 ///
   1269 /// ```no_run
   1270 /// assert!(priv_sep::drop_supplementary_groups().is_ok());
   1271 /// ```
   1272 #[cfg_attr(docsrs, doc(cfg(not(target_os = "macos"))))]
   1273 #[cfg(any(doc, not(target_os = "macos")))]
   1274 #[expect(unsafe_code, reason = "setgroups(2) takes a pointer")]
   1275 #[inline]
   1276 pub fn drop_supplementary_groups() -> Result<(), Errno> {
   1277     #[cfg(target_os = "linux")]
   1278     let size: usize = 0;
   1279     #[cfg(any(
   1280         target_os = "dragonfly",
   1281         target_os = "freebsd",
   1282         target_os = "netbsd",
   1283         target_os = "openbsd"
   1284     ))]
   1285     let size: c_int = 0;
   1286     let gids = ptr::null();
   1287     // SAFETY:
   1288     // Passing in 0 and a null pointer is valid and causes all supplementary groups to be dropped.
   1289     let code = unsafe { c::setgroups(size, gids) };
   1290     if code == SUCCESS {
   1291         Ok(())
   1292     } else {
   1293         Err(Errno::last())
   1294     }
   1295 }