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