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/tests/kernel.rs

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

//! The broker against a real kernel (spec §4, §11 "Broker"). Heimdall is a
//! defensive sandbox: every worker here is heimdall-broker-selftest, which
//! attacks only the broker that supervises it.
//!
//! These need seccomp USER_NOTIF + ADDFD (kernel 5.9+, 5.14+ for the atomic
//! answer), so they are `#[ignore]`d in plain CI; `scripts/container-tests.sh`
//! runs them in a privileged rootless podman container.

use std::io::Write;
use std::net::TcpListener;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::time::Duration;

use heimdall_broker::{
    Access, BrokerPolicy, BrokerReport, DebugKnobs, ExecPolicy, Mode, OnRefuse, Resolve,
    SpawnOptions, Support,
};
use heimdall_core::{Decision, JobId};

const SELFTEST: &str = env!("CARGO_BIN_EXE_heimdall-broker-selftest");
const NEEDS: &str = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh";

struct Dirs {
    base: PathBuf,
    input: PathBuf,
    output: PathBuf,
    tmp: PathBuf,
    no: PathBuf,
}

fn dirs(name: &str) -> Dirs {
    let base = std::env::temp_dir().join(format!("hb-{name}-{}", JobId::random()));
    let d = Dirs {
        input: base.join("in"),
        output: base.join("ou"),
        tmp: base.join("tm"),
        no: base.join("no"),
        base,
    };
    for p in [&d.input, &d.output, &d.tmp, &d.no] {
        std::fs::create_dir_all(p).unwrap();
    }
    std::fs::write(d.input.join("g"), b"GOOD").unwrap();
    std::fs::write(d.no.join("g"), b"BAD").unwrap();
    std::fs::write(d.no.join("secret"), b"host-secret").unwrap();
    d
}

fn s(p: &Path) -> String {
    p.to_str().unwrap().to_string()
}

fn policy(d: &Dirs) -> BrokerPolicy {
    BrokerPolicy::job(JobId::random(), &d.input, &d.output)
        .with_prefix(&d.tmp, Access::ReadWrite, Resolve::Beneath)
        .with_prefix("/proc", Access::Read, Resolve::Proc)
        .with_prefix("/dev/null", Access::ReadWrite, Resolve::Device)
        // The selftest is dynamically linked: its loader reads these.
        .with_prefix("/etc/ld.so.cache", Access::Read, Resolve::File)
        .with_system_read("/lib")
        .with_system_read("/lib64")
        .with_system_read("/usr/lib")
        .with_system_read("/usr/lib64")
}

/// Run the selftest with `args` under the broker; its stdout lines and the report.
fn run_with(
    p: BrokerPolicy,
    opts: SpawnOptions,
    args: &[&str],
) -> (Vec<String>, BrokerReport, std::process::ExitStatus) {
    let mut cmd = Command::new(SELFTEST);
    cmd.args(args)
        .stdout(Stdio::piped())
        .stderr(Stdio::inherit());
    let child = heimdall_broker::spawn_brokered(&mut cmd, p, opts).expect("the broker starts");
    let (st, out, rep) = child.wait_with_output().expect("the broker finishes");
    let lines = String::from_utf8_lossy(&out)
        .lines()
        .map(String::from)
        .collect();
    (lines, rep, st)
}

fn run(p: BrokerPolicy, args: &[&str]) -> (String, BrokerReport) {
    let (lines, rep, _) = run_with(p, SpawnOptions::default(), args);
    (lines.last().cloned().unwrap_or_default(), rep)
}

fn eacces() -> String {
    format!("err {}", libc::EACCES)
}

