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