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