openbsd.rs (52193B)
1 #[cfg(doc)] 2 use super::chroot_then_chdir; 3 use super::{NulOrIoErr, PrivDropErr, SUCCESS, UserInfo}; 4 use alloc::ffi::CString; 5 #[cfg(doc)] 6 use alloc::ffi::NulError; 7 use core::{ 8 ffi::{c_char, c_int}, 9 fmt::{self, Display, Formatter}, 10 ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not}, 11 ptr, 12 }; 13 use std::{io::Error, os::unix::ffi::OsStrExt as _, path::Path}; 14 #[expect(unsafe_code, reason = "FFI requires unsafe")] 15 unsafe extern "C" { 16 /// [`pledge(2)`](https://man.openbsd.org/pledge.2). 17 pub(crate) fn pledge(promises: *const c_char, execpromises: *const c_char) -> c_int; 18 /// [`unveil(2)`](https://man.openbsd.org/unveil.2). 19 pub(crate) fn unveil(path: *const c_char, permissions: *const c_char) -> c_int; 20 } 21 /// A `promise` to [`pledge(2)`](https://man.openbsd.org/pledge.2). 22 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 23 #[non_exhaustive] 24 pub enum Promise { 25 /// [`audio`](https://man.openbsd.org/pledge.2#audio). 26 Audio, 27 /// [`bpf`](https://man.openbsd.org/pledge.2#bpf). 28 Bpf, 29 /// [`chown`](https://man.openbsd.org/pledge.2#chown). 30 Chown, 31 /// [`cpath`](https://man.openbsd.org/pledge.2#cpath). 32 Cpath, 33 /// `disklabel`. 34 Disklabel, 35 /// [`dns`](https://man.openbsd.org/pledge.2#dns). 36 Dns, 37 /// [`dpath`](https://man.openbsd.org/pledge.2#dpath). 38 Dpath, 39 /// `drm`. 40 Drm, 41 /// [`error`](https://man.openbsd.org/pledge.2#error). 42 Error, 43 /// [`exec`](https://man.openbsd.org/pledge.2#exec). 44 Exec, 45 /// [`fattr`](https://man.openbsd.org/pledge.2#fattr). 46 Fattr, 47 /// [`flock`](https://man.openbsd.org/pledge.2#flock). 48 Flock, 49 /// [`getpw`](https://man.openbsd.org/pledge.2#getpw). 50 Getpw, 51 /// [`id`](https://man.openbsd.org/pledge.2#id). 52 Id, 53 /// [`inet`](https://man.openbsd.org/pledge.2#inet). 54 Inet, 55 /// [`mcast`](https://man.openbsd.org/pledge.2#mcast). 56 Mcast, 57 /// [`pf`](https://man.openbsd.org/pledge.2#pf). 58 Pf, 59 /// [`proc`](https://man.openbsd.org/pledge.2#proc). 60 Proc, 61 /// [`prot_exec`](https://man.openbsd.org/pledge.2#prot_exec). 62 ProtExec, 63 /// [`ps`](https://man.openbsd.org/pledge.2#ps). 64 Ps, 65 /// [`recvfd`](https://man.openbsd.org/pledge.2#recvfd). 66 Recvfd, 67 /// [`route`](https://man.openbsd.org/pledge.2#route). 68 Route, 69 /// [`rpath`](https://man.openbsd.org/pledge.2#rpath). 70 Rpath, 71 /// [`sendfd`](https://man.openbsd.org/pledge.2#sendfd). 72 Sendfd, 73 /// [`settime`](https://man.openbsd.org/pledge.2#settime). 74 Settime, 75 /// [`stdio`](https://man.openbsd.org/pledge.2#stdio). 76 Stdio, 77 /// [`tape`](https://man.openbsd.org/pledge.2#tape). 78 Tape, 79 /// [`tmppath`](https://man.openbsd.org/pledge.2#tmppath). 80 Tmppath, 81 /// [`tty`](https://man.openbsd.org/pledge.2#tty). 82 Tty, 83 /// [`unix`](https://man.openbsd.org/pledge.2#unix). 84 Unix, 85 /// [`unveil`](https://man.openbsd.org/pledge.2#unveil). 86 Unveil, 87 /// [`video`](https://man.openbsd.org/pledge.2#video). 88 Video, 89 /// [`vminfo`](https://man.openbsd.org/pledge.2#vminfo). 90 Vminfo, 91 /// `vmm`. 92 Vmm, 93 /// [`wpath`](https://man.openbsd.org/pledge.2#wpath). 94 Wpath, 95 /// [`wroute`](https://man.openbsd.org/pledge.2#wroute). 96 Wroute, 97 } 98 impl Promise { 99 /// Returns `self` as a `u64`. 100 const fn to_u64(self) -> u64 { 101 match self { 102 Self::Audio => 0x1, 103 Self::Bpf => 0x2, 104 Self::Chown => 0x4, 105 Self::Cpath => 0x8, 106 Self::Disklabel => 0x10, 107 Self::Dns => 0x20, 108 Self::Dpath => 0x40, 109 Self::Drm => 0x80, 110 Self::Error => 0x100, 111 Self::Exec => 0x200, 112 Self::Fattr => 0x400, 113 Self::Flock => 0x800, 114 Self::Getpw => 0x1000, 115 Self::Id => 0x2000, 116 Self::Inet => 0x4000, 117 Self::Mcast => 0x8000, 118 Self::Pf => 0x10000, 119 Self::Proc => 0x20000, 120 Self::ProtExec => 0x40000, 121 Self::Ps => 0x80000, 122 Self::Recvfd => 0x0010_0000, 123 Self::Route => 0x0020_0000, 124 Self::Rpath => 0x0040_0000, 125 Self::Sendfd => 0x0080_0000, 126 Self::Settime => 0x0100_0000, 127 Self::Stdio => 0x0200_0000, 128 Self::Tape => 0x0400_0000, 129 Self::Tmppath => 0x0800_0000, 130 Self::Tty => 0x1000_0000, 131 Self::Unix => 0x2000_0000, 132 Self::Unveil => 0x4000_0000, 133 Self::Video => 0x8000_0000, 134 Self::Vminfo => 0x0001_0000_0000, 135 Self::Vmm => 0x0002_0000_0000, 136 Self::Wpath => 0x0004_0000_0000, 137 Self::Wroute => 0x0008_0000_0000, 138 } 139 } 140 } 141 impl Display for Promise { 142 #[inline] 143 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 144 match *self { 145 Self::Audio => f.write_str("pledge(2) 'audio' promise"), 146 Self::Bpf => f.write_str("pledge(2) 'bpf' promise"), 147 Self::Chown => f.write_str("pledge(2) 'chown' promise"), 148 Self::Cpath => f.write_str("pledge(2) 'cpath' promise"), 149 Self::Disklabel => f.write_str("pledge(2) 'disklabel' promise"), 150 Self::Dns => f.write_str("pledge(2) 'dns' promise"), 151 Self::Dpath => f.write_str("pledge(2) 'dpath' promise"), 152 Self::Drm => f.write_str("pledge(2) 'drm' promise"), 153 Self::Error => f.write_str("pledge(2) 'error' promise"), 154 Self::Exec => f.write_str("pledge(2) 'exec' promise"), 155 Self::Fattr => f.write_str("pledge(2) 'fattr' promise"), 156 Self::Flock => f.write_str("pledge(2) 'flock' promise"), 157 Self::Getpw => f.write_str("pledge(2) 'getpw' promise"), 158 Self::Id => f.write_str("pledge(2) 'id' promise"), 159 Self::Inet => f.write_str("pledge(2) 'inet' promise"), 160 Self::Mcast => f.write_str("pledge(2) 'mcast' promise"), 161 Self::Pf => f.write_str("pledge(2) 'pf' promise"), 162 Self::Proc => f.write_str("pledge(2) 'proc' promise"), 163 Self::ProtExec => f.write_str("pledge(2) 'prot_exec' promise"), 164 Self::Ps => f.write_str("pledge(2) 'ps' promise"), 165 Self::Recvfd => f.write_str("pledge(2) 'recvfd' promise"), 166 Self::Route => f.write_str("pledge(2) 'route' promise"), 167 Self::Rpath => f.write_str("pledge(2) 'rpath' promise"), 168 Self::Sendfd => f.write_str("pledge(2) 'sendfd' promise"), 169 Self::Settime => f.write_str("pledge(2) 'settime' promise"), 170 Self::Stdio => f.write_str("pledge(2) 'stdio' promise"), 171 Self::Tape => f.write_str("pledge(2) 'tape' promise"), 172 Self::Tmppath => f.write_str("pledge(2) 'tmppath' promise"), 173 Self::Tty => f.write_str("pledge(2) 'tty' promise"), 174 Self::Unix => f.write_str("pledge(2) 'unix' promise"), 175 Self::Unveil => f.write_str("pledge(2) 'unveil' promise"), 176 Self::Video => f.write_str("pledge(2) 'video' promise"), 177 Self::Vminfo => f.write_str("pledge(2) 'vminfo' promise"), 178 Self::Vmm => f.write_str("pledge(2) 'vmm' promise"), 179 Self::Wpath => f.write_str("pledge(2) 'wpath' promise"), 180 Self::Wroute => f.write_str("pledge(2) 'wroute' promise"), 181 } 182 } 183 } 184 impl PartialEq<&Self> for Promise { 185 #[inline] 186 fn eq(&self, other: &&Self) -> bool { 187 *self == **other 188 } 189 } 190 impl PartialEq<Promise> for &Promise { 191 #[inline] 192 fn eq(&self, other: &Promise) -> bool { 193 **self == *other 194 } 195 } 196 /// A set of [`Promise`]s that can only have `Promise`s removed after creation. 197 /// 198 /// Once a set of `promises` has been [`pledge(2)`](https://man.openbsd.org/pledge.2)d, 199 /// only a subset of those `promises` can be `pledge(2)`d again; as a result, this type can be used 200 /// to ensure that `Promise`s are never added but only removed from an initial set. 201 #[expect( 202 missing_copy_implementations, 203 reason = "a single instance should be created" 204 )] 205 #[derive(Debug, Eq, PartialEq)] 206 pub struct Promises(u64); 207 impl Promises { 208 /// Empty `Promises`. 209 pub const NONE: Self = Self(0); 210 /// `Promises` containing all [`Promise`]s. 211 pub const ALL: Self = Self::NONE 212 .add(Promise::Audio) 213 .add(Promise::Bpf) 214 .add(Promise::Chown) 215 .add(Promise::Cpath) 216 .add(Promise::Disklabel) 217 .add(Promise::Dns) 218 .add(Promise::Dpath) 219 .add(Promise::Drm) 220 .add(Promise::Error) 221 .add(Promise::Exec) 222 .add(Promise::Fattr) 223 .add(Promise::Flock) 224 .add(Promise::Getpw) 225 .add(Promise::Id) 226 .add(Promise::Inet) 227 .add(Promise::Mcast) 228 .add(Promise::Pf) 229 .add(Promise::Proc) 230 .add(Promise::ProtExec) 231 .add(Promise::Ps) 232 .add(Promise::Recvfd) 233 .add(Promise::Route) 234 .add(Promise::Rpath) 235 .add(Promise::Sendfd) 236 .add(Promise::Settime) 237 .add(Promise::Stdio) 238 .add(Promise::Tape) 239 .add(Promise::Tmppath) 240 .add(Promise::Tty) 241 .add(Promise::Unix) 242 .add(Promise::Unveil) 243 .add(Promise::Video) 244 .add(Promise::Vminfo) 245 .add(Promise::Vmm) 246 .add(Promise::Wpath) 247 .add(Promise::Wroute); 248 /// Returns a `Promises` containing all `Promise`s in `self` and `promise`. 249 const fn add(self, promise: Promise) -> Self { 250 Self(self.0 | promise.to_u64()) 251 } 252 /// Returns a `Promises` containing the unique `Promise`s in `initial`. 253 /// 254 /// # Examples 255 /// 256 /// ``` 257 /// # #[cfg(target_os = "openbsd")] 258 /// # use priv_sep::{Promise, Promises}; 259 /// # #[cfg(target_os = "openbsd")] 260 /// assert_eq!(Promises::new([Promise::Stdio]).len(), 1); 261 /// ``` 262 #[inline] 263 #[must_use] 264 pub fn new<P: AsRef<[Promise]>>(initial: P) -> Self { 265 initial 266 .as_ref() 267 .iter() 268 .fold(Self::NONE, |val, promise| val.add(*promise)) 269 } 270 /// Same as [`UserInfo::priv_drop`] except [`Self::pledge`] is called between [`UserInfo::new`] and 271 /// the invocation of `f`. 272 /// 273 /// Note [`Promise::Id`] and [`Promise::Stdio`] are automatically added to `initial`. `retain_id_promise` 274 /// dictates if [`Promise::Id`] should subsequently be retained after [`UserInfo::setresid`]. Callers must 275 /// ensure `initial` contains any necessary [`Promise`]s needed for `f`. 276 /// 277 /// # Errors 278 /// 279 /// Errors iff [`UserInfo::priv_drop`] or [`Self::pledge`] error. 280 /// 281 /// # Examples 282 /// 283 /// ```no_run 284 /// # #[cfg(target_os = "openbsd")] 285 /// # use priv_sep::{Promise, Promises}; 286 /// # use core::net::{Ipv6Addr, SocketAddrV6}; 287 /// # use priv_sep::PrivDropErr; 288 /// # use std::{io::Error, net::TcpListener}; 289 /// # #[cfg(target_os = "openbsd")] 290 /// let (listener, promises) = Promises::new_priv_drop("nobody", [Promise::Inet], false, || { 291 /// TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)) 292 /// })?; 293 /// # Ok::<_, PrivDropErr<Error>>(()) 294 /// ``` 295 #[inline] 296 pub fn new_priv_drop< 297 U: Into<Vec<u8>>, 298 Prom: AsRef<[Promise]>, 299 T, 300 E, 301 F: FnOnce() -> Result<T, E>, 302 >( 303 name: U, 304 initial: Prom, 305 retain_id_promise: bool, 306 f: F, 307 ) -> Result<(T, Self), PrivDropErr<E>> { 308 let mut promises = initial.as_ref().iter().fold( 309 Self::NONE.add(Promise::Id).add(Promise::Stdio), 310 |val, promise| val.add(*promise), 311 ); 312 UserInfo::new(name) 313 .map_err(PrivDropErr::from) 314 .and_then(|opt| { 315 opt.ok_or_else(|| PrivDropErr::NoPasswdEntry) 316 .and_then(|info| { 317 if info.is_root() { 318 Err(PrivDropErr::RootEntry) 319 } else { 320 promises.pledge().map_err(PrivDropErr::Io).and_then(|()| { 321 f().map_err(PrivDropErr::Other).and_then(|res| { 322 info.setresid().map_err(PrivDropErr::Io).and_then(|()| { 323 if retain_id_promise { 324 Ok(()) 325 } else { 326 promises 327 .remove_then_pledge(Promise::Id) 328 .map_err(PrivDropErr::Io) 329 } 330 .map(|()| (res, promises)) 331 }) 332 }) 333 }) 334 } 335 }) 336 }) 337 } 338 /// Same as [`Self::new_priv_drop`] except `f` is `async`. 339 /// 340 /// # Errors 341 /// 342 /// Read [`Self::new_priv_drop`]. 343 /// 344 /// # Examples 345 /// 346 /// ```no_run 347 /// # #[cfg(target_os = "openbsd")] 348 /// # use priv_sep::{Promise, Promises}; 349 /// # use core::net::{Ipv6Addr, SocketAddrV6}; 350 /// # use tokio::net::TcpListener; 351 /// # #[cfg(target_os = "openbsd")] 352 /// let listener_fut = Promises::new_priv_drop_async("nobody", [Promise::Inet], false, async || { 353 /// TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await 354 /// }); 355 /// ``` 356 #[inline] 357 pub async fn new_priv_drop_async< 358 U: Into<Vec<u8>>, 359 Prom: AsRef<[Promise]>, 360 T, 361 E, 362 F: AsyncFnOnce() -> Result<T, E>, 363 >( 364 name: U, 365 initial: Prom, 366 retain_id_promise: bool, 367 f: F, 368 ) -> Result<(T, Self), PrivDropErr<E>> { 369 let mut promises = initial.as_ref().iter().fold( 370 Self::NONE.add(Promise::Id).add(Promise::Stdio), 371 |val, promise| val.add(*promise), 372 ); 373 match UserInfo::new(name) { 374 Ok(opt) => match opt { 375 None => Err(PrivDropErr::NoPasswdEntry), 376 Some(info) => { 377 if info.is_root() { 378 Err(PrivDropErr::RootEntry) 379 } else { 380 match promises.pledge() { 381 Ok(()) => f().await.map_err(PrivDropErr::Other).and_then(|res| { 382 info.setresid().map_err(PrivDropErr::Io).and_then(|()| { 383 if retain_id_promise { 384 Ok(()) 385 } else { 386 promises 387 .remove_then_pledge(Promise::Id) 388 .map_err(PrivDropErr::Io) 389 } 390 .map(|()| (res, promises)) 391 }) 392 }), 393 Err(err) => Err(PrivDropErr::Io(err)), 394 } 395 } 396 } 397 }, 398 Err(err) => Err(PrivDropErr::from(err)), 399 } 400 } 401 /// Same as [`Self::new_priv_drop`] except [`chroot_then_chdir`] is called before [`Self::pledge`]ing `initial`. 402 /// 403 /// # Errors 404 /// 405 /// Errors iff [`Self::new_priv_drop`] or [`chroot_then_chdir`] do. 406 /// 407 /// # Examples 408 /// 409 /// ```no_run 410 /// # #[cfg(target_os = "openbsd")] 411 /// # use priv_sep::{Promise, Promises}; 412 /// # use core::net::{Ipv6Addr, SocketAddrV6}; 413 /// # use priv_sep::PrivDropErr; 414 /// # use std::{io::Error, net::TcpListener}; 415 /// # #[cfg(target_os = "openbsd")] 416 /// let (listener, promises) = Promises::new_chroot_then_priv_drop("nobody", "./", [Promise::Inet], false, || { 417 /// TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)) 418 /// })?; 419 /// # Ok::<_, PrivDropErr<Error>>(()) 420 /// ``` 421 #[inline] 422 pub fn new_chroot_then_priv_drop< 423 U: Into<Vec<u8>>, 424 P: AsRef<Path>, 425 Prom: AsRef<[Promise]>, 426 T, 427 E, 428 F: FnOnce() -> Result<T, E>, 429 >( 430 name: U, 431 path: P, 432 initial: Prom, 433 retain_id_promise: bool, 434 f: F, 435 ) -> Result<(T, Self), PrivDropErr<E>> { 436 let mut promises = initial.as_ref().iter().fold( 437 Self::NONE.add(Promise::Id).add(Promise::Stdio), 438 |val, promise| val.add(*promise), 439 ); 440 UserInfo::new(name) 441 .map_err(PrivDropErr::from) 442 .and_then(|opt| { 443 opt.ok_or_else(|| PrivDropErr::NoPasswdEntry) 444 .and_then(|info| { 445 if info.is_root() { 446 Err(PrivDropErr::RootEntry) 447 } else { 448 super::chroot_then_chdir(path) 449 .map_err(PrivDropErr::from) 450 .and_then(|()| { 451 promises.pledge().map_err(PrivDropErr::Io).and_then(|()| { 452 f().map_err(PrivDropErr::Other).and_then(|res| { 453 info.setresid().map_err(PrivDropErr::Io).and_then( 454 |()| { 455 if retain_id_promise { 456 Ok(()) 457 } else { 458 promises 459 .remove_then_pledge(Promise::Id) 460 .map_err(PrivDropErr::Io) 461 } 462 .map(|()| (res, promises)) 463 }, 464 ) 465 }) 466 }) 467 }) 468 } 469 }) 470 }) 471 } 472 /// Same as [`Self::new_chroot_then_priv_drop`] except `f` is `async`. 473 /// 474 /// # Errors 475 /// 476 /// Read [`Self::new_chroot_then_priv_drop`]. 477 /// 478 /// # Examples 479 /// 480 /// ```no_run 481 /// # #[cfg(target_os = "openbsd")] 482 /// # use priv_sep::{Promise, Promises}; 483 /// # use core::net::{Ipv6Addr, SocketAddrV6}; 484 /// # use tokio::net::TcpListener; 485 /// # #[cfg(target_os = "openbsd")] 486 /// let listener_fut = Promises::new_chroot_then_priv_drop_async("nobody", "./", [Promise::Inet], false, async || { 487 /// TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await 488 /// }); 489 /// ``` 490 #[inline] 491 pub async fn new_chroot_then_priv_drop_async< 492 U: Into<Vec<u8>>, 493 P: AsRef<Path>, 494 Prom: AsRef<[Promise]>, 495 T, 496 E, 497 F: AsyncFnOnce() -> Result<T, E>, 498 >( 499 name: U, 500 path: P, 501 initial: Prom, 502 retain_id_promise: bool, 503 f: F, 504 ) -> Result<(T, Self), PrivDropErr<E>> { 505 let mut promises = initial.as_ref().iter().fold( 506 Self::NONE.add(Promise::Id).add(Promise::Stdio), 507 |val, promise| val.add(*promise), 508 ); 509 match UserInfo::new(name) { 510 Ok(opt) => match opt { 511 None => Err(PrivDropErr::NoPasswdEntry), 512 Some(info) => { 513 if info.is_root() { 514 Err(PrivDropErr::RootEntry) 515 } else { 516 match super::chroot_then_chdir(path) { 517 Ok(()) => match promises.pledge() { 518 Ok(()) => f().await.map_err(PrivDropErr::Other).and_then(|res| { 519 info.setresid().map_err(PrivDropErr::Io).and_then(|()| { 520 if retain_id_promise { 521 Ok(()) 522 } else { 523 promises 524 .remove_then_pledge(Promise::Id) 525 .map_err(PrivDropErr::Io) 526 } 527 .map(|()| (res, promises)) 528 }) 529 }), 530 Err(err) => Err(PrivDropErr::Io(err)), 531 }, 532 Err(err) => Err(PrivDropErr::from(err)), 533 } 534 } 535 } 536 }, 537 Err(err) => Err(PrivDropErr::from(err)), 538 } 539 } 540 /// Returns the number of [`Promise`]s. 541 /// 542 /// # Examples 543 /// 544 /// ``` 545 /// # #[cfg(target_os = "openbsd")] 546 /// # use priv_sep::{Promise, Promises}; 547 /// # #[cfg(target_os = "openbsd")] 548 /// assert_eq!(Promises::new([Promise::Stdio]).len(), 1); 549 /// ``` 550 #[inline] 551 #[must_use] 552 pub const fn len(&self) -> u32 { 553 self.0.count_ones() 554 } 555 /// Returns `true` iff there are no [`Promise`]s. 556 /// 557 /// # Examples 558 /// 559 /// ``` 560 /// # #[cfg(target_os = "openbsd")] 561 /// # use priv_sep::Promises; 562 /// # #[cfg(target_os = "openbsd")] 563 /// assert!(Promises::NONE.is_empty()); 564 /// ``` 565 #[inline] 566 #[must_use] 567 pub const fn is_empty(&self) -> bool { 568 self.len() == 0 569 } 570 /// Returns `true` iff `self` contains `promise`. 571 /// 572 /// # Examples 573 /// 574 /// ``` 575 /// # #[cfg(target_os = "openbsd")] 576 /// # use priv_sep::{Promise, Promises}; 577 /// # #[cfg(target_os = "openbsd")] 578 /// assert!(Promises::new([Promise::Stdio]).contains(Promise::Stdio)); 579 /// # #[cfg(target_os = "openbsd")] 580 /// assert!(!Promises::new([Promise::Stdio]).contains(Promise::Rpath)); 581 /// ``` 582 #[inline] 583 #[must_use] 584 pub const fn contains(&self, promise: Promise) -> bool { 585 let val = promise.to_u64(); 586 self.0 & val == val 587 } 588 /// Removes all `Promise`s _not_ in `promises` from `self`. 589 /// 590 /// # Examples 591 /// 592 /// ``` 593 /// # #[cfg(target_os = "openbsd")] 594 /// # use priv_sep::{Promise, Promises}; 595 /// # #[cfg(target_os = "openbsd")] 596 /// let mut proms = Promises::new([Promise::Rpath, Promise::Stdio]); 597 /// # #[cfg(target_os = "openbsd")] 598 /// proms.retain([Promise::Stdio]); 599 /// # #[cfg(target_os = "openbsd")] 600 /// assert!(proms.len() == 1 && proms.contains(Promise::Stdio)); 601 /// ``` 602 #[inline] 603 pub fn retain<P: AsRef<[Promise]>>(&mut self, promises: P) { 604 self.0 = promises 605 .as_ref() 606 .iter() 607 .fold(Self::NONE, |val, promise| { 608 if self.contains(*promise) { 609 val.add(*promise) 610 } else { 611 val 612 } 613 }) 614 .0; 615 } 616 /// Same as [`Self::retain`] then [`Self::pledge`]; however this is "atomic" in that if an error occurs from `pledge`, 617 /// `self` is left unchanged. This is useful when one wants to "recover" from an error since there is no 618 /// way to add `Promise`s back forcing one to have to create a second `Promises`. 619 /// 620 /// Note that when `retain` doesn't change `self`, `pledge` is not called. 621 /// 622 /// # Errors 623 /// 624 /// Returns [`Error`] iff `pledge` does. 625 /// 626 /// # Examples 627 /// 628 /// ```no_run 629 /// # #[cfg(target_os = "openbsd")] 630 /// # use priv_sep::{Promise, Promises}; 631 /// # #[cfg(target_os = "openbsd")] 632 /// assert!(Promises::new([Promise::Rpath, Promise::Stdio]).retain_then_pledge([Promise::Rpath]).is_ok()); 633 /// ``` 634 #[inline] 635 pub fn retain_then_pledge<P: AsRef<[Promise]>>(&mut self, promises: P) -> Result<(), Error> { 636 // We opt for the easy way by copying `self`. This should be the fastest for the 637 // typical case since `self` likely has very few `Promise`s, and it makes for less code. 638 let cur = Self(self.0); 639 self.retain(promises); 640 if *self == cur { 641 Ok(()) 642 } else { 643 self.pledge().inspect_err(|_| *self = cur) 644 } 645 } 646 /// Removes all `Promise`s in `promises` from `self`. 647 /// 648 /// # Examples 649 /// 650 /// ``` 651 /// # #[cfg(target_os = "openbsd")] 652 /// # use priv_sep::{Promise, Promises}; 653 /// # #[cfg(target_os = "openbsd")] 654 /// let mut proms = Promises::new([Promise::Rpath, Promise::Stdio]); 655 /// # #[cfg(target_os = "openbsd")] 656 /// proms.remove_promises([Promise::Stdio]); 657 /// # #[cfg(target_os = "openbsd")] 658 /// assert!(proms.len() == 1 && proms.contains(Promise::Rpath)); 659 /// ``` 660 #[inline] 661 pub fn remove_promises<P: AsRef<[Promise]>>(&mut self, promises: P) { 662 promises 663 .as_ref() 664 .iter() 665 .fold((), |(), promise| self.remove(*promise)); 666 } 667 /// Same as [`Self::remove_promises`] then [`Self::pledge`]; however this is "atomic" in that if an error occurs from 668 /// `pledge`, `self` is left unchanged. This is useful when one wants to "recover" from an error since there 669 /// is no way to add `Promise`s back forcing one to have to create a second `Promises`. 670 /// 671 /// Note that when `remove_promises` doesn't remove any, `pledge` is not called. 672 /// 673 /// # Errors 674 /// 675 /// Returns [`Error`] iff `pledge` does. 676 /// 677 /// # Examples 678 /// 679 /// ```no_run 680 /// # #[cfg(target_os = "openbsd")] 681 /// # use priv_sep::{Promise, Promises}; 682 /// # #[cfg(target_os = "openbsd")] 683 /// assert!(Promises::new([Promise::Rpath, Promise::Stdio]).remove_promises_then_pledge([Promise::Rpath]).is_ok()); 684 /// ``` 685 #[inline] 686 pub fn remove_promises_then_pledge<P: AsRef<[Promise]>>( 687 &mut self, 688 promises: P, 689 ) -> Result<(), Error> { 690 // We opt for the easy way by copying `self`. This should be the fastest for the 691 // typical case since `self` likely has very few `Promise`s, and it makes for less code. 692 let cur = Self(self.0); 693 self.remove_promises(promises); 694 if *self == cur { 695 Ok(()) 696 } else { 697 self.pledge().inspect_err(|_| *self = cur) 698 } 699 } 700 /// Removes `promise` from `self`. 701 /// 702 /// # Examples 703 /// 704 /// ``` 705 /// # #[cfg(target_os = "openbsd")] 706 /// # use priv_sep::{Promise, Promises}; 707 /// # #[cfg(target_os = "openbsd")] 708 /// let mut proms = Promises::new([Promise::Rpath, Promise::Stdio]); 709 /// # #[cfg(target_os = "openbsd")] 710 /// proms.remove(Promise::Stdio); 711 /// # #[cfg(target_os = "openbsd")] 712 /// assert!(proms.len() == 1 && proms.contains(Promise::Rpath)); 713 /// ``` 714 #[inline] 715 pub const fn remove(&mut self, promise: Promise) { 716 self.0 &= !promise.to_u64(); 717 } 718 /// Same as [`Self::remove`] then [`Self::pledge`]; however this is "atomic" in that if an error occurs from `pledge`, 719 /// `self` is left unchanged. This is useful when one wants to "recover" from an error since there is no 720 /// way to add `Promise`s back forcing one to have to create a second `Promises`. 721 /// 722 /// # Errors 723 /// 724 /// Returns [`Error`] iff `pledge` does. 725 /// 726 /// # Examples 727 /// 728 /// ```no_run 729 /// # #[cfg(target_os = "openbsd")] 730 /// # use priv_sep::{Promise, Promises}; 731 /// # #[cfg(target_os = "openbsd")] 732 /// assert!(Promises::new([Promise::Rpath, Promise::Stdio]).remove_then_pledge(Promise::Rpath).is_ok()); 733 /// ``` 734 #[inline] 735 pub fn remove_then_pledge(&mut self, promise: Promise) -> Result<(), Error> { 736 // We opt for the easy way by copying `self`. This should be the fastest for the 737 // typical case since `self` likely has very few `Promise`s, and it makes for less code. 738 let cur = Self(self.0); 739 self.remove(promise); 740 if *self == cur { 741 Ok(()) 742 } else { 743 self.pledge().inspect_err(|_| *self = cur) 744 } 745 } 746 /// Invokes `pledge(2)` always passing `NULL` for `execpromises` and `promises` for `promises`. 747 /// 748 /// This function MUST only be called by [`Self::pledge`] and [`Self::pledge_none`]. 749 #[expect(unsafe_code, reason = "pledge(2) takes in pointers")] 750 fn inner_pledge(promises: *const c_char) -> Result<(), Error> { 751 // SAFETY: 752 // `promises` is either null or valid as can be seen in the only functions that call this 753 // function: 754 // `Self::pledge` and `Self::pledge_none`. 755 if unsafe { pledge(promises, ptr::null()) } == SUCCESS { 756 Ok(()) 757 } else { 758 Err(Error::last_os_error()) 759 } 760 } 761 /// Invokes [`pledge(2)`](https://man.openbsd.org/pledge.2) always passing in 762 /// `NULL` for `execpromises` and its contained [`Promise`]s for `promises`. 763 /// 764 /// # Errors 765 /// 766 /// Returns [`Error`] iff `pledge(2)` errors. 767 /// 768 /// # Examples 769 /// 770 /// ```no_run 771 /// # #[cfg(target_os = "openbsd")] 772 /// # use priv_sep::{Promise, Promises}; 773 /// # #[cfg(target_os = "openbsd")] 774 /// assert!(Promises::new([Promise::Stdio]).pledge().is_ok()); 775 /// ``` 776 #[expect( 777 unsafe_code, 778 reason = "we manually construct a valid CString and ensure its safety" 779 )] 780 #[expect( 781 clippy::arithmetic_side_effects, 782 clippy::indexing_slicing, 783 reason = "we replace a trailing space with 0 to ensure CString is correctly constructed" 784 )] 785 #[expect( 786 clippy::cognitive_complexity, 787 clippy::too_many_lines, 788 reason = "many if blocks to handle each Promise" 789 )] 790 #[inline] 791 pub fn pledge(&self) -> Result<(), Error> { 792 let mut p = Vec::new(); 793 if self.contains(Promise::Audio) { 794 p.extend_from_slice(b"audio "); 795 } 796 if self.contains(Promise::Bpf) { 797 p.extend_from_slice(b"bpf "); 798 } 799 if self.contains(Promise::Chown) { 800 p.extend_from_slice(b"chown "); 801 } 802 if self.contains(Promise::Cpath) { 803 p.extend_from_slice(b"cpath "); 804 } 805 if self.contains(Promise::Disklabel) { 806 p.extend_from_slice(b"disklabel "); 807 } 808 if self.contains(Promise::Dns) { 809 p.extend_from_slice(b"dns "); 810 } 811 if self.contains(Promise::Dpath) { 812 p.extend_from_slice(b"dpath "); 813 } 814 if self.contains(Promise::Drm) { 815 p.extend_from_slice(b"drm "); 816 } 817 if self.contains(Promise::Error) { 818 p.extend_from_slice(b"error "); 819 } 820 if self.contains(Promise::Exec) { 821 p.extend_from_slice(b"exec "); 822 } 823 if self.contains(Promise::Fattr) { 824 p.extend_from_slice(b"fattr "); 825 } 826 if self.contains(Promise::Flock) { 827 p.extend_from_slice(b"flock "); 828 } 829 if self.contains(Promise::Getpw) { 830 p.extend_from_slice(b"getpw "); 831 } 832 if self.contains(Promise::Id) { 833 p.extend_from_slice(b"id "); 834 } 835 if self.contains(Promise::Inet) { 836 p.extend_from_slice(b"inet "); 837 } 838 if self.contains(Promise::Mcast) { 839 p.extend_from_slice(b"mcast "); 840 } 841 if self.contains(Promise::Pf) { 842 p.extend_from_slice(b"pf "); 843 } 844 if self.contains(Promise::Proc) { 845 p.extend_from_slice(b"proc "); 846 } 847 if self.contains(Promise::ProtExec) { 848 p.extend_from_slice(b"prot_exec "); 849 } 850 if self.contains(Promise::Ps) { 851 p.extend_from_slice(b"ps "); 852 } 853 if self.contains(Promise::Recvfd) { 854 p.extend_from_slice(b"recvfd "); 855 } 856 if self.contains(Promise::Route) { 857 p.extend_from_slice(b"route "); 858 } 859 if self.contains(Promise::Rpath) { 860 p.extend_from_slice(b"rpath "); 861 } 862 if self.contains(Promise::Sendfd) { 863 p.extend_from_slice(b"sendfd "); 864 } 865 if self.contains(Promise::Settime) { 866 p.extend_from_slice(b"settime "); 867 } 868 if self.contains(Promise::Stdio) { 869 p.extend_from_slice(b"stdio "); 870 } 871 if self.contains(Promise::Tape) { 872 p.extend_from_slice(b"tape "); 873 } 874 if self.contains(Promise::Tmppath) { 875 p.extend_from_slice(b"tmppath "); 876 } 877 if self.contains(Promise::Tty) { 878 p.extend_from_slice(b"tty "); 879 } 880 if self.contains(Promise::Unix) { 881 p.extend_from_slice(b"unix "); 882 } 883 if self.contains(Promise::Unveil) { 884 p.extend_from_slice(b"unveil "); 885 } 886 if self.contains(Promise::Video) { 887 p.extend_from_slice(b"video "); 888 } 889 if self.contains(Promise::Vminfo) { 890 p.extend_from_slice(b"vminfo "); 891 } 892 if self.contains(Promise::Vmm) { 893 p.extend_from_slice(b"vmm "); 894 } 895 if self.contains(Promise::Wpath) { 896 p.extend_from_slice(b"wpath "); 897 } 898 if self.contains(Promise::Wroute) { 899 p.extend_from_slice(b"wroute "); 900 } 901 let idx = p.len(); 902 if idx == 0 { 903 p.push(0); 904 } else { 905 // All promises have a space after them which means 906 // we must replace the last promise's space with 907 // 0/nul byte. 908 // `idx` is at least 1 based on the above check. 909 p[idx - 1] = 0; 910 } 911 // SAFETY: 912 // `p` was populated above with correct ASCII-encoding of the literal 913 // values all of which do not have 0 bytes. 914 // `p` ends with a 0/nul byte. 915 let arg = unsafe { CString::from_vec_with_nul_unchecked(p) }; 916 Self::inner_pledge(arg.as_ptr()) 917 } 918 /// Invokes [`pledge(2)`](https://man.openbsd.org/pledge.2) with `NULL` for both `promises` and 919 /// `execpromises`. 920 /// 921 /// # Errors 922 /// 923 /// Returns [`Error`] iff `pledge(2)` errors. 924 /// 925 /// # Examples 926 /// 927 /// ```no_run 928 /// # #[cfg(target_os = "openbsd")] 929 /// # use priv_sep::Promises; 930 /// # #[cfg(target_os = "openbsd")] 931 /// assert!(Promises::pledge_none().is_ok()); 932 /// ``` 933 #[inline] 934 pub fn pledge_none() -> Result<(), Error> { 935 // `NULL` is always valid for `promises`. 936 Self::inner_pledge(ptr::null()) 937 } 938 } 939 impl PartialEq<&Self> for Promises { 940 #[inline] 941 fn eq(&self, other: &&Self) -> bool { 942 *self == **other 943 } 944 } 945 impl PartialEq<Promises> for &Promises { 946 #[inline] 947 fn eq(&self, other: &Promises) -> bool { 948 **self == *other 949 } 950 } 951 /// A permission in [`Permissions`]. 952 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 953 pub enum Permission { 954 /// [c](https://man.openbsd.org/unveil.2#c). 955 Create, 956 /// [x](https://man.openbsd.org/unveil.2#x). 957 Execute, 958 /// [r](https://man.openbsd.org/unveil.2#r). 959 Read, 960 /// [w](https://man.openbsd.org/unveil.2#w). 961 Write, 962 } 963 impl Permission { 964 /// Transforms `self` into a `u8`. 965 const fn to_u8(self) -> u8 { 966 match self { 967 Self::Create => 1, 968 Self::Execute => 2, 969 Self::Read => 4, 970 Self::Write => 8, 971 } 972 } 973 } 974 impl PartialEq<&Self> for Permission { 975 #[inline] 976 fn eq(&self, other: &&Self) -> bool { 977 *self == **other 978 } 979 } 980 impl PartialEq<Permission> for &Permission { 981 #[inline] 982 fn eq(&self, other: &Permission) -> bool { 983 **self == *other 984 } 985 } 986 /// `permissions` to [`unveil(2)`](https://man.openbsd.org/unveil.2). 987 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 988 pub struct Permissions(u8); 989 impl Permissions { 990 /// A `Permissions` with no [`Permission`]s enabled. 991 pub const NONE: Self = Self(0); 992 /// A `Permissions` with all [`Permission`]s enabled. 993 pub const ALL: Self = Self::NONE 994 .enable(Permission::Create) 995 .enable(Permission::Execute) 996 .enable(Permission::Read) 997 .enable(Permission::Write); 998 /// A `Permissions` with only [`Permission::Create`] enabled. 999 pub const CREATE: Self = Self::NONE.enable(Permission::Create); 1000 /// A `Permissions` with only [`Permission::Execute`] enabled. 1001 pub const EXECUTE: Self = Self::NONE.enable(Permission::Execute); 1002 /// A `Permissions` with only [`Permission::Read`] enabled. 1003 pub const READ: Self = Self::NONE.enable(Permission::Read); 1004 /// A `Permissions` with only [`Permission::Write`] enabled. 1005 pub const WRITE: Self = Self::NONE.enable(Permission::Write); 1006 /// `const` version of [`Self::bitor`]. 1007 #[inline] 1008 #[must_use] 1009 pub const fn enable(self, permission: Permission) -> Self { 1010 Self(self.0 | permission.to_u8()) 1011 } 1012 /// Returns `true` iff `self` has `permission` enabled. 1013 /// 1014 /// # Examples 1015 /// 1016 /// ``` 1017 /// # #[cfg(target_os = "openbsd")] 1018 /// # use priv_sep::{Permission, Permissions}; 1019 /// # #[cfg(target_os = "openbsd")] 1020 /// let perms = Permissions::CREATE; 1021 /// # #[cfg(target_os = "openbsd")] 1022 /// assert!(perms.is_enabled(Permission::Create)); 1023 /// # #[cfg(target_os = "openbsd")] 1024 /// assert!(!perms.is_enabled(Permission::Write)); 1025 /// ``` 1026 #[inline] 1027 #[must_use] 1028 pub const fn is_enabled(self, permission: Permission) -> bool { 1029 let val = permission.to_u8(); 1030 self.0 & val == val 1031 } 1032 /// Invokes `unveil(2)` passing `path` for `path` and `permissions` for `permissions`. 1033 /// 1034 /// This function MUST only be called by the functions [`Self::unveil`] and [`Self::unveil_no_more`]. 1035 #[expect(unsafe_code, reason = "unveil(2) takes in pointers")] 1036 fn inner_unveil(path: *const c_char, permissions: *const c_char) -> Result<(), Error> { 1037 // SAFETY: 1038 // `path` and `permissions` are either null or valid as can be seen in the only functions that call this 1039 // function: 1040 // `Self::unveil` and `Self::unveil_no_more`. 1041 if unsafe { unveil(path, permissions) } == SUCCESS { 1042 Ok(()) 1043 } else { 1044 Err(Error::last_os_error()) 1045 } 1046 } 1047 /// Invokes [`unveil(2)`](https://man.openbsd.org/unveil.2) 1048 /// passing `path` for `path` and the contained permissions for `permissions`. 1049 /// 1050 /// # Errors 1051 /// 1052 /// Returns [`NulError`] iff [`CString::new`] does. 1053 /// Returns [`Error`] iff `unveil(2)` errors. 1054 /// 1055 /// # Examples 1056 /// 1057 /// ```no_run 1058 /// # #[cfg(target_os = "openbsd")] 1059 /// # use std::io::ErrorKind; 1060 /// # #[cfg(target_os = "openbsd")] 1061 /// # use priv_sep::{Permissions, NulOrIoErr}; 1062 /// # #[cfg(target_os = "openbsd")] 1063 /// assert!(Permissions::READ.unveil("/path/to/read").is_ok()); 1064 /// # #[cfg(target_os = "openbsd")] 1065 /// assert!(Permissions::READ.unveil("/path/does/not/exist").map_or_else( 1066 /// |err| match err { 1067 /// NulOrIoErr::Io(e) => e.kind() == ErrorKind::NotFound, 1068 /// NulOrIoErr::Nul(_) => false, 1069 /// }, 1070 /// |()| false 1071 /// )); 1072 /// ``` 1073 #[expect( 1074 unsafe_code, 1075 reason = "we manually construct a valid CString and ensure its safety" 1076 )] 1077 #[expect( 1078 clippy::arithmetic_side_effects, 1079 reason = "we pre-allocate a Vec with the exact capacity which is capped at 5" 1080 )] 1081 #[inline] 1082 pub fn unveil<P: AsRef<Path>>(self, path: P) -> Result<(), NulOrIoErr> { 1083 CString::new(path.as_ref().as_os_str().as_bytes()).map_or_else( 1084 |e| Err(NulOrIoErr::Nul(e)), 1085 |path_c| { 1086 // The max sum is 5, so overflow is not possible. 1087 let mut vec = Vec::with_capacity( 1088 usize::from(self.is_enabled(Permission::Create)) 1089 + usize::from(self.is_enabled(Permission::Execute)) 1090 + usize::from(self.is_enabled(Permission::Read)) 1091 + usize::from(self.is_enabled(Permission::Write)) 1092 + 1, 1093 ); 1094 if self.is_enabled(Permission::Create) { 1095 vec.push(b'c'); 1096 } 1097 if self.is_enabled(Permission::Execute) { 1098 vec.push(b'x'); 1099 } 1100 if self.is_enabled(Permission::Read) { 1101 vec.push(b'r'); 1102 } 1103 if self.is_enabled(Permission::Write) { 1104 vec.push(b'w'); 1105 } 1106 vec.push(0); 1107 // SAFETY: 1108 // `vec` was populated above with correct ASCII-encoding of the literal 1109 // values all of which do not have 0 bytes. 1110 // `vec` ends with a 0/nul byte. 1111 let perm_c = unsafe { CString::from_vec_with_nul_unchecked(vec) }; 1112 Self::inner_unveil(path_c.as_ptr(), perm_c.as_ptr()).map_err(NulOrIoErr::Io) 1113 }, 1114 ) 1115 } 1116 /// Invokes [`unveil(2)`](https://man.openbsd.org/unveil.2) by passing `NULL` for both `path` and 1117 /// `permissions`. 1118 /// 1119 /// # Errors 1120 /// 1121 /// Returns [`Error`] when a problem occurs. 1122 /// 1123 /// # Examples 1124 /// 1125 /// ```no_run 1126 /// # #[cfg(target_os = "openbsd")] 1127 /// # use priv_sep::Permissions; 1128 /// # #[cfg(target_os = "openbsd")] 1129 /// assert!(Permissions::unveil_no_more().is_ok()); 1130 /// ``` 1131 #[inline] 1132 pub fn unveil_no_more() -> Result<(), Error> { 1133 // `NULL` is valid for both `path` and `permissions`. 1134 Self::inner_unveil(ptr::null(), ptr::null()) 1135 } 1136 } 1137 impl Display for Permissions { 1138 #[inline] 1139 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 1140 write!( 1141 f, 1142 "unveil(2) '{}{}{}{}' permissions", 1143 if self.is_enabled(Permission::Create) { 1144 "c" 1145 } else { 1146 "" 1147 }, 1148 if self.is_enabled(Permission::Execute) { 1149 "x" 1150 } else { 1151 "" 1152 }, 1153 if self.is_enabled(Permission::Read) { 1154 "r" 1155 } else { 1156 "" 1157 }, 1158 if self.is_enabled(Permission::Write) { 1159 "w" 1160 } else { 1161 "" 1162 }, 1163 ) 1164 } 1165 } 1166 impl BitAnd<Permission> for Permissions { 1167 type Output = Self; 1168 #[inline] 1169 fn bitand(self, rhs: Permission) -> Self::Output { 1170 Self(self.0 & rhs.to_u8()) 1171 } 1172 } 1173 impl BitAndAssign<Permission> for Permissions { 1174 #[inline] 1175 fn bitand_assign(&mut self, rhs: Permission) { 1176 self.0 &= rhs.to_u8(); 1177 } 1178 } 1179 impl BitOr<Permission> for Permissions { 1180 type Output = Self; 1181 #[inline] 1182 fn bitor(self, rhs: Permission) -> Self::Output { 1183 Self(self.0 | rhs.to_u8()) 1184 } 1185 } 1186 impl BitOrAssign<Permission> for Permissions { 1187 #[inline] 1188 fn bitor_assign(&mut self, rhs: Permission) { 1189 self.0 |= rhs.to_u8(); 1190 } 1191 } 1192 impl BitXor<Permission> for Permissions { 1193 type Output = Self; 1194 #[inline] 1195 fn bitxor(self, rhs: Permission) -> Self::Output { 1196 Self(self.0 ^ rhs.to_u8()) 1197 } 1198 } 1199 impl BitXorAssign<Permission> for Permissions { 1200 #[inline] 1201 fn bitxor_assign(&mut self, rhs: Permission) { 1202 self.0 ^= rhs.to_u8(); 1203 } 1204 } 1205 impl Not for Permissions { 1206 type Output = Self; 1207 #[inline] 1208 fn not(self) -> Self::Output { 1209 Self(Self::ALL.0 & !self.0) 1210 } 1211 } 1212 impl PartialEq<&Self> for Permissions { 1213 #[inline] 1214 fn eq(&self, other: &&Self) -> bool { 1215 *self == **other 1216 } 1217 } 1218 impl PartialEq<Permissions> for &Permissions { 1219 #[inline] 1220 fn eq(&self, other: &Permissions) -> bool { 1221 **self == *other 1222 } 1223 } 1224 #[cfg(test)] 1225 mod tests { 1226 use super::{Permissions, Promise, Promises}; 1227 use crate::{PrivDropErr, Uid}; 1228 use core::net::{Ipv6Addr, SocketAddrV6}; 1229 use std::{fs, io::Error, net::TcpListener}; 1230 const README: &str = "README.md"; 1231 #[test] 1232 #[ignore] 1233 fn test_pledge_unveil() { 1234 const FILE_EXISTS: &str = "/home/zack/foo.txt"; 1235 _ = fs::metadata(FILE_EXISTS) 1236 .expect(format!("{FILE_EXISTS} does not exist, so unit testing cannot occur").as_str()); 1237 const FILE_NOT_EXISTS: &str = "/home/zack/aadkjfasj3s23"; 1238 drop(fs::metadata(FILE_NOT_EXISTS).expect_err( 1239 format!("{FILE_NOT_EXISTS} exists, so unit testing cannot occur").as_str(), 1240 )); 1241 const DIR_NOT_EXISTS: &str = "/home/zack/aadkjfasj3s23/"; 1242 drop( 1243 fs::metadata(DIR_NOT_EXISTS).expect_err( 1244 format!("{DIR_NOT_EXISTS} exists, so unit testing cannot occur").as_str(), 1245 ), 1246 ); 1247 // This tests that a NULL `promise` does nothing. 1248 assert!(Promises::pledge_none().is_ok()); 1249 print!(""); 1250 assert!(Promises::ALL.pledge().is_ok()); 1251 // This tests that duplicates are ignored as well as the implementation of PartialEq. 1252 let mut initial_promises = Promises::new([ 1253 Promise::Stdio, 1254 Promise::Unveil, 1255 Promise::Rpath, 1256 Promise::Stdio, 1257 ]); 1258 assert!(initial_promises.len() == 3); 1259 assert!( 1260 initial_promises == Promises::new([Promise::Rpath, Promise::Stdio, Promise::Unveil]) 1261 ); 1262 // Test retain. 1263 assert!({ 1264 let mut vals = Promises::new([ 1265 Promise::Audio, 1266 Promise::Bpf, 1267 Promise::Chown, 1268 Promise::Cpath, 1269 Promise::Error, 1270 Promise::Exec, 1271 ]); 1272 vals.retain([Promise::Error, Promise::Chown]); 1273 vals.len() == 2 && vals.contains(Promise::Chown) && vals.contains(Promise::Error) 1274 }); 1275 assert!(initial_promises.pledge().is_ok()); 1276 // This tests unveil with no permissions. 1277 assert!(Permissions::NONE.unveil(FILE_EXISTS).is_ok()); 1278 assert!(fs::metadata(FILE_EXISTS).is_err()); 1279 // This tests unveil with read permissions, 1280 // and one can unveil more permissions (unlike pledge which can only remove promises). 1281 assert!(Permissions::READ.unveil(FILE_EXISTS).is_ok()); 1282 assert!(fs::metadata(FILE_EXISTS).is_ok()); 1283 // This tests that calls to unveil on missing files don't error. 1284 assert!(Permissions::NONE.unveil(FILE_NOT_EXISTS).is_ok()); 1285 // This tests that calls to unveil on missing directories error. 1286 assert!(Permissions::NONE.unveil(DIR_NOT_EXISTS).is_err()); 1287 // This tests that unveil can no longer be called. 1288 assert!(Permissions::unveil_no_more().is_ok()); 1289 assert!(Permissions::NONE.unveil(FILE_EXISTS).is_err()); 1290 assert!(fs::metadata(FILE_EXISTS).is_ok()); 1291 // The below tests that Promises can only be removed and not added. 1292 initial_promises.remove_promises([Promise::Unveil]); 1293 assert_eq!(initial_promises.len(), 2); 1294 initial_promises.remove(Promise::Rpath); 1295 assert_eq!(initial_promises.len(), 1); 1296 initial_promises.remove(Promise::Rpath); 1297 assert_eq!(initial_promises.len(), 1); 1298 assert!(initial_promises.pledge().is_ok()); 1299 print!(""); 1300 assert!(Promises::new([Promise::Rpath]).pledge().is_err()); 1301 // If the below is uncommented, the program should crash since the above 1302 // call to pledge no longer allows access to the file system. 1303 // drop(fs::metadata(FILE_EXISTS)); 1304 } 1305 #[test] 1306 #[ignore] 1307 fn test_pledge_priv_drop() -> Result<(), PrivDropErr<Error>> { 1308 if Uid::geteuid().is_root() { 1309 Promises::new_priv_drop( 1310 "zack", 1311 [Promise::Inet, Promise::Rpath, Promise::Unveil], 1312 false, 1313 || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)), 1314 ) 1315 .and_then(|(_, mut promises)| { 1316 Permissions::READ 1317 .unveil(README) 1318 .map_err(PrivDropErr::from) 1319 .and_then(|()| { 1320 fs::exists(README) 1321 .map_err(PrivDropErr::Io) 1322 .and_then(|exists| { 1323 Permissions::NONE 1324 .unveil(README) 1325 .map_err(PrivDropErr::from) 1326 .and_then(|()| { 1327 promises 1328 .remove_promises_then_pledge([ 1329 Promise::Rpath, 1330 Promise::Unveil, 1331 ]) 1332 .map_err(PrivDropErr::Io) 1333 .map(|()| { 1334 assert!(exists); 1335 assert!( 1336 TcpListener::bind(SocketAddrV6::new( 1337 Ipv6Addr::LOCALHOST, 1338 80, 1339 0, 1340 0 1341 )) 1342 .is_err() 1343 ); 1344 }) 1345 }) 1346 }) 1347 }) 1348 }) 1349 } else { 1350 Ok(()) 1351 } 1352 } 1353 #[test] 1354 #[ignore] 1355 fn test_pledge_chroot_priv_drop() -> Result<(), PrivDropErr<Error>> { 1356 if Uid::geteuid().is_root() { 1357 Promises::new_chroot_then_priv_drop( 1358 "zack", 1359 "./", 1360 [Promise::Inet, Promise::Rpath, Promise::Unveil], 1361 false, 1362 || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)), 1363 ) 1364 .and_then(|(_, mut promises)| { 1365 Permissions::READ 1366 .unveil(README) 1367 .map_err(PrivDropErr::from) 1368 .and_then(|()| { 1369 fs::exists(README) 1370 .map_err(PrivDropErr::Io) 1371 .and_then(|exists| { 1372 Permissions::NONE 1373 .unveil(README) 1374 .map_err(PrivDropErr::from) 1375 .and_then(|()| { 1376 promises 1377 .remove_promises_then_pledge([ 1378 Promise::Rpath, 1379 Promise::Unveil, 1380 ]) 1381 .map_err(PrivDropErr::Io) 1382 .map(|()| { 1383 assert!(exists); 1384 assert!( 1385 TcpListener::bind(SocketAddrV6::new( 1386 Ipv6Addr::LOCALHOST, 1387 80, 1388 0, 1389 0 1390 )) 1391 .is_err() 1392 ); 1393 }) 1394 }) 1395 }) 1396 }) 1397 }) 1398 } else { 1399 Ok(()) 1400 } 1401 } 1402 }