use std::{
env,
ffi::{CStr, OsStr, OsString},
fmt::Display,
fs::{exists, read_to_string, OpenOptions},
io::Write,
os::{
fd::{AsFd, AsRawFd, RawFd},
unix::{fs::OpenOptionsExt, process::ExitStatusExt},
},
path::Path,
process::Command,
str::FromStr,
sync::atomic::Ordering,
};
use btoi::btoi;
#[expect(deprecated)]
use libc::SOCK_PACKET;
use libc::{
c_int, c_long, c_ulong, prctl, EACCES, EFAULT, EINVAL, EOPNOTSUPP, EPERM, RTLD_LOCAL,
RTLD_NOLOAD, RTLD_NOW, SOCK_RAW,
};
use libloading::{os::unix::Library, Error as LibraryError};
use libseccomp::{
scmp_cmp, RawSyscall, ScmpAction, ScmpArch, ScmpArgCompare, ScmpCompareOp, ScmpFilterContext,
ScmpSyscall,
};
use memchr::arch::all::is_equal;
use nix::{
dir::Dir,
errno::Errno,
fcntl::{AtFlags, OFlag},
sched::{unshare, CloneFlags},
sys::{
resource::{rlim_t, setrlimit, Resource},
socket::SockFlag,
stat::Mode,
wait::{Id, WaitPidFlag},
},
unistd::{chdir, chroot, fchdir, gettid, read, write, Gid, Pid, Uid},
};
use procfs_core::process::{MMPermissions, MMapPath, MemoryMap};
use serde::{Serialize, Serializer};
use crate::{
caps,
compat::{
openat2, seccomp_data, seccomp_notif, waitid, AddressFamily, Persona, RenameFlags,
ResolveFlag, SockType, WaitStatus, PIDFD_GET_INFO, SHM_EXEC,
},
config::{
KeyValue, DENY_SETSOCKOPT, EPOLL_SYSCALLS, FADVISE_SYSCALLS, HAVE_AT_EXECVE_CHECK,
HAVE_LANDLOCK_ACCESS_FS_REFER, HAVE_LANDLOCK_SCOPED_SIGNALS, HAVE_PIDFD_GET_INFO,
HAVE_PROCMAP_QUERY, HAVE_RWF_NOAPPEND, LANDLOCK_ABI, MMAP_MIN_ADDR, SAFE_PERSONAS,
SYD_MADVISE, SYSCALL_PTR_ARGS, UNSAFE_PERSONA,
},
cookie::{
safe_socket, CookieIdx, SYSCOOKIE_POOL, SYS_ACCEPT4, SYS_BIND, SYS_CONNECT, SYS_RECVMMSG,
SYS_RECVMMSG_TIME64, SYS_RECVMSG, SYS_SENDFILE64, SYS_SENDMMSG, SYS_SENDMSG, SYS_SOCKET,
SYS_SOCKETPAIR, SYS_UTIMENSAT, SYS_UTIMENSAT_TIME64,
},
err::{err2no, err2set, SydResult},
fd::{fdclone, nlmsg_align, AT_BADFD, AT_EXECVE_CHECK, PROC_FD},
fs::{
readlinkat, seccomp_export_pfc, SECCOMP_IOCTL_NOTIF_ADDFD, SECCOMP_IOCTL_NOTIF_LIST,
SECCOMP_IOCTL_NOTIF_SEND,
},
info,
landlock::{
path_beneath_rules, Access, AccessFs, AccessNet, CompatLevel, Compatible, NetPort,
PathBeneath, RestrictSelfFlags, RestrictionStatus, Ruleset, RulesetAttr,
RulesetCreatedAttr, RulesetError, RulesetStatus, Scope, ABI,
},
lookup::{safe_open_how, FileType},
mount::api::MountAttrFlags,
path::{dotdot_with_nul, empty_argv, empty_envp, empty_path, mask_path, XPath, XPathBuf},
proc::{proc_find_vma, proc_open, ProcmapQueryFlags, Vma, PROCMAP_QUERY},
retry::retry_on_eintr,
rwrite, rwriteln,
sandbox::{RawIoctlMap, Sandbox},
sealbox::{mprotect_xonly, mseal},
syslog::{SYSLOG_ACTION_READ, SYSLOG_ACTION_READ_ALL, SYSLOG_ACTION_READ_CLEAR},
};
pub(crate) const EIDRM: i32 = -libc::EIDRM;
pub(crate) const EOWNERDEAD: i32 = -libc::EOWNERDEAD;
pub(crate) const ARCH_OLD_MMAP: bool = cfg!(any(
target_arch = "x86",
target_arch = "m68k",
target_arch = "s390x",
));
pub fn secure_getenv<K: AsRef<OsStr>>(key: K) -> Option<OsString> {
if !cfg!(feature = "trusted") {
None
} else {
env::var_os(key)
}
}
pub fn resolve_syscall(name: &str) -> Option<c_long> {
ScmpSyscall::from_name(name)
.map(i32::from)
.map(c_long::from)
.ok()
.filter(|&n| n >= 0)
}
pub fn confine_mdwe(no_inherit: bool) -> Result<(), Errno> {
let is_mips = cfg!(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
));
if is_mips {
return Err(Errno::ENOTSUP);
}
const PR_SET_MDWE: c_int = 65;
const PR_MDWE_REFUSE_EXEC_GAIN: c_ulong = 1;
const PR_MDWE_NO_INHERIT: c_ulong = 2;
let mut flags = PR_MDWE_REFUSE_EXEC_GAIN;
if no_inherit {
flags |= PR_MDWE_NO_INHERIT;
}
Errno::result(unsafe { prctl(PR_SET_MDWE, flags, 0, 0, 0) }).map(drop)
}
pub fn confine_rlimit(resource: Resource, lim: Option<rlim_t>) -> Result<(), Errno> {
let lim = lim.unwrap_or(0);
setrlimit(resource, lim, lim)
}
pub fn confine_rlimit_zero(resources: &[Resource]) -> Result<(), Errno> {
for resource in resources {
confine_rlimit(*resource, None)?;
}
Ok(())
}
pub fn confine_executable_maps() -> Result<(), Errno> {
const SKIP_XONLY: &[&[u8]] = &[b"[vdso]", b"[vsyscall]"];
let mut mseal_nosys = false;
for (idx, vma) in proc_find_vma(Pid::this(), ProcmapQueryFlags::VMA_EXECUTABLE)?
.iter()
.enumerate()
{
if idx > 0 && SKIP_XONLY.binary_search(&vma.name_bytes()).is_err() {
let _ = confine_vma_xonly(vma);
}
if mseal_nosys {
continue;
}
mseal_nosys = confine_vma_mseal(vma) == Err(Errno::ENOSYS);
}
Ok(())
}
fn confine_vma_xonly(vma: &Vma) -> Result<(), Errno> {
match mprotect_xonly(vma.as_ptr(), vma.len()) {
Ok(()) => {
info!("ctx": "seal_executable_maps", "op": "mprotect_xonly",
"msg": format!("made vma `{}' at {:#x} execute-only",
vma.name(), vma.addr()),
"vma": &vma);
Ok(())
}
Err(errno) => {
info!("ctx": "seal_executable_maps", "op": "mprotect_xonly",
"msg": format!("error making vma `{}' at {:#x} execute-only: {errno}",
vma.name(), vma.addr()),
"err": errno as i32, "vma": &vma);
Err(errno)
}
}
}
fn confine_vma_mseal(vma: &Vma) -> Result<(), Errno> {
match mseal(vma.as_ptr(), vma.len()) {
Ok(()) => {
info!("ctx": "seal_executable_maps", "op": "mseal",
"msg": format!("sealed vma `{}' at {:#x}",
vma.name(), vma.addr()),
"vma": &vma);
Ok(())
}
Err(errno) => {
info!("ctx": "seal_executable_maps", "op": "mseal",
"msg": format!("error sealing vma `{}' at {:#x}: {errno}",
vma.name(), vma.addr()),
"err": errno as i32, "vma": &vma);
Err(errno)
}
}
}
pub fn confine_scmp(action: ScmpAction, sysnames: &[&str]) -> SydResult<()> {
if action == ScmpAction::Allow {
return Err(Errno::EINVAL.into());
}
let mut ctx = ScmpFilterContext::new(ScmpAction::Allow)?;
let _ = ctx.set_api_sysrawrc(true);
let _ = ctx.set_act_badarch(ScmpAction::KillProcess);
let _ = ctx.set_ctl_optimize(2);
seccomp_add_architectures(&mut ctx)?;
for sysname in sysnames {
let syscall = if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
syscall
} else {
continue;
};
ctx.add_rule(action, syscall)?;
}
ctx.load()?;
Ok(())
}
pub fn confine_scmp_wx_all() -> SydResult<()> {
let is_mips = cfg!(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
));
if is_mips {
return Err(Errno::ENOTSUP.into());
}
let mut ctx = ScmpFilterContext::new(ScmpAction::Allow)?;
let _ = ctx.set_api_sysrawrc(true);
let _ = ctx.set_act_badarch(ScmpAction::KillProcess);
let _ = ctx.set_ctl_optimize(2);
seccomp_add_architectures(&mut ctx)?;
confine_scmp_personality(&mut ctx, false )?;
const MAP_FIXED: u64 = libc::MAP_FIXED as u64;
const MAP_FIXED_NOREPLACE: u64 = crate::compat::MAP_FIXED_NOREPLACE as u64;
const MREMAP_FIXED: u64 = libc::MREMAP_FIXED as u64;
const W: u64 = libc::PROT_WRITE as u64;
const X: u64 = libc::PROT_EXEC as u64;
const WX: u64 = W | X;
const SHM_X: u64 = SHM_EXEC as u64;
const MAP_A: u64 = libc::MAP_ANONYMOUS as u64;
const MAP_S: u64 = libc::MAP_SHARED as u64;
let mmap_min_addr = *MMAP_MIN_ADDR;
for (idx, sysname) in ["mmap", "mmap2"].iter().enumerate() {
if ARCH_OLD_MMAP && idx == 0 {
continue;
}
let syscall = if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
syscall
} else {
continue;
};
ctx.add_rule_conditional(
ScmpAction::KillProcess,
syscall,
&[
scmp_cmp!($arg0 < mmap_min_addr),
scmp_cmp!($arg3 & MAP_FIXED == MAP_FIXED),
],
)?;
ctx.add_rule_conditional(
ScmpAction::KillProcess,
syscall,
&[
scmp_cmp!($arg0 < mmap_min_addr),
scmp_cmp!($arg3 & MAP_FIXED_NOREPLACE == MAP_FIXED_NOREPLACE),
],
)?;
ctx.add_rule_conditional(
ScmpAction::KillProcess,
syscall,
&[scmp_cmp!($arg2 & WX == WX)],
)?;
ctx.add_rule_conditional(
ScmpAction::KillProcess,
syscall,
&[scmp_cmp!($arg2 & X == X), scmp_cmp!($arg3 & MAP_A == MAP_A)],
)?;
ctx.add_rule_conditional(
ScmpAction::KillProcess,
syscall,
&[scmp_cmp!($arg2 & X == X), scmp_cmp!($arg3 & MAP_S == MAP_S)],
)?;
}
if let Ok(syscall) = ScmpSyscall::from_name("mremap") {
ctx.add_rule_conditional(
ScmpAction::KillProcess,
syscall,
&[
scmp_cmp!($arg4 < mmap_min_addr),
scmp_cmp!($arg3 & MREMAP_FIXED == MREMAP_FIXED),
],
)?;
}
for sysname in ["mprotect", "pkey_mprotect"] {
let syscall = if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
syscall
} else {
continue;
};
ctx.add_rule_conditional(
ScmpAction::KillProcess,
syscall,
&[scmp_cmp!($arg2 & X == X)],
)?;
}
if let Ok(syscall) = ScmpSyscall::from_name("shmat") {
ctx.add_rule_conditional(
ScmpAction::KillProcess,
syscall,
&[scmp_cmp!($arg2 & SHM_X == SHM_X)],
)?;
}
ctx.load()?;
Ok(())
}
pub fn confine_scmp_wx_syd(ctx: &mut ScmpFilterContext) -> SydResult<()> {
const MAP_ALLOW: u64 = (libc::MAP_ANONYMOUS | libc::MAP_PRIVATE) as u64;
const MAP_FLAGS: u64 = MAP_ALLOW
| (libc::MAP_SHARED |
libc::MAP_SHARED_VALIDATE |
0x40 |
libc::MAP_HUGETLB |
0x4000000) as u64;
const PROT_EXEC: u64 = libc::PROT_EXEC as u64;
for (idx, sysname) in ["mprotect", "mmap", "mmap2"].iter().enumerate() {
let mut rules = vec![scmp_cmp!($arg2 & PROT_EXEC == 0)];
if idx > 0 {
rules.push(scmp_cmp!($arg3 & MAP_FLAGS == MAP_ALLOW));
}
match ScmpSyscall::from_name(sysname) {
Ok(syscall) => {
if ARCH_OLD_MMAP && idx == 1 {
ctx.add_rule(ScmpAction::Allow, syscall)?;
} else {
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {sysname}"));
}
}
}
Ok(())
}
pub fn confine_scmp_ioctl_cld(denylist: &RawIoctlMap, ssb: bool) -> SydResult<()> {
let syscall = ScmpSyscall::from_name("ioctl").or(Err(Errno::ENOSYS))?;
for arch in SCMP_ARCH.iter().copied() {
let denylist = if let Some(denylist) = denylist.get(&arch) {
denylist
} else {
continue;
};
let mut ctx = ScmpFilterContext::new(ScmpAction::Allow)?;
ctx.set_ctl_nnp(true)?;
ctx.set_ctl_ssb(ssb)?;
ctx.set_ctl_tsync(false)?;
ctx.set_act_badarch(ScmpAction::Allow)?;
let _ = ctx.set_ctl_optimize(2);
let _ = ctx.set_api_sysrawrc(true);
ctx.remove_arch(ScmpArch::native())?;
ctx.add_arch(arch)?;
for op in denylist {
ctx.add_rule_conditional(
ScmpAction::Errno(EACCES),
syscall,
&[scmp_cmp!($arg1 & 0xFFFFFFFF == u64::from(*op))],
)?;
}
ctx.load()?;
}
Ok(())
}
#[expect(clippy::cognitive_complexity)]
pub fn confine_scmp_kptr(ssb: bool) -> SydResult<()> {
const SYSCALL_EINVAL: &[&str] = &[
"madvise",
"map_shadow_stack",
"mbind",
"mlock",
"mlock2",
"mmap",
"mmap2",
"mprotect",
"mremap",
"mseal",
"msync",
"munlock",
"munmap",
];
const KEYCTL_PTR: &[(u64, &[u32])] = &[
(1 , &[1]),
(2 , &[2]),
(6 , &[2]),
(10 , &[2, 3]),
(11 , &[2]),
(12 , &[2]),
(17 , &[2]),
(20 , &[2]),
(23 , &[1, 2, 4]),
(29 , &[2, 3]),
];
const PRCTL_PTR: &[(u64, &[u32])] = &[
(libc::PR_GET_CHILD_SUBREAPER as u64, &[1]),
(libc::PR_GET_ENDIAN as u64, &[1]),
(libc::PR_GET_FPEMU as u64, &[1]),
(libc::PR_GET_FPEXC as u64, &[1]),
(libc::PR_SET_VMA as u64, &[2, 4]),
(libc::PR_SET_NAME as u64, &[1]),
(libc::PR_GET_NAME as u64, &[1]),
(libc::PR_GET_PDEATHSIG as u64, &[1]),
(libc::PR_GET_TID_ADDRESS as u64, &[1]),
(libc::PR_GET_TSC as u64, &[1]),
(libc::PR_GET_UNALIGN as u64, &[1]),
(0x41555856 , &[1]),
];
const SYSLOG_PTR: &[(u64, &[u32])] = &[
(SYSLOG_ACTION_READ as u64, &[1]),
(SYSLOG_ACTION_READ_ALL as u64, &[1]),
(SYSLOG_ACTION_READ_CLEAR as u64, &[1]),
];
for arch in SCMP_ARCH.iter().copied() {
let mut ctx = ScmpFilterContext::new(ScmpAction::Allow)?;
ctx.set_ctl_nnp(true)?;
ctx.set_ctl_ssb(ssb)?;
ctx.set_ctl_tsync(false)?;
ctx.set_act_badarch(ScmpAction::Allow)?;
let _ = ctx.set_ctl_optimize(2);
let _ = ctx.set_api_sysrawrc(true);
ctx.remove_arch(ScmpArch::native())?;
ctx.add_arch(arch)?;
let is32 = scmp_arch_bits(arch) == 32;
for (sysname, args) in SYSCALL_PTR_ARGS {
if !SydArch(arch).has_syscall(sysname) {
continue;
}
let syscall = if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
syscall
} else {
continue;
};
let errno = if SYSCALL_EINVAL.binary_search(sysname).is_ok() {
EINVAL
} else {
EFAULT
};
if args.is_empty() {
if is_equal(sysname.as_bytes(), b"keyctl") {
for (op, args) in KEYCTL_PTR {
for arg in args.iter().copied() {
ctx.add_rule_conditional(
ScmpAction::Errno(errno),
syscall,
&[
scmp_cmp!($arg0 & 0xFFFFFFFF == *op),
scmp_kernel_ptr(arch, arg),
],
)?;
}
}
} else if is_equal(sysname.as_bytes(), b"prctl") {
for (op, args) in PRCTL_PTR {
for arg in args.iter().copied() {
ctx.add_rule_conditional(
ScmpAction::Errno(errno),
syscall,
&[
scmp_cmp!($arg0 & 0xFFFFFFFF == *op),
scmp_kernel_ptr(arch, arg),
],
)?;
}
}
let op = libc::PR_SET_MM as u64;
let subop = libc::PR_SET_MM_EXE_FILE as u64;
ctx.add_rule_conditional(
ScmpAction::Errno(errno),
syscall,
&[
scmp_cmp!($arg0 & 0xFFFFFFFF == op),
scmp_cmp!($arg1 != subop),
scmp_kernel_ptr(arch, 2),
],
)?;
let op = libc::PR_SET_SECCOMP as u64;
let subop = libc::SECCOMP_MODE_FILTER;
ctx.add_rule_conditional(
ScmpAction::Errno(errno),
syscall,
&[
scmp_cmp!($arg0 & 0xFFFFFFFF == op),
scmp_cmp!($arg1 == subop.into()),
scmp_kernel_ptr(arch, 2),
],
)?;
let op = 59u64 ;
let subops = [
1,
2,
];
for subop in subops {
ctx.add_rule_conditional(
ScmpAction::Errno(errno),
syscall,
&[
scmp_cmp!($arg0 & 0xFFFFFFFF == op),
scmp_cmp!($arg1 & 0xFFFFFFFF == subop),
scmp_kernel_ptr(arch, 3),
],
)?;
}
} else if is_equal(sysname.as_bytes(), b"syslog") {
for (op, args) in SYSLOG_PTR {
for arg in args.iter().copied() {
ctx.add_rule_conditional(
ScmpAction::Errno(errno),
syscall,
&[
scmp_cmp!($arg0 & 0xFFFFFFFF == *op),
scmp_kernel_ptr(arch, arg),
],
)?;
}
}
} else {
unreachable!("BUG: Invalid syscall `{sysname}' in SYSCALL_PTR_ARGS!");
}
continue;
}
for mut arg in args.iter().copied() {
#[expect(clippy::arithmetic_side_effects)]
if is32 && is_equal(sysname.as_bytes(), b"fanotify_mark") {
arg += 1;
}
if is_equal(sysname.as_bytes(), b"clone") {
const CLONE_PARENT_SETTID: u64 = libc::CLONE_PARENT_SETTID as u64;
const CLONE_PIDFD: u64 = libc::CLONE_PIDFD as u64;
const CLONE_SETTLS: u64 = libc::CLONE_SETTLS as u64;
const CLONE_CHILD_SETTID: u64 = libc::CLONE_CHILD_SETTID as u64;
const CLONE_CHILD_CLEARTID: u64 = libc::CLONE_CHILD_CLEARTID as u64;
match arg {
1 if matches!(arch, ScmpArch::S390X | ScmpArch::S390) => {
arg = 0;
}
2 => {
for flag in [CLONE_PARENT_SETTID, CLONE_PIDFD] {
ctx.add_rule_conditional(
ScmpAction::Errno(errno),
syscall,
&[scmp_cmp!($arg0 & flag == flag), scmp_kernel_ptr(arch, arg)],
)?;
}
continue;
}
3 => {
ctx.add_rule_conditional(
ScmpAction::Errno(errno),
syscall,
&[
scmp_cmp!($arg0 & CLONE_SETTLS == CLONE_SETTLS),
scmp_kernel_ptr(arch, arg),
],
)?;
continue;
}
4 => {
for flag in [CLONE_CHILD_SETTID, CLONE_CHILD_CLEARTID] {
ctx.add_rule_conditional(
ScmpAction::Errno(errno),
syscall,
&[scmp_cmp!($arg0 & flag == flag), scmp_kernel_ptr(arch, arg)],
)?;
}
continue;
}
_ => {} }
}
ctx.add_rule_conditional(
ScmpAction::Errno(errno),
syscall,
&[scmp_kernel_ptr(arch, arg)],
)?;
}
}
let arch = SydArch(arch);
match ExportMode::from_env() {
None => ctx.load()?,
Some(ExportMode::BerkeleyPacketFilter) => {
#[expect(clippy::disallowed_methods)]
let file = OpenOptions::new()
.write(true)
.create_new(true)
.mode(0o400)
.open(format!("syd_ptr_{arch}.bpf"))?;
ctx.export_bpf(file)?;
}
Some(ExportMode::PseudoFiltercode) => {
let mut stdout = std::io::stdout().lock();
rwriteln!(stdout, "# Syd pointer rules for arch:{arch}")?;
rwrite!(stdout, "{}", seccomp_export_pfc(&ctx)?)?;
}
};
}
Ok(())
}
pub const RWF_NOAPPEND: u64 = 0x00000020;
pub fn confine_scmp_pwritev2(ssb: bool) -> SydResult<()> {
if !*HAVE_RWF_NOAPPEND {
return Ok(());
}
let syscall = if let Ok(syscall) = ScmpSyscall::from_name("pwritev2") {
syscall
} else {
return Ok(());
};
if !SCMP_ARCH.contains(&ScmpArch::X32) {
let mut ctx = ScmpFilterContext::new(ScmpAction::Allow)?;
ctx.set_ctl_nnp(true)?;
ctx.set_ctl_ssb(ssb)?;
ctx.set_ctl_tsync(true)?;
ctx.set_act_badarch(ScmpAction::KillProcess)?;
let _ = ctx.set_ctl_optimize(2);
let _ = ctx.set_api_sysrawrc(true);
seccomp_add_architectures(&mut ctx)?;
let rule = scmp_cmp!($arg5 & RWF_NOAPPEND == RWF_NOAPPEND);
ctx.add_rule_conditional(ScmpAction::Errno(EOPNOTSUPP), syscall, &[rule])?;
return Ok(ctx.load()?);
}
for arch in SCMP_ARCH {
let mut ctx = ScmpFilterContext::new(ScmpAction::Allow)?;
ctx.set_ctl_nnp(true)?;
ctx.set_ctl_ssb(ssb)?;
ctx.set_ctl_tsync(false)?;
ctx.set_act_badarch(ScmpAction::Allow)?;
let _ = ctx.set_ctl_optimize(2);
let _ = ctx.set_api_sysrawrc(true);
ctx.remove_arch(ScmpArch::native())?;
ctx.add_arch(*arch)?;
let rule = if *arch == ScmpArch::X32 {
scmp_cmp!($arg4 & RWF_NOAPPEND == RWF_NOAPPEND)
} else {
scmp_cmp!($arg5 & RWF_NOAPPEND == RWF_NOAPPEND)
};
ctx.add_rule_conditional(ScmpAction::Errno(EOPNOTSUPP), syscall, &[rule])?;
ctx.load()?;
}
Ok(())
}
pub fn confine_scmp_clone(ctx: &mut ScmpFilterContext) -> SydResult<()> {
let syscall = match ScmpSyscall::from_name("clone") {
Ok(s) => s,
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": "invalid or unsupported syscall clone");
return Ok(());
}
};
let ns_mask = CloneFlags::CLONE_NEWNS
| CloneFlags::CLONE_NEWUTS
| CloneFlags::CLONE_NEWIPC
| CloneFlags::CLONE_NEWUSER
| CloneFlags::CLONE_NEWNET
| CloneFlags::CLONE_NEWPID
| CloneFlags::CLONE_NEWCGROUP
| CLONE_NEWTIME;
#[expect(clippy::cast_sign_loss)]
let ns_mask = ns_mask.bits() as u64;
let filter = if !cfg!(target_arch = "s390x") {
scmp_cmp!($arg0 & ns_mask == 0)
} else {
scmp_cmp!($arg1 & ns_mask == 0)
};
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &[filter])?;
Ok(())
}
pub fn confine_scmp_clone3(ctx: &mut ScmpFilterContext) -> SydResult<()> {
let syscall = match ScmpSyscall::from_name("clone3") {
Ok(s) => s,
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": "invalid or unsupported syscall clone3");
return Ok(());
}
};
ctx.add_rule(ScmpAction::Errno(libc::ENOSYS), syscall)?;
Ok(())
}
pub fn confine_scmp_write(
ctx: &mut ScmpFilterContext,
max: Option<u64>,
chk_mem: bool,
) -> SydResult<()> {
let syscall = match ScmpSyscall::from_name("write") {
Ok(syscall) => syscall,
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": "invalid or unsupported syscall write");
return Ok(());
}
};
if cfg!(feature = "prof") || (chk_mem && Sandbox::memory_access() < 2) {
ctx.add_rule(ScmpAction::Allow, syscall)?;
return Ok(());
}
if let Ok(log_fd) = u64::try_from(crate::log::LOG_FD.load(Ordering::Relaxed)) {
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &[scmp_cmp!($arg0 == log_fd)])?;
if let Some(max) = max {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[scmp_cmp!($arg0 != log_fd), scmp_cmp!($arg2 <= max)],
)?;
}
} else if let Some(max) = max {
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &[scmp_cmp!($arg2 <= max)])?;
}
Ok(())
}
pub fn confine_scmp_faccessat2(
ctx: &mut ScmpFilterContext,
restrict_cookie: bool,
) -> SydResult<()> {
const SYSNAME: &str = "faccessat2";
#[expect(clippy::cast_sign_loss)]
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
let empty_flag = AtFlags::AT_EMPTY_PATH.bits() as u64;
let mut rules = vec![
scmp_cmp!($arg0 <= RawFd::MAX as u64),
scmp_cmp!($arg1 == empty_path()),
scmp_cmp!($arg3 & empty_flag == empty_flag),
];
if restrict_cookie {
rules.extend(&[
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::Faccessat2Arg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::Faccessat2Arg5).into()),
]);
}
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_fcntl(ctx: &mut ScmpFilterContext, ops: &[u64]) -> SydResult<()> {
for sysname in ["fcntl", "fcntl64"] {
let syscall = match ScmpSyscall::from_name(sysname) {
Ok(syscall) => syscall,
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {sysname}"));
continue;
}
};
for op in ops {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[scmp_cmp!($arg1 & 0xFFFFFFFF == *op & 0xFFFFFFFF)],
)?;
}
}
Ok(())
}
pub fn confine_scmp_prctl<'a, I>(ctx: &mut ScmpFilterContext, ops: I) -> SydResult<()>
where
I: IntoIterator<Item = &'a KeyValue<'a>>,
{
const SYSNAME: &str = "prctl";
if let Ok(syscall) = ScmpSyscall::from_name(SYSNAME) {
for (_, op) in ops {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[scmp_cmp!($arg0 & 0xFFFFFFFF == *op & 0xFFFFFFFF)],
)?;
}
} else {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
Ok(())
}
#[expect(clippy::cognitive_complexity)]
pub fn confine_scmp_ioctl_syd(
ctx: &mut ScmpFilterContext,
restrict_cookie: bool,
seccomp_fd: Option<RawFd>,
) -> SydResult<()> {
const SYSNAME: &str = "ioctl";
#[expect(clippy::cast_sign_loss)]
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if *HAVE_PIDFD_GET_INFO {
let mut rules = Vec::with_capacity(if restrict_cookie { 4 } else { 1 });
if restrict_cookie {
rules.extend(&[
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::PidfdGetInfoArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::PidfdGetInfoArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::PidfdGetInfoArg5).into()),
]);
}
rules.push(scmp_cmp!($arg1 & 0xFFFFFFFF == u64::from(PIDFD_GET_INFO)));
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
if *HAVE_PROCMAP_QUERY {
#[expect(clippy::unnecessary_cast)]
let ioctl_request = PROCMAP_QUERY as u64;
let mut rules = Vec::with_capacity(if restrict_cookie { 4 } else { 1 });
if restrict_cookie {
rules.extend(&[
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::ProcmapQueryArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::ProcmapQueryArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::ProcmapQueryArg5).into()),
]);
}
rules.push(scmp_cmp!($arg1 & 0xFFFFFFFF == ioctl_request & 0xFFFFFFFF));
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
let seccomp_fd = if let Some(seccomp_fd) = seccomp_fd {
seccomp_fd
} else {
return Ok(());
};
for ioctl_request in SECCOMP_IOCTL_NOTIF_LIST {
let mut rules = vec![scmp_cmp!($arg0 == seccomp_fd as u64)];
if restrict_cookie && *ioctl_request == SECCOMP_IOCTL_NOTIF_ADDFD {
rules.extend(&[
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::SeccompIoctlNotifAddfdArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::SeccompIoctlNotifAddfdArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::SeccompIoctlNotifAddfdArg5).into()),
]);
}
if restrict_cookie && *ioctl_request == SECCOMP_IOCTL_NOTIF_SEND {
rules.extend(&[
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::SeccompIoctlNotifSendArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::SeccompIoctlNotifSendArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::SeccompIoctlNotifSendArg5).into()),
]);
}
rules.push(scmp_cmp!($arg1 & 0xFFFFFFFF == u64::from(*ioctl_request) & 0xFFFFFFFF));
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_sigaction(ctx: &mut ScmpFilterContext) -> SydResult<()> {
for sysname in ["sigaction", "rt_sigaction"] {
let syscall = match ScmpSyscall::from_name(sysname) {
Ok(syscall) => syscall,
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {sysname}"));
continue;
}
};
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &[scmp_cmp!($arg1 == 0)])?;
}
Ok(())
}
pub fn confine_scmp_fchown(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
const SYSNAME: &str = "fchown";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::FchownArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::FchownArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::FchownArg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_fchownat(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
const SYSNAME: &str = "fchownat";
#[expect(clippy::cast_sign_loss)]
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
let empty_flag = AtFlags::AT_EMPTY_PATH.bits();
let mut rules = vec![
scmp_cmp!($arg0 <= RawFd::MAX as u64),
scmp_cmp!($arg1 == empty_path()),
scmp_cmp!($arg4 == empty_flag as u64),
];
if restrict_cookie {
rules.push(scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::FchownatArg5).into()));
}
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_fchmodat(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
const SYSNAME: &str = "fchmodat";
#[expect(clippy::cast_sign_loss)]
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
let mut rules = vec![scmp_cmp!($arg0 == PROC_FD() as u64)];
if restrict_cookie {
rules.push(scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::FchmodatArg3).into()));
rules.push(scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::FchmodatArg4).into()));
rules.push(scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::FchmodatArg5).into()));
}
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_fchmod(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
const SYSNAME: &str = "fchmod";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg2 == SYSCOOKIE_POOL.get(CookieIdx::FchmodArg2).into()),
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::FchmodArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::FchmodArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::FchmodArg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_fchmodat2(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
const SYSNAME: &str = "fchmodat2";
#[expect(clippy::cast_sign_loss)]
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
let empty_flag = AtFlags::AT_EMPTY_PATH.bits() as u64;
let mut rules = vec![
scmp_cmp!($arg0 <= RawFd::MAX as u64),
scmp_cmp!($arg1 == empty_path()),
scmp_cmp!($arg3 == empty_flag),
];
if restrict_cookie {
rules.push(scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::Fchmodat2Arg4).into()));
rules.push(scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::Fchmodat2Arg5).into()));
}
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_linkat(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
const SYSNAME: &str = "linkat";
#[expect(clippy::cast_sign_loss)]
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
let follow_flag = AtFlags::AT_SYMLINK_FOLLOW.bits() as u64;
let mut rules = vec![
scmp_cmp!($arg0 == PROC_FD() as u64),
scmp_cmp!($arg2 <= RawFd::MAX as u64),
scmp_cmp!($arg4 == follow_flag),
];
if restrict_cookie {
rules.push(scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::LinkatArg5_1).into()));
}
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
let empty_flag = AtFlags::AT_EMPTY_PATH.bits() as u64;
let mut rules = vec![
scmp_cmp!($arg0 <= RawFd::MAX as u64),
scmp_cmp!($arg1 == empty_path()),
scmp_cmp!($arg2 <= RawFd::MAX as u64),
scmp_cmp!($arg4 == empty_flag),
];
if restrict_cookie {
rules.push(scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::LinkatArg5_2).into()));
}
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_pipe2(
ctx: &mut ScmpFilterContext,
restrict_cookie: bool,
flags: OFlag,
) -> SydResult<()> {
const SYSNAME: &str = "pipe2";
#[expect(clippy::cast_sign_loss)]
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
let mut rules = vec![scmp_cmp!($arg1 == flags.bits() as u64)];
if restrict_cookie {
rules.extend(&[
scmp_cmp!($arg2 == SYSCOOKIE_POOL.get(CookieIdx::Pipe2Arg2).into()),
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::Pipe2Arg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::Pipe2Arg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::Pipe2Arg5).into()),
]);
}
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_splice(ctx: &mut ScmpFilterContext) -> SydResult<()> {
const SYSNAME: &str = "splice";
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_sendfile(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
let (sysname, cookie_arg4, cookie_arg5) = if SYS_SENDFILE64.is_some() {
(
"sendfile64",
CookieIdx::Sendfile64Arg4,
CookieIdx::Sendfile64Arg5,
)
} else {
("sendfile", CookieIdx::SendfileArg4, CookieIdx::SendfileArg5)
};
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(sysname) {
Ok(syscall) => {
let mut rules = vec![scmp_cmp!($arg2 == 0)];
if restrict_cookie {
rules.extend([
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(cookie_arg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(cookie_arg5).into()),
]);
}
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {sysname}"));
}
}
Ok(())
}
pub fn confine_scmp_renameat2(
ctx: &mut ScmpFilterContext,
restrict_cookie: bool,
restrict_mkcdev: bool,
) -> SydResult<()> {
const SYSNAME: &str = "renameat2";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
let mut rules = vec![scmp_cmp!($arg0 <= RawFd::MAX as u64)];
if restrict_mkcdev {
let flag_wht = RenameFlags::RENAME_WHITEOUT.bits().into();
rules.push(scmp_cmp!($arg4 & flag_wht == 0));
}
if restrict_cookie {
rules.push(scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::Renameat2Arg5).into()));
}
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_symlinkat(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
const SYSNAME: &str = "symlinkat";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
let mut rules = vec![scmp_cmp!($arg1 <= RawFd::MAX as u64)];
if restrict_cookie {
rules.push(scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::SymlinkatArg3).into()));
rules.push(scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::SymlinkatArg4).into()));
rules.push(scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::SymlinkatArg5).into()));
}
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_unlinkat(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
const SYSNAME: &str = "unlinkat";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
let mut rules = vec![scmp_cmp!($arg0 <= RawFd::MAX as u64)];
if restrict_cookie {
rules.push(scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::UnlinkatArg3).into()));
rules.push(scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::UnlinkatArg4).into()));
rules.push(scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::UnlinkatArg5).into()));
}
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_mkdirat(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
const SYSNAME: &str = "mkdirat";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
let mut rules = vec![scmp_cmp!($arg0 <= RawFd::MAX as u64)];
if restrict_cookie {
rules.push(scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::MkdiratArg3).into()));
rules.push(scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::MkdiratArg4).into()));
rules.push(scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::MkdiratArg5).into()));
}
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_mknodat(
ctx: &mut ScmpFilterContext,
restrict_cookie: bool,
restrict_mkbdev: bool,
restrict_mkcdev: bool,
) -> SydResult<()> {
const SYSNAME: &str = "mknodat";
const S_IFMT: u64 = libc::S_IFMT as u64;
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
let mut allowed_types: Vec<u64> = vec![
u64::from(libc::S_IFIFO),
u64::from(libc::S_IFREG),
u64::from(libc::S_IFSOCK),
];
if !restrict_mkbdev {
allowed_types.push(u64::from(libc::S_IFBLK));
}
if !restrict_mkcdev {
allowed_types.push(u64::from(libc::S_IFCHR));
}
for f_type in allowed_types {
let mut rules = vec![
scmp_cmp!($arg0 <= RawFd::MAX as u64),
scmp_cmp!($arg2 & S_IFMT == f_type),
];
if restrict_cookie {
rules.extend(&[
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::MknodatArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::MknodatArg5).into()),
]);
}
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_open(ctx: &mut ScmpFilterContext) -> SydResult<()> {
for sysname in ["open", "stat", "lstat"] {
match ScmpSyscall::from_name(sysname) {
Ok(syscall) => {
let action = if !cfg!(feature = "prof") {
ScmpAction::Errno(Errno::ENOSYS as i32)
} else {
ScmpAction::Allow
};
ctx.add_rule(action, syscall)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {sysname}"));
}
}
}
Ok(())
}
pub fn confine_scmp_openat(ctx: &mut ScmpFilterContext) -> SydResult<()> {
const SYSNAME: &str = "openat";
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if !cfg!(feature = "prof") {
let dotdot = dotdot_with_nul();
let oflags = (libc::O_RDONLY
| libc::O_CLOEXEC
| libc::O_DIRECTORY
| libc::O_LARGEFILE
| libc::O_NOCTTY
| libc::O_NOFOLLOW) as u64;
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg0 <= RawFd::MAX as u64),
scmp_cmp!($arg1 == dotdot),
scmp_cmp!($arg2 & oflags == oflags),
],
)?;
ctx.add_rule_conditional(
ScmpAction::Errno(Errno::ENOSYS as i32),
syscall,
&[scmp_cmp!($arg0 > RawFd::MAX as u64)],
)?;
ctx.add_rule_conditional(
ScmpAction::Errno(Errno::ENOSYS as i32),
syscall,
&[scmp_cmp!($arg1 != dotdot)],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_openat2(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
const SYSNAME: &str = "openat2";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
let mut rules = vec![scmp_cmp!($arg0 <= RawFd::MAX as u64)];
if restrict_cookie {
rules.push(scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::Openat2Arg4).into()));
rules.push(scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::Openat2Arg5).into()));
}
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_close(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
const SYSNAME: &str = "close";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg1 == SYSCOOKIE_POOL.get(CookieIdx::CloseArg1).into()),
scmp_cmp!($arg2 == SYSCOOKIE_POOL.get(CookieIdx::CloseArg2).into()),
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::CloseArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::CloseArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::CloseArg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_close_range(
ctx: &mut ScmpFilterContext,
restrict_cookie: bool,
) -> SydResult<()> {
const SYSNAME: &str = "close_range";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::CloseRangeArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::CloseRangeArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::CloseRangeArg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_fadvise(ctx: &mut ScmpFilterContext) -> SydResult<()> {
for sysname in FADVISE_SYSCALLS {
if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
ctx.add_rule(ScmpAction::Allow, syscall)?;
} else {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {sysname}"));
}
}
Ok(())
}
pub fn confine_scmp_memfd_create(
ctx: &mut ScmpFilterContext,
restrict_cookie: bool,
) -> SydResult<()> {
const SYSNAME: &str = "memfd_create";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg2 == SYSCOOKIE_POOL.get(CookieIdx::MemfdCreateArg2).into()),
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::MemfdCreateArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::MemfdCreateArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::MemfdCreateArg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_memfd_secret(
ctx: &mut ScmpFilterContext,
restrict_cookie: bool,
) -> SydResult<()> {
const SYSNAME: &str = "memfd_secret";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg1 == SYSCOOKIE_POOL.get(CookieIdx::MemfdSecretArg1).into()),
scmp_cmp!($arg2 == SYSCOOKIE_POOL.get(CookieIdx::MemfdSecretArg2).into()),
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::MemfdSecretArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::MemfdSecretArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::MemfdSecretArg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_truncate(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
let sysname = "truncate";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(sysname) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg2 == SYSCOOKIE_POOL.get(CookieIdx::TruncateArg2).into()),
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::TruncateArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::TruncateArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::TruncateArg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {sysname}"));
}
}
let sysname = "truncate64";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(sysname) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
#[cfg(target_arch = "x86")]
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::Truncate64Arg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::Truncate64Arg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::Truncate64Arg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {sysname}"));
}
}
Ok(())
}
pub fn confine_scmp_ftruncate(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
let sysname = "ftruncate";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(sysname) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg2 == SYSCOOKIE_POOL.get(CookieIdx::FtruncateArg2).into()),
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::FtruncateArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::FtruncateArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::FtruncateArg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {sysname}"));
}
}
let sysname = "ftruncate64";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(sysname) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
#[cfg(target_arch = "x86")]
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::Ftruncate64Arg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::Ftruncate64Arg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::Ftruncate64Arg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {sysname}"));
}
}
Ok(())
}
pub fn confine_scmp_fallocate(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
const SYSNAME: &str = "fallocate";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if restrict_cookie && cfg!(target_pointer_width = "64") {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::FallocateArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::FallocateArg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_execveat(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
const SYSNAME: &str = "execveat";
if !*HAVE_AT_EXECVE_CHECK {
return Ok(()); }
#[expect(clippy::cast_sign_loss)]
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
let flags = AT_EXECVE_CHECK | AtFlags::AT_EMPTY_PATH;
let mut rules = vec![
scmp_cmp!($arg0 <= RawFd::MAX as u64),
scmp_cmp!($arg1 == empty_path()),
scmp_cmp!($arg2 == empty_argv()),
scmp_cmp!($arg3 == empty_envp()),
scmp_cmp!($arg4 == flags.bits() as u64),
];
if restrict_cookie {
rules.push(scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::ExecveatArg5).into()));
}
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_umask(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
const SYSNAME: &str = "umask";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg1 == SYSCOOKIE_POOL.get(CookieIdx::UmaskArg1).into()),
scmp_cmp!($arg2 == SYSCOOKIE_POOL.get(CookieIdx::UmaskArg2).into()),
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::UmaskArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::UmaskArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::UmaskArg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_uname(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
const SYSNAME: &str = "uname";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg1 == SYSCOOKIE_POOL.get(CookieIdx::UnameArg1).into()),
scmp_cmp!($arg2 == SYSCOOKIE_POOL.get(CookieIdx::UnameArg2).into()),
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::UnameArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::UnameArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::UnameArg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_pidfd_open(
ctx: &mut ScmpFilterContext,
restrict_cookie: bool,
) -> SydResult<()> {
const SYSNAME: &str = "pidfd_open";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg2 == SYSCOOKIE_POOL.get(CookieIdx::PidfdOpenArg2).into()),
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::PidfdOpenArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::PidfdOpenArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::PidfdOpenArg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_pidfd_getfd(
ctx: &mut ScmpFilterContext,
restrict_cookie: bool,
) -> SydResult<()> {
const SYSNAME: &str = "pidfd_getfd";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::PidfdGetfdArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::PidfdGetfdArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::PidfdGetfdArg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_pidfd_send_signal(
ctx: &mut ScmpFilterContext,
restrict_cookie: bool,
) -> SydResult<()> {
const SYSNAME: &str = "pidfd_send_signal";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::PidfdSendSignalArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::PidfdSendSignalArg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_ptrace(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
const SYSNAME: &str = "ptrace";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::PtraceArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::PtraceArg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_fchdir(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
const SYSNAME: &str = "fchdir";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg1 == SYSCOOKIE_POOL.get(CookieIdx::FchdirArg1).into()),
scmp_cmp!($arg2 == SYSCOOKIE_POOL.get(CookieIdx::FchdirArg2).into()),
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::FchdirArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::FchdirArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::FchdirArg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_getdents64(
ctx: &mut ScmpFilterContext,
restrict_cookie: bool,
) -> SydResult<()> {
const SYSNAME: &str = "getdents64";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::Getdents64Arg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::Getdents64Arg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::Getdents64Arg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_unshare(ctx: &mut ScmpFilterContext, clone_flags: CloneFlags) -> SydResult<()> {
const SYSNAME: &str = "unshare";
#[expect(clippy::cast_sign_loss)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
let clone_flags = clone_flags.bits() as u64;
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[scmp_cmp!($arg0 == clone_flags)],
)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_utimensat(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
if SYS_UTIMENSAT_TIME64.is_some() {
do_confine_scmp_utimensat(ctx, "utimensat_time64", restrict_cookie)
} else if SYS_UTIMENSAT.is_some() {
do_confine_scmp_utimensat(ctx, "utimensat", restrict_cookie)
} else {
Err(Errno::ENOSYS.into())
}
}
fn do_confine_scmp_utimensat(
ctx: &mut ScmpFilterContext,
name: &str,
restrict_cookie: bool,
) -> SydResult<()> {
#[expect(clippy::cast_sign_loss)]
let empty_flag = AtFlags::AT_EMPTY_PATH.bits() as u64;
#[expect(clippy::useless_conversion)]
if let Ok(syscall) = ScmpSyscall::from_name(name) {
let mut rules = vec![
scmp_cmp!($arg0 <= RawFd::MAX as u64),
scmp_cmp!($arg1 == empty_path()),
scmp_cmp!($arg3 == empty_flag),
];
if restrict_cookie {
rules.extend(&[
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::UtimensatArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::UtimensatArg5).into()),
]);
}
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
} else {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {name}"));
}
Ok(())
}
pub fn confine_scmp_madvise(ctx: &mut ScmpFilterContext) -> SydResult<()> {
if let Ok(syscall) = ScmpSyscall::from_name("madvise") {
for advice in SYD_MADVISE {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[scmp_cmp!($arg2 & 0xFFFFFFFF == *advice & 0xFFFFFFFF)],
)?;
}
} else {
info!("ctx": "confine", "op": "allow_syscall",
"msg": "invalid or unsupported syscall madvise");
}
Ok(())
}
pub fn confine_scmp_msg_oob(ctx: &mut ScmpFilterContext) -> SydResult<()> {
let oob = libc::MSG_OOB as u64;
for (idx, sysname) in [
"recvmsg", "sendmsg", "send", "sendto", "sendmmsg", "recv", "recvfrom", "recvmmsg",
]
.iter()
.enumerate()
{
let sys = if let Ok(sys) = ScmpSyscall::from_name(sysname) {
sys
} else {
info!("ctx": "confine", "op": "allow_syscall",
"msg": "invalid or unsupported syscall {sysname}");
continue;
};
let (deny, allow) = if idx <= 1 {
(scmp_cmp!($arg2 & oob == oob), scmp_cmp!($arg2 & oob == 0))
} else {
(scmp_cmp!($arg3 & oob == oob), scmp_cmp!($arg3 & oob == 0))
};
ctx.add_rule_conditional(ScmpAction::Errno(libc::EOPNOTSUPP), sys, &[deny])?;
if matches!(*sysname, "send" | "recv") {
ctx.add_rule_conditional(ScmpAction::Allow, sys, &[allow])?;
}
}
Ok(())
}
pub fn confine_scmp_bind(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::useless_conversion)]
if let Some(syscall) = SYS_BIND.map(|n| ScmpSyscall::from_raw_syscall(n as RawSyscall)) {
let mut rules = vec![];
if restrict_cookie {
rules.extend(&[
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::BindArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::BindArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::BindArg5).into()),
]);
}
if rules.is_empty() {
ctx.add_rule(ScmpAction::Allow, syscall)?;
} else {
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
} else {
match ScmpSyscall::from_name("bind") {
Ok(syscall) => {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": "invalid or unsupported syscall bind");
}
}
}
Ok(())
}
pub fn confine_scmp_connect(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::useless_conversion)]
if let Some(syscall) = SYS_CONNECT.map(|n| ScmpSyscall::from_raw_syscall(n as RawSyscall)) {
let mut rules = vec![];
if restrict_cookie {
rules.extend(&[
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::ConnectArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::ConnectArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::ConnectArg5).into()),
]);
}
if rules.is_empty() {
ctx.add_rule(ScmpAction::Allow, syscall)?;
} else {
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
} else {
match ScmpSyscall::from_name("connect") {
Ok(syscall) => {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": "invalid or unsupported syscall connect");
}
}
}
Ok(())
}
pub fn confine_scmp_accept4(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::useless_conversion)]
if let Some(syscall) = SYS_ACCEPT4.map(|n| ScmpSyscall::from_raw_syscall(n as RawSyscall)) {
let mut rules = vec![];
if restrict_cookie {
rules.extend(&[
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::Accept4Arg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::Accept4Arg5).into()),
]);
}
if rules.is_empty() {
ctx.add_rule(ScmpAction::Allow, syscall)?;
} else {
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
} else {
match ScmpSyscall::from_name("accept4") {
Ok(syscall) => {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": "invalid or unsupported syscall accept4");
}
}
}
Ok(())
}
pub fn confine_scmp_sendmsg(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::useless_conversion)]
if let Some(syscall) = SYS_SENDMSG.map(|n| ScmpSyscall::from_raw_syscall(n as RawSyscall)) {
let mut rules = vec![];
if restrict_cookie {
rules.extend(&[
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::SendMsgArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::SendMsgArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::SendMsgArg5).into()),
]);
}
if rules.is_empty() {
ctx.add_rule(ScmpAction::Allow, syscall)?;
} else {
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
} else {
match ScmpSyscall::from_name("sendmsg") {
Ok(syscall) => {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": "invalid or unsupported syscall sendmsg");
}
}
}
Ok(())
}
pub fn confine_scmp_sendmmsg(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::useless_conversion)]
if let Some(syscall) = SYS_SENDMMSG.map(|n| ScmpSyscall::from_raw_syscall(n as RawSyscall)) {
let mut rules = vec![];
if restrict_cookie {
rules.extend(&[
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::SendMmsgArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::SendMmsgArg5).into()),
]);
}
if rules.is_empty() {
ctx.add_rule(ScmpAction::Allow, syscall)?;
} else {
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
} else {
match ScmpSyscall::from_name("sendmmsg") {
Ok(syscall) => {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": "invalid or unsupported syscall sendmmsg");
}
}
}
Ok(())
}
pub fn confine_scmp_recvmsg(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::useless_conversion)]
if let Some(syscall) = SYS_RECVMSG.map(|n| ScmpSyscall::from_raw_syscall(n as RawSyscall)) {
let mut rules = vec![];
if restrict_cookie {
rules.extend(&[
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::RecvMsgArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::RecvMsgArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::RecvMsgArg5).into()),
]);
}
if rules.is_empty() {
ctx.add_rule(ScmpAction::Allow, syscall)?;
} else {
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
} else {
match ScmpSyscall::from_name("recvmsg") {
Ok(syscall) => {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": "invalid or unsupported syscall recvmsg");
}
}
}
Ok(())
}
pub fn confine_scmp_recvmmsg(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
if let Some(sys) = *SYS_RECVMMSG_TIME64 {
do_confine_scmp_recvmmsg(ctx, "recvmmsg_time64", Some(sys), restrict_cookie)
} else if let Some(sys) = *SYS_RECVMMSG {
do_confine_scmp_recvmmsg(ctx, "recvmmsg", Some(sys), restrict_cookie)
} else {
do_confine_scmp_recvmmsg(ctx, "recvmmsg_time64", None, restrict_cookie)?;
do_confine_scmp_recvmmsg(ctx, "recvmmsg", None, restrict_cookie)
}
}
fn do_confine_scmp_recvmmsg(
ctx: &mut ScmpFilterContext,
name: &str,
sys: Option<c_long>,
restrict_cookie: bool,
) -> SydResult<()> {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::useless_conversion)]
if let Some(syscall) = sys.map(|n| ScmpSyscall::from_raw_syscall(n as RawSyscall)) {
let mut rules = vec![];
if restrict_cookie {
rules.extend(&[scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::RecvMmsgArg5).into())]);
}
if rules.is_empty() {
ctx.add_rule(ScmpAction::Allow, syscall)?;
} else {
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
} else {
match ScmpSyscall::from_name(name) {
Ok(syscall) => {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {name}"));
}
}
}
Ok(())
}
pub fn confine_scmp_socket(
ctx: &mut ScmpFilterContext,
restrict_domain: Option<&[c_int]>,
restrict_socket: bool,
restrict_cookie: bool,
) -> SydResult<()> {
const SOCK_TYPE_MASK: u64 = crate::compat::SOCK_TYPE_MASK as u64;
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
#[expect(clippy::useless_conversion)]
#[expect(deprecated)]
if let Some(syscall) = SYS_SOCKET.map(|n| ScmpSyscall::from_raw_syscall(n as RawSyscall)) {
if restrict_socket {
ctx.add_rule_conditional(
ScmpAction::Errno(EACCES),
syscall,
&[
scmp_cmp!($arg0 != libc::AF_NETLINK as u64),
scmp_cmp!($arg1 & SOCK_TYPE_MASK == SOCK_RAW as u64),
],
)?;
ctx.add_rule_conditional(
ScmpAction::Errno(EACCES),
syscall,
&[scmp_cmp!($arg1 & SOCK_TYPE_MASK == SOCK_PACKET as u64)],
)?;
}
let mut rules = vec![];
if restrict_cookie {
rules.extend(&[
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::SocketArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::SocketArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::SocketArg5).into()),
]);
}
if let Some(domains) = restrict_domain {
for domain in domains {
rules.push(scmp_cmp!($arg0 == *domain as u64));
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
rules.pop();
}
} else if rules.is_empty() {
ctx.add_rule(ScmpAction::Allow, syscall)?;
} else {
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
} else {
match ScmpSyscall::from_name("socket") {
Ok(syscall) => {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": "invalid or unsupported syscall socket");
}
}
}
Ok(())
}
pub fn confine_scmp_socketpair(
ctx: &mut ScmpFilterContext,
restrict_domain: Option<&[c_int]>,
restrict_socket: bool,
restrict_cookie: bool,
) -> SydResult<()> {
#[expect(clippy::cast_possible_truncation)]
#[expect(clippy::cast_sign_loss)]
#[expect(clippy::useless_conversion)]
#[expect(deprecated)]
if let Some(syscall) = SYS_SOCKETPAIR.map(|n| ScmpSyscall::from_raw_syscall(n as RawSyscall)) {
if restrict_socket {
const SOCK_TYPE_MASK: u64 = crate::compat::SOCK_TYPE_MASK as u64;
for ty in [SOCK_RAW as u64, SOCK_PACKET as u64] {
ctx.add_rule_conditional(
ScmpAction::Errno(EACCES),
syscall,
&[scmp_cmp!($arg1 & SOCK_TYPE_MASK == ty)],
)?;
}
}
let mut rules = vec![];
if restrict_cookie {
rules.extend(&[
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::SocketpairArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::SocketpairArg5).into()),
]);
}
if let Some(domains) = restrict_domain {
for domain in domains {
rules.push(scmp_cmp!($arg0 == *domain as u64));
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
rules.pop();
}
} else if rules.is_empty() {
ctx.add_rule(ScmpAction::Allow, syscall)?;
} else {
ctx.add_rule_conditional(ScmpAction::Allow, syscall, &rules)?;
}
} else {
match ScmpSyscall::from_name("socketpair") {
Ok(syscall) => {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": "invalid or unsupported syscall socketpair");
}
}
}
Ok(())
}
pub fn confine_scmp_setsockopt(ctx: &mut ScmpFilterContext) -> SydResult<()> {
if let Ok(syscall) = ScmpSyscall::from_name("setsockopt") {
for &(level, optname) in DENY_SETSOCKOPT {
#[expect(clippy::cast_sign_loss)]
ctx.add_rule_conditional(
ScmpAction::Errno(opt2errno(level, optname)),
syscall,
&[
scmp_cmp!($arg1 & 0xFFFFFFFF == level as u64),
scmp_cmp!($arg2 & 0xFFFFFFFF == optname as u64),
],
)?;
}
} else {
info!("ctx": "confine", "op": "deny_syscall",
"msg": "invalid or unsupported syscall setsockopt");
}
Ok(())
}
pub fn confine_scmp_personality(ctx: &mut ScmpFilterContext, allow: bool) -> SydResult<()> {
let syscall = if let Ok(syscall) = ScmpSyscall::from_name("personality") {
syscall
} else {
return Ok(());
};
for persona in UNSAFE_PERSONA {
let persona = persona.bits().into();
ctx.add_rule_conditional(
ScmpAction::KillProcess,
syscall,
&[scmp_cmp!($arg0 & persona == persona)],
)?;
}
if !allow {
return Ok(());
}
for &(_, persona) in SAFE_PERSONAS {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[scmp_cmp!($arg0 & 0xFFFFFFFF == persona)],
)?;
}
Ok(())
}
pub fn confine_scmp_open_stat(ctx: &mut ScmpFilterContext, openat2: bool) -> SydResult<()> {
const OPENAT2_CALL: &[&str] = &["openat2"];
const OPSTAT_CALLS: &[&str] = &["open", "openat", "stat", "lstat", "statx", "newfstatat"];
let action = if !cfg!(feature = "prof") {
ScmpAction::Errno(Errno::ENOSYS as i32)
} else {
ScmpAction::Allow
};
for sysname in OPSTAT_CALLS
.iter()
.chain(if openat2 { OPENAT2_CALL } else { &[] })
{
match ScmpSyscall::from_name(sysname) {
Ok(syscall) => {
ctx.add_rule(action, syscall)?;
}
Err(_) => {
info!("ctx": "confine", "op": "deny_syscall",
"msg": format!("invalid or unsupported syscall {sysname}"));
}
}
}
Ok(())
}
pub fn confine_scmp_epoll(ctx: &mut ScmpFilterContext, epoll_fd: RawFd) -> SydResult<()> {
let epoll_fd = epoll_fd.try_into().or(Err(Errno::EOVERFLOW))?;
for sysname in EPOLL_SYSCALLS {
match ScmpSyscall::from_name(sysname) {
Ok(syscall) => {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[scmp_cmp!($arg0 == epoll_fd)],
)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {sysname}"));
}
}
}
Ok(())
}
pub fn confine_landlock_scope<Fd: AsFd>(
root_fd: Option<Fd>,
access_fs: AccessFs,
access_net: AccessNet,
scoped_abs: bool,
) -> Result<(), Errno> {
const LEVEL: CompatLevel = CompatLevel::HardRequirement;
if !*HAVE_LANDLOCK_ACCESS_FS_REFER {
return Ok(());
}
let mut ruleset = Ruleset::default();
ruleset = ruleset.set_compatibility(LEVEL);
ruleset = ruleset
.handle_access(AccessFs::Refer)
.map_err(|error| err2set(&error))?;
let access_fs = access_fs & AccessFs::from_all(*LANDLOCK_ABI);
if !access_fs.is_empty() {
ruleset = ruleset
.handle_access(access_fs)
.map_err(|error| err2set(&error))?;
}
let access_net = access_net & AccessNet::from_all(*LANDLOCK_ABI);
if !access_net.is_empty() {
ruleset = ruleset
.handle_access(access_net)
.map_err(|error| err2set(&error))?;
}
if *HAVE_LANDLOCK_SCOPED_SIGNALS {
ruleset = ruleset
.scope(Scope::Signal)
.map_err(|error| err2set(&error))?;
if scoped_abs {
ruleset = ruleset
.scope(Scope::AbstractUnixSocket)
.map_err(|error| err2set(&error))?;
}
}
let mut ruleset = ruleset.create().map_err(|error| err2set(&error))?;
if !access_fs.contains(AccessFs::Refer) {
if let Some(fd) = root_fd {
ruleset = ruleset
.add_rule(PathBeneath::new(fd, AccessFs::Refer))
.map_err(|error| err2set(&error))?;
}
}
ruleset
.restrict_self(RestrictSelfFlags::empty())
.map(drop)
.map_err(|error| err2set(&error))
}
pub fn run_cmd(cmd: &mut Command) -> u8 {
#![allow(clippy::arithmetic_side_effects)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_sign_loss)]
match cmd.status() {
Ok(status) => {
if let Some(code) = status.code() {
code as u8
} else if let Some(sig) = status.signal() {
128 + (sig as u8)
} else {
127
}
}
Err(error) => err2no(&error) as i32 as u8,
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Sydcall(pub ScmpSyscall, pub u32);
impl Display for Sydcall {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let arch = match scmp_arch(self.1) {
Ok(arch) => arch,
Err(_) => return write!(f, "?"),
};
match self.0.get_name_by_arch(arch).ok() {
Some(name) => write!(f, "{name}"),
None => write!(f, "?"),
}
}
}
impl Serialize for Sydcall {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let arch = match scmp_arch(self.1) {
Ok(arch) => arch,
Err(_) => return serializer.serialize_none(),
};
match self.0.get_name_by_arch(arch).ok() {
Some(name) => serializer.serialize_str(&name),
None => serializer.serialize_none(),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct SydArch(pub ScmpArch);
impl SydArch {
pub fn native() -> Self {
ScmpArch::native().into()
}
pub fn has_syscall(&self, name: &str) -> bool {
ScmpSyscall::from_name_by_arch(name, self.0)
.map(|sys| sys.as_raw_syscall())
.map(|sno| sno >= 0)
.unwrap_or(false)
}
pub fn has_native_syscall(name: &str) -> bool {
Self::native().has_syscall(name)
}
pub fn has_socketcall(&self) -> bool {
self.has_syscall("socketcall")
}
pub fn has_ipc(&self) -> bool {
self.has_syscall("ipc")
}
pub fn has_native_socketcall() -> bool {
Self::native().has_socketcall()
}
pub fn has_native_ipc() -> bool {
Self::native().has_ipc()
}
}
impl Display for SydArch {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let arch = format!("{:?}", self.0).to_ascii_lowercase();
let arch = if arch == { "x8664" } { "x86_64" } else { &arch };
write!(f, "{arch}")
}
}
impl Serialize for SydArch {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let arch = format!("{:?}", self.0).to_ascii_lowercase();
let arch = if arch == { "x8664" } { "x86_64" } else { &arch };
serializer.serialize_str(arch)
}
}
impl From<ScmpArch> for SydArch {
fn from(arch: ScmpArch) -> Self {
SydArch(arch)
}
}
impl From<SydArch> for ScmpArch {
fn from(arch: SydArch) -> Self {
arch.0
}
}
impl From<&ScmpArch> for SydArch {
fn from(arch: &ScmpArch) -> Self {
SydArch(*arch)
}
}
impl From<&SydArch> for ScmpArch {
fn from(arch: &SydArch) -> Self {
arch.0
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct SydMemoryMap(pub MemoryMap);
impl SydMemoryMap {
pub fn is_stack(&self) -> bool {
matches!(self.0.pathname, MMapPath::Stack | MMapPath::TStack(_))
}
}
impl Display for SydMemoryMap {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let mmap = &self.0;
let perms = format!(
"{}{}{}{}",
if mmap.perms.contains(MMPermissions::READ) {
"r"
} else {
"-"
},
if mmap.perms.contains(MMPermissions::WRITE) {
"w"
} else {
"-"
},
if mmap.perms.contains(MMPermissions::EXECUTE) {
"x"
} else {
"-"
},
if mmap.perms.contains(MMPermissions::SHARED) {
"s"
} else if mmap.perms.contains(MMPermissions::PRIVATE) {
"p"
} else {
"-"
}
);
let pathname = match &mmap.pathname {
MMapPath::Path(path) => mask_path(path),
MMapPath::Heap => "[heap]".to_string(),
MMapPath::Stack => "[stack]".to_string(),
MMapPath::TStack(tid) => format!("[stack:{tid}]"),
MMapPath::Vdso => "[vdso]".to_string(),
MMapPath::Vvar => "[vvar]".to_string(),
MMapPath::Vsyscall => "[vsyscall]".to_string(),
MMapPath::Rollup => "[rollup]".to_string(),
MMapPath::Anonymous => "[anon]".to_string(),
MMapPath::Vsys(key) => format!("[vsys:{key}]"),
MMapPath::Other(pseudo_path) => mask_path(Path::new(pseudo_path)),
};
write!(
f,
"{:x}-{:x} {perms:<4} {:08x} {:02x}:{:02x} {:<10} {pathname}",
mmap.address.0, mmap.address.1, mmap.offset, mmap.dev.0, mmap.dev.1, mmap.inode,
)
}
}
impl Serialize for SydMemoryMap {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct SydPersona(pub Persona);
impl SydPersona {
pub fn get() -> Result<Self, Errno> {
#[expect(clippy::cast_sign_loss)]
Errno::result(unsafe { libc::personality(0xFFFFFFFF) })
.map(|pers| Persona::from_bits_retain(pers as u32))
.map(Self)
}
pub fn set(&self) -> Result<(), Errno> {
#[cfg(target_os = "android")]
{
Errno::result(unsafe { libc::personality(self.bits() as libc::c_uint) }).map(drop)
}
#[cfg(not(target_os = "android"))]
{
Errno::result(unsafe { libc::personality(libc::c_ulong::from(self.bits())) }).map(drop)
}
}
pub fn bits(&self) -> u32 {
self.0.bits()
}
}
impl Display for SydPersona {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
const PER_LINUX: u32 = 0;
const PER_LINUX_32BIT: u32 = PER_LINUX | ADDR_LIMIT_32BIT;
const PER_LINUX_FDPIC: u32 = PER_LINUX | FDPIC_FUNCPTRS;
const PER_SVR4: u32 = 1 | STICKY_TIMEOUTS | MMAP_PAGE_ZERO;
const PER_SVR3: u32 = 2 | STICKY_TIMEOUTS | SHORT_INODE;
const PER_SCOSVR3: u32 = 3 | STICKY_TIMEOUTS | WHOLE_SECONDS | SHORT_INODE;
const PER_OSR5: u32 = 3 | STICKY_TIMEOUTS | WHOLE_SECONDS;
const PER_WYSEV386: u32 = 4 | STICKY_TIMEOUTS | SHORT_INODE;
const PER_ISCR4: u32 = 5 | STICKY_TIMEOUTS;
const PER_BSD: u32 = 6;
const PER_SUNOS: u32 = PER_BSD | STICKY_TIMEOUTS;
const PER_XENIX: u32 = 7 | STICKY_TIMEOUTS | SHORT_INODE;
const PER_LINUX32: u32 = 8;
const PER_LINUX32_3GB: u32 = PER_LINUX32 | ADDR_LIMIT_3GB;
const PER_IRIX32: u32 = 9 | STICKY_TIMEOUTS;
const PER_IRIXN32: u32 = 0xa | STICKY_TIMEOUTS;
const PER_IRIX64: u32 = 0x0b | STICKY_TIMEOUTS;
const PER_RISCOS: u32 = 0xc;
const PER_SOLARIS: u32 = 0xd | STICKY_TIMEOUTS;
const PER_UW7: u32 = 0xe | STICKY_TIMEOUTS | MMAP_PAGE_ZERO;
const PER_OSF4: u32 = 0xf;
const PER_HPUX: u32 = 0x10;
const PER_MASK: u32 = 0xff;
const UNAME26: u32 = 0x0020000;
const ADDR_NO_RANDOMIZE: u32 = 0x0040000;
const FDPIC_FUNCPTRS: u32 = 0x0080000;
const MMAP_PAGE_ZERO: u32 = 0x0100000;
const ADDR_COMPAT_LAYOUT: u32 = 0x0200000;
const READ_IMPLIES_EXEC: u32 = 0x0400000;
const ADDR_LIMIT_32BIT: u32 = 0x0800000;
const SHORT_INODE: u32 = 0x1000000;
const WHOLE_SECONDS: u32 = 0x2000000;
const STICKY_TIMEOUTS: u32 = 0x4000000;
const ADDR_LIMIT_3GB: u32 = 0x8000000;
let domain = match self.0.bits() & PER_MASK {
PER_LINUX => "linux",
PER_LINUX_32BIT => "linux_32bit",
PER_LINUX_FDPIC => "linux_fdpic",
PER_SVR4 => "svr4",
PER_SVR3 => "svr3",
PER_SCOSVR3 => "scosvr3",
PER_OSR5 => "osr5",
PER_WYSEV386 => "wysev386",
PER_ISCR4 => "iscr4",
PER_BSD => "bsd",
PER_SUNOS => "sunos",
PER_XENIX => "xenix",
PER_LINUX32 => "linux32",
PER_LINUX32_3GB => "linux32_3gb",
PER_IRIX32 => "irix32",
PER_IRIXN32 => "irixn32",
PER_IRIX64 => "irix64",
PER_RISCOS => "riscos",
PER_SOLARIS => "solaris",
PER_UW7 => "uw7",
PER_OSF4 => "osf4",
PER_HPUX => "hpux",
_ => "unknown",
};
let flags = [
(UNAME26, "uname26"),
(ADDR_NO_RANDOMIZE, "addr-no-randomize"),
(FDPIC_FUNCPTRS, "fdpic-funcptrs"),
(MMAP_PAGE_ZERO, "mmap-page-zero"),
(ADDR_COMPAT_LAYOUT, "addr-compat-layout"),
(READ_IMPLIES_EXEC, "read-implies-exec"),
(ADDR_LIMIT_32BIT, "addr-limit-32bit"),
(SHORT_INODE, "short-inode"),
(WHOLE_SECONDS, "whole-seconds"),
(STICKY_TIMEOUTS, "sticky-timeouts"),
(ADDR_LIMIT_3GB, "addr-limit-3gb"),
]
.iter()
.filter_map(|&(flag, name)| {
if self.0.bits() & flag == flag {
Some(name)
} else {
None
}
})
.collect::<Vec<_>>()
.join(",");
if flags.is_empty() {
write!(f, "{domain}")
} else {
write!(f, "{domain},{flags}")
}
}
}
impl Serialize for SydPersona {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
pub(crate) struct SydMountAttrFlags(pub(crate) MountAttrFlags);
impl SydMountAttrFlags {
pub(crate) fn from_name(name: &str) -> Option<Self> {
match name {
"ro" => Some(Self(MountAttrFlags::MOUNT_ATTR_RDONLY)),
"nosuid" => Some(Self(MountAttrFlags::MOUNT_ATTR_NOSUID)),
"nodev" => Some(Self(MountAttrFlags::MOUNT_ATTR_NODEV)),
"noexec" => Some(Self(MountAttrFlags::MOUNT_ATTR_NOEXEC)),
"noatime" => Some(Self(MountAttrFlags::MOUNT_ATTR_NOATIME)),
"relatime" => Some(Self(MountAttrFlags::empty())), "strictatime" => Some(Self(MountAttrFlags::MOUNT_ATTR_STRICTATIME)),
"nodiratime" => Some(Self(MountAttrFlags::MOUNT_ATTR_NODIRATIME)),
"nosymfollow" => Some(Self(MountAttrFlags::MOUNT_ATTR_NOSYMFOLLOW)),
_ => None,
}
}
pub(crate) fn to_names(&self) -> Vec<&str> {
let mut names = Vec::with_capacity(self.0.iter().count());
if self.0.contains(MountAttrFlags::MOUNT_ATTR_RDONLY) {
names.push("ro");
}
if self.0.contains(MountAttrFlags::MOUNT_ATTR_NOSUID) {
names.push("nosuid");
}
if self.0.contains(MountAttrFlags::MOUNT_ATTR_NODEV) {
names.push("nodev");
}
if self.0.contains(MountAttrFlags::MOUNT_ATTR_NOEXEC) {
names.push("noexec");
}
if self.0.contains(MountAttrFlags::MOUNT_ATTR_NOATIME) {
names.push("noatime");
}
if self.0.contains(MountAttrFlags::MOUNT_ATTR_STRICTATIME) {
names.push("strictatime");
} else if self.0.contains(MountAttrFlags::MOUNT_ATTR_NODIRATIME) {
names.push("nodiratime");
} else {
names.push("relatime"); }
if self.0.contains(MountAttrFlags::MOUNT_ATTR_NOSYMFOLLOW) {
names.push("nosymfollow");
}
names
}
}
pub(crate) fn opt2errno(level: i32, optname: i32) -> i32 {
match (level, optname) {
(libc::SOL_SOCKET, libc::SO_DEBUG) => EACCES,
(libc::SOL_SOCKET, 25) => EPERM,
(libc::SOL_SOCKET, 62) => EPERM,
_ => 0,
}
}
pub fn is_valid_ptr(ptr: u64, arch: ScmpArch) -> bool {
(*MMAP_MIN_ADDR..=limit_kernel_ptr(arch)).contains(&ptr)
}
pub fn scmp_kernel_ptr(arch: ScmpArch, arg: u32) -> ScmpArgCompare {
ScmpArgCompare::new(arg, ScmpCompareOp::Greater, limit_kernel_ptr(arch))
}
fn limit_kernel_ptr(arch: ScmpArch) -> u64 {
if scmp_arch_is_compat32(arch) {
0x0000_0000_ffff_f000
} else if arch == ScmpArch::Aarch64 {
0x0000_ffff_ffff_ffff
} else {
0x7fff_ffff_ffff_ffff
}
}
pub fn chdir_void<Fd: AsFd + Send>(proc_fd: Option<Fd>) -> Result<(), Errno> {
do_void(move || do_chdir_void(proc_fd))
}
pub fn chroot_void<Fd: AsFd + Send>(proc_fd: Option<Fd>) -> Result<(), Errno> {
do_void(move || do_chroot_void(proc_fd))
}
fn do_void<F>(func: F) -> Result<(), Errno>
where
F: FnOnce() -> Result<(), Errno> + Send,
{
let (pid_fd, _) = fdclone(
move || {
let code = match func() {
Ok(_) => 0,
Err(errno) => errno as i32,
};
unsafe { libc::_exit(code) };
},
CloneFlags::CLONE_FS | CloneFlags::CLONE_FILES,
Some(libc::SIGCHLD),
)?;
loop {
break match waitid(Id::PIDFd(pid_fd.as_fd()), WaitPidFlag::WEXITED) {
Ok(WaitStatus::Exited(_, 0)) => Ok(()),
Ok(WaitStatus::Exited(_, code)) => Err(Errno::from_raw(code)),
Ok(WaitStatus::Signaled(_, _, _)) => Err(Errno::EOWNERDEAD),
Ok(_) => Err(Errno::ECHILD),
Err(Errno::EINTR) => continue,
Err(errno) => Err(errno),
};
}
}
fn do_chroot_void<Fd: AsFd>(proc_fd: Option<Fd>) -> Result<(), Errno> {
do_chdir_void(proc_fd)
.and_then(|_| retry_on_eintr(|| chroot(".")))
.and_then(|_| retry_on_eintr(|| chdir("/")))
}
fn do_chdir_void<Fd: AsFd>(proc_fd: Option<Fd>) -> Result<(), Errno> {
let how = safe_open_how(
OFlag::O_PATH | OFlag::O_DIRECTORY,
ResolveFlag::RESOLVE_NO_XDEV,
);
let mut pfd = XPathBuf::from_pid(gettid())?;
pfd.push(b"fdinfo");
let my_proc;
let proc_fd = if let Some(proc_fd) = &proc_fd {
proc_fd.as_fd()
} else {
my_proc = proc_open(None)?;
my_proc.as_fd()
};
#[expect(clippy::disallowed_methods)]
retry_on_eintr(|| openat2(proc_fd, &pfd, how)).and_then(|fd| retry_on_eintr(|| fchdir(&fd)))
}
pub fn ns_enabled(ns_flags: CloneFlags) -> Result<bool, Errno> {
const SAFE_CLONE_FLAGS: CloneFlags =
CloneFlags::from_bits_retain(libc::CLONE_FS | libc::CLONE_FILES | libc::CLONE_IO);
let (pid_fd, _) = fdclone(
|| {
let code = if unshare(ns_flags).is_ok() { 0 } else { 127 };
unsafe { libc::_exit(code) };
},
SAFE_CLONE_FLAGS,
Some(libc::SIGCHLD),
)?;
loop {
break match waitid(Id::PIDFd(pid_fd.as_fd()), WaitPidFlag::WEXITED) {
Ok(crate::compat::WaitStatus::Exited(_, 0)) => Ok(true),
Ok(_) => Ok(false),
Err(Errno::EINTR) => continue,
Err(errno) => Err(errno),
};
}
}
pub fn lock_enabled(abi: ABI) -> u8 {
let path_ro = vec![XPathBuf::from("/")];
let path_rw = vec![XPathBuf::from("/")];
let port_if = if abi as i32 >= ABI::V4 as i32 {
Some((2525, 22))
} else {
None
};
fn landlock_operation(
abi: ABI,
path_ro: &[XPathBuf],
path_rw: &[XPathBuf],
port_if: Option<(u16, u16)>,
) -> Result<RestrictionStatus, RulesetError> {
let mut ruleset = Ruleset::default().handle_access(AccessFs::from_all(abi))?;
let ruleset_ref = &mut ruleset;
let mut network_rules: Vec<Result<NetPort, RulesetError>> = vec![];
if let Some((port_bind, port_conn)) = port_if {
ruleset_ref.handle_access(AccessNet::BindTcp)?;
network_rules.push(Ok(NetPort::new(port_bind, AccessNet::BindTcp)));
ruleset_ref.handle_access(AccessNet::ConnectTcp)?;
network_rules.push(Ok(NetPort::new(port_conn, AccessNet::ConnectTcp)));
}
if abi as i32 >= ABI::V6 as i32 {
ruleset_ref.scope(Scope::AbstractUnixSocket)?;
ruleset_ref.scope(Scope::Signal)?;
}
ruleset
.create()?
.add_rules(path_beneath_rules(path_ro, AccessFs::from_read(abi)))?
.add_rules(path_beneath_rules(path_rw, AccessFs::from_all(abi)))?
.add_rules(network_rules)?
.restrict_self(RestrictSelfFlags::empty())
}
match landlock_operation(abi, &path_ro, &path_rw, port_if) {
Ok(status) => match status.ruleset {
RulesetStatus::FullyEnforced => 0,
RulesetStatus::PartiallyEnforced => 1,
RulesetStatus::NotEnforced => 2,
},
Err(_) => 127,
}
}
pub(crate) fn seccomp_arch_native_name() -> Option<&'static str> {
match ScmpArch::native() {
ScmpArch::X86 => Some("x86"),
ScmpArch::X8664 => Some("x86_64"),
ScmpArch::X32 => Some("x32"),
ScmpArch::Arm => Some("arm"),
ScmpArch::Aarch64 => Some("aarch64"),
ScmpArch::Loongarch64 => Some("loongarch64"),
ScmpArch::M68k => Some("m68k"),
ScmpArch::Mips => Some("mips"),
ScmpArch::Mips64 => Some("mips64"),
ScmpArch::Mips64N32 => Some("mips64n32"),
ScmpArch::Mipsel => Some("mipsel"),
ScmpArch::Mipsel64 => Some("mipsel64"),
ScmpArch::Mipsel64N32 => Some("mipsel64n32"),
ScmpArch::Ppc => Some("ppc"),
ScmpArch::Ppc64 => Some("ppc64"),
ScmpArch::Ppc64Le => Some("ppc64le"),
ScmpArch::S390 => Some("s390"),
ScmpArch::S390X => Some("s390x"),
ScmpArch::Parisc => Some("parisc"),
ScmpArch::Parisc64 => Some("parisc64"),
ScmpArch::Riscv64 => Some("riscv64"),
ScmpArch::Sheb => Some("sheb"),
ScmpArch::Sh => Some("sh"),
_ => None,
}
}
const SECCOMP_ARCH_LIST: &[ScmpArch] = &[
ScmpArch::X86,
ScmpArch::X8664,
ScmpArch::X32,
ScmpArch::Arm,
ScmpArch::Aarch64,
ScmpArch::Loongarch64,
ScmpArch::M68k,
ScmpArch::Mips,
ScmpArch::Mips64,
ScmpArch::Mips64N32,
ScmpArch::Mipsel,
ScmpArch::Mipsel64,
ScmpArch::Mipsel64N32,
ScmpArch::Ppc,
ScmpArch::Ppc64,
ScmpArch::Ppc64Le,
ScmpArch::S390,
ScmpArch::S390X,
ScmpArch::Parisc,
ScmpArch::Parisc64,
ScmpArch::Riscv64,
ScmpArch::Sheb,
ScmpArch::Sh,
];
pub fn print_seccomp_architectures() {
let native = ScmpArch::native();
for arch in SECCOMP_ARCH_LIST {
let mut repr = format!("{arch:?}").to_ascii_lowercase();
if repr == "x8664" {
repr = "x86_64".to_string();
}
if *arch == native {
println!("- {repr} [*]")
} else {
println!("- {repr}");
}
}
}
pub(crate) const X32_SYSCALL_BIT: i32 = 0x4000_0000;
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64",))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::X8664, ScmpArch::X86, ScmpArch::X32];
#[cfg(all(target_arch = "x86_64", target_pointer_width = "32",))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::X32, ScmpArch::X86];
#[cfg(target_arch = "x86")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::X86];
#[cfg(target_arch = "arm")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Arm];
#[cfg(target_arch = "aarch64")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Aarch64, ScmpArch::Arm];
#[cfg(target_arch = "m68k")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::M68k];
#[cfg(all(target_arch = "mips", target_endian = "big"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Mips];
#[cfg(all(target_arch = "mips", target_endian = "little"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Mipsel];
#[cfg(all(target_arch = "mips32r6", target_endian = "big"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Mips];
#[cfg(all(target_arch = "mips32r6", target_endian = "little"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Mipsel];
#[cfg(all(target_arch = "mips64", target_endian = "big"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Mips64, ScmpArch::Mips64N32, ScmpArch::Mips];
#[cfg(all(target_arch = "mips64", target_endian = "little"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] =
&[ScmpArch::Mipsel64, ScmpArch::Mipsel64N32, ScmpArch::Mipsel];
#[cfg(all(target_arch = "mips64r6", target_endian = "big"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Mips64, ScmpArch::Mips64N32, ScmpArch::Mips];
#[cfg(all(target_arch = "mips64r6", target_endian = "little"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] =
&[ScmpArch::Mipsel64, ScmpArch::Mipsel64N32, ScmpArch::Mipsel];
#[cfg(all(target_arch = "powerpc", target_endian = "big"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Ppc];
#[cfg(all(target_arch = "powerpc64", target_endian = "big"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Ppc64, ScmpArch::Ppc];
#[cfg(all(target_arch = "powerpc64", target_endian = "little"))]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Ppc64Le];
#[cfg(target_arch = "riscv64")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Riscv64];
#[cfg(target_arch = "s390x")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::S390X, ScmpArch::S390];
#[cfg(target_arch = "loongarch64")]
pub(crate) const SCMP_ARCH: &[ScmpArch] = &[ScmpArch::Loongarch64];
pub fn seccomp_add_architectures(ctx: &mut ScmpFilterContext) -> SydResult<()> {
for arch in SCMP_ARCH {
seccomp_add_arch(ctx, *arch)?;
}
Ok(())
}
fn seccomp_add_arch(ctx: &mut ScmpFilterContext, arch: ScmpArch) -> SydResult<()> {
Ok(ctx.add_arch(arch).map(drop)?)
}
pub const fn scmp_arch_bits(arch: ScmpArch) -> usize {
match arch {
ScmpArch::X8664
| ScmpArch::X32
| ScmpArch::Aarch64
| ScmpArch::Loongarch64
| ScmpArch::Mips64
| ScmpArch::Mips64N32
| ScmpArch::Mipsel64
| ScmpArch::Mipsel64N32
| ScmpArch::Ppc64
| ScmpArch::Ppc64Le
| ScmpArch::Parisc64
| ScmpArch::Riscv64
| ScmpArch::S390X => 64,
ScmpArch::X86
| ScmpArch::Arm
| ScmpArch::M68k
| ScmpArch::Mips
| ScmpArch::Mipsel
| ScmpArch::Ppc
| ScmpArch::Parisc
| ScmpArch::S390
| ScmpArch::Sheb
| ScmpArch::Sh => 32,
_ => 64, }
}
pub const fn scmp_arch_is_compat32(arch: ScmpArch) -> bool {
scmp_arch_bits(arch) == 32
|| matches!(
arch,
ScmpArch::X32 | ScmpArch::Mips64N32 | ScmpArch::Mipsel64N32
)
}
pub const fn scmp_arch_is_compat_long32(arch: ScmpArch) -> bool {
scmp_arch_bits(arch) == 32 || matches!(arch, ScmpArch::Mips64N32 | ScmpArch::Mipsel64N32)
}
pub const fn scmp_arch_is_big_endian(arch: ScmpArch) -> bool {
matches!(
arch,
ScmpArch::Mips
| ScmpArch::Mips64
| ScmpArch::Ppc
| ScmpArch::Ppc64
| ScmpArch::S390
| ScmpArch::S390X
| ScmpArch::Mips64N32
| ScmpArch::M68k
| ScmpArch::Sheb
| ScmpArch::Parisc
| ScmpArch::Parisc64
)
}
pub const fn scmp_arch_is_old_mmap(arch: ScmpArch) -> bool {
matches!(
arch,
ScmpArch::X86 | ScmpArch::M68k | ScmpArch::S390 | ScmpArch::S390X
)
}
#[expect(clippy::arithmetic_side_effects)]
pub const fn scmp_arch_old_mmap_size(arch: ScmpArch) -> usize {
6 * (scmp_arch_bits(arch) / 8)
}
pub const fn scmp_arch_is_mips(arch: ScmpArch) -> bool {
matches!(
arch,
ScmpArch::Mips
| ScmpArch::Mips64
| ScmpArch::Mips64N32
| ScmpArch::Mipsel
| ScmpArch::Mipsel64
| ScmpArch::Mipsel64N32
)
}
pub const fn scmp_arch_has_single_step(arch: ScmpArch) -> bool {
matches!(
arch,
ScmpArch::X8664
| ScmpArch::X32
| ScmpArch::X86
| ScmpArch::Aarch64
| ScmpArch::M68k
| ScmpArch::Parisc
| ScmpArch::Parisc64
| ScmpArch::Ppc
| ScmpArch::Ppc64
| ScmpArch::Ppc64Le
| ScmpArch::S390
| ScmpArch::S390X
| ScmpArch::Sh
| ScmpArch::Sheb
)
}
pub const fn scmp_arch_has_uid16(arch: ScmpArch) -> bool {
matches!(
arch,
ScmpArch::Arm | ScmpArch::M68k | ScmpArch::Sh | ScmpArch::Sheb | ScmpArch::X86,
)
}
pub const fn scmp_arch_nsig(arch: ScmpArch) -> libc::c_int {
if scmp_arch_is_mips(arch) {
128
} else {
64
}
}
pub const fn scmp_arch_sigstop(arch: ScmpArch) -> libc::c_int {
match arch {
ScmpArch::Mips
| ScmpArch::Mipsel
| ScmpArch::Mips64
| ScmpArch::Mipsel64
| ScmpArch::Mips64N32
| ScmpArch::Mipsel64N32 => 23,
ScmpArch::Parisc | ScmpArch::Parisc64 => 24,
_ => 19,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct ScmpNotifData {
pub(crate) syscall: ScmpSyscall,
pub(crate) arch: ScmpArch,
pub(crate) instr_pointer: u64,
pub(crate) args: [u64; 6],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ScmpNotifReq {
pub(crate) id: u64,
pub(crate) pid: u32,
pub(crate) flags: u32,
pub(crate) data: ScmpNotifData,
}
impl ScmpNotifData {
fn from_sys(data: seccomp_data) -> Result<Self, Errno> {
Ok(Self {
syscall: ScmpSyscall::from(data.nr),
arch: scmp_arch(data.arch)?,
instr_pointer: data.instruction_pointer,
args: data.args,
})
}
}
impl ScmpNotifReq {
pub(crate) fn from_sys(req: seccomp_notif) -> Result<Self, Errno> {
Ok(Self {
id: req.id,
pid: req.pid,
flags: req.flags,
data: ScmpNotifData::from_sys(req.data)?,
})
}
#[inline(always)]
pub(crate) fn pid(&self) -> Pid {
#[expect(clippy::cast_possible_wrap)]
Pid::from_raw(self.pid as libc::pid_t)
}
}
pub const fn scmp_arch(arch: u32) -> Result<ScmpArch, Errno> {
match arch {
libseccomp_sys::SCMP_ARCH_NATIVE => Ok(ScmpArch::Native),
libseccomp_sys::SCMP_ARCH_X86 => Ok(ScmpArch::X86),
libseccomp_sys::SCMP_ARCH_X86_64 => Ok(ScmpArch::X8664),
libseccomp_sys::SCMP_ARCH_X32 => Ok(ScmpArch::X32),
libseccomp_sys::SCMP_ARCH_ARM => Ok(ScmpArch::Arm),
libseccomp_sys::SCMP_ARCH_AARCH64 => Ok(ScmpArch::Aarch64),
libseccomp_sys::SCMP_ARCH_LOONGARCH64 => Ok(ScmpArch::Loongarch64),
libseccomp_sys::SCMP_ARCH_M68K => Ok(ScmpArch::M68k),
libseccomp_sys::SCMP_ARCH_MIPS => Ok(ScmpArch::Mips),
libseccomp_sys::SCMP_ARCH_MIPS64 => Ok(ScmpArch::Mips64),
libseccomp_sys::SCMP_ARCH_MIPS64N32 => Ok(ScmpArch::Mips64N32),
libseccomp_sys::SCMP_ARCH_MIPSEL => Ok(ScmpArch::Mipsel),
libseccomp_sys::SCMP_ARCH_MIPSEL64 => Ok(ScmpArch::Mipsel64),
libseccomp_sys::SCMP_ARCH_MIPSEL64N32 => Ok(ScmpArch::Mipsel64N32),
libseccomp_sys::SCMP_ARCH_PPC => Ok(ScmpArch::Ppc),
libseccomp_sys::SCMP_ARCH_PPC64 => Ok(ScmpArch::Ppc64),
libseccomp_sys::SCMP_ARCH_PPC64LE => Ok(ScmpArch::Ppc64Le),
libseccomp_sys::SCMP_ARCH_S390 => Ok(ScmpArch::S390),
libseccomp_sys::SCMP_ARCH_S390X => Ok(ScmpArch::S390X),
libseccomp_sys::SCMP_ARCH_PARISC => Ok(ScmpArch::Parisc),
libseccomp_sys::SCMP_ARCH_PARISC64 => Ok(ScmpArch::Parisc64),
libseccomp_sys::SCMP_ARCH_RISCV64 => Ok(ScmpArch::Riscv64),
libseccomp_sys::SCMP_ARCH_SHEB => Ok(ScmpArch::Sheb),
libseccomp_sys::SCMP_ARCH_SH => Ok(ScmpArch::Sh),
_ => Err(Errno::ENOSYS),
}
}
pub const fn scmp_arch_raw(arch: ScmpArch) -> u32 {
match arch {
ScmpArch::Native => libseccomp_sys::SCMP_ARCH_NATIVE,
ScmpArch::X86 => libseccomp_sys::SCMP_ARCH_X86,
ScmpArch::X8664 => libseccomp_sys::SCMP_ARCH_X86_64,
ScmpArch::X32 => libseccomp_sys::SCMP_ARCH_X32,
ScmpArch::Arm => libseccomp_sys::SCMP_ARCH_ARM,
ScmpArch::Aarch64 => libseccomp_sys::SCMP_ARCH_AARCH64,
ScmpArch::Loongarch64 => libseccomp_sys::SCMP_ARCH_LOONGARCH64,
ScmpArch::M68k => libseccomp_sys::SCMP_ARCH_M68K,
ScmpArch::Mips => libseccomp_sys::SCMP_ARCH_MIPS,
ScmpArch::Mips64 => libseccomp_sys::SCMP_ARCH_MIPS64,
ScmpArch::Mips64N32 => libseccomp_sys::SCMP_ARCH_MIPS64N32,
ScmpArch::Mipsel => libseccomp_sys::SCMP_ARCH_MIPSEL,
ScmpArch::Mipsel64 => libseccomp_sys::SCMP_ARCH_MIPSEL64,
ScmpArch::Mipsel64N32 => libseccomp_sys::SCMP_ARCH_MIPSEL64N32,
ScmpArch::Ppc => libseccomp_sys::SCMP_ARCH_PPC,
ScmpArch::Ppc64 => libseccomp_sys::SCMP_ARCH_PPC64,
ScmpArch::Ppc64Le => libseccomp_sys::SCMP_ARCH_PPC64LE,
ScmpArch::S390 => libseccomp_sys::SCMP_ARCH_S390,
ScmpArch::S390X => libseccomp_sys::SCMP_ARCH_S390X,
ScmpArch::Parisc => libseccomp_sys::SCMP_ARCH_PARISC,
ScmpArch::Parisc64 => libseccomp_sys::SCMP_ARCH_PARISC64,
ScmpArch::Riscv64 => libseccomp_sys::SCMP_ARCH_RISCV64,
ScmpArch::Sheb => libseccomp_sys::SCMP_ARCH_SHEB,
ScmpArch::Sh => libseccomp_sys::SCMP_ARCH_SH,
_ => unreachable!(),
}
}
pub(crate) fn scmp_add_mknod(
ctx: &mut ScmpFilterContext,
action: ScmpAction,
f_type: FileType,
) -> SydResult<()> {
const S_IFMT: u64 = libc::S_IFMT as u64;
let f_type = u64::from(f_type.mode().ok_or(Errno::EINVAL)?);
let sysname = "mknod";
if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
ctx.add_rule_conditional(action, syscall, &[scmp_cmp!($arg1 & S_IFMT == f_type)])?;
} else {
info!("ctx": "confine", "op": "deny_syscall",
"msg": format!("invalid or unsupported syscall {sysname}"));
}
let sysname = "mknodat";
if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
ctx.add_rule_conditional(action, syscall, &[scmp_cmp!($arg2 & S_IFMT == f_type)])?;
} else {
info!("ctx": "confine", "op": "deny_syscall",
"msg": format!("invalid or unsupported syscall {sysname}"));
}
Ok(())
}
pub(crate) fn scmp_add_renameat2(ctx: &mut ScmpFilterContext) -> SydResult<()> {
const SYSNAME: &str = "renameat2";
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
let flag_xch: u64 = RenameFlags::RENAME_EXCHANGE.bits().into();
let flag_wht: u64 = RenameFlags::RENAME_WHITEOUT.bits().into();
ctx.add_rule_conditional(
ScmpAction::KillProcess,
syscall,
&[scmp_cmp!($arg4 & (flag_xch | flag_wht) == flag_wht)],
)?;
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_fgetxattr(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
const SYSNAME: &str = "fgetxattr";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::FgetxattrArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::FgetxattrArg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_flistxattr(
ctx: &mut ScmpFilterContext,
restrict_cookie: bool,
) -> SydResult<()> {
const SYSNAME: &str = "flistxattr";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::FlistxattrArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::FlistxattrArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::FlistxattrArg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_fremovexattr(
ctx: &mut ScmpFilterContext,
restrict_cookie: bool,
) -> SydResult<()> {
const SYSNAME: &str = "fremovexattr";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg2 == SYSCOOKIE_POOL.get(CookieIdx::FremovexattrArg2).into()),
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::FremovexattrArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::FremovexattrArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::FremovexattrArg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_lremovexattr(
ctx: &mut ScmpFilterContext,
restrict_cookie: bool,
) -> SydResult<()> {
const SYSNAME: &str = "lremovexattr";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg2 == SYSCOOKIE_POOL.get(CookieIdx::LremovexattrArg2).into()),
scmp_cmp!($arg3 == SYSCOOKIE_POOL.get(CookieIdx::LremovexattrArg3).into()),
scmp_cmp!($arg4 == SYSCOOKIE_POOL.get(CookieIdx::LremovexattrArg4).into()),
scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::LremovexattrArg5).into()),
],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_fsetxattr(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
const SYSNAME: &str = "fsetxattr";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::FsetxattrArg5).into())],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
pub fn confine_scmp_lsetxattr(ctx: &mut ScmpFilterContext, restrict_cookie: bool) -> SydResult<()> {
const SYSNAME: &str = "lsetxattr";
#[expect(clippy::useless_conversion)]
match ScmpSyscall::from_name(SYSNAME) {
Ok(syscall) => {
if restrict_cookie {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[scmp_cmp!($arg5 == SYSCOOKIE_POOL.get(CookieIdx::LsetxattrArg5).into())],
)?;
} else {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
}
Err(_) => {
info!("ctx": "confine", "op": "allow_syscall",
"msg": format!("invalid or unsupported syscall {SYSNAME}"));
}
}
Ok(())
}
#[expect(clippy::cognitive_complexity)]
pub(crate) fn confine_scmp_setid(
tag: &str,
ctx: &mut ScmpFilterContext,
safe_setuid: bool,
safe_setgid: bool,
transit_uids: &[(Uid, Uid)],
transit_gids: &[(Gid, Gid)],
) -> SydResult<()> {
const NULL_ID: u64 = u64::MAX;
let op_a = format!("allow_{tag}_syscall");
let op_f = format!("filter_{tag}_syscall");
for sysname in ["sigaction", "rt_sigaction", "sigreturn", "rt_sigreturn"] {
match ScmpSyscall::from_name(sysname) {
Ok(syscall) => {
ctx.add_rule(ScmpAction::Allow, syscall)?;
}
Err(_) => {
info!("ctx": "confine", "op": &op_a,
"msg": format!("invalid or unsupported syscall {sysname}"));
}
}
}
for sysname in ["setgroups", "setgroups32"] {
match ScmpSyscall::from_name(sysname) {
Ok(syscall) => {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[scmp_cmp!($arg0 == 0), scmp_cmp!($arg1 == 0)],
)?;
}
Err(_) => {
info!("ctx": "confine", "op": &op_a,
"msg": format!("invalid or unsupported syscall {sysname}"));
}
}
}
if safe_setuid {
let source_uid = Uid::current();
for sysname in &["setuid", "setuid32"] {
if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
for (s_uid, t_uid) in transit_uids {
if source_uid == *s_uid {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[scmp_cmp!($arg0 == u64::from(t_uid.as_raw()))],
)?;
}
}
} else {
info!("ctx": "confine", "op": &op_f,
"msg": format!("invalid or unsupported syscall {sysname}"));
}
}
for sysname in &["setreuid", "setreuid32"] {
if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
for (s_uid, t_uid) in transit_uids {
if source_uid == *s_uid {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg0 == u64::from(t_uid.as_raw())),
scmp_cmp!($arg1 == u64::from(t_uid.as_raw())),
],
)?;
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg0 == NULL_ID),
scmp_cmp!($arg1 == u64::from(t_uid.as_raw())),
],
)?;
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg0 == u64::from(t_uid.as_raw())),
scmp_cmp!($arg1 == NULL_ID),
],
)?;
}
}
} else {
info!("ctx": "confine", "op": &op_f,
"msg": format!("invalid or unsupported syscall {sysname}"));
}
}
for sysname in &["setresuid", "setresuid32"] {
if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
for (s_uid, t_uid) in transit_uids {
if source_uid == *s_uid {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg0 == u64::from(t_uid.as_raw())),
scmp_cmp!($arg1 == u64::from(t_uid.as_raw())),
scmp_cmp!($arg2 == u64::from(t_uid.as_raw())),
],
)?;
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg0 == NULL_ID),
scmp_cmp!($arg1 == u64::from(t_uid.as_raw())),
scmp_cmp!($arg2 == u64::from(t_uid.as_raw())),
],
)?;
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg0 == u64::from(t_uid.as_raw())),
scmp_cmp!($arg1 == NULL_ID),
scmp_cmp!($arg2 == u64::from(t_uid.as_raw())),
],
)?;
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg0 == u64::from(t_uid.as_raw())),
scmp_cmp!($arg1 == u64::from(t_uid.as_raw())),
scmp_cmp!($arg2 == NULL_ID),
],
)?;
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg0 == NULL_ID),
scmp_cmp!($arg1 == NULL_ID),
scmp_cmp!($arg2 == u64::from(t_uid.as_raw())),
],
)?;
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg0 == NULL_ID),
scmp_cmp!($arg1 == u64::from(t_uid.as_raw())),
scmp_cmp!($arg2 == NULL_ID),
],
)?;
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg0 == u64::from(t_uid.as_raw())),
scmp_cmp!($arg1 == NULL_ID),
scmp_cmp!($arg2 == NULL_ID),
],
)?;
}
}
} else {
info!("ctx": "confine", "op": &op_f,
"msg": format!("invalid or unsupported syscall {sysname}"));
}
}
}
if safe_setgid {
let source_gid = Gid::current();
for sysname in &["setgid", "setgid32"] {
if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
for (s_gid, t_gid) in transit_gids {
if source_gid == *s_gid {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[scmp_cmp!($arg0 == u64::from(t_gid.as_raw()))],
)?;
}
}
} else {
info!("ctx": "confine", "op": &op_f,
"msg": format!("invalid or unsupported syscall {sysname}"));
}
}
for sysname in &["setregid", "setregid32"] {
if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
for (s_gid, t_gid) in transit_gids {
if source_gid == *s_gid {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg0 == u64::from(t_gid.as_raw())),
scmp_cmp!($arg1 == u64::from(t_gid.as_raw())),
],
)?;
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg0 == NULL_ID),
scmp_cmp!($arg1 == u64::from(t_gid.as_raw())),
],
)?;
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg0 == u64::from(t_gid.as_raw())),
scmp_cmp!($arg1 == NULL_ID),
],
)?;
}
}
} else {
info!("ctx": "confine", "op": &op_f,
"msg": format!("invalid or unsupported syscall {sysname}"));
}
}
for sysname in &["setresgid", "setresgid32"] {
if let Ok(syscall) = ScmpSyscall::from_name(sysname) {
for (s_gid, t_gid) in transit_gids {
if source_gid == *s_gid {
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg0 == u64::from(t_gid.as_raw())),
scmp_cmp!($arg1 == u64::from(t_gid.as_raw())),
scmp_cmp!($arg2 == u64::from(t_gid.as_raw())),
],
)?;
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg0 == NULL_ID),
scmp_cmp!($arg1 == u64::from(t_gid.as_raw())),
scmp_cmp!($arg2 == u64::from(t_gid.as_raw())),
],
)?;
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg0 == u64::from(t_gid.as_raw())),
scmp_cmp!($arg1 == NULL_ID),
scmp_cmp!($arg2 == u64::from(t_gid.as_raw())),
],
)?;
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg0 == u64::from(t_gid.as_raw())),
scmp_cmp!($arg1 == u64::from(t_gid.as_raw())),
scmp_cmp!($arg2 == NULL_ID),
],
)?;
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg0 == NULL_ID),
scmp_cmp!($arg1 == NULL_ID),
scmp_cmp!($arg2 == u64::from(t_gid.as_raw())),
],
)?;
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg0 == NULL_ID),
scmp_cmp!($arg1 == u64::from(t_gid.as_raw())),
scmp_cmp!($arg2 == NULL_ID),
],
)?;
ctx.add_rule_conditional(
ScmpAction::Allow,
syscall,
&[
scmp_cmp!($arg0 == u64::from(t_gid.as_raw())),
scmp_cmp!($arg1 == NULL_ID),
scmp_cmp!($arg2 == NULL_ID),
],
)?;
}
}
} else {
info!("ctx": "confine", "op": &op_f,
"msg": format!("invalid or unsupported syscall {sysname}"));
}
}
}
Ok(())
}
pub(crate) fn low32_ge_blocks(val: u64) -> Vec<(u64, u64)> {
let mut out = Vec::new();
let val = val.min(u64::from(u32::MAX));
let end = u64::from(u32::MAX);
let mut start = val;
loop {
if start > end {
break;
}
#[expect(clippy::arithmetic_side_effects)]
let remaining = end - start + 1;
let n = start.trailing_zeros().min(remaining.ilog2()).min(32);
let size = 1u64 << n;
let mask = (!size.wrapping_sub(1)) & 0xFFFF_FFFFu64;
out.push((mask, start));
match start.checked_add(size) {
Some(next) => start = next,
None => break,
}
}
out
}
pub(crate) fn low32_le_blocks(val: u64) -> Vec<(u64, u64)> {
let mut out = Vec::new();
let val = val.min(u64::from(u32::MAX));
let mut start = 0u64;
loop {
let n = start
.trailing_zeros()
.min(val.saturating_sub(start).saturating_add(1).ilog2())
.min(32);
let size = 1u64 << n;
let mask = (!size.wrapping_sub(1)) & 0xFFFF_FFFFu64;
out.push((mask, start));
match start.checked_add(size) {
Some(next) if next <= val => start = next,
_ => break,
}
}
out
}
pub(crate) fn scmp_add_low32_ge(
ctx: &mut ScmpFilterContext,
act: ScmpAction,
sys: ScmpSyscall,
arg: u32,
val: u64,
) -> SydResult<()> {
for (mask, datum) in low32_ge_blocks(val) {
ctx.add_rule_conditional(
act,
sys,
&[ScmpArgCompare::new(
arg,
ScmpCompareOp::MaskedEqual(mask),
datum,
)],
)?;
}
Ok(())
}
pub(crate) fn scmp_add_low32_le(
ctx: &mut ScmpFilterContext,
act: ScmpAction,
sys: ScmpSyscall,
arg: u32,
val: u64,
) -> SydResult<()> {
for (mask, datum) in low32_le_blocks(val) {
ctx.add_rule_conditional(
act,
sys,
&[ScmpArgCompare::new(
arg,
ScmpCompareOp::MaskedEqual(mask),
datum,
)],
)?;
}
Ok(())
}
pub const CLONE_NEWTIME: CloneFlags = CloneFlags::from_bits_retain(128);
pub(crate) const NAMESPACE_FLAGS: &[libc::c_int] = &[
libc::CLONE_NEWNS,
libc::CLONE_NEWIPC,
libc::CLONE_NEWNET,
libc::CLONE_NEWPID,
libc::CLONE_NEWUTS,
libc::CLONE_NEWUSER,
libc::CLONE_NEWCGROUP,
CLONE_NEWTIME.bits(),
];
pub(crate) const NAMESPACE_FLAGS_ALL: libc::c_int = libc::CLONE_NEWNS
| libc::CLONE_NEWIPC
| libc::CLONE_NEWNET
| libc::CLONE_NEWPID
| libc::CLONE_NEWUTS
| libc::CLONE_NEWUSER
| libc::CLONE_NEWCGROUP
| CLONE_NEWTIME.bits();
pub(crate) const NAMESPACE_NAMES: &[&str] = &[
"user", "mount", "ipc", "net", "pid", "uts", "cgroup", "time",
];
pub fn nsflag_name(flag: libc::c_int) -> String {
match flag {
libc::CLONE_NEWNS => "mount",
libc::CLONE_NEWIPC => "ipc",
libc::CLONE_NEWNET => "net",
libc::CLONE_NEWPID => "pid",
libc::CLONE_NEWUTS => "uts",
libc::CLONE_NEWUSER => "user",
libc::CLONE_NEWCGROUP => "cgroup",
n if n == CLONE_NEWTIME.bits() => "time",
_ => "?",
}
.to_string()
}
pub fn check_cross_memory_attach() -> bool {
!matches!(
Errno::result(unsafe {
libc::process_vm_readv(0, std::ptr::null(), 0, std::ptr::null(), 0, 0)
}),
Err(Errno::ENOSYS)
)
}
pub fn check_vdso_has_getrandom() -> bool {
has_vdso_symbol(c"__vdso_getrandom")
}
pub fn vdso_list_calls() -> Result<Vec<&'static CStr>, libloading::Error> {
const KERN_LEN: usize = 9; const VDSO_LEN: usize = 7; const VDSO_IDX: usize = 10; const VDSO_CALL_NAMES: &[&CStr] = &[
c"__kernel_clock_getres",
c"__kernel_clock_getres_time64",
c"__kernel_clock_gettime",
c"__kernel_clock_gettime64",
c"__kernel_getcpu",
c"__kernel_getrandom",
c"__kernel_gettimeofday",
c"__kernel_get_tbfreq", c"__kernel_riscv_hwprobe", c"__kernel_time",
c"__vdso_clock_getres",
c"__vdso_clock_getres_time64",
c"__vdso_clock_gettime",
c"__vdso_clock_gettime64",
c"__vdso_getcpu",
c"__vdso_getrandom",
c"__vdso_gettimeofday",
c"__vdso_get_tbfreq", c"__vdso_riscv_hwprobe", c"__vdso_time",
];
let vdso = vdso_open()?;
let mut out = Vec::with_capacity(VDSO_CALL_NAMES.len());
for (idx, sym) in VDSO_CALL_NAMES.iter().enumerate() {
let sym = sym.to_bytes_with_nul();
if unsafe { vdso.get::<*const ()>(sym) }.is_ok() {
let plen = if idx < VDSO_IDX { KERN_LEN } else { VDSO_LEN };
out.push(unsafe { CStr::from_bytes_with_nul_unchecked(&sym[plen..]) });
}
}
Ok(out)
}
pub fn has_vdso_symbol(sym: &CStr) -> bool {
let vdso = if let Ok(vdso) = vdso_open() {
vdso
} else {
return false;
};
unsafe { vdso.get::<*const ()>(sym.to_bytes_with_nul()).is_ok() }
}
pub fn vdso_open() -> Result<Library, LibraryError> {
match vdso_open_by_name("linux-vdso.so.1") {
Ok(lib) => Ok(lib),
Err(LibraryError::DlOpen { .. }) => vdso_open_by_name("linux-vdso64.so.1"),
Err(error) => Err(error),
}
}
pub fn vdso_open_by_name(name: &str) -> Result<Library, LibraryError> {
unsafe { Library::open(Some(name), RTLD_NOLOAD | RTLD_LOCAL | RTLD_NOW) }
}
#[expect(clippy::arithmetic_side_effects)]
#[expect(clippy::cast_possible_truncation)]
pub fn check_unix_diag() -> Result<bool, Errno> {
const SOCK_DIAG_BY_FAMILY: u16 = 20;
const NL_HDR_LEN: usize = 16;
const UD_REQ_LEN: usize = 24;
let nlmsg_done: u16 = libc::NLMSG_DONE as u16;
let nlmsg_error: u16 = libc::NLMSG_ERROR as u16;
let nl = match safe_socket(
AddressFamily::Netlink,
SockType::Datagram,
SockFlag::SOCK_CLOEXEC,
libc::NETLINK_SOCK_DIAG,
) {
Ok(fd) => fd,
Err(Errno::EPROTONOSUPPORT | Errno::EAFNOSUPPORT | Errno::ENOTSUP) => return Ok(false),
Err(errno) => return Err(errno),
};
let total_len = (NL_HDR_LEN + UD_REQ_LEN) as u32;
let mut req = [0u8; NL_HDR_LEN + UD_REQ_LEN];
let mut p = 0usize;
req[p..p + 4].copy_from_slice(&total_len.to_ne_bytes());
p += 4;
req[p..p + 2].copy_from_slice(&SOCK_DIAG_BY_FAMILY.to_ne_bytes());
p += 2;
let nl_flags = (libc::NLM_F_REQUEST | libc::NLM_F_DUMP) as u16; req[p..p + 2].copy_from_slice(&nl_flags.to_ne_bytes());
p += 2;
req[p..p + 4].copy_from_slice(&1u32.to_ne_bytes());
p += 4; req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
p += 4;
req[p] = libc::AF_UNIX as u8;
p += 1; req[p] = 0;
p += 1; req[p..p + 2].copy_from_slice(&0u16.to_ne_bytes());
p += 2; req[p..p + 4].copy_from_slice(&u32::MAX.to_ne_bytes());
p += 4; req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
p += 4; req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
p += 4; req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
p += 4; req[p..p + 4].copy_from_slice(&0u32.to_ne_bytes());
p += 4; assert_eq!(p, req.len());
let mut off = 0;
while off < req.len() {
let n = retry_on_eintr(|| write(&nl, &req[off..]))?;
if n == 0 {
return Err(Errno::EIO);
}
off += n;
}
let mut rbuf = [0u8; 8192];
loop {
let n = retry_on_eintr(|| read(&nl, &mut rbuf))?;
if n == 0 {
return Err(Errno::EIO);
}
let mut pos = 0usize;
while pos + NL_HDR_LEN <= n {
let nlmsg_len = {
let b: [u8; 4] = rbuf[pos..pos + 4].try_into().or(Err(Errno::EOVERFLOW))?;
u32::from_ne_bytes(b) as usize
};
if nlmsg_len == 0 || pos + nlmsg_len > n {
return Err(Errno::EIO);
}
let nlmsg_type = {
let b: [u8; 2] = rbuf[pos + 4..pos + 6]
.try_into()
.or(Err(Errno::EOVERFLOW))?;
u16::from_ne_bytes(b)
};
if nlmsg_type == nlmsg_error {
if nlmsg_len < NL_HDR_LEN + 4 {
return Err(Errno::EIO);
}
let b: [u8; 4] = rbuf[pos + NL_HDR_LEN..pos + NL_HDR_LEN + 4]
.try_into()
.or(Err(Errno::EOVERFLOW))?;
let neg = i32::from_ne_bytes(b);
if neg == 0 {
return Ok(true);
} if neg == -libc::ENOENT {
return Ok(false);
} return Ok(true);
}
if nlmsg_type == SOCK_DIAG_BY_FAMILY || nlmsg_type == nlmsg_done {
return Ok(true); }
pos = nlmsg_align(pos + nlmsg_len);
}
}
}
pub fn has_symbol(sym: &CStr) -> bool {
unsafe { Library::this().get::<*const ()>(sym.to_bytes_with_nul()) }.is_ok()
}
pub fn check_fd_leaks(fd_max: Option<RawFd>) -> u32 {
let proc_fd_path = Path::new("/proc/self/fd");
let mut dir = match Dir::open(proc_fd_path, OFlag::O_RDONLY, Mode::empty()) {
Ok(d) => d,
Err(e) => {
eprintln!("Failed to open /proc/self/fd: {e}");
return u32::MAX;
}
};
let mut leaks_found: u32 = 0;
let dir_fd = dir.as_raw_fd();
let fd_limit = fd_max.unwrap_or(2);
for entry in dir.iter() {
let entry = match entry {
Ok(e) => e,
Err(_) => continue,
};
let fd_str = entry.file_name().to_string_lossy(); let fd = match fd_str.parse::<RawFd>() {
Ok(fd) => fd,
Err(_) => continue,
};
if fd <= fd_limit || fd == dir_fd {
continue;
}
let link_path = proc_fd_path.join(fd_str.into_owned());
#[expect(clippy::disallowed_methods)]
match std::fs::read_link(&link_path) {
Ok(target_path) => {
eprintln!("!!! Leaked file descriptor {fd} -> {target_path:?} !!!");
leaks_found = leaks_found.saturating_add(1);
}
Err(error) => {
eprintln!("Failed to read link for FD {fd}: {error}");
}
}
}
leaks_found
}
pub fn list_fds(pid: Option<Pid>) {
let mut path = match pid {
Some(pid) => XPathBuf::from(format!("/proc/{}/fd", pid.as_raw())),
None => XPathBuf::from("/proc/self/fd"),
};
let mut dir = match Dir::open(&path, OFlag::O_RDONLY, Mode::empty()) {
Ok(dir) => dir,
Err(errno) => {
eprintln!("list_fds: Failed to open {path}: {errno}");
return;
}
};
eprintln!(
"list_fds: {}",
pid.map(|p| p.as_raw().to_string())
.unwrap_or_else(|| "self".to_string())
);
eprintln!("fd\ttarget");
let dfd = dir.as_raw_fd();
for entry in dir.iter() {
let entry = match entry {
Ok(entry) => entry,
Err(_) => continue,
};
let fd = match btoi::<RawFd>(entry.file_name().to_bytes()) {
Ok(fd) => fd,
Err(_) => continue,
};
if fd == dfd {
continue;
}
path.push_fd(fd);
match readlinkat(AT_BADFD, &path) {
Ok(target) => eprintln!("{fd}\t{target}"),
Err(errno) => eprintln!("{fd}\t!!! {errno}"),
}
path.pop();
}
}
pub fn safe_drop_cap(cap: caps::Capability) -> Result<(), caps::errors::CapsError> {
caps::drop(None, caps::CapSet::Effective, cap)?;
caps::drop(None, caps::CapSet::Ambient, cap)?;
caps::drop(None, caps::CapSet::Inheritable, cap)?;
caps::drop(None, caps::CapSet::Permitted, cap)
}
pub fn safe_drop_caps() -> SydResult<()> {
Ok(caps::set_all(
None,
caps::Capabilities::empty(),
caps::Capabilities::empty(),
caps::Capabilities::empty(),
)?)
}
#[inline]
#[expect(unreachable_patterns)]
pub(crate) fn is_coredump(sig: i32) -> bool {
matches!(
sig,
libc::SIGABRT
| libc::SIGBUS
| libc::SIGFPE
| libc::SIGILL
| libc::SIGIOT
| libc::SIGKILL
| libc::SIGQUIT
| libc::SIGSEGV
| libc::SIGSYS
| libc::SIGTRAP
| libc::SIGXCPU
| libc::SIGXFSZ
)
}
#[expect(clippy::disallowed_methods)]
pub fn selinux_enabled() -> Result<bool, Errno> {
let data = read_to_string("/proc/thread-self/mounts").map_err(|err| err2no(&err))?;
let data = XPath::from_bytes(data.as_bytes());
Ok(data.contains(b"selinux"))
}
#[expect(clippy::disallowed_methods)]
pub fn apparmor_enabled() -> Result<bool, Errno> {
if !exists("/sys/kernel/security/apparmor").map_err(|err| err2no(&err))? {
return Ok(false);
}
let data =
read_to_string("/sys/module/apparmor/parameters/enabled").map_err(|err| err2no(&err))?;
Ok(data.as_bytes().first() == Some(&b'Y'))
}
#[expect(clippy::disallowed_methods)]
pub fn selinux_enforced() -> Result<bool, Errno> {
let path = if exists("/sys/fs/selinux").map_err(|err| err2no(&err))? {
"/sys/fs/selinux/enforce"
} else if exists("/selinux").map_err(|err| err2no(&err))? {
"/selinux/enforce"
} else {
return Ok(false);
};
let data: u8 = read_to_string(path)
.map_err(|err| err2no(&err))?
.parse()
.or(Err(Errno::EINVAL))?;
Ok(data != 0)
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ExportMode {
BerkeleyPacketFilter,
PseudoFiltercode,
}
impl FromStr for ExportMode {
type Err = Errno;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"bpf" => Ok(Self::BerkeleyPacketFilter),
"pfc" => Ok(Self::PseudoFiltercode),
_ => Err(Errno::EINVAL),
}
}
}
impl ExportMode {
#[expect(clippy::disallowed_methods)]
pub fn from_env() -> Option<ExportMode> {
Self::from_str(&std::env::var(crate::config::ENV_DUMP_SCMP).ok()?).ok()
}
}
#[cfg(target_arch = "x86")]
#[inline(always)]
pub unsafe fn fork_fast() {
std::arch::asm!(
"mov eax, 0x2", "int 0x80", out("eax") _,
);
}
#[cfg(target_arch = "x86_64")]
#[inline(always)]
pub unsafe fn fork_fast() {
std::arch::asm!(
"mov rax, 57", "syscall",
out("rax") _,
);
}
#[cfg(target_arch = "aarch64")]
#[inline(always)]
pub unsafe fn fork_fast() {
std::arch::asm!(
"mov x0, 17", "mov x1, 0", "mov x8, 220", "svc 0",
options(nostack),
);
}
#[cfg(target_arch = "arm")]
#[inline(always)]
pub unsafe fn fork_fast() {
std::arch::asm!(
"mov r7, #2", "swi #0", out("r0") _,
options(nostack),
);
}
#[cfg(target_arch = "riscv64")]
#[inline(always)]
pub unsafe fn fork_fast() {
std::arch::asm!(
"li a7, 220", "li a0, 17", "li a1, 0", "ecall", out("a0") _, options(nostack),
);
}
#[cfg(any(
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "s390x"
))]
#[inline(always)]
pub unsafe fn fork_fast() {
let _ = libc::syscall(libc::SYS_fork);
}
#[cfg(not(any(
target_arch = "aarch64",
target_arch = "arm",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "riscv64",
target_arch = "riscv64",
target_arch = "s390x",
target_arch = "x86",
target_arch = "x86_64",
)))]
#[inline(always)]
pub unsafe fn fork_fast() {
let _ = libc::fork();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_export_mode_1() {
assert_eq!(
"bpf".parse::<ExportMode>().unwrap(),
ExportMode::BerkeleyPacketFilter
);
}
#[test]
fn test_export_mode_2() {
assert_eq!(
"pfc".parse::<ExportMode>().unwrap(),
ExportMode::PseudoFiltercode
);
}
#[test]
fn test_export_mode_3() {
assert_eq!(
"BPF".parse::<ExportMode>().unwrap(),
ExportMode::BerkeleyPacketFilter
);
}
#[test]
fn test_export_mode_4() {
assert!("invalid".parse::<ExportMode>().is_err());
}
#[test]
fn test_scmp_arch_1() {
assert_eq!(scmp_arch_bits(ScmpArch::X8664), 64);
assert_eq!(scmp_arch_bits(ScmpArch::X86), 32);
assert_eq!(scmp_arch_bits(ScmpArch::Aarch64), 64);
assert_eq!(scmp_arch_bits(ScmpArch::Arm), 32);
assert_eq!(scmp_arch_bits(ScmpArch::Riscv64), 64);
assert_eq!(scmp_arch_bits(ScmpArch::Mips), 32);
assert_eq!(scmp_arch_bits(ScmpArch::Mips64), 64);
}
#[test]
fn test_scmp_arch_2() {
assert!(!scmp_arch_is_compat32(ScmpArch::X8664));
assert!(scmp_arch_is_compat32(ScmpArch::X86));
assert!(scmp_arch_is_compat32(ScmpArch::X32));
assert!(scmp_arch_is_compat32(ScmpArch::Arm));
assert!(scmp_arch_is_compat32(ScmpArch::Mips64N32));
}
#[test]
fn test_scmp_arch_3() {
assert!(scmp_arch_is_big_endian(ScmpArch::Mips));
assert!(scmp_arch_is_big_endian(ScmpArch::Ppc64));
assert!(scmp_arch_is_big_endian(ScmpArch::S390X));
assert!(!scmp_arch_is_big_endian(ScmpArch::X86));
assert!(!scmp_arch_is_big_endian(ScmpArch::Aarch64));
assert!(!scmp_arch_is_big_endian(ScmpArch::Riscv64));
}
#[test]
fn test_scmp_arch_4() {
assert!(scmp_arch_is_mips(ScmpArch::Mips));
assert!(scmp_arch_is_mips(ScmpArch::Mipsel));
assert!(scmp_arch_is_mips(ScmpArch::Mips64));
assert!(!scmp_arch_is_mips(ScmpArch::X86));
assert!(!scmp_arch_is_mips(ScmpArch::Arm));
}
#[test]
fn test_scmp_arch_5() {
let raw = scmp_arch_raw(ScmpArch::X8664);
assert_eq!(scmp_arch(raw), Ok(ScmpArch::X8664));
let raw = scmp_arch_raw(ScmpArch::Aarch64);
assert_eq!(scmp_arch(raw), Ok(ScmpArch::Aarch64));
let raw = scmp_arch_raw(ScmpArch::Riscv64);
assert_eq!(scmp_arch(raw), Ok(ScmpArch::Riscv64));
let raw = scmp_arch_raw(ScmpArch::Arm);
assert_eq!(scmp_arch(raw), Ok(ScmpArch::Arm));
}
#[test]
fn test_scmp_arch_6() {
assert_eq!(scmp_arch(0xDEAD_BEEF), Err(Errno::ENOSYS));
}
#[test]
fn test_scmp_arch_7() {
assert_eq!(scmp_arch_raw(ScmpArch::X86), libseccomp_sys::SCMP_ARCH_X86);
}
#[test]
fn test_scmp_arch_8() {
assert_eq!(
scmp_arch_raw(ScmpArch::Mips),
libseccomp_sys::SCMP_ARCH_MIPS
);
}
#[test]
fn test_is_valid_ptr_1() {
let arch = ScmpArch::X8664;
assert!(!is_valid_ptr(0, arch));
assert!(is_valid_ptr(0x7fff_ffff_ffff, arch));
assert!(is_valid_ptr(0x7fff_ffff_ffff_ffff, arch));
assert!(!is_valid_ptr(0x8000_0000_0000_0000, arch));
assert!(!is_valid_ptr(u64::MAX, arch));
}
#[test]
fn test_is_valid_ptr_2() {
let arch = ScmpArch::X32;
assert!(!is_valid_ptr(0, arch));
assert!(!is_valid_ptr(0xffff_ffff, arch));
assert!(!is_valid_ptr(0x1_0000_0000, arch));
assert!(!is_valid_ptr(u64::MAX, arch));
}
#[test]
fn test_is_valid_ptr_3() {
let arch = ScmpArch::X86;
assert!(!is_valid_ptr(0, arch));
assert!(is_valid_ptr(0xbfff_ffff, arch));
assert!(is_valid_ptr(0xc000_0000, arch));
assert!(!is_valid_ptr(0xffff_ffff, arch));
assert!(!is_valid_ptr(0x1_0000_0000, arch));
assert!(!is_valid_ptr(u64::MAX, arch));
}
#[test]
fn test_is_valid_ptr_4() {
let arch = ScmpArch::Mips;
assert!(!is_valid_ptr(0, arch));
assert!(is_valid_ptr(0x7fff_ffff, arch));
assert!(is_valid_ptr(0x8000_0000, arch));
assert!(!is_valid_ptr(0xffff_ffff, arch));
assert!(!is_valid_ptr(0x1_0000_0000, arch));
assert!(!is_valid_ptr(u64::MAX, arch));
}
#[test]
fn test_is_valid_ptr_5() {
let arch = ScmpArch::Aarch64;
assert!(!is_valid_ptr(0, arch));
assert!(is_valid_ptr(0x0000_ffff_ffff_ffff, arch));
assert!(!is_valid_ptr(0x0001_0000_0000_0000, arch));
}
#[test]
fn test_limit_kernel_ptr() {
assert_eq!(limit_kernel_ptr(ScmpArch::X8664), 0x7fff_ffff_ffff_ffff);
assert_eq!(limit_kernel_ptr(ScmpArch::X86), 0x0000_0000_ffff_f000);
assert_eq!(limit_kernel_ptr(ScmpArch::Aarch64), 0x0000_ffff_ffff_ffff);
assert_eq!(limit_kernel_ptr(ScmpArch::Arm), 0x0000_0000_ffff_f000);
}
#[test]
fn test_resolve_syscall_1() {
let nr = resolve_syscall("read");
assert!(nr.is_some());
assert!(nr.unwrap() >= 0);
}
#[test]
fn test_resolve_syscall_2() {
let nr = resolve_syscall("write");
assert!(nr.is_some());
}
#[test]
fn test_resolve_syscall_3() {
let nr = resolve_syscall("nonexistent_syscall_xyz");
assert!(nr.is_none());
}
#[test]
fn test_secure_getenv() {
let result = secure_getenv("PATH");
if cfg!(feature = "trusted") {
assert!(result.is_some());
} else {
assert!(result.is_none());
}
}
#[test]
fn test_low32_ge_blocks_1() {
let bs = low32_ge_blocks(0);
for v in 0u64..=4096 {
assert!(bs.iter().any(|&(m, d)| (v & m) == d), "v={v}");
}
assert!(bs.iter().any(|&(m, d)| (u64::from(u32::MAX) & m) == d));
}
#[test]
fn test_low32_ge_blocks_2() {
let bs = low32_ge_blocks(1);
assert!(!bs.iter().any(|&(m, d)| (0u64 & m) == d));
for v in 1u64..=4096 {
assert!(bs.iter().any(|&(m, d)| (v & m) == d), "v={v}");
}
assert!(bs.iter().any(|&(m, d)| (u64::from(u32::MAX) & m) == d));
}
#[test]
fn test_low32_ge_blocks_3() {
let bs = low32_ge_blocks(4);
for v in 0u64..=3 {
assert!(!bs.iter().any(|&(m, d)| (v & m) == d), "v={v}");
}
for v in 4u64..=4096 {
assert!(bs.iter().any(|&(m, d)| (v & m) == d), "v={v}");
}
assert!(bs.iter().any(|&(m, d)| (u64::from(u32::MAX) & m) == d));
assert!(bs.iter().any(|&(m, d)| (u64::from(u32::MAX - 1) & m) == d));
}
#[test]
fn test_low32_ge_blocks_4() {
let bs = low32_ge_blocks(38);
for v in 0u64..=37 {
assert!(!bs.iter().any(|&(m, d)| (v & m) == d), "v={v}");
}
for v in 38u64..=4096 {
assert!(bs.iter().any(|&(m, d)| (v & m) == d), "v={v}");
}
}
#[test]
fn test_low32_ge_blocks_5() {
let bs = low32_ge_blocks(46);
for v in 0u64..=45 {
assert!(!bs.iter().any(|&(m, d)| (v & m) == d), "v={v}");
}
for v in 46u64..=4096 {
assert!(bs.iter().any(|&(m, d)| (v & m) == d), "v={v}");
}
}
#[test]
fn test_low32_ge_blocks_6() {
let bs = low32_ge_blocks(u32::MAX as u64);
for v in [0u64, 1, 100, 0xFFFFFFFE] {
assert!(!bs.iter().any(|&(m, d)| (v & m) == d), "v={v}");
}
assert!(bs.iter().any(|&(m, d)| (0xFFFFFFFFu64 & m) == d));
}
#[test]
fn test_low32_ge_blocks_7() {
let bs = low32_ge_blocks(u64::from(u32::MAX) + 1);
for v in [0u64, 1, 100, 0xFFFFFFFE] {
assert!(!bs.iter().any(|&(m, d)| (v & m) == d), "v={v}");
}
assert!(bs.iter().any(|&(m, d)| (0xFFFFFFFFu64 & m) == d));
}
#[test]
fn test_low32_ge_blocks_8() {
let bs = low32_ge_blocks(u64::MAX);
for v in [0u64, 1, 100, 0xFFFFFFFE] {
assert!(!bs.iter().any(|&(m, d)| (v & m) == d), "v={v}");
}
assert!(bs.iter().any(|&(m, d)| (0xFFFFFFFFu64 & m) == d));
}
#[test]
fn test_low32_ge_blocks_9() {
for k in 0u32..=31 {
let val = 1u64 << k;
let bs = low32_ge_blocks(val);
if val > 0 {
let below = val - 1;
assert!(!bs.iter().any(|&(m, d)| (below & m) == d), "k={k} below");
}
assert!(bs.iter().any(|&(m, d)| (val & m) == d), "k={k} val");
if val < u32::MAX as u64 {
let above = val + 1;
assert!(bs.iter().any(|&(m, d)| (above & m) == d), "k={k} above");
}
}
}
#[test]
fn test_low32_ge_blocks_10() {
for val in 0u64..=300 {
let bs = low32_ge_blocks(val);
for v in 0u64..=4096 {
let actual = bs.iter().any(|&(m, d)| (v & m) == d);
assert_eq!(actual, v >= val, "val={val} v={v}");
}
}
}
#[test]
fn test_low32_ge_blocks_11() {
for val in [
0u64,
1,
4,
5,
17,
38,
46,
64,
0x80000000,
u32::MAX as u64,
u64::from(u32::MAX) + 1,
u64::MAX,
] {
for &(m, d) in &low32_ge_blocks(val) {
assert_eq!(m & !0xFFFF_FFFFu64, 0, "val={val} mask out of u32");
assert_eq!(d & !m, 0, "val={val} datum bit outside mask");
}
}
}
#[test]
fn test_low32_ge_blocks_12() {
for val in [0u64, 1, 17, 38, 46, 100, 0x80000000, u32::MAX as u64] {
assert!(low32_ge_blocks(val).len() <= 64, "val={val}");
}
}
#[test]
fn test_low32_ge_blocks_13() {
let bs = low32_ge_blocks(0xFFFFFFFE);
assert!(!bs.iter().any(|&(m, d)| (0xFFFFFFFDu64 & m) == d));
assert!(bs.iter().any(|&(m, d)| (0xFFFFFFFEu64 & m) == d));
assert!(bs.iter().any(|&(m, d)| (0xFFFFFFFFu64 & m) == d));
}
#[test]
fn test_low32_ge_blocks_14() {
let bs = low32_ge_blocks(0x80000000);
assert!(!bs.iter().any(|&(m, d)| (0x7FFFFFFFu64 & m) == d));
for v in [0x80000000u64, 0xC0000000, 0xE0000000, 0xFFFFFFFF] {
assert!(bs.iter().any(|&(m, d)| (v & m) == d), "v={v:#x}");
}
}
#[test]
fn test_low32_ge_blocks_15() {
for val in [4u64, 38, 46, 64, 1024, 0x80000000, u32::MAX as u64] {
let ge = low32_ge_blocks(val);
let le = low32_le_blocks(val - 1);
for v in 0u64..=8192 {
let in_ge = ge.iter().any(|&(m, d)| (v & m) == d);
let in_le = le.iter().any(|&(m, d)| (v & m) == d);
assert!(in_ge ^ in_le, "val={val:#x} v={v:#x} ge={in_ge} le={in_le}");
}
for &v in &[u64::from(u32::MAX) - 1, u64::from(u32::MAX)] {
let in_ge = ge.iter().any(|&(m, d)| (v & m) == d);
let in_le = le.iter().any(|&(m, d)| (v & m) == d);
assert!(in_ge ^ in_le, "val={val:#x} v={v:#x} ge={in_ge} le={in_le}");
}
}
}
#[test]
fn test_low32_ge_blocks_16() {
let bs = low32_ge_blocks(38);
for hi in [
0u64,
0x1234_5678_0000_0000,
0x8000_0000_0000_0000,
0xFFFF_FFFF_0000_0000,
] {
for lo in [37u64, 38, 39, 100, 0xFFFFFFFF] {
let v = lo | hi;
let actual = bs.iter().any(|&(m, d)| (v & m) == d);
assert_eq!(actual, lo >= 38, "hi={hi:#x} lo={lo:#x}");
}
}
}
#[test]
fn test_low32_ge_blocks_17() {
for v in 0u64..=u16::MAX as u64 {
let bs = low32_ge_blocks(v);
assert!(bs.iter().any(|&(m, d)| (v & m) == d), "self v={v}");
if v > 0 {
let below = v - 1;
assert!(!bs.iter().any(|&(m, d)| (below & m) == d), "below v={v}");
}
}
}
#[test]
fn test_low32_ge_blocks_18() {
for val in [4u64, 38, 46, 1024, 0x80000000] {
let bs = low32_ge_blocks(val);
assert!(
!bs.iter().any(|&(m, d)| ((val - 1) & m) == d),
"val={val} below"
);
assert!(bs.iter().any(|&(m, d)| (val & m) == d), "val={val} self");
assert!(
bs.iter().any(|&(m, d)| ((val + 1) & m) == d),
"val={val} above"
);
}
}
#[test]
fn test_low32_le_blocks_1() {
let bs = low32_le_blocks(0);
assert!(bs.iter().any(|&(m, d)| (0u64 & m) == d));
for v in 1u64..=4096 {
assert!(!bs.iter().any(|&(m, d)| (v & m) == d), "v={v}");
}
assert!(!bs.iter().any(|&(m, d)| (u64::from(u32::MAX) & m) == d));
}
#[test]
fn test_low32_le_blocks_2() {
let bs = low32_le_blocks(1);
assert!(bs.iter().any(|&(m, d)| (0u64 & m) == d));
assert!(bs.iter().any(|&(m, d)| (1u64 & m) == d));
for v in 2u64..=4096 {
assert!(!bs.iter().any(|&(m, d)| (v & m) == d), "v={v}");
}
}
#[test]
fn test_low32_le_blocks_3() {
let bs = low32_le_blocks(4);
for v in 0u64..=4 {
assert!(bs.iter().any(|&(m, d)| (v & m) == d), "v={v}");
}
for v in 5u64..=4096 {
assert!(!bs.iter().any(|&(m, d)| (v & m) == d), "v={v}");
}
}
#[test]
fn test_low32_le_blocks_4() {
let bs = low32_le_blocks(37);
for v in 0u64..=37 {
assert!(bs.iter().any(|&(m, d)| (v & m) == d), "v={v}");
}
for v in 38u64..=4096 {
assert!(!bs.iter().any(|&(m, d)| (v & m) == d), "v={v}");
}
}
#[test]
fn test_low32_le_blocks_5() {
let bs = low32_le_blocks(u32::MAX as u64);
for v in [0u64, 1, 100, 0xFFFFFFFE, 0xFFFFFFFF] {
assert!(bs.iter().any(|&(m, d)| (v & m) == d), "v={v:#x}");
}
}
#[test]
fn test_low32_le_blocks_6() {
let bs = low32_le_blocks(u64::from(u32::MAX) + 1);
for v in [0u64, 1, 0xFFFFFFFE, 0xFFFFFFFF] {
assert!(bs.iter().any(|&(m, d)| (v & m) == d), "v={v:#x}");
}
}
#[test]
fn test_low32_le_blocks_7() {
let bs = low32_le_blocks(u64::MAX);
for v in [0u64, 1, 0xFFFFFFFE, 0xFFFFFFFF] {
assert!(bs.iter().any(|&(m, d)| (v & m) == d), "v={v:#x}");
}
}
#[test]
fn test_low32_le_blocks_8() {
for k in 0u32..=31 {
let val = 1u64 << k;
let bs = low32_le_blocks(val);
if val > 0 {
let below = val - 1;
assert!(bs.iter().any(|&(m, d)| (below & m) == d), "k={k} below");
}
assert!(bs.iter().any(|&(m, d)| (val & m) == d), "k={k} val");
if val < u32::MAX as u64 {
let above = val + 1;
assert!(!bs.iter().any(|&(m, d)| (above & m) == d), "k={k} above");
}
}
}
#[test]
fn test_low32_le_blocks_9() {
for val in 0u64..=300 {
let bs = low32_le_blocks(val);
for v in 0u64..=4096 {
let actual = bs.iter().any(|&(m, d)| (v & m) == d);
assert_eq!(actual, v <= val, "val={val} v={v}");
}
}
}
#[test]
fn test_low32_le_blocks_10() {
for val in [
0u64,
1,
4,
5,
17,
38,
46,
64,
0x80000000,
u32::MAX as u64,
u64::from(u32::MAX) + 1,
u64::MAX,
] {
for &(m, d) in &low32_le_blocks(val) {
assert_eq!(m & !0xFFFF_FFFFu64, 0, "val={val} mask out of u32");
assert_eq!(d & !m, 0, "val={val} datum bit outside mask");
}
}
}
#[test]
fn test_low32_le_blocks_11() {
for val in [0u64, 1, 17, 38, 46, 100, 0x80000000, u32::MAX as u64] {
assert!(low32_le_blocks(val).len() <= 64, "val={val}");
}
}
#[test]
fn test_low32_le_blocks_12() {
let bs = low32_le_blocks(0x7FFFFFFF);
assert!(bs.iter().any(|&(m, d)| (0x7FFFFFFEu64 & m) == d));
assert!(bs.iter().any(|&(m, d)| (0x7FFFFFFFu64 & m) == d));
assert!(!bs.iter().any(|&(m, d)| (0x80000000u64 & m) == d));
assert!(!bs.iter().any(|&(m, d)| (0xFFFFFFFFu64 & m) == d));
}
#[test]
fn test_low32_le_blocks_13() {
let bs = low32_le_blocks(0xFFFFFFFE);
assert!(bs.iter().any(|&(m, d)| (0xFFFFFFFDu64 & m) == d));
assert!(bs.iter().any(|&(m, d)| (0xFFFFFFFEu64 & m) == d));
assert!(!bs.iter().any(|&(m, d)| (0xFFFFFFFFu64 & m) == d));
}
#[test]
fn test_low32_le_blocks_14() {
let bs = low32_le_blocks(37);
for hi in [0u64, 0x1234_5678_0000_0000, 0xFFFF_FFFF_0000_0000] {
for lo in [0u64, 36, 37, 38, 0xFFFFFFFF] {
let v = lo | hi;
let actual = bs.iter().any(|&(m, d)| (v & m) == d);
assert_eq!(actual, lo <= 37, "hi={hi:#x} lo={lo:#x}");
}
}
}
#[test]
fn test_low32_le_blocks_15() {
for v in 0u64..=u16::MAX as u64 {
let bs = low32_le_blocks(v);
assert!(bs.iter().any(|&(m, d)| (v & m) == d), "self v={v}");
if v < u32::MAX as u64 {
let above = v + 1;
assert!(!bs.iter().any(|&(m, d)| (above & m) == d), "above v={v}");
}
}
}
#[test]
fn test_low32_le_blocks_16() {
for val in [4u64, 38, 46, 1024, 0x80000000] {
let bs = low32_le_blocks(val);
assert!(
bs.iter().any(|&(m, d)| ((val - 1) & m) == d),
"val={val} below"
);
assert!(bs.iter().any(|&(m, d)| (val & m) == d), "val={val} self");
assert!(
!bs.iter().any(|&(m, d)| ((val + 1) & m) == d),
"val={val} above"
);
}
}
}