Gunnar

nordisk/heimdall

crates/heimdall-wasm/tests/properties.rs

// SPDX-License-Identifier: AGPL-3.0-only
//! Property tests: random module bytes never get past load and never panic
//! the host; random input to a good guest always ends in a verdict, the same
//! one twice. Heimdall is a defensive sandbox.

mod common;
use common::*;
use heimdall_wasm::RunLimits;
use proptest::prelude::*;
use std::sync::OnceLock;

fn kit() -> &'static Kit {
    static K: OnceLock<Kit> = OnceLock::new();
    K.get_or_init(|| Kit::new("prop"))
}

fn good() -> &'static heimdall_wasm::Guest {
    static G: OnceLock<heimdall_wasm::Guest> = OnceLock::new();
    G.get_or_init(|| kit().guest("good", &good_wat()))
}

proptest! {
    #![proptest_config(ProptestConfig { cases: 64, ..ProptestConfig::default() })]

    #[test]
    fn signed_random_bytes_never_load_and_never_panic(bytes in proptest::collection::vec(any::<u8>(), 0..4096)) {
        prop_assert!(kit().try_load("random", &bytes).is_err());
    }

    #[test]
    fn signed_random_bytes_behind_a_component_header_never_load(tail in proptest::collection::vec(any::<u8>(), 0..4096)) {
        let mut bytes = b"\0asm\x0d\0\x01\0".to_vec();
        bytes.extend(tail);
        prop_assert!(kit().try_load("random-component", &bytes).is_err());
    }

    #[test]
    fn any_input_ends_in_a_verdict_and_the_same_one_twice(input in proptest::collection::vec(any::<u8>(), 0..2048)) {
        let a = good().run(&input, &none(), &RunLimits::default());
        let b = good().run(&input, &none(), &RunLimits::default());
        prop_assert_eq!(&a, &b);
        match input.first() {
            Some(b'T') => prop_assert_eq!(a.verdict.reason(), Some("input signature mismatch")),
            Some(b'M') => prop_assert!(a.verdict.reason().unwrap().starts_with("guest trapped (")),
            _ => {
                prop_assert!(a.verdict.is_accepted());
                prop_assert_eq!(&a.files[0].bytes, &input);
            }
        }
    }

    #[test]
    fn any_params_reach_the_guest_without_harm(params in proptest::collection::btree_map(".{0,40}", ".{0,40}", 0..20)) {
        let o = good().run(b"x", &params, &RunLimits::default());
        prop_assert!(o.verdict.is_accepted());
    }
}