use std::sync::{Arc, RwLock};
use nix::{
sys::signal::{killpg, Signal},
unistd::{getpgid, getpgrp, Pid},
};
use crate::{
confine::SydNotifResp,
cookie::safe_kill,
error,
proc::{proc_task_limit, proc_task_nr_syd, proc_task_nr_sys},
sandbox::{Action, Capability, Sandbox, SandboxGuard},
warn,
};
#[expect(clippy::cognitive_complexity)]
pub(crate) fn sysevent_fork(
pid: Pid,
cpid: Pid,
sandbox: &Arc<RwLock<Sandbox>>,
) -> Option<SydNotifResp> {
#[cfg(feature = "kcov")]
{
crate::kcov::inherit_kcov_tid(pid, cpid);
crate::kcov::abi::kcov_attach(cpid);
crate::kcov::abi::kcov_set_syscall(libc::SYS_clone);
let _ = crate::kcov::abi::kcov_enter_for(cpid);
crate::kcov_edge!();
}
let sandbox = SandboxGuard::Read(sandbox.read().unwrap_or_else(|err| err.into_inner()));
if !sandbox.enabled(Capability::CAP_PID) {
return Some(SydNotifResp::Cont { pid, signal: None });
}
let pid_max = if sandbox.pid_max > 0 {
sandbox.pid_max
} else {
return Some(SydNotifResp::Cont { pid, signal: None });
};
let pid_act = sandbox.default_action(Capability::CAP_PID);
drop(sandbox);
let errno = match proc_task_limit(pid, pid_max) {
Ok(false) => {
return Some(SydNotifResp::Cont { pid, signal: None });
}
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();
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")]
{
crate::kcov_edge!();
let _ = crate::kcov::abi::kcov_exit_for(cpid);
}
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 _ = safe_kill(pid, kill_sig as libc::c_int);
}
None
} else {
Some(SydNotifResp::Cont { pid, signal: None })
}
}