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)
}