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 }