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},
};
#[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()?;
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)*);
}
};
}
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());
}
}
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(())
}