guests/verify-bundle/tests/probe_corpus.rs
// SPDX-License-Identifier: AGPL-3.0-only
//! Writes the REAL verify-bundle guest's part of the `heimdall-wasm-host`
//! probe corpus (profiles/heimdall-wasm-host/verify-bundle/) and its runs in
//! profiles/heimdall-wasm-host/workload.toml (between the marker lines): the
//! single-instance guest and the split guest (verifier + decoder, N = 1 and
//! N = 8 decoder instances), signed with a throwaway CA, over a good stored
//! bundle, a good Holger-packed OpenZL bundle, a many-chunk OpenZL bundle, a
//! tampered bundle and two bombs. The rest of the corpus (WAT guests) is
//! crates/heimdall-cli/tests/profile_corpus.rs. Heimdall is a defensive
//! sandbox; the bundles attack only the guest's own checks.
//!
//! Run by hand when the guest or the corpus changes, then probe and regenerate:
//! HEIMDALL_WRITE_VB_CORPUS=$PWD/../../profiles/heimdall-wasm-host \
//! cargo test --release --test probe_corpus -- --ignored
//! heimdall profile probe heimdall-wasm-host && heimdall profile gen heimdall-wasm-host
#![cfg(feature = "openzl")]
mod common;
use common::guests::{component, Build, Lab};
use common::zwriter::{openzl_frame, RawArchive};
use common::*;
use std::path::Path;
const PAYLOAD: &str = "payload/firmware.bin";
const BEGIN: &str = "# BEGIN verify-bundle runs: written by guests/verify-bundle/tests/probe_corpus.rs; do not edit by hand";
const END: &str = "# END verify-bundle runs";
/// The policy the CLI tests use for the guest (tests/cli_split.rs), with
/// `decoders` decoder instances.
fn policy_text(decoders: u32) -> String {
format!(
"# Heimdall is a defensive sandbox: probe corpus policy (verify-bundle guest).\n\
version = 1\n[job]\nkind = \"wasm\"\n[limits]\n\
fuel = 20000000000\nwall_time_ms = 120000\nmemory_bytes = 536870912\n\
instances = 16\ntables = 16\nmemories = 4\ndecoders = {decoders}\n"
)
}
/// The bundles, all of ONE fixture (so one policy parameter serves every
/// run): name, bytes, expected exit (0 accepted, 4 refused).
fn bundles(fx: &Fixture) -> Vec<(&'static str, Vec<u8>, i32)> {
let mut tampered = fx.members.clone();
tampered.get_mut(PAYLOAD).unwrap()[0] ^= 1;
let bomb = openzl_frame(&vec![0u8; 256 << 20]);
// A frame of 256 MiB of zeros behind the payload's small declared size:
// refused when the decoder reads the frame header, nothing allocated.
let lying = {
let mut a = RawArchive::from_members_openzl(&fx.members, 1 << 20);
let off = a.blobs.len() as u64;
a.blobs.extend_from_slice(&bomb);
let row = a.row_mut(PAYLOAD);
row.blob_offset = off;
row.blob_size = bomb.len() as u64;
a.encode()
};
// The same frame declared honestly: refused by the expansion ratio
// before anything is decoded.
let honest = {
let mut a = RawArchive::from_members_openzl(&fx.members, 1 << 20);
let off = a.blobs.len() as u64;
a.blobs.extend_from_slice(&bomb);
let row = a.row_mut(PAYLOAD);
row.blob_offset = off;
row.blob_size = bomb.len() as u64;
row.uncompressed_size = 256 << 20;
a.encode()
};
vec![
("stored", fx.pack(), 0),
("openzl", pack_holger(&fx.members), 0),
(
"manychunk",
RawArchive::from_members_openzl(&fx.members, 4096).encode(),
0,
),
("tampered", pack_holger(&tampered), 4),
("bomb-lying", lying, 4),
("bomb-ratio", honest, 4),
]
}
fn signed(lab: &Lab, out: &Path, name: &str, bytes: &[u8]) {
let m = lab.signed(name, bytes);
for ext in ["", ".p7s", ".blake3"] {
let from = format!("{}{ext}", m.display());
std::fs::copy(&from, out.join(format!("{name}.wasm{ext}"))).unwrap();
}
}
fn quote(s: &str) -> String {
// A TOML basic string.
let mut q = String::from("\"");
for c in s.chars() {
match c {
'"' => q.push_str("\\\""),
'\\' => q.push_str("\\\\"),
'\n' => q.push_str("\\n"),
'\t' => q.push_str("\\t"),
c if (c as u32) < 0x20 => q.push_str(&format!("\\u{:04x}", c as u32)),
c => q.push(c),
}
}
q.push('"');
q
}
#[test]
#[ignore = "writes the checked-in probe corpus; run by hand"]
fn writes_the_verify_bundle_part_of_the_heimdall_wasm_host_probe_corpus() {
let Some(dir) = std::env::var_os("HEIMDALL_WRITE_VB_CORPUS") else {
panic!("set HEIMDALL_WRITE_VB_CORPUS=<profiles/heimdall-wasm-host>");
};
let dir = Path::new(&dir);
let workload = dir.join("workload.toml");
let toml = std::fs::read_to_string(&workload).expect("workload.toml of heimdall-wasm-host");
let out = dir.join("verify-bundle");
let _ = std::fs::remove_dir_all(&out);
std::fs::create_dir_all(&out).unwrap();
let lab = Lab::new();
std::fs::copy(lab.dir.path().join("root.der"), out.join("root.der")).unwrap();
signed(&lab, &out, "job", component(Build::Job));
signed(&lab, &out, "verifier", component(Build::Verifier));
signed(&lab, &out, "decoder", component(Build::Decoder));
for n in [1, 8] {
std::fs::write(out.join(format!("policy-n{n}.toml")), policy_text(n)).unwrap();
}
// One fixture with a payload big enough for many 4 KiB OpenZL chunks.
let fx = build(Spec {
payloads: vec![(
"firmware.bin".into(),
(0..1_200_000u32)
.map(|i| (i % 251) as u8 ^ (i >> 9) as u8)
.collect(),
"application/octet-stream".into(),
)],
..Spec::default()
});
let mut runs = String::new();
runs.push_str(BEGIN);
runs.push('\n');
let params = [
("now", t0().to_rfc3339()),
("policy", fx.policy.to_ron().unwrap()),
];
for (name, bytes, exit) in bundles(&fx) {
let input = out.join(format!("in-{name}"));
std::fs::create_dir_all(&input).unwrap();
std::fs::write(input.join("bundle.holger.znippy"), &bytes).unwrap();
for (mode, module, decoders) in [
("single", "job", 1),
("split_n1", "verifier", 1),
("split_n8", "verifier", 8),
] {
let mut args: Vec<String> = [
"run",
"--job",
"wasm",
"--module",
&format!("{{dir}}/verify-bundle/{module}.wasm"),
"--trust-root",
"{dir}/verify-bundle/root.der",
"--in",
&format!("{{dir}}/verify-bundle/in-{name}"),
"--out",
"{tmp}/out",
"--policy",
&format!("{{dir}}/verify-bundle/policy-n{decoders}.toml"),
]
.iter()
.map(|s| s.to_string())
.collect();
if module == "verifier" {
args.push("--decoder".into());
args.push("{dir}/verify-bundle/decoder.wasm".into());
}
for (k, v) in ¶ms {
args.push("--param".into());
args.push(format!("{k}={v}"));
}
let run_name = format!(
"verify_bundle_{mode}_{}_is_{}",
name.replace('-', "_"),
if exit == 0 { "accepted" } else { "refused" }
);
runs.push_str("\n[[run]]\n");
runs.push_str(&format!("name = {}\n", quote(&run_name)));
if exit != 0 {
runs.push_str("error_path = true\n");
}
let list: Vec<String> = args.iter().map(|a| quote(a)).collect();
runs.push_str(&format!("args = [{}]\n", list.join(", ")));
runs.push_str(&format!("expect_exit = {exit}\n"));
}
}
runs.push_str(END);
runs.push('\n');
// Replace the marked block, or append it.
let new = match (toml.find(BEGIN), toml.find(END)) {
(Some(a), Some(b)) => {
let end = b + END.len() + toml[b + END.len()..].find('\n').map_or(0, |i| i + 1);
format!("{}{runs}{}", &toml[..a], &toml[end..])
}
_ => format!("{}\n{runs}", toml.trim_end_matches('\n').to_string() + "\n"),
};
std::fs::write(&workload, new).unwrap();
std::fs::write(
out.join("README.md"),
"# heimdall-wasm-host probe corpus: the verify-bundle guest\n\n\
Heimdall is a defensive sandbox. Generated by\n\
`guests/verify-bundle/tests/probe_corpus.rs`: the real guest (single, and\n\
split = verifier + decoder), signed with a throwaway test CA (`root.der`),\n\
and Holger bundles made with Holger's own library; the runs are in\n\
`../workload.toml` between the verify-bundle marker lines.\n",
)
.unwrap();
}