syd 3.58.0

rock-solid application kernel
Documentation
//
// Syd: rock-solid application kernel
// src/utils/syd-ring.rs: Run a program under io_uring(7) restrictions
//
// Copyright (c) 2026 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::{os::unix::ffi::OsStrExt, process::ExitCode, str::from_utf8};

use nix::errno::Errno;
use syd::{
    config::HAVE_URING_RESTRICTIONS_TASK,
    confine::run_syd_shell,
    eprintfln,
    err::err2exit,
    printfln,
    uring::{UringFilter, URING_FLAGS, URING_OPS},
};

// Set global allocator to GrapheneOS allocator.
#[cfg(all(
    not(target_os = "android"),
    not(target_arch = "loongarch64"),
    not(target_arch = "riscv64"),
    target_page_size_4k,
    target_pointer_width = "64"
))]
#[global_allocator]
static GLOBAL: hardened_malloc::HardenedMalloc = hardened_malloc::HardenedMalloc;

syd::main! {
    use lexopt::prelude::*;

    syd::set_sigpipe_dfl()?;

    // Parse CLI options.
    //
    // Note, option parsing is POSIXly correct:
    // POSIX recommends that no more options are parsed after the first
    // positional argument. The other arguments are then all treated as
    // positional arguments.
    // See: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02
    let mut opt_check = false;
    let mut opt_verbose = false;
    let mut opt_cmd = None;
    let mut opt_arg = Vec::new();
    let mut filter = UringFilter::empty();

    let mut parser = lexopt::Parser::from_env();
    while let Some(arg) = parser.next()? {
        match arg {
            Short('h') => {
                help()?;
                return Ok(ExitCode::SUCCESS);
            }
            Short('v') => opt_verbose = true,
            Short('V') => opt_check = true,
            Short('o') => {
                let val = parser.value()?;
                let ops = from_utf8(val.as_bytes())?;

                if ops == "list" {
                    for op in URING_OPS {
                        printfln!("{op}")?;
                    }
                    return Ok(ExitCode::SUCCESS);
                }

                filter.allow_ops(ops).inspect_err(|errno| {
                    let _ = eprintfln!("Failed to allow io_uring(7) operations `{ops}': {errno}!");
                })?;
            }
            Short('f') => {
                let val = parser.value()?;
                let flags = from_utf8(val.as_bytes())?;

                if flags == "list" {
                    for flag in URING_FLAGS {
                        printfln!("{flag}")?;
                    }
                    return Ok(ExitCode::SUCCESS);
                }

                filter.allow_flags(flags).inspect_err(|errno| {
                    let _ = eprintfln!("Failed to allow io_uring(7) flags `{flags}': {errno}!");
                })?;
            }
            Value(prog) => {
                opt_cmd = Some(prog);
                opt_arg.extend(parser.raw_args()?);
            }
            _ => return Err(arg.unexpected().into()),
        }
    }

    if opt_check {
        if *HAVE_URING_RESTRICTIONS_TASK {
            let _ = printfln!("io_uring(7) task-level restrictions are supported.");
            return Ok(ExitCode::SUCCESS);
        }
        let _ = printfln!("io_uring(7) task-level restrictions are not supported.");
        return Ok(ExitCode::from(127));
    }

    macro_rules! vprintln {
        ($($arg:tt)*) => {
            if opt_verbose {
                let _ = eprintfln!($($arg)*);
            }
        };
    }

    // Use default filter if filter is empty.
    if filter.is_empty() {
        filter = UringFilter::default();
    }

    match filter.register() {
        Ok(()) => {
            vprintln!("syd-ring: io_uring(7) task-level restrictions installed.");
        }
        Err(errno) => {
            let _ = eprintfln!("syd-ring: Failed to install io_uring(7) restrictions: {errno}!");
            return Err(errno.into());
        }
    }

    // Execute command, Syd shell by default.
    Ok(err2exit(run_syd_shell(opt_cmd, opt_arg)))
}

fn help() -> Result<(), Errno> {
    printfln!(
        "Usage: syd-ring [-hvV] [-o op[,op...]]... [-f flag[,flag...]]... {{command [args...]}}"
    )?;
    printfln!("Run a program under io_uring(7) task-level restrictions.")?;
    printfln!("Use -v to increase verbosity.")?;
    printfln!("Use -V to check for io_uring(7) task-level restriction support.")?;
    printfln!("Use -o op[,op...] to allow submission queue operations.")?;
    printfln!("Use -f flag[,flag...] to allow submission queue entry flags.")?;
    printfln!("Use `list' with -o or -f to list operations and flags.")?;
    printfln!(
        "With no -o/-f options, defaults to operations epoll_ctl, read, readv, write, writev and flag async."
    )?;
    printfln!("Passing any -o or -f option builds the allowlist from an empty set instead.")?;
    printfln!("Refer to \"Sandboxing\" and \"Uring Sandboxing\" sections of syd(7) manual page.")?;
    Ok(())
}