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