use std::sync::{Arc, RwLock};
use libc::pid_t;
use nix::{
sys::signal::{kill, killpg, Signal},
unistd::{getpgid, getpgrp, Pid},
};
use crate::{
error,
proc::{proc_task_limit, proc_task_nr_syd, proc_task_nr_sys},
ptrace::{ptrace_cont, ptrace_getevent},
sandbox::{Action, Capability, Sandbox, SandboxGuard},
warn,
};
#[expect(clippy::cognitive_complexity)]
pub(crate) fn sysevent_fork(pid: Pid, sandbox: &Arc<RwLock<Sandbox>>) {
let mut _child_tid: Option<Pid> = None;
#[cfg(feature = "kcov")]
{
#[expect(clippy::cast_possible_truncation)]
if let Ok(child_tid) = ptrace_getevent(pid).map(|p| Pid::from_raw(p as libc::pid_t)) {
crate::kcov::inherit_kcov_tid(pid, child_tid);
crate::kcov::abi::kcov_attach(child_tid);
crate::kcov::abi::kcov_set_syscall(libc::SYS_clone);
let _ = crate::kcov::abi::kcov_enter_for(child_tid);
crate::kcov_edge!();
_child_tid = Some(child_tid);
}
}
let sandbox = SandboxGuard::Read(sandbox.read().unwrap_or_else(|err| err.into_inner()));
if !sandbox.enabled(Capability::CAP_PID) {
let _ = ptrace_cont(pid, None);
return;
}
let pid_max = if sandbox.pid_max > 0 {
sandbox.pid_max
} else {
let _ = ptrace_cont(pid, None);
return;
};
let pid_act = sandbox.default_action(Capability::CAP_PID);
drop(sandbox);
let errno = match proc_task_limit(pid, pid_max) {
Ok(false) => {
let _ = ptrace_cont(pid, None);
return;
}
Ok(true) => None, Err(errno) => Some(errno as i32), };
let pgid = getpgid(Some(pid)).map(|p| p.as_raw()).unwrap_or(0);
let syd_pgid = getpgrp().as_raw();
let kill_gid = pgid != 0 && pgid != syd_pgid;
if pid_act != Action::Filter {
let cnt_sys = proc_task_nr_sys().unwrap_or(0);
let cnt_syd = proc_task_nr_syd().unwrap_or(0);
let syd_pid = Pid::this().as_raw();
#[expect(clippy::cast_possible_truncation)]
let cpid = ptrace_getevent(pid)
.map(|p| Pid::from_raw(p as pid_t))
.unwrap_or(pid);
match pid_act {
action if action.is_signaling() => {
#[expect(clippy::disallowed_methods)]
let kill_sig = action.signal().unwrap();
let kill_it = if kill_gid {
format!("kill process group {pgid} with {kill_sig}")
} else {
format!("kill process {pid} with {kill_sig}")
};
error!("ctx": "limit_pid",
"msg": format!("process limit {pid_max} reached, {kill_it}"),
"err": errno.unwrap_or(0), "tip": "increase `pid/max'",
"pid_max": pid_max, "sig": kill_sig as libc::c_int,
"sys_tasks": cnt_sys, "syd_tasks": cnt_syd,
"pid": cpid.as_raw(), "ppid": pid.as_raw(), "pgid": pgid,
"syd_pid": syd_pid, "syd_pgid": syd_pgid);
}
Action::Warn => {
warn!("ctx": "pid_limit",
"msg": format!("process limit {pid_max} reached with pid {pid}"),
"err": errno.unwrap_or(0), "tip": "increase `pid/max'",
"sys_tasks": cnt_sys, "syd_tasks": cnt_syd,
"pid": cpid.as_raw(), "ppid": pid.as_raw(), "pgid": pgid,
"syd_pid": syd_pid, "syd_pgid": syd_pgid);
}
Action::Exit => {
let act = pid_act.to_string().to_ascii_lowercase();
error!("ctx": "limit_pid",
"msg": format!("process limit {pid_max} reached with pid {cpid}, {act}ing!"),
"err": errno.unwrap_or(0), "tip": "increase `pid/max'",
"sys_tasks": cnt_sys, "syd_tasks": cnt_syd,
"pid": cpid.as_raw(), "ppid": pid.as_raw(), "pgid": pgid,
"syd_pid": syd_pid, "syd_pgid": syd_pgid);
}
_ => unreachable!(),
};
}
#[cfg(feature = "kcov")]
{
if let Some(child_tid) = _child_tid {
crate::kcov_edge!();
let _ = crate::kcov::abi::kcov_exit_for(child_tid);
}
}
let kill_sig = match pid_act {
action if action.is_signaling() => action.signal(),
Action::Filter => Some(Signal::SIGKILL),
Action::Warn => None,
Action::Exit => std::process::exit(errno.unwrap_or(libc::EACCES)),
_ => unreachable!(),
};
if let Some(kill_sig) = kill_sig {
if kill_gid {
let _ = killpg(Pid::from_raw(pgid), Some(kill_sig));
} else {
let _ = kill(pid, Some(kill_sig));
}
} else {
let _ = ptrace_cont(pid, None);
}
}