Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/heimdall

crates/heimdall-broker/src/support.rs

// SPDX-License-Identifier: AGPL-3.0-only
// Copyright (C) 2026 Ignalina ApS and Vetra Automation AB

//! What this kernel gives the broker, found out by asking the kernel (spec
//! §10), never from a version string. Heimdall is a defensive sandbox: a
//! feature is reported only when it was seen working.

use std::os::fd::AsRawFd;
use std::sync::OnceLock;

use std::os::fd::AsFd;

use crate::filter;
use crate::sys;

/// The kernel features the broker relies on.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Support {
    /// `SECCOMP_RET_USER_NOTIF` (kernel ≥ 5.0).
    pub user_notif: bool,
    /// `SECCOMP_USER_NOTIF_FLAG_CONTINUE` (≥ 5.5).
    pub notif_continue: bool,
    /// `SECCOMP_IOCTL_NOTIF_ADDFD` (≥ 5.9).
    pub addfd: bool,
    /// `SECCOMP_ADDFD_FLAG_SEND` (≥ 5.14).
    pub addfd_send: bool,
    /// `openat2` (≥ 5.6).
    pub openat2: bool,
    /// `pidfd_open` + `pidfd_getfd` (≥ 5.6).
    pub pidfd_getfd: bool,
}

/// How the broker runs on this kernel (spec §10).
#[derive(
    Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
#[serde(rename_all = "snake_case")]
pub enum Mode {
    /// The broker opens files and sockets itself and injects the fd: the
    /// worker's racy pointers are never used. The only mode that reports
    /// `Features.broker = true`.
    Full,
    /// No `ADDFD`: the broker checks and lets the worker's own call proceed
    /// (`CONTINUE`). Not TOCTOU-safe; a warning is logged with every job.
    CheckOnly,
    /// No `USER_NOTIF` (or neither `ADDFD` nor `CONTINUE`): every brokered
    /// call is refused (fail closed).
    DenyOnly,
}

impl Support {
    /// Probes once per process (cached).
    pub fn probe() -> Support {
        static CACHE: OnceLock<Support> = OnceLock::new();
        *CACHE.get_or_init(probe_now)
    }

    /// The mode these features allow.
    pub fn mode(&self) -> Mode {
        if !self.user_notif {
            Mode::DenyOnly
        } else if self.addfd && self.openat2 && self.pidfd_getfd && self.notif_continue {
            Mode::Full
        } else if self.notif_continue {
            Mode::CheckOnly
        } else {
            Mode::DenyOnly
        }
    }

    /// Can the broker run in `mode` here?
    pub fn allows(&self, mode: Mode) -> bool {
        match mode {
            Mode::Full => self.mode() == Mode::Full,
            Mode::CheckOnly => self.user_notif && self.notif_continue,
            Mode::DenyOnly => true,
        }
    }
}

/// `Features.broker` for a run in `mode`: true only for [`Mode::Full`].
pub fn broker_feature(mode: Mode) -> bool {
    mode == Mode::Full
}

fn probe_now() -> Support {
    let user_notif = filter::user_notif_available();
    let openat2 = sys::openat2(
        libc::AT_FDCWD,
        std::path::Path::new("/"),
        &sys::OpenHow {
            flags: (libc::O_PATH | libc::O_CLOEXEC) as u64,
            mode: 0,
            resolve: libc::RESOLVE_NO_MAGICLINKS,
        },
    )
    .is_ok();
    let pidfd_getfd = sys::pidfd_open(std::process::id() as i32)
        .and_then(|p| sys::pidfd_getfd(&p, p.as_raw_fd()))
        .is_ok();
    let (notif_continue, addfd, addfd_send) = if user_notif {
        probe_listener()
    } else {
        (false, false, false)
    };
    Support {
        user_notif,
        notif_continue,
        addfd,
        addfd_send,
        openat2,
        pidfd_getfd,
    }
}

/// Forks a child that installs an allow-all filter with a listener and asks
/// the listener about an id that does not exist: `ENOENT` means the kernel
/// understood the request, `EINVAL` means it did not. The child makes system
/// calls only (no allocation) and reports in its exit status.
fn probe_listener() -> (bool, bool, bool) {
    use heimdall_seccomp::notif::{AddFd, Listener, Response};
    let Ok(prog) =
        heimdall_seccomp::bpf::build_rules(filter::arch(), &[], heimdall_seccomp::Action::Allow)
    else {
        return (false, false, false);
    };
    // SAFETY: the child only makes system calls and _exits.
    let pid = unsafe { libc::fork() };
    if pid < 0 {
        return (false, false, false);
    }
    if pid == 0 {
        let fd = match heimdall_seccomp::launch::install(
            &prog,
            heimdall_seccomp::launch::FILTER_FLAG_NEW_LISTENER,
        ) {
            Ok(Some(fd)) => fd,
            // SAFETY: leaving the forked child.
            _ => unsafe { libc::_exit(64) },
        };
        let l = Listener::from_fd(fd);
        let bogus = u64::MAX - 7;
        let mut bits = 0;
        let enoent =
            |r: std::io::Result<()>| matches!(r, Err(e) if e.raw_os_error() == Some(libc::ENOENT));
        if enoent(l.respond(bogus, Response::Continue)) {
            bits |= 1;
        }
        if enoent(l.add_fd(bogus, l.as_fd(), AddFd::default()).map(|_| ())) {
            bits |= 2;
        }
        if enoent(l.add_fd(bogus, l.as_fd(), AddFd::send()).map(|_| ())) {
            bits |= 4;
        }
        // SAFETY: leaving the forked child.
        unsafe { libc::_exit(bits) };
    }
    let mut status = 0;
    // SAFETY: waiting for our own child.
    unsafe { while libc::waitpid(pid, &mut status, 0) < 0 && sys::errno() == libc::EINTR {} }
    if !libc::WIFEXITED(status) {
        return (false, false, false);
    }
    let bits = libc::WEXITSTATUS(status);
    if bits == 64 {
        return (false, false, false);
    }
    (bits & 1 != 0, bits & 2 != 0, bits & 4 != 0)
}