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 }