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 }