rpz

Response policy zone (RPZ) file generator.
git clone https://git.philomathiclife.com/repos/rpz
Log | Files | Refs | README

priv_sep.rs (7996B)


      1 #[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
      2 pub use priv_sep::UnveilErr;
      3 #[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
      4 use priv_sep::{self, Permission, Permissions, Promise, Promises};
      5 #[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
      6 use std::env;
      7 #[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
      8 use std::fs;
      9 use std::{
     10     io::{Error, ErrorKind},
     11     path::Path,
     12 };
     13 /// Used instead of `()` for the parameter
     14 /// in the `pledge` functions. This allows
     15 /// one to avoid having to disable certain lints.
     16 #[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
     17 #[derive(Clone, Copy)]
     18 pub struct Zst;
     19 /// Calls `pledge` with only the sys calls necessary for a minimal application
     20 /// to run. Specifically, the `Promise`s `Cpath`, `Dns`, `Inet`, `Rpath`, `Stdio`, `Unveil`, and `Wpath`
     21 /// are passed.
     22 #[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
     23 pub fn pledge_init() -> Result<Promises, Error> {
     24     let promises = Promises::new([
     25         Promise::Cpath,
     26         Promise::Dns,
     27         Promise::Inet,
     28         Promise::Rpath,
     29         Promise::Stdio,
     30         Promise::Unveil,
     31         Promise::Wpath,
     32     ]);
     33     match promises.pledge() {
     34         Ok(()) => Ok(promises),
     35         Err(e) => Err(e),
     36     }
     37 }
     38 /// No-op that always returns `Ok`.
     39 #[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")]
     40 #[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
     41 pub const fn pledge_init() -> Result<Zst, !> {
     42     Ok(Zst)
     43 }
     44 /// Removes `Cpath` and `Wpath` `Promise`s.
     45 ///
     46 /// This should only be called when `stdout` is written to
     47 /// instead of an RPZ file.
     48 #[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
     49 pub fn pledge_away_create_write(promises: &mut Promises) -> Result<(), Error> {
     50     promises.remove_promises_then_pledge([Promise::Cpath, Promise::Wpath])
     51 }
     52 /// No-op that always returns `Ok`.
     53 #[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")]
     54 #[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
     55 pub fn pledge_away_create_write(_: &mut Zst) -> Result<(), !> {
     56     Ok(())
     57 }
     58 /// Removes `Promise::Unveil`.
     59 #[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
     60 pub fn pledge_away_unveil(promises: &mut Promises) -> Result<(), Error> {
     61     promises.remove_then_pledge(Promise::Unveil)
     62 }
     63 /// No-op that always returns `Ok`.
     64 #[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")]
     65 #[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
     66 pub fn pledge_away_unveil(_: &mut Zst) -> Result<(), !> {
     67     Ok(())
     68 }
     69 /// Removes `Promise::Dns` and `Promise::Inet`.
     70 #[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
     71 pub fn pledge_away_net(promises: &mut Promises) -> Result<(), Error> {
     72     promises.remove_promises_then_pledge([Promise::Dns, Promise::Inet])
     73 }
     74 /// No-op that always returns `Ok`.
     75 #[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")]
     76 #[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
     77 pub fn pledge_away_net(_: &mut Zst) -> Result<(), !> {
     78     Ok(())
     79 }
     80 /// Removes all `Promise`s except `Stdio`.
     81 #[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
     82 pub fn pledge_away_all_but_stdio(promises: &mut Promises) -> Result<(), Error> {
     83     promises.retain_then_pledge([Promise::Stdio])
     84 }
     85 /// No-op that always returns `Ok`.
     86 #[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")]
     87 #[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
     88 pub fn pledge_away_all_but_stdio(_: &mut Zst) -> Result<(), !> {
     89     Ok(())
     90 }
     91 /// Calls `unveil`_on `path` with no `Permissions`.
     92 #[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
     93 pub fn unveil_none<P: AsRef<Path>>(path: P) -> Result<(), UnveilErr> {
     94     Permissions::NONE.unveil(path)
     95 }
     96 /// No-op that always returns `Ok`.
     97 #[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
     98 #[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")]
     99 pub fn unveil_none<P: AsRef<Path>>(_: P) -> Result<(), !> {
    100     Ok(())
    101 }
    102 /// Calls `unveil_none` on `/`.
    103 #[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
    104 pub fn veil_all() -> Result<(), UnveilErr> {
    105     unveil_none("/")
    106 }
    107 /// No-op that always returns `Ok`.
    108 #[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
    109 #[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")]
    110 pub const fn veil_all() -> Result<(), !> {
    111     Ok(())
    112 }
    113 /// Calls `unveil`_on `path` with `Permissions::CREATE`.
    114 #[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
    115 pub fn unveil_create<P: AsRef<Path>>(path: P) -> Result<(), UnveilErr> {
    116     Permissions::CREATE.unveil(path)
    117 }
    118 /// No-op that always returns `Ok`.
    119 #[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
    120 #[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")]
    121 pub fn unveil_create<P: AsRef<Path>>(_: P) -> Result<(), !> {
    122     Ok(())
    123 }
    124 /// Calls `unveil`_on `path` with `Permissions::READ` and returns
    125 /// `true` iff `path` exists.
    126 #[expect(clippy::wildcard_enum_match_arm)]
    127 #[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
    128 pub fn unveil_read_dir<P: AsRef<Path>>(path: P) -> Result<bool, UnveilErr> {
    129     Permissions::READ.unveil(path).map_or_else(
    130         |err| match err {
    131             UnveilErr::Io(ref err2) => match err2.kind() {
    132                 ErrorKind::NotFound => Ok(false),
    133                 _ => Err(err),
    134             },
    135             UnveilErr::Nul(_) => Err(err),
    136         },
    137         |()| Ok(true),
    138     )
    139 }
    140 /// Returns `true` iff `path` exists
    141 #[expect(
    142     clippy::wildcard_enum_match_arm,
    143     reason = "too many branches to write out manually"
    144 )]
    145 #[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
    146 pub fn unveil_read_dir<P: AsRef<Path>>(path: P) -> Result<bool, Error> {
    147     fs::metadata(path).map_or_else(
    148         |err| match err.kind() {
    149             ErrorKind::NotFound => Ok(false),
    150             _ => Err(err),
    151         },
    152         |dir| Ok(dir.is_dir()),
    153     )
    154 }
    155 /// Calls `unveil`_on `path` with `Permissions::READ`.
    156 #[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
    157 pub fn unveil_read_file<P: AsRef<Path>>(path: P) -> Result<(), UnveilErr> {
    158     Permissions::READ.unveil(path)
    159 }
    160 /// No-op that always returns `Ok`.
    161 #[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
    162 #[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")]
    163 pub fn unveil_read_file<P: AsRef<Path>>(_: P) -> Result<(), !> {
    164     Ok(())
    165 }
    166 /// Calls `unveil` on all files necessary for HTTP(S) with `Permissions::READ`
    167 /// in addition to setting the `SSL_CERT_FILE` variable to `/etc/ssl/cert.pem`.
    168 /// We rely on `SSL_CERT_FILE` as that allows one to `unveil(2)` only `/etc/ssl/cert.pem`
    169 /// instead of `/etc/ssl/`.
    170 #[expect(unsafe_code)]
    171 #[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
    172 pub fn unveil_https() -> Result<(), UnveilErr> {
    173     /// The path to the root certificate store.
    174     const CERTS: &str = "/etc/ssl/cert.pem";
    175     unveil_read_file(CERTS).map(|()| {
    176         // SAFETY:
    177         // `unveil_https` is only called in `super::main`
    178         // in a single-threaded context; thus this is OK.
    179         unsafe { env::set_var("SSL_CERT_FILE", CERTS) }
    180     })
    181 }
    182 /// No-op that always returns `Ok`.
    183 #[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
    184 #[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")]
    185 pub const fn unveil_https() -> Result<(), !> {
    186     Ok(())
    187 }
    188 /// Calls `unveil`_on `path` with create, read, and write `Permissions`.
    189 #[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
    190 pub fn unveil_create_read_write<P: AsRef<Path>>(path: P) -> Result<(), UnveilErr> {
    191     let mut perms = Permissions::ALL;
    192     perms.disable(Permission::Execute);
    193     perms.unveil(path)
    194 }
    195 /// No-op that always returns `Ok`.
    196 #[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
    197 #[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")]
    198 pub fn unveil_create_read_write<P: AsRef<Path>>(_: P) -> Result<(), !> {
    199     Ok(())
    200 }