Gunnar

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