fn events_for<'a>(r: &'a BrokerReport, needle: &str) -> Vec<&'a heimdall_core::BrokerEvent> {
    r.events
        .iter()
        .filter(|e| e.args.iter().any(|a| a.contains(needle)))
        .collect()
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn this_kernel_gives_the_broker_full_mode() {
    let _ = NEEDS;
    let s = Support::probe();
    assert_eq!(s.mode(), Mode::Full, "{s:?}");
    assert!(s.addfd_send, "{s:?}");
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn an_allowed_read_under_in_gets_the_file_through_an_injected_fd_and_is_logged() {
    let d = dirs("read");
    let path = s(&d.input.join("g"));
    let (out, rep) = run(policy(&d), &["open", &path, "r"]);
    assert!(out.starts_with("ok fd=") && out.ends_with("GOOD"), "{out}");
    assert_eq!(rep.mode, Mode::Full);
    assert!(rep.broker_feature());
    let ev = events_for(&rep, &format!("path={path}"));
    assert_eq!(ev.len(), 1, "{:?}", rep.events);
    assert_eq!(ev[0].call, "openat");
    assert_eq!(ev[0].decision, Decision::Allowed);
    assert!(
        ev[0].args.iter().any(|a| a.starts_with("fd=")),
        "{:?}",
        ev[0]
    );
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn an_open_outside_the_policy_is_refused_with_eacces_and_logged_as_refused() {
    let d = dirs("outside");
    let path = s(&d.no.join("secret"));
    let (out, rep) = run(policy(&d), &["open", &path, "r"]);
    assert_eq!(out, eacces());
    let ev = events_for(&rep, &format!("path={path}"));
    assert_eq!(ev[0].decision, Decision::Refused);
    assert!(
        ev[0].args.iter().any(|a| a == "refused: outside policy"),
        "{:?}",
        ev[0]
    );
    // The legacy open(2) and openat2(2) are brokered the same way.
    let (out, _) = run(policy(&d), &["open-syscall", &path]);
    assert_eq!(out, eacces());
    let (out, _) = run(policy(&d), &["openat2", &path]);
    assert_eq!(out, eacces());
    let (out, _) = run(policy(&d), &["openat2", &s(&d.input.join("g"))]);
    assert!(out.ends_with("GOOD"), "{out}");
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn writing_or_creating_under_in_is_refused_and_the_input_is_unchanged() {
    let d = dirs("roin");
    let g = s(&d.input.join("g"));
    let new = s(&d.input.join("new"));
    for (path, how) in [(&g, "w"), (&g, "rw"), (&g, "trunc"), (&new, "c")] {
        let (out, _) = run(policy(&d), &["open", path, how]);
        assert_eq!(out, eacces(), "{path} {how}");
    }
    assert_eq!(std::fs::read(d.input.join("g")).unwrap(), b"GOOD");
    assert!(!d.input.join("new").exists());
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn out_takes_new_files_but_refuses_reading_back_and_listing() {
    let d = dirs("wo");
    let r = s(&d.output.join("r"));
    let (out, _) = run(policy(&d), &["open", &r, "c"]);
    assert!(
        out.starts_with("ok fd=") && out.contains("wrote=22"),
        "{out}"
    );
    assert_eq!(
        std::fs::read(d.output.join("r")).unwrap(),
        b"written by the worker\n"
    );
    let (out, _) = run(policy(&d), &["open", &r, "r"]);
    assert_eq!(out, eacces(), "read-back of /out");
    let (out, _) = run(policy(&d), &["open", &r, "rw"]);
    assert_eq!(out, eacces(), "O_RDWR on /out");
    let (out, _) = run(policy(&d), &["open", &s(&d.output), "d"]);
    assert_eq!(out, eacces(), "listing /out");
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn a_dotdot_path_is_refused_even_when_it_lands_inside_the_policy() {
    let d = dirs("dotdot");
    let p = format!("{}/../in/g", s(&d.output));
    let (out, rep) = run(policy(&d), &["open", &p, "r"]);
    assert_eq!(out, eacces());
    assert!(
        events_for(&rep, "refused: `..` in path").len() == 1,
        "{:?}",
        rep.events
    );
    let p = format!("{}/../no/secret", s(&d.input));
    let (out, _) = run(policy(&d), &["open", &p, "r"]);
    assert_eq!(out, eacces());
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn a_symlink_out_of_in_or_tmp_is_refused_and_a_symlink_in_out_is_refused() {
    let d = dirs("symlink");
    // Absolute target outside: refused (RESOLVE_BENEATH).
    std::os::unix::fs::symlink(d.no.join("secret"), d.input.join("abs")).unwrap();
    // Relative target climbing out: refused.
    std::os::unix::fs::symlink("../no/secret", d.input.join("rel")).unwrap();
    // A link that stays inside /in: followed.
    std::os::unix::fs::symlink("g", d.input.join("inside")).unwrap();
    for l in ["abs", "rel"] {
        let (out, rep) = run(policy(&d), &["open", &s(&d.input.join(l)), "r"]);
        assert_eq!(out, eacces(), "{l}");
        assert!(
            !events_for(&rep, "refused: escapes its prefix").is_empty(),
            "{l}: {:?}",
            rep.events
        );
    }
    let (out, _) = run(policy(&d), &["open", &s(&d.input.join("inside")), "r"]);
    assert!(out.ends_with("GOOD"), "{out}");
    // The worker makes a link in /tmp to the secret and opens it.
    let (out, _) = run(
        policy(&d),
        &[
            "symlink",
            &s(&d.no.join("secret")),
            &s(&d.tmp.join("l")),
            &s(&d.tmp.join("l")),
        ],
    );
    assert_eq!(out, eacces());
    // /out refuses every symbolic link, even one pointing into /out.
    std::fs::write(d.output.join("a"), b"x").unwrap();
    std::os::unix::fs::symlink("a", d.output.join("l")).unwrap();
    let (out, rep) = run(policy(&d), &["open", &s(&d.output.join("l")), "w"]);
    assert_eq!(out, eacces());
    assert!(
        !events_for(&rep, "refused: symbolic link").is_empty(),
        "{:?}",
        rep.events
    );
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn reopening_a_write_only_fd_through_proc_self_fd_is_refused_as_a_magic_link() {
    let d = dirs("procfd");
    let (out, rep) = run(policy(&d), &["proc-fd", &s(&d.output.join("x"))]);
    assert_eq!(out, eacces(), "{:?}", rep.events);
    assert!(
        !events_for(&rep, "refused: symbolic link").is_empty()
            || !events_for(&rep, "refused: escapes its prefix").is_empty(),
        "{:?}",
        rep.events
    );
    assert_eq!(std::fs::read(d.output.join("x")).unwrap(), b"secret-out\n");
    // /proc/self/root and /proc/self/cwd are magic links too.
    let p = format!("/proc/self/root{}", s(&d.no.join("secret")));
    let (out, _) = run(policy(&d), &["open", &p, "r"]);
    assert_eq!(out, eacces());
    // An ordinary /proc file of the worker opens (self is rewritten).
    let (lines, _, _) = run_with(
        policy(&d),
        SpawnOptions::default(),
        &["open", "/proc/self/status", "r"],
    );
    assert!(
        lines[0].starts_with("ok fd=") && lines.iter().any(|l| l.starts_with("Pid:")),
        "{lines:?}"
    );
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn openat2_resolve_flags_from_the_worker_only_ever_add_strictness() {
    let d = dirs("resolve");
    let g = s(&d.input.join("g"));
    std::os::unix::fs::symlink("g", d.input.join("inside")).unwrap();
    let inside = s(&d.input.join("inside"));
    // RESOLVE_NO_SYMLINKS asked by the worker: the in-prefix link now fails.
    let (out, _) = run(policy(&d), &["openat2-resolve", &inside, "4"]);
    assert_eq!(out, eacces());
    let (out, _) = run(policy(&d), &["openat2-resolve", &g, "4"]);
    assert!(out.ends_with("GOOD"), "{out}");
    // RESOLVE_IN_ROOT / RESOLVE_BENEATH (scoping relative to the worker's
    // dirfd) and unknown bits are refused, never widened.
    for r in ["16", "8", "256"] {
        let (out, _) = run(policy(&d), &["openat2-resolve", &g, r]);
        assert_eq!(out, eacces(), "resolve {r}");
    }
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn a_racing_thread_flipping_the_path_after_the_notification_never_opens_the_wrong_file() {
    let d = dirs("race");
    // Same length: <base>/in/g (GOOD, allowed) and <base>/no/g (BAD, outside).
    let good = s(&d.input.join("g"));
    let bad = s(&d.no.join("g"));
    assert_eq!(good.len(), bad.len());
    let opts = SpawnOptions {
        knobs: DebugKnobs {
            hold_before_answer: Some(Duration::from_micros(200)),
        },
        ..SpawnOptions::default()
    };
    let (lines, _rep, _) = run_with(policy(&d), opts, &["race", &good, &bad, "3000"]);
    let out = lines.last().unwrap();
    assert!(
        out.contains(" bad=0 "),
        "the broker opened the wrong file: {out}"
    );
    let good_n: u64 = out
        .split("good=")
        .nth(1)
        .unwrap()
        .split(' ')
        .next()
        .unwrap()
        .parse()
        .unwrap();
    let refused: u64 = out
        .split("refused=")
        .nth(1)
        .unwrap()
        .split(' ')
        .next()
        .unwrap()
        .parse()
        .unwrap();
    assert!(
        good_n > 0 && refused > 0,
        "the race really flipped both ways: {out}"
    );
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn fd_exhaustion_in_the_worker_answers_emfile_and_the_broker_keeps_serving() {
    let d = dirs("emfile");
    let (out, rep) = run(policy(&d), &["fd-exhaust", &s(&d.input.join("g"))]);
    assert!(out.contains(&format!("errno={}", libc::EMFILE)), "{out}");
    assert!(out.ends_with("again=true"), "{out}");
    assert!(
        !events_for(&rep, "result=EMFILE").is_empty(),
        "{:?}",
        rep.events.len()
    );
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn relative_opens_resolve_against_the_workers_cwd_and_dirfd_and_are_checked_as_such() {
    let d = dirs("rel");
    let (out, rep) = run(policy(&d), &["rel", &s(&d.input), "g"]);
    assert!(out.ends_with("GOOD"), "{out} {:?}", rep.events);
    assert!(!events_for(&rep, &format!("resolved={}/g", s(&d.input))).is_empty());
    let (out, _) = run(policy(&d), &["rel", &s(&d.no), "secret"]);
    assert_eq!(out, eacces(), "cwd outside the policy");
    let (out, _) = run(policy(&d), &["rel", &s(&d.input), "../no/secret"]);
    assert_eq!(out, eacces(), "relative `..`");
    let (out, _) = run(policy(&d), &["rel", &s(&d.output), "made", "c"]);
    assert!(out.contains("wrote="), "{out}");
    assert!(d.output.join("made").exists());
    let (out, rep) = run(policy(&d), &["dirfd", &s(&d.input), "g"]);
    assert!(
        out.ends_with("GOOD"),
        "{out} {:?}",
        events_for(&rep, &s(&d.base))
    );
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn opens_from_a_forked_child_and_from_a_thread_are_brokered_too() {
    let d = dirs("forks");
    let g = s(&d.input.join("g"));
    let (out, rep) = run(policy(&d), &["fork-open", &g]);
    assert_eq!(out, "ok child read GOOD");
    assert!(!events_for(&rep, &format!("path={g}")).is_empty());
    let (out, _) = run(policy(&d), &["thread-open", &g]);
    assert!(out.ends_with("GOOD"), "{out}");
    let (out, _) = run(policy(&d), &["fork-open", &s(&d.no.join("g"))]);
    assert_eq!(out, format!("err {}", libc::EIO), "the child was refused");
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn a_fifo_or_a_device_under_a_file_prefix_is_refused_without_blocking_the_broker() {
    let d = dirs("fifo");
    let (out, rep) = run(policy(&d), &["fifo", &s(&d.tmp)]);
    assert_eq!(out, eacces());
    assert!(
        !events_for(&rep, "refused: not a regular file").is_empty(),
        "{:?}",
        rep.events
    );
    // /dev/null is a listed device and opens; /dev/zero is not listed.
    let (out, _) = run(policy(&d), &["open", "/dev/null", "w"]);
    assert!(out.starts_with("ok fd="), "{out}");
    let (out, _) = run(policy(&d), &["open", "/dev/zero", "r"]);
    assert_eq!(out, eacces());
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn without_a_fetch_peer_sockets_bind_listen_fastopen_and_io_uring_are_refused() {
    let d = dirs("nonet");
    for k in ["tcp4", "tcp6", "udp", "unix", "raw", "netlink"] {
        let (out, _) = run(policy(&d), &["socket", k]);
        assert_eq!(out, eacces(), "socket {k}");
    }
    let (out, _) = run(policy(&d), &["bind", "127.0.0.1:0"]);
    assert_eq!(out, eacces());
    let (out, _) = run(policy(&d), &["listen"]);
    assert_eq!(out, eacces());
    let (out, _) = run(policy(&d), &["fastopen", "127.0.0.1:9"]);
    assert_eq!(out, eacces());
    let (out, rep) = run(policy(&d), &["uring"]);
    assert_eq!(out, eacces());
    assert!(!events_for(&rep, "refused: io_uring bypasses the broker").is_empty());
}

fn serve(tag: &'static [u8]) -> (TcpListener, u16) {
    let l = TcpListener::bind("127.0.0.1:0").unwrap();
    let port = l.local_addr().unwrap().port();
    let c = l.try_clone().unwrap();
    std::thread::spawn(move || {
        for mut s in c.incoming().flatten() {
            let _ = s.write_all(tag);
        }
    });
    (l, port)
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn a_fetch_job_connects_to_its_one_peer_and_a_different_port_is_refused() {
    let d = dirs("fetch");
    let (_a, pa) = serve(b"ALLOWED");
    let (_f, pf) = serve(b"FORBIDDEN");
    let peer: std::net::SocketAddr = format!("127.0.0.1:{pa}").parse().unwrap();
    let (out, rep) = run(policy(&d).with_fetch(peer), &["connect", &peer.to_string()]);
    assert_eq!(out, "ok connected ALLOWED", "{:?}", rep.events);
    let c = rep.events.iter().find(|e| e.call == "connect").unwrap();
    assert_eq!(c.decision, Decision::Allowed);
    assert!(c.args.iter().any(|a| a == "result=ok"), "{c:?}");
    let (out, rep) = run(
        policy(&d).with_fetch(peer),
        &["connect", &format!("127.0.0.1:{pf}")],
    );
    assert_eq!(out, eacces());
    let c = rep.events.iter().find(|e| e.call == "connect").unwrap();
    assert_eq!(c.decision, Decision::Refused);
    // Only TCP of the peer's family: a raw, UDP or v6 socket is refused.
    for k in ["raw", "udp", "tcp6", "unix"] {
        let (out, _) = run(policy(&d).with_fetch(peer), &["socket", k]);
        assert_eq!(out, eacces(), "socket {k}");
    }
    let (out, _) = run(policy(&d).with_fetch(peer), &["bind", "127.0.0.1:0"]);
    assert_eq!(out, eacces(), "bind stays refused for a fetch job");
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn a_racing_thread_flipping_the_sockaddr_never_connects_to_the_forbidden_peer() {
    let d = dirs("connrace");
    let (_a, pa) = serve(b"ALLOWED");
    let (_f, pf) = serve(b"FORBIDDEN");
    let a = format!("127.0.0.1:{pa}");
    let f = format!("127.0.0.1:{pf}");
    let (out, _) = run(
        policy(&d).with_fetch(a.parse().unwrap()),
        &["connect-race", &a, &f, "400"],
    );
    assert!(out.contains(" forbidden=0 "), "{out}");
    let allowed: u64 = out
        .split("allowed=")
        .nth(1)
        .unwrap()
        .split(' ')
        .next()
        .unwrap()
        .parse()
        .unwrap();
    assert!(allowed > 0, "{out}");
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn exec_after_the_launchers_first_is_refused_and_exec_allow_lets_it_run_logged() {
    let d = dirs("exec");
    let (out, rep) = run(policy(&d), &["exec", "/bin/true"]);
    assert_eq!(out, eacces());
    let ex: Vec<_> = rep
        .events
        .iter()
        .filter(|e| e.call.starts_with("execve"))
        .collect();
    assert_eq!(ex.len(), 2, "{ex:?}");
    assert_eq!(ex[0].decision, Decision::Allowed, "the launcher's own exec");
    assert_eq!(ex[1].decision, Decision::Refused);
    let (out, _) = run(policy(&d), &["execveat", "/bin/true"]);
    assert_eq!(out, eacces());
    let (lines, rep, st) = run_with(
        policy(&d).with_exec(ExecPolicy::Allow),
        SpawnOptions::default(),
        &["exec", "/bin/true"],
    );
    assert!(st.success() && lines.is_empty(), "{lines:?} {st:?}");
    assert!(rep.events.iter().filter(|e| e.call == "execve").count() >= 2);
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn on_refuse_kill_kills_the_worker_and_logs_killed() {
    let d = dirs("kill");
    let (lines, rep, st) = run_with(
        policy(&d).with_on_refuse(OnRefuse::Kill),
        SpawnOptions::default(),
        &["open", &s(&d.no.join("secret")), "r"],
    );
    use std::os::unix::process::ExitStatusExt;
    assert_eq!(st.signal(), Some(libc::SIGKILL), "{lines:?}");
    assert!(
        rep.events.iter().any(|e| e.decision == Decision::Killed),
        "{:?}",
        rep.events
    );
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn a_worker_dying_in_the_middle_of_a_brokered_call_leaves_the_broker_finishing_cleanly() {
    let d = dirs("die");
    let opts = SpawnOptions {
        knobs: DebugKnobs {
            hold_before_answer: Some(Duration::from_millis(400)),
        },
        ..SpawnOptions::default()
    };
    let (lines, rep, st) = run_with(policy(&d), opts, &["die-mid-call", &s(&d.input.join("g"))]);
    assert_eq!(st.code(), Some(7), "{lines:?}");
    assert!(
        rep.events
            .iter()
            .any(|e| e.args.iter().any(|a| a.contains("gone"))),
        "{:?}",
        rep.events
    );
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn check_only_mode_still_refuses_outside_the_policy_and_warns_that_it_is_not_toctou_safe() {
    let d = dirs("checkonly");
    let opts = SpawnOptions {
        force_mode: Some(Mode::CheckOnly),
        ..SpawnOptions::default()
    };
    let (lines, rep, _) = run_with(
        policy(&d),
        opts.clone(),
        &["open", &s(&d.input.join("g")), "r"],
    );
    assert!(lines.last().unwrap().ends_with("GOOD"), "{lines:?}");
    assert_eq!(rep.mode, Mode::CheckOnly);
    assert!(
        !rep.broker_feature(),
        "Features.broker is true only in full mode"
    );
    assert!(
        rep.warnings.iter().any(|w| w.contains("not TOCTOU-safe")),
        "{:?}",
        rep.warnings
    );
    let (lines, _, _) = run_with(policy(&d), opts, &["open", &s(&d.no.join("secret")), "r"]);
    assert_eq!(lines.last().unwrap(), &eacces());
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn deny_only_mode_fails_every_brokered_call_closed() {
    let d = dirs("denyonly");
    let out = Command::new(SELFTEST)
        .args(["deny-only", "open", &s(&d.input.join("g")), "r"])
        .output()
        .unwrap();
    assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), eacces());
    let out = Command::new(SELFTEST)
        .args(["deny-only", "socket", "tcp4"])
        .output()
        .unwrap();
    assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), eacces());
    // spawn_brokered refuses to run a deny-only broker at all.
    let mut cmd = Command::new(SELFTEST);
    let r = heimdall_broker::spawn_brokered(
        &mut cmd,
        policy(&d),
        SpawnOptions {
            force_mode: Some(Mode::DenyOnly),
            ..SpawnOptions::default()
        },
    );
    assert!(r.is_err());
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn events_beyond_max_events_are_counted_not_kept() {
    let d = dirs("cap");
    let mut p = policy(&d);
    p.max_events = 5;
    let (out, rep) = run(p, &["bench", &s(&d.input.join("g")), "50"]);
    assert!(out.starts_with("ok opens=50"), "{out}");
    assert_eq!(rep.events.len(), 5);
    assert!(rep.dropped_events >= 45, "{}", rep.dropped_events);
}

#[test]
#[ignore = "needs seccomp USER_NOTIF + ADDFD: scripts/container-tests.sh"]
fn a_brokered_open_costs_little_enough_for_parsing_jobs() {
    let d = dirs("bench");
    let mut p = policy(&d);
    p.max_events = 0;
    let (out, _) = run(p, &["bench", &s(&d.input.join("g")), "2000"]);
    let ns: u64 = out
        .split("ns_per_open=")
        .nth(1)
        .unwrap()
        .split(' ')
        .next()
        .unwrap()
        .parse()
        .unwrap();
    eprintln!("brokered open: {out}");
    assert!(ns < 2_000_000, "{out}");
    let _ = std::fs::remove_dir_all(&d.base);
}