use std::sync::{Arc, RwLock};
use nix::{errno::Errno, unistd::Pid};
#[cfg(feature = "kcov")]
use crate::confine::{
SYS_CHDIR, SYS_EXECVE, SYS_EXECVEAT, SYS_FCHDIR, SYS_MMAP, SYS_MMAP2, SYS_SETGROUPS,
SYS_SETGROUPS32,
};
use crate::{
confine::{
scmp_arch, scmp_arch_raw, SydArch, SydSys,
SydSys::{
SysChdir, SysExecve, SysExecveat, SysFchdir, SysMmap, SysMmap2, SysSetgroups,
SysSetgroups32,
},
},
cookie::safe_kill,
kernel::ptrace::{
chdir::{sysenter_chdir, sysenter_fchdir, sysexit_chdir},
exec::sysenter_exec,
mmap::{ptrace_mmap_args, sysenter_mmap, sysexit_mmap, MmapSyscall},
setgroups::{sysenter_setgroups, sysenter_setgroups32},
},
ptrace::{
ptrace_cont, ptrace_set_arg, ptrace_set_return, ptrace_skip_syscall, ptrace_syscall_info,
},
sandbox::{Capability, Sandbox, SandboxGuard},
workers::WorkerCache,
};
pub(crate) mod chdir;
pub(crate) mod exec;
pub(crate) mod mmap;
pub(crate) mod setgroups;
pub(crate) mod event;
pub(crate) fn handle_ptrace_sysenter(
pid: Pid,
info: ptrace_syscall_info,
cache: &Arc<WorkerCache>,
sandbox: &Arc<RwLock<Sandbox>>,
) -> Result<(), Errno> {
#[expect(clippy::disallowed_methods)]
let arch: SydArch = scmp_arch(info.arch).unwrap().into();
#[expect(clippy::disallowed_methods)]
let info_scmp = info.seccomp().unwrap();
#[expect(clippy::cast_possible_truncation)]
let scmp_trace_data = info_scmp.ret_data as u16;
let syscall = match SydSys::try_from(scmp_trace_data) {
Ok(syscall) => syscall,
Err(_) => unreachable!("BUG: invalid syscall data {scmp_trace_data}!"),
};
match syscall {
SysChdir | SysFchdir => {
#[cfg(feature = "kcov")]
{
let scno = if syscall == SysChdir {
*SYS_CHDIR
} else {
*SYS_FCHDIR
};
let scno = if let Some(scno) = scno {
scno
} else {
let scno: libc::c_long = 4000;
scno.saturating_add(scmp_trace_data.into())
};
crate::kcov::abi::kcov_attach(pid);
crate::kcov::abi::kcov_set_syscall(scno);
let _ = crate::kcov::abi::kcov_enter_for(pid);
crate::kcov_edge!();
}
let my_sandbox =
SandboxGuard::Read(sandbox.read().unwrap_or_else(|err| err.into_inner()));
let mut cont = false;
let result = if my_sandbox.flags.ghost() {
Err(Errno::ENOSYS)
} else if !my_sandbox.enabled(Capability::CAP_CHDIR) {
cont = true;
Err(Errno::ECANCELED)
} else if syscall == SysChdir {
sysenter_chdir(pid, &my_sandbox, arch.into(), info_scmp)
} else {
sysenter_fchdir(pid, &my_sandbox, arch.into(), info_scmp)
};
drop(my_sandbox);
#[cfg(feature = "kcov")]
{
crate::kcov_edge!();
let _ = crate::kcov::abi::kcov_exit_for(pid);
}
if cont {
return Err(Errno::ECANCELED);
}
let (file_info, move_path) = match result {
Ok((file_info, move_path)) => (file_info, move_path),
Err(errno) => return deny_syscall(pid, info.arch, errno, cache),
};
if cache
.add_chdir(pid, scmp_trace_data, file_info, move_path)
.is_err()
{
let _ = safe_kill(pid, libc::SIGKILL);
return Err(Errno::ESRCH);
}
Ok(())
}
SysMmap | SysMmap2 => {
let data = if let Some(data) = info.seccomp() {
data
} else {
unreachable!("BUG: Invalid system call information returned by kernel!");
};
let sys_mmap = if syscall == SysMmap {
MmapSyscall::Mmap
} else {
MmapSyscall::Mmap2
};
#[cfg(feature = "kcov")]
{
let scno = if syscall == SysMmap {
*SYS_MMAP
} else {
*SYS_MMAP2
};
let scno = if let Some(scno) = scno {
scno
} else {
let scno: libc::c_long = 4000;
scno.saturating_add(scmp_trace_data.into())
};
crate::kcov::abi::kcov_attach(pid);
crate::kcov::abi::kcov_set_syscall(scno);
let _ = crate::kcov::abi::kcov_enter_for(pid);
crate::kcov_edge!();
}
let args = if sys_mmap == MmapSyscall::Mmap {
match ptrace_mmap_args(pid, arch.into(), data.args) {
Ok(args) => args,
Err(errno) => return deny_syscall(pid, info.arch, errno, cache),
}
} else {
data.args
};
let my_sandbox =
SandboxGuard::Read(sandbox.read().unwrap_or_else(|err| err.into_inner()));
let result = if my_sandbox.flags.ghost() {
Err(Errno::ENOSYS)
} else {
sysenter_mmap(pid, &my_sandbox, sys_mmap, &args)
};
drop(my_sandbox);
#[cfg(feature = "kcov")]
{
crate::kcov_edge!();
let _ = crate::kcov::abi::kcov_exit_for(pid);
}
match result {
Ok((true, move_path)) => {
if cache.add_mmap(pid, sys_mmap, move_path).is_err() {
let _ = safe_kill(pid, libc::SIGKILL);
return Err(Errno::ESRCH);
}
Ok(())
}
Ok((false, _)) => {
Err(Errno::ECANCELED)
}
Err(errno) => deny_syscall(pid, info.arch, errno, cache),
}
}
SysExecve | SysExecveat => {
#[cfg(feature = "kcov")]
{
let scno = if syscall == SysExecve {
*SYS_EXECVE
} else {
*SYS_EXECVEAT
};
let scno = if let Some(scno) = scno {
scno
} else {
let scno: libc::c_long = 4000;
scno.saturating_add(scmp_trace_data.into())
};
crate::kcov::abi::kcov_attach(pid);
crate::kcov::abi::kcov_set_syscall(scno);
let _ = crate::kcov::abi::kcov_enter_for(pid);
crate::kcov_edge!();
}
let my_sandbox =
SandboxGuard::Read(sandbox.read().unwrap_or_else(|err| err.into_inner()));
let result = if my_sandbox.flags.ghost() {
Err(Errno::ENOSYS)
} else {
sysenter_exec(pid, info, cache, &my_sandbox)
};
drop(my_sandbox);
#[cfg(feature = "kcov")]
{
crate::kcov_edge!();
let _ = crate::kcov::abi::kcov_exit_for(pid);
}
if let Err(errno) = result {
return deny_syscall(pid, info.arch, errno, cache);
}
Err(Errno::ECANCELED)
}
SysSetgroups | SysSetgroups32 => {
#[cfg(feature = "kcov")]
{
let scno = if syscall == SysSetgroups {
*SYS_SETGROUPS
} else {
*SYS_SETGROUPS32
};
let scno = if let Some(scno) = scno {
scno
} else {
let scno: libc::c_long = 4000;
scno.saturating_add(scmp_trace_data.into())
};
crate::kcov::abi::kcov_attach(pid);
crate::kcov::abi::kcov_set_syscall(scno);
let _ = crate::kcov::abi::kcov_enter_for(pid);
crate::kcov_edge!();
}
let result = if syscall == SysSetgroups {
sysenter_setgroups(pid, arch.into(), info_scmp)
} else {
sysenter_setgroups32(pid, arch.into(), info_scmp)
};
#[cfg(feature = "kcov")]
{
crate::kcov_edge!();
let _ = crate::kcov::abi::kcov_exit_for(pid);
}
if let Err(errno) = result {
return deny_syscall(pid, info.arch, errno, cache);
}
if let Err(errno) = ptrace_set_arg(pid, scmp_arch_raw(arch.into()), 0, 0) {
if errno != Errno::ESRCH {
let _ = safe_kill(pid, libc::SIGKILL);
}
return Err(Errno::ESRCH);
}
Err(Errno::ECANCELED)
}
_ => unreachable!("BUG: invalid syscall data {scmp_trace_data}!"),
}
}
pub(crate) fn handle_ptrace_sysexit(
pid: Pid,
info: ptrace_syscall_info,
cache: &Arc<WorkerCache>,
sandbox: &Arc<RwLock<Sandbox>>,
) -> Result<(), Errno> {
if let Some(entry) = cache.get_chdir(pid) {
#[cfg(feature = "kcov")]
{
let scno = if entry.data == u16::from(SysChdir) {
*SYS_CHDIR
} else {
*SYS_FCHDIR
};
let scno = if let Some(scno) = scno {
scno
} else {
let scno: libc::c_long = 4000;
scno.saturating_add(entry.data.into())
};
crate::kcov::abi::kcov_attach(pid);
crate::kcov::abi::kcov_set_syscall(scno);
let _ = crate::kcov::abi::kcov_enter_for(pid);
crate::kcov_edge!();
}
let result = {
let sandbox = SandboxGuard::Read(sandbox.read().unwrap_or_else(|err| err.into_inner()));
sysexit_chdir(pid, &sandbox, info, entry.info, entry.path)
};
#[cfg(feature = "kcov")]
{
crate::kcov_edge!();
let _ = crate::kcov::abi::kcov_exit_for(pid);
}
result
} else if let Some(entry) = cache.get_mmap(pid) {
#[cfg(feature = "kcov")]
{
let scno = if entry.sys == MmapSyscall::Mmap {
*SYS_MMAP
} else {
*SYS_MMAP2
};
let scno = if let Some(scno) = scno {
scno
} else if entry.sys == MmapSyscall::Mmap {
let scno: libc::c_long = 4000;
scno.saturating_add(u16::from(SysMmap).into())
} else {
let scno: libc::c_long = 4000;
scno.saturating_add(u16::from(SysMmap2).into())
};
crate::kcov::abi::kcov_attach(pid);
crate::kcov::abi::kcov_set_syscall(scno);
let _ = crate::kcov::abi::kcov_enter_for(pid);
crate::kcov_edge!();
}
let result = {
let sandbox = SandboxGuard::Read(sandbox.read().unwrap_or_else(|err| err.into_inner()));
sysexit_mmap(pid, &sandbox, info, entry.sys, entry.path)
};
#[cfg(feature = "kcov")]
{
crate::kcov_edge!();
let _ = crate::kcov::abi::kcov_exit_for(pid);
}
result
} else if let Some((pid, errno)) = cache.get_error(pid) {
ptrace_set_return(pid, info.arch, errno)
} else {
unreachable!("BUG: Invalid syscall exit stop: {info:?}");
}
}
fn deny_syscall(pid: Pid, arch: u32, errno: Errno, cache: &Arc<WorkerCache>) -> Result<(), Errno> {
let errno = if errno == Errno::ECANCELED {
None } else {
Some(errno)
};
if let Err(errno) = ptrace_skip_syscall(pid, arch, errno) {
if errno != Errno::ESRCH {
let _ = safe_kill(pid, libc::SIGKILL);
}
Err(Errno::ESRCH)
} else if cfg!(any(
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "mips64",
target_arch = "mips64r6",
target_arch = "s390x"
)) {
if cache.add_error(pid, errno).is_ok() {
Ok(())
} else {
let _ = safe_kill(pid, libc::SIGKILL);
Err(Errno::ESRCH)
}
} else {
let _ = ptrace_cont(pid, None);
Err(Errno::ESRCH)
}
}