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