#![forbid(clippy::as_ptr_cast_mut)]
#![forbid(clippy::cast_ptr_alignment)]
#![deny(missing_docs)]
#![deny(clippy::arithmetic_side_effects)]
#![deny(clippy::as_underscore)]
#![deny(clippy::assertions_on_result_states)]
#![deny(clippy::borrow_as_ptr)]
#![deny(clippy::branches_sharing_code)]
#![deny(clippy::case_sensitive_file_extension_comparisons)]
#![deny(clippy::cast_lossless)]
#![deny(clippy::cast_possible_truncation)]
#![deny(clippy::cast_possible_wrap)]
#![deny(clippy::cast_precision_loss)]
#![deny(clippy::cast_sign_loss)]
#![deny(clippy::checked_conversions)]
#![deny(clippy::clear_with_drain)]
#![deny(clippy::clone_on_ref_ptr)]
#![deny(clippy::cloned_instead_of_copied)]
#![deny(clippy::cognitive_complexity)]
#![deny(clippy::collection_is_never_read)]
#![deny(clippy::copy_iterator)]
#![deny(clippy::create_dir)]
#![deny(clippy::dbg_macro)]
#![deny(clippy::debug_assert_with_mut_call)]
#![deny(clippy::decimal_literal_representation)]
#![deny(clippy::default_trait_access)]
#![deny(clippy::default_union_representation)]
#![deny(clippy::derive_partial_eq_without_eq)]
#![deny(clippy::doc_link_with_quotes)]
#![deny(clippy::doc_markdown)]
#![deny(clippy::explicit_into_iter_loop)]
#![deny(clippy::explicit_iter_loop)]
#![deny(clippy::fallible_impl_from)]
#![deny(clippy::missing_safety_doc)]
#![deny(clippy::undocumented_unsafe_blocks)]
use std::{
env,
env::VarError,
ffi::OsString,
fs::OpenOptions,
io::{stdin, stdout, BufWriter, Write},
os::{
fd::{AsFd, AsRawFd, BorrowedFd, IntoRawFd},
unix::{ffi::OsStrExt, fs::OpenOptionsExt},
},
process::{exit, ExitCode},
str::FromStr,
};
use data_encoding::HEXLOWER;
use digest::Digest;
use libseccomp::{scmp_cmp, ScmpAction, ScmpFilterContext, ScmpSyscall};
use memchr::arch::all::is_equal;
use nix::{
errno::Errno,
fcntl::OFlag,
sched::{unshare, CloneFlags},
sys::{
resource::Resource,
wait::{Id, WaitPidFlag},
},
unistd::{getgid, getpid, getuid, isatty},
};
use syd::{
bins::{pty::pty_bin_main, tor::tor_bin_main},
caps,
compat::{set_name, set_no_new_privs, waitid, WaitStatus},
config::*,
confine::{
chdir_void, confine_rlimit_zero, confine_scmp_madvise, confine_scmp_wx_syd, secure_getenv,
ExportMode,
},
err::err2no,
error,
fd::{closeexcept, fdclone},
fs::{format_clone_flags, format_clone_names},
hash::{get_at_random_hex, SafeHash},
hook::Supervisor,
ignore_signals, info,
landlock_policy::LandlockPolicy,
log::log_init,
namespace::{
ns_setup_net, ns_setup_pid, ns_setup_time, ns_setup_tor, ns_setup_user, ns_setup_uts,
},
path::XPathBuf,
proc::proc_open,
pty::pty_setup,
rng::duprand,
sandbox::Sandbox,
seal::ensure_sealed,
set_sigpipe_dfl, syd_code_name, syd_info,
syslog::LogLevel,
IgnoreSignalOpts,
};
#[cfg(all(
not(coverage),
not(feature = "prof"),
not(target_os = "android"),
not(target_arch = "riscv64"),
target_page_size_4k,
target_pointer_width = "64"
))]
#[global_allocator]
static GLOBAL: hardened_malloc::HardenedMalloc = hardened_malloc::HardenedMalloc;
#[cfg(feature = "prof")]
#[global_allocator]
static GLOBAL: tcmalloc::TCMalloc = tcmalloc::TCMalloc;
syd::main! {
use lexopt::prelude::*;
let name = env::args_os().next();
if let Some(name) = name {
let name = name.as_bytes();
if is_equal(name, b"syd-pty") {
return Ok(pty_bin_main());
} else if is_equal(name, b"syd-tor") {
return Ok(tor_bin_main());
}
}
log_init(LogLevel::Warn, Some(libc::STDERR_FILENO))?;
let _ = set_name(c"syd");
env::remove_var("SYD_PID_PTY");
env::remove_var("SYD_PID_TOR");
let mut parser = lexopt::Parser::from_env();
let is_login = parser
.bin_name()
.map(|name| name.starts_with('-'))
.unwrap_or(false);
let mut is_quick = env::var_os(ENV_QUICK_BOOT).is_some();
if !is_login {
if let Some(raw) = parser.try_raw_args() {
if let Some(Some(arg)) = raw.peek().map(|arg| arg.to_str()) {
match arg {
"-h" | "--help" => {
set_sigpipe_dfl()?;
help();
return Ok(ExitCode::SUCCESS);
}
"-C" | "--check" => {
set_sigpipe_dfl()?;
syd_info(true)?;
return Ok(ExitCode::SUCCESS);
}
"-V" | "--version" => {
set_sigpipe_dfl()?;
syd_info(false)?;
return Ok(ExitCode::SUCCESS);
}
"--el" => {
set_sigpipe_dfl()?;
stdout().write_all(SYD_EL.as_bytes())?;
return Ok(ExitCode::SUCCESS);
}
"--sh" => {
set_sigpipe_dfl()?;
stdout().write_all(ESYD_SH.as_bytes())?;
return Ok(ExitCode::SUCCESS);
}
"--api" => {
set_sigpipe_dfl()?;
#[expect(clippy::disallowed_methods)]
let api = serde_json::to_string_pretty(&*syd::api::API_SPEC).expect("JSON");
stdout().write_all(api.as_bytes())?;
return Ok(ExitCode::SUCCESS);
}
"-q" => is_quick = true,
_ => {}
}
}
}
}
set_no_new_privs()?;
#[expect(clippy::disallowed_methods)]
let cookie = if !is_quick {
match env::var(ENV_RAND) {
Ok(cookie0) => {
assert_eq!(cookie0.len(), 32,
"PANIC: Internal environment variable {ENV_RAND} tampered by user!");
assert!(cookie0.bytes().all(|b| b.is_ascii_hexdigit() && !b.is_ascii_uppercase()),
"PANIC: Internal environment variable {ENV_RAND} tampered by user!");
let cookie1 = get_at_random_hex(false);
env::set_var(ENV_RAND, format!("{cookie0}{cookie1}"));
info!("ctx": "set_random_cookie",
"cookie": [&cookie0, &cookie1], "src": "AT_RANDOM",
"msg": format!("appended random cookie from AT_RANDOM {cookie0}+{cookie1}={cookie0}{cookie1} after memfd-reexec"));
}
Err(VarError::NotPresent) => {
let cookie = get_at_random_hex(false);
env::set_var(ENV_RAND, &cookie);
info!("ctx": "set_random_cookie",
"cookie": &cookie, "src": "AT_RANDOM",
"msg": format!("set random cookie from AT_RANDOM to {cookie}"));
}
Err(VarError::NotUnicode(cookie)) => {
error!("ctx": "set_random_cookie",
"cookie": &cookie, "src": "AT_RANDOM", "err": libc::EINVAL,
"msg": format!("get random cookie from {ENV_RAND} failed: {}", Errno::EINVAL));
}
}
match ensure_sealed() {
Ok(()) => env::var(ENV_RAND).unwrap(),
Err(errno) => {
error!("ctx": "memfd_reexec",
"err": errno as i32,
"msg": format!("reexecute self with a sealed memfd failed: {errno}"),
"tip": "set SYD_QUICK_BOOT and/or submit a bug report");
return Err(errno.into());
}
}
} else {
match env::var_os("RUST_BACKTRACE") {
Some(val) => env::set_var("SYD_RUST_BACKTRACE", val),
None => env::remove_var("SYD_RUST_BACKTRACE"),
};
if secure_getenv(ENV_SKIP_SCMP).is_none() {
env::set_var("RUST_BACKTRACE", "0");
}
env::set_var(ENV_RAND, get_at_random_hex(false));
env::var(ENV_RAND).unwrap()
};
if let Some(sandbox_id) = env::var_os(ENV_ID) {
assert_eq!(sandbox_id.len(), 64,
"PANIC: Sandbox ID in SYD_ID environment variable isn't in correct format!");
assert!(sandbox_id.as_bytes().iter().all(|b| b.is_ascii_hexdigit() && !b.is_ascii_uppercase()),
"PANIC: Sandbox ID in SYD_ID environment variable isn't in correct format!");
let machine_id = &sandbox_id.as_bytes()[..32];
assert!(machine_id.iter().any(|&b| b != b'0'),
"PANIC: Sandbox ID in SYD_ID environment variable isn't in correct format!");
} else {
let digest = <SafeHash as Digest>::digest(cookie.as_bytes());
let sandbox_id = HEXLOWER.encode(digest.as_slice());
let backend = SafeHash::backend();
env::set_var(ENV_ID, &sandbox_id);
info!("ctx": "set_sandbox_id",
"id": &sandbox_id, "cookie": &cookie, "hash": backend,
"msg": format!("generated Syd id:{sandbox_id} from cookie:{cookie} using {backend}"));
}
if let Some(pid_fn) = env::var_os(ENV_PID_FN).map(XPathBuf::from) {
let pid = getpid().as_raw();
let mut pid_str = itoa::Buffer::new();
let pid_str = pid_str.format(pid);
let mut openopts = OpenOptions::new();
openopts
.mode(0o400)
.write(true)
.create_new(true);
#[expect(clippy::disallowed_methods)]
let mut pid_file = match openopts.open(&pid_fn).map(BufWriter::new) {
Ok(pid_file) => pid_file,
Err(error) => {
let errno = err2no(&error);
error!("ctx": "write_pid_file",
"pid_file": &pid_fn, "err": errno as i32,
"msg": format!("pid file create error: {error}"),
"tip": format!("remove file `{pid_fn}' or unset SYD_PID_FN"));
return Err(error.into());
}
};
match pid_file.write_all(pid_str.as_bytes()) {
Ok(_) => {
info!("ctx": "write_pid_file",
"msg": format!("Syd pid {pid} written to file `{pid_fn}'"),
"pid_file": &pid_fn);
}
Err(error) => {
let errno = err2no(&error);
error!("ctx": "write_pid_file",
"pid_fn": &pid_fn, "err": errno as i32,
"msg": format!("pid file write error: {error}"),
"tip": format!("remove file `{pid_fn}' or unset SYD_PID_FN"));
return Err(error.into());
}
}
}
let mut export: Option<ExportMode> = ExportMode::from_env();
let mut sandbox: Sandbox = Sandbox::default();
let mut cmd_arg0: Option<OsString> = None;
let mut cmd_argv: Vec<OsString> = vec![];
#[expect(clippy::disallowed_methods)]
match env::var(ENV_PROXY_HOST) {
Ok(host) => sandbox
.config(&format!("proxy/ext/host:{host}"))
.expect(ENV_PROXY_HOST),
Err(env::VarError::NotPresent) => {}
Err(error) => panic!("Invalid UTF-8 in {ENV_PROXY_HOST}: {error}"),
};
#[expect(clippy::disallowed_methods)]
match env::var(ENV_PROXY_PORT) {
Ok(port) => sandbox
.config(&format!("proxy/ext/port:{port}"))
.expect(ENV_PROXY_PORT),
Err(env::VarError::NotPresent) => {}
Err(error) => panic!("Invalid UTF-8 in {ENV_PROXY_PORT}: {error}"),
};
#[expect(clippy::disallowed_methods)]
match env::var(ENV_PROXY_UNIX) {
Ok(unix) => sandbox
.config(&format!("proxy/ext/unix:{unix}"))
.expect(ENV_PROXY_UNIX),
Err(env::VarError::NotPresent) => {}
Err(error) => panic!("Invalid UTF-8 in {ENV_PROXY_UNIX}: {error}"),
};
let mut user_parse = false;
let user_done = if is_login
|| parser
.try_raw_args()
.map(|raw| raw.peek().is_none())
.unwrap_or(true)
{
sandbox.parse_profile(b"user")?;
true
} else {
false
};
let mut is_rbash_def = false;
#[expect(clippy::disallowed_methods)]
let sh: Vec<_> = match env::var(ENV_SH) {
Ok(val) => shell_words::split(&val),
Err(VarError::NotPresent) => {
is_rbash_def = true;
shell_words::split(SYD_SH)
}
Err(error) => {
error!("ctx": "parse_shell", "op": "get_environment",
"msg": format!("detected invalid unicode in {ENV_SH}: {error}"),
"tip": format!("unset {ENV_SH} environment variable"));
return Err(error.into());
}
}?.into_iter().map(OsString::from).collect();
if sh.is_empty() {
error!("ctx": "parse_shell", "op": "split_shell",
"msg": format!("detected empty {ENV_SH}"),
"tip": format!("unset {ENV_SH} environment variable"));
return Err(shell_words::ParseError.into());
}
while let Some(arg) = parser.next()? {
match arg {
Short('h') | Long("help") => {
set_sigpipe_dfl()?;
help();
return Ok(ExitCode::SUCCESS);
}
Short('C') | Long("check") => {
set_sigpipe_dfl()?;
syd_info(true)?;
return Ok(ExitCode::SUCCESS);
}
Short('V') | Long("version") => {
set_sigpipe_dfl()?;
syd_info(false)?;
return Ok(ExitCode::SUCCESS);
}
Short('v') | Long("verbose") => sandbox.increase_verbosity(),
Long("el") => {
set_sigpipe_dfl()?;
stdout().write_all(SYD_EL.as_bytes())?;
return Ok(ExitCode::SUCCESS);
}
Long("sh") => {
set_sigpipe_dfl()?;
stdout().write_all(ESYD_SH.as_bytes())?;
return Ok(ExitCode::SUCCESS);
}
Long("api") => {
set_sigpipe_dfl()?;
#[expect(clippy::disallowed_methods)]
let api = serde_json::to_string_pretty(&*syd::api::API_SPEC).expect("JSON");
stdout().write_all(api.as_bytes())?;
return Ok(ExitCode::SUCCESS);
}
Short('q') => {}
Short('E') => {
export = Some(
parser
.value()?
.parse::<String>()
.map(|arg| ExportMode::from_str(&arg))??,
);
}
Short('m') => {
let cmd = parser.value().map(XPathBuf::from)?;
if sandbox.is_locked() {
eprintln!("Failed to execute magic command `{cmd}': sandbox locked!");
return Err(Errno::EPERM.into());
} else {
sandbox.config(&cmd.to_string())?;
}
}
Short('t') => {
let tmout = parser.value()
.ok()
.and_then(|ostr| ostr.into_string().ok())
.ok_or(Errno::EINVAL)?;
if sandbox.is_locked() {
eprintln!("Failed to set sandbox timeout: sandbox locked!");
return Err(Errno::EPERM.into());
} else {
sandbox.config(&format!("timeout:{tmout}"))?;
}
}
Short('x') => sandbox.parse_profile(b"trace")?,
Short('f') => {
user_parse = true;
}
Short('l') | Long("login") => {
user_parse = true;
}
Short('c') => {
user_parse = true;
if cmd_argv.is_empty() {
cmd_argv.extend(sh.clone());
cmd_argv.push(OsString::from("-c"));
cmd_argv.push(parser.value()?);
}
}
Short('P') => {
let path = parser.value().map(XPathBuf::from)?;
if sandbox.is_locked() {
eprintln!("Failed to parse config file `{path}': sandbox locked!");
return Err(Errno::EPERM.into());
}
sandbox.parse_config_file(&path)?;
}
Short('p') | Long("profile") => {
let profile = parser.value()?.parse::<String>()?;
if sandbox.is_locked() {
eprintln!("Failed to parse profile `{profile}': sandbox locked!");
return Err(Errno::EPERM.into());
}
sandbox.parse_profile(profile.as_bytes())?;
}
Short('a') => cmd_arg0 = Some(parser.value()?),
Short('e') => {
let value = parser.value()?.parse::<String>()?;
match value.split_once('=') {
Some((var, val)) => {
sandbox.env_add_pass(var)?;
if !val.is_empty() {
env::set_var(var, val);
}
}
None => {
sandbox.env_del_pass(&value)?;
env::remove_var(value);
}
}
}
#[cfg(feature = "prof")]
Long("prof") => match parser.value()?.parse::<String>()?.as_str() {
"cpu" => env::set_var("SYD_PROF", "cpu"),
"mem" => env::set_var("SYD_PROF", "mem"),
val => {
eprintln!("Invalid profile mode `{val}'!");
eprintln!("Expected exactly one of `cpu' or `mem'!");
help();
return Ok(ExitCode::FAILURE);
}
},
Value(prog) => {
cmd_argv.push(prog);
cmd_argv.extend(parser.raw_args()?);
}
_ => return Err(arg.unexpected().into()),
}
}
if let Some(export_mode) = export {
match export_mode {
ExportMode::BerkeleyPacketFilter => env::set_var(ENV_DUMP_SCMP, "bpf"),
ExportMode::PseudoFiltercode => env::set_var(ENV_DUMP_SCMP, "pfc"),
}
} else {
env::remove_var(ENV_DUMP_SCMP);
}
if user_parse && !user_done && !sandbox.is_locked() {
sandbox.parse_profile(b"user")?;
}
let mut is_rbash = env::var_os(ENV_CD).is_some();
if cmd_argv.is_empty() {
cmd_argv = sh;
if cmd_arg0.is_none() {
cmd_arg0 = Some(OsString::from("-"));
}
if is_rbash_def && export.is_none() {
is_rbash = true;
}
}
let argv0 = cmd_argv.remove(0);
let mut opts = IgnoreSignalOpts::SkipIgnoreAlarm;
if sandbox.options.allow_unsafe_prlimit() {
opts.insert(IgnoreSignalOpts::SkipIgnoreCoreDump);
}
ignore_signals(opts).inspect_err(|errno| {
error!("ctx": "ignore_signals",
"opt": opts, "err": *errno as i32,
"msg": format!("ignore signals failed: {errno}"),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
})?;
info!("ctx": "ignore_signals",
"opt": opts, "msg": "ignored all signals for signal safety");
if sandbox.options.unshare_pid() {
sandbox.set_unshare_mount(true);
}
if sandbox.options.unshare_mount() {
sandbox.set_unshare_pid(true);
}
let has_ns_user = sandbox.options.unshare_user();
let has_pid_max = sandbox.options.unshare_pid() && sandbox.has_pid() && sandbox.pid_max > 0;
let has_ns_time = sandbox.options.unshare_time();
let (uid, gid) = if has_ns_user {
(Some(getuid()), Some(getgid()))
} else {
(None, None)
};
let fd_proc = if is_rbash || has_ns_user || has_pid_max || has_ns_time {
let fd = proc_open(None).inspect_err(|errno| {
error!("ctx": "setup_namespaces", "op": "open_proc",
"err": *errno as i32,
"msg": format!("open proc(5) filesystem failed: {errno}"),
"tip": "mount proc(5) on top of /proc directory");
})?;
Some(fd)
} else {
None
};
if is_rbash {
#[expect(clippy::disallowed_methods)]
let fd_proc = fd_proc.as_ref().unwrap();
chdir_void(Some(fd_proc)).inspect_err(|errno| {
error!("ctx": "setup_restricted_shell", "op": "chdir_proc",
"err": *errno as i32,
"msg": format!("change dir to proc_pid_fdinfo(5) failed: {errno}"),
"tip": "mount proc(5) on top of /proc directory");
})?;
}
let pty_child = if sandbox.has_pty()
&& isatty(stdin()).unwrap_or(false)
&& isatty(stdout()).unwrap_or(false)
{
let pty_debug = secure_getenv("SYD_PTY_DEBUG").is_some();
let pty_child = pty_setup(sandbox.pty_ws_x(), sandbox.pty_ws_y(), pty_debug)?;
let mut buf = itoa::Buffer::new();
env::set_var(ENV_PTY_FD, buf.format(pty_child.as_raw_fd()));
Some(pty_child)
} else {
env::remove_var(ENV_PTY_FD);
None
};
let proxy_debug = secure_getenv("SYD_TOR_DEBUG").is_some();
let proxy = if sandbox.has_proxy() {
sandbox.set_unshare_net(true);
Some(ns_setup_tor(
sandbox.proxy_ext_addr,
sandbox.proxy_ext_port,
sandbox.proxy_ext_unix.as_deref(),
sandbox.proxy_repr().as_str(),
proxy_debug)?)
} else {
None
};
let namespaces = sandbox.options.namespaces();
if namespaces == 0 {
drop(fd_proc);
return match Supervisor::run(
sandbox,
pty_child,
&argv0,
cmd_argv,
cmd_arg0,
) {
Ok(code) => Ok(ExitCode::from(code)),
Err(error) => {
let errno = error.errno().unwrap_or(Errno::ENOSYS);
error!("ctx": "run", "op": "run_supervisor",
"msg": format!("failed to run supervisor: {error:?}"),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
Ok(ExitCode::from(u8::try_from(errno as i32).unwrap_or(127)))
}
};
}
caps::securebits::set_keepcaps(true)?;
let clone_flags = CloneFlags::from_bits_retain(namespaces);
let clone_names = format_clone_flags(clone_flags);
let clone_types = format_clone_names(&clone_names);
unshare(clone_flags).inspect_err(|errno| {
error!("ctx": "unshare_namespaces",
"ns": &clone_names, "err": *errno as i32,
"msg": format!("unshare into {clone_types} failed: {errno}"),
"tip": "check with SYD_LOG=debug and/or set `unshare/user:1'");
})?;
info!("ctx": "setup_namespaces", "op": "unshare", "ns": &clone_names,
"msg": format!("unshared into {clone_types}"));
drop(clone_names);
drop(clone_types);
#[expect(clippy::disallowed_methods)]
if has_ns_user {
let fd_proc = fd_proc.as_ref().unwrap();
ns_setup_user(fd_proc, uid.unwrap(), gid.unwrap(), sandbox.options.map_root())?;
}
#[expect(clippy::disallowed_methods)]
if has_pid_max {
let fd_proc = fd_proc.as_ref().unwrap();
ns_setup_pid(fd_proc, sandbox.pid_max)?;
}
if has_ns_time {
#[expect(clippy::disallowed_methods)]
let fd_proc = fd_proc.as_ref().unwrap();
ns_setup_time(fd_proc, sandbox.boottime, sandbox.monotime)?;
}
drop(fd_proc);
if sandbox.options.unshare_net() {
ns_setup_net(proxy.as_ref(), sandbox.proxy_addr, sandbox.proxy_port, proxy_debug)?;
}
drop(proxy);
if sandbox.options.unshare_uts() {
ns_setup_uts(sandbox.hostname.as_deref(), sandbox.domainname.as_deref())?;
}
let ssb = sandbox.options.allow_unsafe_exec_speculative();
let (pid_fd, _) = fdclone(
move || {
let code = match Supervisor::run(
sandbox,
pty_child,
&argv0,
cmd_argv,
cmd_arg0,
).map(i32::from) {
Ok(code) => code,
Err(error) => {
let errno = error.errno().unwrap_or(Errno::ENOSYS);
error!("ctx": "run", "op": "run_supervisor",
"msg": format!("failed to run supervisor: {error:?}"),
"tip": "check with SYD_LOG=debug and/or submit a bug report");
errno as i32
}
};
exit(code);
},
CloneFlags::empty(),
Some(libc::SIGCHLD),
)?;
let pid_fd_rand = duprand(pid_fd.as_raw_fd(), OFlag::O_CLOEXEC)?;
drop(pid_fd);
let pid_fd = unsafe { BorrowedFd::borrow_raw(pid_fd_rand.into_raw_fd()) };
#[expect(clippy::cast_sign_loss)]
let mut set = vec![
libc::STDERR_FILENO as libc::c_uint,
pid_fd.as_raw_fd() as libc::c_uint,
];
set.sort_unstable();
closeexcept(&set)?;
drop(set);
confine_rlimit_zero(&[
Resource::RLIMIT_FSIZE,
Resource::RLIMIT_NOFILE,
Resource::RLIMIT_NPROC,
Resource::RLIMIT_LOCKS,
Resource::RLIMIT_MEMLOCK,
Resource::RLIMIT_MSGQUEUE,
])?;
let abi = syd::landlock::ABI::new_current();
let policy = LandlockPolicy {
scoped_abs: true,
scoped_sig: true,
..Default::default()
};
let _ = policy.restrict_self(abi);
let mut ctx = ScmpFilterContext::new(ScmpAction::KillProcess)?;
ctx.set_ctl_nnp(true)?;
ctx.set_ctl_ssb(ssb)?;
ctx.set_ctl_tsync(false)?;
ctx.set_act_badarch(ScmpAction::KillProcess)?;
let _ = ctx.set_ctl_optimize(2);
const ALLOW_SYSCALLS: &[&str] = &[
"exit",
"exit_group",
"waitid",
"brk",
"mremap",
"munmap",
"sigaction",
"sigaltstack",
"sigpending",
"sigprocmask",
"sigsuspend",
"sigreturn",
"rt_sigaction",
"rt_sigpending",
"rt_sigprocmask",
"rt_sigqueueinfo",
"rt_sigreturn",
"rt_sigtimedwait",
"rt_sigtimedwait_time64",
#[cfg(feature = "prof")]
"getpid",
#[cfg(feature = "prof")]
"gettid",
];
for name in ALLOW_SYSCALLS.iter().chain(ALLOC_SYSCALLS).chain(VDSO_SYSCALLS) {
if let Ok(syscall) = ScmpSyscall::from_name(name) {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
confine_scmp_madvise(&mut ctx)?;
if let Ok(syscall) = ScmpSyscall::from_name("write") {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[scmp_cmp!($arg0 == libc::STDERR_FILENO as u64)],
)?;
}
confine_scmp_wx_syd(&mut ctx)?;
ctx.load()?;
loop {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
break match waitid(Id::PIDFd(pid_fd.as_fd()), WaitPidFlag::WEXITED) {
Ok(WaitStatus::Exited(_, code)) =>
{
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
Ok(ExitCode::from(code as u8))
}
Ok(WaitStatus::Signaled(_, signal, _)) => {
Ok(ExitCode::from(128_u8.saturating_add(signal as u8)))
}
Ok(WaitStatus::StillAlive) | Err(Errno::EINTR) => continue,
Ok(_status) => Err(Errno::EINVAL.into()),
Err(errno) => Err(errno.into()),
};
}
}
fn help() {
let mut c_blue = "\x1b[0;1;35;95m";
let mut c_bold = "\x1b[1m";
let mut c_cyan = "\x1b[0;1;36;96m";
let mut c_green = "\x1b[0;1;32;92m";
let mut c_orng = "\x1b[0;1;34;94m";
let mut c_red = "\x1b[0;1;31;91m";
let mut c_res = "\x1b[0m";
let mut c_yll = "\x1b[0;1;33;93m";
if !isatty(std::io::stdout()).unwrap_or(false) {
c_blue = "";
c_bold = "";
c_cyan = "";
c_green = "";
c_orng = "";
c_red = "";
c_res = "";
c_yll = "";
}
println!(
"{c_red}syd{c_res} {c_cyan}{}{c_res} ({c_orng}{}{c_res})",
*syd::config::VERSION,
syd_code_name()
);
println!("{c_yll}Rock solid application kernel{c_res}");
println!("{c_blue}Author:{c_res} {c_yll}Ali Polatel{c_res} <{c_bold}alip@chesswob.org{c_res}>");
println!("{c_blue}License:{c_res} {c_yll}GPL-3.0-only{c_res}");
println!();
println!("{c_green}$ syd [-acefhlmpqxEPV] [--] {{command [arg...]}}{c_res}");
println!(" {c_bold}Run a program under Syd.{c_res}");
println!("{c_green}$ syd --api{c_res}");
println!(" {c_bold}Print syd(2) API specification.{c_res}");
println!("{c_green}$ syd --check{c_res}");
println!(" {c_bold}Print sandboxing support information.{c_res}");
println!("{c_green}$ syd --el{c_res}");
println!(" {c_bold}Output syd.el the Emacs Lisp implementation of syd(2) interface.{c_res}");
println!("{c_green}$ syd --sh{c_res}");
println!(" {c_bold}Output a shell script which defines the esyd helper function.{c_res}");
println!();
print!("{SEE_EMILY_PLAY}");
println!();
println!("{c_orng}Send bug reports to{c_res} {c_bold}https://gitlab.exherbo.org/groups/sydbox/-/issues{c_res}");
println!("{c_orng}Attaching poems encourages consideration tremendously.{c_res}");
}