syd 3.52.0

rock-solid application kernel
Documentation
//
// 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() })
    })
}