use std::sync::{Arc, RwLock};
use libc::{PTRACE_CONT, PTRACE_SINGLESTEP};
use nix::{
errno::Errno,
sys::signal::{kill, Signal},
unistd::Pid,
};
#[cfg(any(
target_arch = "aarch64",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "s390x",
))]
use crate::ptrace::ptrace_get_link_register;
use crate::{
cache::{SigreturnTrampolineIP, SIG_NEST_DEEP},
confine::{is_coredump, scmp_arch, scmp_arch_has_single_step},
cookie::safe_ptrace,
error,
ptrace::{ptrace_get_arch, ptrace_getsiginfo},
sandbox::{Action, Sandbox, SandboxGuard},
workers::WorkerCache,
};
#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "m68k"))]
use crate::{ptrace::ptrace_get_stack_ptr, req::RemoteProcess};
pub(crate) fn sysevent_sig(
pid: Pid,
sig: i32,
cache: &Arc<WorkerCache>,
sandbox: &Arc<RwLock<Sandbox>>,
) {
let restrict_sigreturn = {
!SandboxGuard::Read(sandbox.read().unwrap_or_else(|err| err.into_inner()))
.options
.allow_unsafe_sigreturn()
};
if !restrict_sigreturn {
let _ = unsafe {
safe_ptrace(
PTRACE_CONT,
pid.as_raw(),
std::ptr::null_mut(),
sig as *mut libc::c_void,
)
};
return;
}
if sig == libc::SIGTRAP && cache.get_sig_in_singlestep(pid) {
let si_code = ptrace_getsiginfo(pid).map(|i| i.si_code).unwrap_or(0);
if si_code == libc::TRAP_TRACE {
cache.set_sig_in_singlestep(pid, false);
} else if let Some(ip) = read_sig_trampoline_ip(pid) {
cache.set_sig_trampoline_ip(pid, ip);
} else {
cache.set_sig_in_singlestep(pid, false);
}
let _ = unsafe {
safe_ptrace(
PTRACE_CONT,
pid.as_raw(),
std::ptr::null_mut(),
std::ptr::null_mut(),
)
};
return;
}
if handle_srop(pid, sig, cache).is_err() {
return;
}
let has_single_step = ptrace_get_arch(pid)
.ok()
.and_then(|a| scmp_arch(a).ok())
.is_some_and(scmp_arch_has_single_step);
let request = if has_single_step {
cache.set_sig_in_singlestep(pid, true);
PTRACE_SINGLESTEP
} else {
PTRACE_CONT
};
let _ = unsafe {
safe_ptrace(
request,
pid.as_raw(),
std::ptr::null_mut(),
sig as *mut libc::c_void,
)
};
}
#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "m68k"))]
fn read_sig_trampoline_ip(pid: Pid) -> Option<SigreturnTrampolineIP> {
use libseccomp_sys::{SCMP_ARCH_M68K, SCMP_ARCH_X32, SCMP_ARCH_X86, SCMP_ARCH_X86_64};
let arch = ptrace_get_arch(pid).ok()?;
let sp = ptrace_get_stack_ptr(pid, Some(arch)).ok()?;
let scmp = scmp_arch(arch).ok()?;
let (ptr_size, is_be) = match arch {
SCMP_ARCH_X86_64 | SCMP_ARCH_X32 => (8usize, false),
SCMP_ARCH_X86 => (4usize, false),
SCMP_ARCH_M68K => (4usize, true),
_ => return None,
};
let mut buf = [0u8; 8];
let n = unsafe { RemoteProcess::new(pid).read_mem(scmp, &mut buf[..ptr_size], sp, ptr_size) }
.ok()?;
if n != ptr_size {
return None;
}
let mut ip = [0u8; 8];
#[expect(clippy::arithmetic_side_effects)]
let ip = if is_be {
ip[8 - ptr_size..].copy_from_slice(&buf[..ptr_size]);
u64::from_be_bytes(ip)
} else {
ip[..ptr_size].copy_from_slice(&buf[..ptr_size]);
u64::from_le_bytes(ip)
};
Some(SigreturnTrampolineIP { lo: ip, hi: ip })
}
#[cfg(any(
target_arch = "aarch64",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "s390x",
))]
fn read_sig_trampoline_ip(pid: Pid) -> Option<SigreturnTrampolineIP> {
let lr = ptrace_get_link_register(pid).ok()?;
Some(SigreturnTrampolineIP { lo: lr, hi: lr })
}
#[cfg(not(any(
target_arch = "x86_64",
target_arch = "x86",
target_arch = "m68k",
target_arch = "aarch64",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "s390x",
)))]
fn read_sig_trampoline_ip(_pid: Pid) -> Option<SigreturnTrampolineIP> {
None
}
#[expect(clippy::cognitive_complexity)]
fn handle_srop(pid: Pid, sig: i32, cache: &Arc<WorkerCache>) -> Result<(), Errno> {
if is_coredump(sig) {
let depth = cache.depth_sig_handle(pid);
if depth > 0 {
let user_sig = match ptrace_getsiginfo(pid) {
Ok(info) => info.si_code <= 0 && unsafe { info.si_pid() } != pid.as_raw(),
Err(Errno::ESRCH) => return Err(Errno::ESRCH),
Err(_) => true,
};
if user_sig || usize::from(depth) >= SIG_NEST_DEEP {
error!("ctx": "sigreturn", "op": "check_SROP", "act": Action::Kill,
"pid": pid.as_raw(), "sig": sig, "depth": depth,
"msg": "fatal signal during handler dispatch: assume SROP!",
"tip": "configure `trace/allow_unsafe_sigreturn:1'");
let _ = kill(pid, Some(Signal::SIGKILL));
return Err(Errno::ESRCH);
}
}
}
if let Err(errno) = cache.push_sig_handle(pid) {
error!("ctx": "handle_signal", "op": "push_sig_handle",
"pid": pid.as_raw(), "err": errno as i32,
"msg": format!("per-TID signal delivery cookie ring full: {errno}"),
"tip": "configure `trace/allow_unsafe_sigreturn:1'");
let _ = kill(pid, Some(Signal::SIGKILL));
return Err(Errno::ESRCH);
}
Ok(())
}