syd 3.52.0

rock-solid application kernel
Documentation
//
// Syd: rock-solid application kernel
// src/kernel/shm.rs: Shared memory syscall handlers
//
// Copyright (c) 2023, 2024, 2025, 2026 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use libseccomp::ScmpNotifResp;
use nix::errno::Errno;

use crate::{
    compat::SHM_EXEC, config::SHM_UNSAFE_MASK, req::UNotifyEventRequest, sandbox::Action, warn,
};

const O_CREAT: u64 = libc::O_CREAT as u64;

const SHM_X: u64 = SHM_EXEC as u64;

const IPC_64: u64 = 0x100;
const IPC_SET: u64 = crate::compat::IPC_SET as u64;

const MSG_STAT_ANY: u64 = 13;
const SEM_STAT_ANY: u64 = 20;
const SHM_STAT_ANY: u64 = 15;

const MSG_COPY: u64 = 0o40000;

pub(crate) fn sys_ipc(request: UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.scmpreq;

    // Linux performs version check on IPC subcall.
    let call = req.data.args[0] & 0xffff;
    let version = req.data.args[0] >> 16;
    if call == 21 && version == 1 {
        return request.fail_syscall(Errno::EINVAL);
    }

    // Determine system call.
    // 2 -> semget
    // 3 -> semctl
    // 12 -> msgrcv
    // 13 -> msgget
    // 14 -> msgctl
    // 21 -> shmat
    // 23 -> shmget
    // 24 -> shmctl
    match call {
        2 => syscall_semget_handler(request, req.data.args[3]),
        3 => syscall_semctl_handler(request, req.data.args[3]),
        12 => syscall_msgrcv_handler(request, req.data.args[3]),
        13 => syscall_msgget_handler(request, req.data.args[2]),
        14 => syscall_msgctl_handler(request, req.data.args[2]),
        21 => syscall_shmat_handler(request, req.data.args[2]),
        23 => syscall_shmget_handler(request, req.data.args[3]),
        24 => syscall_shmctl_handler(request, req.data.args[2]),
        _ => {
            // SAFETY: Safe ipc(2) call, continue.
            // No pointer-dereference in access check.
            unsafe { request.continue_syscall() }
        }
    }
}

pub(crate) fn sys_shmat(request: UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.scmpreq;

    syscall_shmat_handler(request, req.data.args[2])
}

pub(crate) fn sys_msgctl(request: UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.scmpreq;

    syscall_msgctl_handler(request, req.data.args[1])
}

pub(crate) fn sys_semctl(request: UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.scmpreq;

    syscall_semctl_handler(request, req.data.args[2])
}

pub(crate) fn sys_shmctl(request: UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.scmpreq;

    syscall_shmctl_handler(request, req.data.args[1])
}

pub(crate) fn sys_msgget(request: UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.scmpreq;

    syscall_msgget_handler(request, req.data.args[1])
}

pub(crate) fn sys_msgrcv(request: UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.scmpreq;

    syscall_msgrcv_handler(request, req.data.args[4])
}

pub(crate) fn sys_semget(request: UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.scmpreq;

    syscall_semget_handler(request, req.data.args[2])
}

pub(crate) fn sys_shmget(request: UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.scmpreq;

    syscall_shmget_handler(request, req.data.args[2])
}

fn syscall_shmat_handler(request: UNotifyEventRequest, flags: u64) -> ScmpNotifResp {
    if flags & SHM_X == 0 {
        // SAFETY: No pointer dereference in access check.
        return unsafe { request.continue_syscall() };
    }

    let act = Action::Kill;
    let req = request.scmpreq;
    warn!("ctx": "ipc", "op": "check_shm",
        "sys": "shmat", "shm_flags": flags,
        "act": act, "pid": req.pid,
        "msg": "unsafe shmat call with SHM_EXEC",
        "tip": "configure `trace/allow_unsafe_perm_shm:1'");

    let _ = request.kill(act);
    request.fail_syscall(Errno::EACCES)
}

fn syscall_msgctl_handler(request: UNotifyEventRequest, cmd: u64) -> ScmpNotifResp {
    let cmd = to_ipc_cmd(cmd);
    if !matches!(cmd, IPC_SET | MSG_STAT_ANY) {
        // SAFETY: No pointer dereference in access check.
        return unsafe { request.continue_syscall() };
    }

    let act = Action::Kill;
    let req = request.scmpreq;
    warn!("ctx": "ipc", "op": "check_shm",
        "sys": "msgctl", "msg_cmd": cmd,
        "act": act, "pid": req.pid,
        "msg": "unsafe msgctl call",
        "tip": "configure `trace/allow_unsafe_perm_shm:1'");

    let _ = request.kill(act);
    request.fail_syscall(Errno::EACCES)
}

fn syscall_semctl_handler(request: UNotifyEventRequest, cmd: u64) -> ScmpNotifResp {
    let cmd = to_ipc_cmd(cmd);
    if !matches!(cmd, IPC_SET | SEM_STAT_ANY) {
        // SAFETY: No pointer dereference in access check.
        return unsafe { request.continue_syscall() };
    }

    let act = Action::Kill;
    let req = request.scmpreq;
    warn!("ctx": "ipc", "op": "check_shm",
        "sys": "semctl", "sem_cmd": cmd,
        "act": act, "pid": req.pid,
        "msg": "unsafe semctl call",
        "tip": "configure `trace/allow_unsafe_perm_shm:1'");

    let _ = request.kill(act);
    request.fail_syscall(Errno::EACCES)
}

