crates/heimdall-cli/tests/trace_cli.rs
// SPDX-License-Identifier: AGPL-3.0-only
//! `heimdall trace diff|verify`: exit codes and output. Heimdall is a
//! defensive sandbox; `heimdall trace run` needs a kernel and runs in
//! crates/heimdall-trace/tests/kernel.rs's setup (scripts/trace-tests.sh).
use std::path::{Path, PathBuf};
use std::process::Command;
use heimdall_core::*;
use heimdall_trace::record::Recorder;
use heimdall_trace::*;
fn bin() -> Command {
Command::new(env!("CARGO_BIN_EXE_heimdall"))
}
fn scratch() -> PathBuf {
let d = std::env::temp_dir().join(format!("heimdall-cli-trace-{}", JobId::random()));
std::fs::create_dir_all(&d).unwrap();
d
}
fn trace(connect: Option<&str>) -> Vec<u8> {
let mut r = Recorder::new(vec!["/usr/bin/python3".into()], 1 << 20);
if let Some(a) = connect {
r.net(NetEvent {
program: "/usr/bin/python3.13".into(),
op: "connect".into(),
family: "inet".into(),
address: a.into(),
refused: true,
});
}
r.trace_mut().exit = "exit 0".into();
r.finish().1
}
fn write(d: &Path, n: &str, b: &[u8]) -> PathBuf {
let p = d.join(n);
std::fs::write(&p, b).unwrap();
p
}
#[test]
fn trace_diff_exits_0_on_the_same_behaviour_and_1_on_a_new_connect() {
let d = scratch();
let base = write(&d, "base.json", &trace(None));
let same = write(&d, "same.json", &trace(None));
let evil = write(&d, "evil.json", &trace(Some("10.0.0.1:443")));
let o = bin()
.args(["trace", "diff"])
.arg(&base)
.arg(&same)
.output()
.unwrap();
assert_eq!(
o.status.code(),
Some(0),
"{}",
String::from_utf8_lossy(&o.stderr)
);
let o = bin()
.args(["trace", "diff"])
.arg(&base)
.arg(&evil)
.output()
.unwrap();
assert_eq!(o.status.code(), Some(1));
assert!(String::from_utf8_lossy(&o.stdout).contains("10.0.0.1:443"));
let bad = write(&d, "bad.json", b"{\"format\": 1}");
let o = bin()
.args(["trace", "diff"])
.arg(&base)
.arg(&bad)
.output()
.unwrap();
assert_eq!(o.status.code(), Some(3));
let _ = std::fs::remove_dir_all(&d);
}
#[test]
fn trace_verify_exits_0_on_a_signed_trace_and_1_when_one_bit_of_it_flips() {
let d = scratch();
let out = d.join("out");
std::fs::create_dir_all(&out).unwrap();
let t = trace(Some("10.0.0.1:443"));
std::fs::write(out.join(TRACE_FILE), &t).unwrap();
let key = BootKey::generate();
let result = JobResult {
job_id: JobId::random(),
nonce: Nonce::random(),
verdict: Verdict::Accepted,
outputs: vec![Output::of(TRACE_FILE, &t)],
exit: ExitInfo::exited(0),
started_unix: 1,
finished_unix: 2,
runner: Features {
runner: "heimdall".into(),
container: true,
seccomp: Protection::Default,
landlock: true,
broker: false,
wasm: false,
image_verified_each_start: true,
},
events: vec![],
kernel: None,
};
let signed = key.sign(result).unwrap();
let res = write(&d, "result.signed", &signed.to_bytes());
let pin = key.pinned().to_text();
let run = || {
bin()
.args(["trace", "verify"])
.arg(&res)
.args(["--key", &pin, "--files"])
.arg(&out)
.output()
.unwrap()
};
let o = run();
assert_eq!(
o.status.code(),
Some(0),
"{}",
String::from_utf8_lossy(&o.stdout)
);
assert!(String::from_utf8_lossy(&o.stdout).contains("\"traced\""));
let mut f = t.clone();
f[10] ^= 0x01;
std::fs::write(out.join(TRACE_FILE), &f).unwrap();
let o = run();
assert_eq!(o.status.code(), Some(1));
assert!(String::from_utf8_lossy(&o.stdout).contains("\"unknown\""));
let _ = std::fs::remove_dir_all(&d);
}