Gunnar

nordisk/heimdall

crates/heimdall-wasm/tests/guest_output.rs

// SPDX-License-Identifier: AGPL-3.0-only
//! The value `run` returns is untrusted too: an oversized note or reason, a
//! result pointer outside memory, a bad variant tag or invalid UTF-8 are all
//! refusals. Heimdall is a defensive sandbox.

mod common;
use common::*;
use heimdall_wasm::RunLimits;

/// Returns a note (or reason) of `len` zero bytes from a buffer it allocates.
fn text_of_len(kit: &Kit, refuse: bool) -> heimdall_wasm::Guest {
    let f = if refuse { "$refuse" } else { "$ok" };
    kit.guest(
        if refuse { "big-reason" } else { "big-note" },
        &Wat::new(&format!(
            "(call {f} (call $realloc (i32.const 0) (i32.const 0) (i32.const 1) (i32.load (local.get $in))) (i32.load (local.get $in)))"
        ))
        .text(),
    )
}

#[test]
fn a_note_at_the_text_limit_is_accepted() {
    let kit = Kit::new("note-ok");
    let limits = RunLimits {
        max_text_bytes: 1000,
        ..RunLimits::default()
    };
    let o = text_of_len(&kit, false).run(&1000u32.to_le_bytes(), &none(), &limits);
    assert!(o.verdict.is_accepted(), "{}", o.verdict);
}

#[test]
fn a_huge_note_is_refused() {
    let kit = Kit::new("note-huge");
    let o =
        text_of_len(&kit, false).run(&(8u32 << 20).to_le_bytes(), &none(), &RunLimits::default());
    assert_trapped(&o, "note");
}

#[test]
fn a_huge_refusal_reason_is_refused_as_a_trap_not_passed_on() {
    let kit = Kit::new("reason-huge");
    let o =
        text_of_len(&kit, true).run(&(1u32 << 20).to_le_bytes(), &none(), &RunLimits::default());
    assert_trapped(&o, "reason");
    assert!(
        o.verdict.reason().unwrap().len() < 1000,
        "the huge reason leaked into the verdict"
    );
}

#[test]
fn a_note_that_is_not_utf8_is_refused() {
    let kit = Kit::new("note-utf8");
    let g = kit.guest(
        "badutf8",
        &Wat::new("(call $ok (i32.const 16) (i32.const 3))")
            .extra(r#"(data (i32.const 16) "\ff\fe\fd")"#)
            .text(),
    );
    assert_trapped(&g.run(b"", &none(), &RunLimits::default()), "UTF-8");
}

#[test]
fn a_result_pointer_outside_memory_is_refused() {
    let kit = Kit::new("ret-oob");
    let g = kit.guest("retoob", &Wat::new("(i32.const 0xfffffff0)").text());
    assert_trapped(&g.run(b"", &none(), &RunLimits::default()), "out of bounds");
}

#[test]
fn a_note_pointer_outside_memory_is_refused() {
    let kit = Kit::new("note-oob");
    let g = kit.guest(
        "noteoob",
        &Wat::new("(call $ok (i32.const 0xffff0000) (i32.const 10))").text(),
    );
    assert_trapped(&g.run(b"", &none(), &RunLimits::default()), "out of bounds");
}

#[test]
fn a_result_tag_that_is_neither_ok_nor_err_is_refused() {
    let kit = Kit::new("bad-tag");
    let g = kit.guest(
        "badtag",
        &Wat::new("(drop (call $ok (i32.const 0) (i32.const 0))) (i32.store8 (i32.const 1024) (i32.const 7)) (i32.const 1024)").text(),
    );
    assert_trapped(&g.run(b"", &none(), &RunLimits::default()), "discriminant");
}

#[test]
fn a_misaligned_result_pointer_is_refused() {
    let kit = Kit::new("misaligned");
    let g = kit.guest("misaligned", &Wat::new("(i32.const 1025)").text());
    assert_trapped(&g.run(b"", &none(), &RunLimits::default()), "align");
}