1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
//
// Syd: rock-solid application kernel
// src/kernel/sigaction.rs: {,rt_}sigaction(2) handler
//
// Copyright (c) 2023, 2024, 2025, 2026 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0
use libseccomp::ScmpNotifResp;
use nix::{errno::Errno, sys::signal::SaFlags};
use crate::{
confine::{is_valid_ptr, scmp_arch_nsig, scmp_arch_sigstop},
proc::proc_tgid,
req::UNotifyEventRequest,
};
pub(crate) fn sys_sigaction(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_sigaction_handler(request, true)
}
pub(crate) fn sys_rt_sigaction(request: UNotifyEventRequest) -> ScmpNotifResp {
syscall_sigaction_handler(request, false)
}
fn syscall_sigaction_handler(request: UNotifyEventRequest, old: bool) -> ScmpNotifResp {
syscall_handler!(request, |request: UNotifyEventRequest| {
// Check if the handler is a restarting one.
//
// This allows us to selectively unblock system calls
// from the interrupt thread.
let req = request.scmpreq;
// Ensure signal number is a valid signal including reserved signals.
let nsig = scmp_arch_nsig(req.data.arch);
let sigstop = scmp_arch_sigstop(req.data.arch);
// Linux rejects size when it's not equal to sizeof(sigset_t) which is nsig/8.
#[expect(clippy::cast_sign_loss)]
if !old && req.data.args[3] != nsig as u64 / 8 {
return Err(Errno::EINVAL);
}
// We do not hook into sigaction(2) when the first argument is NULL.
let addr = req.data.args[1];
assert_ne!(addr, 0);
// Check pointer against mmap_min_addr.
if !is_valid_ptr(addr, req.data.arch) {
return Err(Errno::EFAULT);
}
// Read remote SaFlags.
let sa_flags = request.read_sa_flags(addr, old)?;
// Linux kernel truncates upper bits.
#[expect(clippy::cast_possible_truncation)]
let sig_num: libc::c_int = match req.data.args[0] as libc::c_int {
libc::SIGKILL => return Err(Errno::EINVAL),
sig_num if sig_num == sigstop => return Err(Errno::EINVAL),
sig_num if !(1..=nsig).contains(&sig_num) => return Err(Errno::EINVAL),
sig_num => sig_num,
};
// Signal handlers are per-process not per-thread.
let tgid = proc_tgid(request.scmpreq.pid())?;
let _is_restart = if sa_flags.contains(SaFlags::SA_RESTART) {
// This may only fail under memory-pressure.
// Better to be on the safe side and deny the syscall.
//
// TODO: Log an alert here.
request.cache.add_sig_restart(tgid, sig_num)?;
true
} else {
request.cache.del_sig_restart(tgid, sig_num);
false
};
// SAFETY: Continue system call. There's nothing we can do if
// the system call fails, or if an attacker changes the sa_flags
// element of `struct sigaction` but we did our best by
// validating all the things we can.
Ok(unsafe { request.continue_syscall() })
})
}