diff options
| author | murilo ijanc | 2026-03-25 23:23:10 -0300 |
|---|---|---|
| committer | murilo ijanc | 2026-03-25 23:23:10 -0300 |
| commit | a96da5f0704c50e8a4e4f047dcd3fb7c73fdf600 (patch) | |
| tree | f98bb20411dbc6a49e8c286b054a88d89eea795f /src/sandbox.rs | |
| download | tesseras-url-main.tar.gz | |
Decentralized URL shortener built on tesseras-dht. Includes:
- tud daemon: DHT node, Unix socket API, HTTP 302 redirect server
- tu CLI: shorten, resolve, del, list, status commands
- Auto-generated slugs (8-byte SHA256, base58) or custom slugs
- TTL support (default: forever)
- Automatic re-join of bootstrap nodes when routing table is empty
- OpenBSD pledge(2) and unveil(2) sandboxing
- DNS SRV bootstrap discovery
- Verbose mode (-v) for both binaries
Diffstat (limited to 'src/sandbox.rs')
| -rw-r--r-- | src/sandbox.rs | 113 |
1 files changed, 113 insertions, 0 deletions
diff --git a/src/sandbox.rs b/src/sandbox.rs new file mode 100644 index 0000000..43ce4d2 --- /dev/null +++ b/src/sandbox.rs @@ -0,0 +1,113 @@ +//! OpenBSD pledge(2) and unveil(2) wrappers. + +use std::ffi::{CString, c_char}; +use std::path::Path; + +unsafe extern "C" { + fn pledge(promises: *const c_char, execpromises: *const c_char) -> i32; + fn unveil(path: *const c_char, permissions: *const c_char) -> i32; +} + +/// Valid pledge promises on OpenBSD. +/// See `pledgereq[]` in `/usr/src/sys/kern/kern_pledge.c`. +const VALID_PROMISES: &[&str] = &[ + "audio", + "bpf", + "chown", + "cpath", + "disklabel", + "dns", + "dpath", + "drm", + "error", + "exec", + "fattr", + "flock", + "getpw", + "id", + "inet", + "mcast", + "pf", + "proc", + "prot_exec", + "ps", + "recvfd", + "route", + "rpath", + "sendfd", + "settime", + "stdio", + "tape", + "tmppath", + "tty", + "unix", + "unveil", + "video", + "vminfo", + "vmm", + "wpath", + "wroute", +]; + +/// Valid unveil permission characters. +const VALID_PERMS: &[u8] = b"rwcx"; + +/// Restrict the process to the given pledge promises. +pub fn do_pledge(promises: &str) { + for word in promises.split_whitespace() { + if !VALID_PROMISES.contains(&word) { + log::error!("pledge: unknown promise: {word}"); + std::process::exit(1); + } + } + let c = CString::new(promises).unwrap_or_else(|_| { + log::error!("pledge: promises contain NUL byte"); + std::process::exit(1); + }); + let ret = unsafe { pledge(c.as_ptr(), std::ptr::null()) }; + if ret != 0 { + let err = std::io::Error::last_os_error(); + log::error!("pledge failed: {err}"); + std::process::exit(1); + } + log::debug!("pledge applied"); +} + +/// Add a path to the unveil whitelist with the given permissions. +/// Permissions: "r" read, "w" write, "c" create, "x" execute. +pub fn do_unveil(path: &Path, perms: &str) { + if perms.is_empty() + || !perms.as_bytes().iter().all(|b| VALID_PERMS.contains(b)) + { + log::error!("unveil: invalid permissions"); + std::process::exit(1); + } + let p = CString::new(path.as_os_str().as_encoded_bytes()).unwrap_or_else( + |_| { + log::error!("unveil: path contains NUL byte"); + std::process::exit(1); + }, + ); + let f = CString::new(perms).unwrap_or_else(|_| { + log::error!("unveil: permissions contain NUL byte"); + std::process::exit(1); + }); + let ret = unsafe { unveil(p.as_ptr(), f.as_ptr()) }; + if ret != 0 { + let err = std::io::Error::last_os_error(); + log::error!("unveil failed: {err}"); + std::process::exit(1); + } + log::debug!("unveil: path added"); +} + +/// Lock the unveil list — no further unveil calls allowed. +pub fn unveil_lock() { + let ret = unsafe { unveil(std::ptr::null(), std::ptr::null()) }; + if ret != 0 { + let err = std::io::Error::last_os_error(); + log::error!("unveil lock failed: {err}"); + std::process::exit(1); + } + log::debug!("unveil locked"); +} |