priv_sep

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

lib.rs (54607B)


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