nordisk/heimdall
crates/heimdall-wasm/tests/refusal_codes.rs
// SPDX-License-Identifier: AGPL-3.0-only
//! Every kind of refusal carries its stable `heimdall-wasm-<n>` code, so a
//! caller branches on the code and never parses the reason text.
//! Heimdall is a defensive sandbox.
mod common;
use common::*;
use heimdall_wasm::{codes, RunLimits};
use std::time::Duration;
fn code_of(kit: &Kit, name: &str, wat: Wat, input: &[u8], limits: &RunLimits) -> &'static str {
let g = kit.guest(name, &wat.text());
g.run(input, &none(), limits)
.verdict
.code()
.unwrap_or_else(|| panic!("{name} was accepted"))
}
#[test]
fn each_run_refusal_has_its_own_code() {
let kit = Kit::new("codes");
let spin = || Wat::new("(loop $l (br $l)) unreachable");
let l = RunLimits::default();
let cases: Vec<(&str, Wat, &[u8], RunLimits, &str)> = vec![
(
"refuse",
Wat::new("(call $refuse (i32.const 16) (i32.const 1))")
.extra(r#"(data (i32.const 16) "n")"#),
b"",
l.clone(),
codes::GUEST_REFUSED,
),
(
"abort",
Wat::new("unreachable"),
b"",
l.clone(),
codes::TRAPPED,
),
(
"fuel",
spin(),
b"",
RunLimits {
fuel: 100_000,
..l.clone()
},
codes::OUT_OF_FUEL,
),
(
"clock",
spin(),
b"",
RunLimits {
fuel: u64::MAX,
timeout: Duration::from_millis(100),
..l.clone()
},
codes::TIMEOUT,
),
(
"mem",
Wat::new("(drop (memory.grow (i32.const 1000))) unreachable"),
b"",
tight(),
codes::MEMORY_LIMIT,
),
(
"table",
Wat::new("(drop (table.grow $t (ref.null func) (i32.const 1000000))) unreachable")
.extra("(table $t 1 funcref)"),
b"",
tight(),
codes::TABLE_LIMIT,
),
(
"stack",
Wat::new("(call $d)").extra("(func $d (result i32) (call $d))"),
b"",
RunLimits {
fuel: u64::MAX,
..l.clone()
},
codes::STACK_OVERFLOW,
),
(
"inst",
Wat::new("(call $ok (i32.const 0) (i32.const 0))"),
b"",
RunLimits {
instances: 1,
..l.clone()
},
codes::INSTANTIATION_LIMIT,
),
(
"emit",
Wat::new(
"(call $emit (i32.const 16) (i32.const 2) (i32.const 0) (i32.const 0)) unreachable",
)
.extra(r#"(data (i32.const 16) "..")"#),
b"",
l.clone(),
codes::EMIT_REFUSED,
),
(
"note",
Wat::new("(call $ok (i32.const 2048) (i32.const 1500))"),
b"",
RunLimits {
max_text_bytes: 1000,
..l.clone()
},
codes::OUTPUT_TOO_LARGE,
),
];
for (name, wat, input, limits, want) in cases {
assert_eq!(code_of(&kit, name, wat, input, &limits), want, "{name}");
}
}
#[test]
fn a_wrong_run_signature_has_the_interface_code() {
let kit = Kit::new("code-iface");
let wit = "package heimdall:attack@0.1.0;\nworld other { export run: func() -> u32; }";
let core =
r#"(module (memory (export "memory") 1) (func (export "run") (result i32) (i32.const 7)))"#;
let g = kit
.try_load("runtype", &component_with(core, &[("o.wit", wit)], "other"))
.unwrap();
assert_eq!(
g.run(b"", &none(), &RunLimits::default()).verdict.code(),
Some(codes::INTERFACE_MISMATCH)
);
}
#[test]
fn each_load_refusal_has_its_own_code() {
let kit = Kit::new("load-codes");
let host = kit.host();
assert_eq!(
host.load(&kit.dir.join("none.wasm")).err().unwrap().code(),
codes::LOAD_UNREADABLE
);
let p = kit.place("good", &component(&good_wat()));
assert_eq!(
host.load_pinned(&p, &"0".repeat(64)).err().unwrap().code(),
codes::LOAD_DIGEST_MISMATCH
);
std::fs::remove_file(sidecar(&p, "p7s")).unwrap();
assert_eq!(host.load(&p).err().unwrap().code(), codes::LOAD_UNTRUSTED);
std::fs::remove_file(sidecar(&p, "blake3")).unwrap();
assert_eq!(
host.load(&p).err().unwrap().code(),
codes::LOAD_DIGEST_MISSING
);
let core = wat::parse_str("(module)").unwrap();
assert_eq!(
kit.try_load("core", &core).err().unwrap().code(),
codes::LOAD_NOT_A_COMPONENT
);
assert_eq!(
kit.try_load("junk", b"junk").err().unwrap().code(),
codes::LOAD_INVALID
);
let e = kit.try_load("junk2", b"junk").err().unwrap();
assert!(e.to_string().ends_with("[heimdall-wasm-26]"), "{e}");
}