summaryrefslogtreecommitdiffstats
path: root/src/sandbox.rs
diff options
context:
space:
mode:
authormurilo ijanc2026-03-25 23:23:10 -0300
committermurilo ijanc2026-03-25 23:23:10 -0300
commita96da5f0704c50e8a4e4f047dcd3fb7c73fdf600 (patch)
treef98bb20411dbc6a49e8c286b054a88d89eea795f /src/sandbox.rs
downloadtesseras-url-main.tar.gz
Initial implementation of tesseras-urlHEADmain
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.rs113
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");
+}