rpz

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

priv_sep.rs (8595B)


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