rpz

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

priv_sep.rs (8638B)


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