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_RT_SIGRETURN,
SYS_SETGROUPS, SYS_SETGROUPS32, SYS_SIGRETURN,
};
use crate::{
cache::SigreturnTrampolineIP,
confine::{
scmp_arch, scmp_arch_has_single_step, scmp_arch_raw, SydArch, SydSys,
SydSys::{
SysChdir, SysExecve, SysExecveat, SysFchdir, SysMmap, SysMmap2, SysRtSigreturn,
SysSetgroups, SysSetgroups32, SysSigreturn,
},
},
cookie::safe_kill,
error,
hash::hex_encode_lower,
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},
},
proc::{proc_ip_in_sigtramp, proc_maps},
ptrace::{
ptrace_cont, ptrace_set_arg, ptrace_set_return, ptrace_skip_syscall, ptrace_syscall_info,
},
req::RemoteProcess,
sandbox::{Action, 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;
#[expect(clippy::cognitive_complexity)]
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)
}
SysSigreturn | SysRtSigreturn => {
#[cfg(feature = "kcov")]
{
let scno = if syscall == SysSigreturn {
*SYS_SIGRETURN
} else {
*SYS_RT_SIGRETURN
};
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 has_handler = cache.enter_sig_handle(pid);
let ip = info.instruction_pointer;
let has_savedip = if has_handler {
match cache.get_sig_trampoline_ip(pid) {
None if proc_ip_in_sigtramp(pid, ip) => {
cache.set_sig_trampoline_ip(pid, SigreturnTrampolineIP { lo: ip, hi: ip });
true
}
None if !scmp_arch_has_single_step(arch.into()) => {
cache.set_sig_trampoline_ip(pid, SigreturnTrampolineIP { lo: ip, hi: ip });
true
}
None => false,
Some(cookie) => cookie.matches(ip),
}
} else {
false
};
#[cfg(feature = "kcov")]
{
crate::kcov_edge!();
let _ = crate::kcov::abi::kcov_exit_for(pid);
}
if has_savedip {
return Ok(());
}
let log_scmp = {
SandboxGuard::Read(sandbox.read().unwrap_or_else(|err| err.into_inner())).log_scmp()
};
let memmap = if log_scmp {
match proc_maps(pid) {
Ok(memmap) => Some(memmap),
Err(Errno::ESRCH) => return Err(Errno::ESRCH), Err(_) => None,
}
} else {
None
};
let ip = info.instruction_pointer;
let sp = (info.stack_pointer & !0xF).saturating_sub(16);
let ip_mem = if log_scmp { Some([0u8; 64]) } else { None };
let sp_mem = if log_scmp { Some([0u8; 64]) } else { None };
let process = RemoteProcess::new(pid);
#[expect(clippy::disallowed_methods)]
let arch: SydArch = scmp_arch(info.arch).unwrap().into();
let is_realtime = syscall == SysRtSigreturn;
if let Some(mut ip_mem) = ip_mem {
if unsafe { process.read_mem(arch.into(), &mut ip_mem, ip, 64) }
== Err(Errno::ESRCH)
{
return Err(Errno::ESRCH); }
}
if let Some(mut sp_mem) = sp_mem {
if unsafe { process.read_mem(arch.into(), &mut sp_mem, sp, 64) }
== Err(Errno::ESRCH)
{
return Err(Errno::ESRCH); }
}
if safe_kill(pid, libc::SIGKILL) == Err(Errno::ESRCH) {
return Err(Errno::ESRCH);
}
let cookie = cache.get_sig_trampoline_ip(pid);
let depth = cache.depth_sig_handle(pid);
if !log_scmp {
error!("ctx": "sigreturn", "op": "check_SROP",
"msg": "Artificial sigreturn(2) detected: assume SROP!",
"act": Action::Kill,
"pid": process.pid.as_raw(), "arch": arch,
"sys": if is_realtime { "rt_sigreturn" } else { "sigreturn" },
"ip": ip, "depth": depth,
"trampoline_lo": cookie.map_or(0, |c| c.lo),
"trampoline_hi": cookie.map_or(0, |c| c.hi),
"tip": "configure `trace/allow_unsafe_sigreturn:1'");
} else {
let ip_hex = ip_mem.map(|m| hex_encode_lower(m.as_ref()).ok());
let sp_hex = sp_mem.map(|m| hex_encode_lower(m.as_ref()).ok());
error!("ctx": "sigreturn", "op": "check_SROP",
"msg": "Artificial sigreturn(2) detected: assume SROP!",
"act": Action::Kill,
"pid": process.pid.as_raw(), "arch": arch,
"sys": if is_realtime { "rt_sigreturn" } else { "sigreturn" },
"args": info_scmp.args, "ip": ip, "sp": sp,
"ip_mem": ip_hex,
"sp_mem": sp_hex,
"memmap": memmap,
"tip": "configure `trace/allow_unsafe_sigreturn:1'");
}
Err(Errno::ESRCH)
}
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 if cache.has_sig_handle(pid) {
if cache.exit_sig_handle(pid) {
return Ok(());
}
let _ = safe_kill(pid, libc::SIGKILL);
#[expect(clippy::disallowed_methods)]
let arch: SydArch = scmp_arch(info.arch).unwrap().into();
error!("ctx": "sigreturn", "op": "check_SROP",
"msg": "Artificial sigreturn(2) without signal delivery cookie: assume SROP!",
"act": Action::Kill, "pid": pid.as_raw(), "arch": arch,
"tip": "configure `trace/allow_unsafe_sigreturn:1'");
Err(Errno::ESRCH)
} 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)
}
}