tesseras-paste 0.1.3

Decentralized pastebin built on tesseras-dht
//! 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");
}