nordisk/heimdall
crates/heimdall-core/tests/fuzz_seeds.rs
// SPDX-License-Identifier: AGPL-3.0-only
// Copyright (C) 2026 Ignalina ApS and Vetra Automation AB
//! The core fuzz targets' invariants, replayed over the checked-in seed corpus
//! and random inputs on every ordinary test run (no nightly needed).
//! Heimdall is a defensive sandbox; these are its own parsers.
use std::path::PathBuf;
use heimdall_core::fuzz_api;
use proptest::prelude::*;
fn seed_dir(target: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../fuzz/seeds")
.join(target)
}
fn seeds(target: &str) -> Vec<Vec<u8>> {
let dir = seed_dir(target);
let mut v: Vec<_> = std::fs::read_dir(&dir)
.unwrap_or_else(|e| panic!("{}: {e}", dir.display()))
.map(|e| std::fs::read(e.unwrap().path()).unwrap())
.collect();
v.sort();
v
}
#[test]
fn signed_result_wire_fuzz_invariants_hold_on_every_seed() {
let s = seeds("signed_result_wire");
assert!(s.len() >= 5);
let mut accepted = 0;
for b in s {
if fuzz_api::signed_result_wire(&b) {
accepted += 1;
}
}
assert!(accepted >= 1, "the valid seed must verify");
}
#[test]
fn result_encoding_fuzz_invariants_hold_on_every_seed() {
let s = seeds("result_encoding");
assert!(s.len() >= 4);
for b in s {
fuzz_api::result_encoding(&b);
}
}
#[test]
fn pinned_key_text_fuzz_invariants_hold_on_every_seed() {
let s = seeds("pinned_key_text");
assert!(s.len() >= 4);
let ok = s.iter().filter(|b| fuzz_api::pinned_key_text(b)).count();
assert!(ok >= 1, "the valid seed must parse");
}
#[test]
fn fuzz_fixture_key_signs_a_result_the_wire_target_accepts() {
assert!(fuzz_api::signed_result_wire(&fuzz_api::fixture_wire()));
}
/// Regenerates the seed corpus: `cargo test -p nordisk-heimdall-core --test fuzz_seeds -- --ignored`.
#[test]
#[ignore]
fn write_fuzz_seeds() {
use heimdall_core::*;
let put = |t: &str, n: &str, b: &[u8]| {
let d = seed_dir(t);
std::fs::create_dir_all(&d).unwrap();
std::fs::write(d.join(n), b).unwrap();
};
let wire = fuzz_api::fixture_wire();
put("signed_result_wire", "valid", &wire);
let mut v2 = wire.clone();
v2[8..10].copy_from_slice(&2u16.to_be_bytes());
put("signed_result_wire", "version2", &v2);
put("signed_result_wire", "truncated", &wire[..wire.len() / 2]);
let mut flip = wire.clone();
flip[60] ^= 4;
put("signed_result_wire", "bitflip", &flip);
put("signed_result_wire", "empty", b"");
put("signed_result_wire", "magic_only", WIRE_MAGIC);
let s = SignedResult::from_bytes(&wire).unwrap();
put("result_encoding", "sample", &encode_result(&s.result));
let refused = JobResult {
verdict: Verdict::Refused {
code: "heimdall-wasm-1".into(),
reason: "guest trapped (out of fuel)".into(),
},
outputs: vec![],
events: vec![],
..s.result.clone()
};
put("result_encoding", "refused", &encode_result(&refused));
put(
"result_encoding",
"trailing",
&[encode_result(&refused), vec![0]].concat(),
);
put("result_encoding", "short", &[0u8; 47]);
let p = fuzz_api::fixture_key().pinned();
put("pinned_key_text", "valid", p.to_text().as_bytes());
put(
"pinned_key_text",
"v2",
p.to_text().replace(" v1 ", " v2 ").as_bytes(),
);
let mut wrong = p;
wrong.key_id = KeyId([0; 16]);
put(
"pinned_key_text",
"wrong_key_id",
wrong.to_text().as_bytes(),
);
put(
"pinned_key_text",
"badutf8",
b"heimdall-boot-key v1 \xff\xfe\n",
);
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(2000))]
#[test]
fn prop_signed_result_wire_fuzz_invariants_hold_on_random_bytes(b in proptest::collection::vec(any::<u8>(), 0..300)) {
fuzz_api::signed_result_wire(&b);
}
#[test]
fn prop_signed_result_wire_fuzz_invariants_hold_on_mutated_valid_wire(
pos in any::<prop::sample::Index>(), val in any::<u8>()
) {
let mut w = fuzz_api::fixture_wire();
let i = pos.index(w.len());
let changed = w[i] != val;
w[i] = val;
let ok = fuzz_api::signed_result_wire(&w);
prop_assert!(!(changed && ok), "mutated wire verified");
}
#[test]
fn prop_result_encoding_fuzz_invariants_hold_on_random_bytes(b in proptest::collection::vec(any::<u8>(), 0..300)) {
fuzz_api::result_encoding(&b);
}
#[test]
fn prop_pinned_key_text_fuzz_invariants_hold_on_random_text(s in "(heimdall-boot-key )?(v[0-9] )?[0-9a-f ]{0,140}\n?") {
fuzz_api::pinned_key_text(s.as_bytes());
}
}