fn syscall_shmctl_handler(request: UNotifyEventRequest, cmd: u64) -> ScmpNotifResp {
    let cmd = to_ipc_cmd(cmd);
    if !matches!(cmd, IPC_SET | SHM_STAT_ANY) {
        // SAFETY: No pointer dereference in access check.
        return unsafe { request.continue_syscall() };
    }

    let act = Action::Kill;
    let req = request.scmpreq;
    warn!("ctx": "ipc", "op": "check_shm",
        "sys": "shmctl", "shm_cmd": cmd,
        "act": act, "pid": req.pid,
        "msg": "unsafe shmctl call",
        "tip": "configure `trace/allow_unsafe_perm_shm:1'");

    let _ = request.kill(act);
    request.fail_syscall(Errno::EACCES)
}

fn syscall_msgrcv_handler(request: UNotifyEventRequest, flags: u64) -> ScmpNotifResp {
    if flags & MSG_COPY == 0 {
        // SAFETY: No pointer dereference in access check.
        return unsafe { request.continue_syscall() };
    }

    // Check trace/allow_unsafe_copy:1.
    let sandbox = request.get_sandbox();
    let allow_copy = sandbox.options.allow_unsafe_copy();
    drop(sandbox); // release the read-lock.
    if allow_copy {
        // SAFETY: No pointer dereference in access check.
        return unsafe { request.continue_syscall() };
    }

    let act = Action::Kill;
    let req = request.scmpreq;
    warn!("ctx": "ipc", "op": "check_shm",
        "sys": "msgrcv", "msg_flags": flags,
        "act": act, "pid": req.pid,
        "msg": "unsafe msgrcv MSG_COPY call",
        "tip": "configure `trace/allow_unsafe_copy:1'");

    let _ = request.kill(act);
    request.fail_syscall(Errno::EACCES)
}

fn syscall_msgget_handler(request: UNotifyEventRequest, flags: u64) -> ScmpNotifResp {
    if flags & SHM_UNSAFE_MASK == 0 {
        // SAFETY: No pointer dereference in access check.
        return unsafe { request.continue_syscall() };
    }

    let act = Action::Kill;
    let req = request.scmpreq;
    warn!("ctx": "ipc", "op": "check_shm",
        "sys": "msgget", "msg_flags": flags,
        "act": act, "pid": req.pid,
        "msg": "unsafe msgget call",
        "tip": "configure `trace/allow_unsafe_perm_shm:1'");

    let _ = request.kill(act);
    request.fail_syscall(Errno::EACCES)
}

fn syscall_semget_handler(request: UNotifyEventRequest, flags: u64) -> ScmpNotifResp {
    if flags & SHM_UNSAFE_MASK == 0 {
        // SAFETY: No pointer dereference in access check.
        return unsafe { request.continue_syscall() };
    }

    let act = Action::Kill;
    let req = request.scmpreq;
    warn!("ctx": "ipc", "op": "check_shm",
        "sys": "semget", "sem_flags": flags,
        "act": act, "pid": req.pid,
        "msg": "unsafe semget call",
        "tip": "configure `trace/allow_unsafe_perm_shm:1'");

    let _ = request.kill(act);
    request.fail_syscall(Errno::EACCES)
}

fn syscall_shmget_handler(request: UNotifyEventRequest, flags: u64) -> ScmpNotifResp {
    if flags & SHM_UNSAFE_MASK == 0 {
        // SAFETY: No pointer dereference in access check.
        return unsafe { request.continue_syscall() };
    }

    let act = Action::Kill;
    let req = request.scmpreq;
    warn!("ctx": "ipc", "op": "check_shm",
        "sys": "shmget", "shm_flags": flags,
        "act": act, "pid": req.pid,
        "msg": "unsafe shmget call",
        "tip": "configure `trace/allow_unsafe_perm_shm:1'");

    let _ = request.kill(act);
    request.fail_syscall(Errno::EACCES)
}

pub(crate) fn sys_mq_open(request: UNotifyEventRequest) -> ScmpNotifResp {
    let req = request.scmpreq;

    // Mode is only valid with O_CREAT!
    let oflag = req.data.args[1];
    if oflag & O_CREAT == 0 {
        // SAFETY: No pointer dereference in access check.
        return unsafe { request.continue_syscall() };
    }

    let mode = req.data.args[2];
    if mode & SHM_UNSAFE_MASK == 0 {
        // SAFETY: No pointer dereference in access check.
        return unsafe { request.continue_syscall() };
    }

    let act = Action::Kill;
    warn!("ctx": "ipc", "op": "check_shm",
        "sys": "mq_open", "oflag": oflag, "mode": mode,
        "act": act, "pid": req.pid,
        "msg": "unsafe mq_open call",
        "tip": "configure `trace/allow_unsafe_perm_msgqueue:1'");

    let _ = request.kill(act);
    request.fail_syscall(Errno::EACCES)
}

// Convert an argument to an IPC command.
//
// Linux truncates upper bits of command.
// Linux strips IPC_64 from command.
#[expect(clippy::cast_possible_truncation)]
const fn to_ipc_cmd(op: u64) -> u64 {
    (op as u32 & !(IPC_64 as u32)) as u64
}