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