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