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