use rwasm::{
instruction_set, ExecutionEngine, ImportLinker, ImportName, InstructionSet, RwasmModule,
RwasmModuleBuilder, RwasmStore, StoreTr, TrapCode, TypedCaller, Value,
};
use rwasm_fuel_policy::SyscallFuelParams;
use std::sync::Arc;
use wasmparser::ValType;
fn execute(code_section: InstructionSet) -> Result<(), TrapCode> {
let module = RwasmModuleBuilder::new(code_section).build();
let mut store = RwasmStore::new(
Arc::new(ImportLinker::default()),
(),
rwasm::always_failing_syscall_handler,
Some(1_000_000),
None,
);
ExecutionEngine::new().execute(&mut store, &module, &[], &mut [])
}
fn verify(code_section: InstructionSet) -> Result<(), rwasm::RwasmModuleError> {
let sink = RwasmModuleBuilder::new(code_section).build().serialize();
RwasmModule::new_verified(&sink).map(|_| ())
}
#[test]
fn local_get_beyond_the_stack_traps_instead_of_reading_out_of_bounds() {
let code = instruction_set! {
StackCheck(16)
LocalGet(0x0800_0000)
Drop
Return
};
assert!(verify(code.clone()).is_err());
assert_eq!(execute(code), Err(TrapCode::StackOverflow));
}
#[test]
fn local_set_beyond_the_stack_traps_instead_of_writing_out_of_bounds() {
let code = instruction_set! {
StackCheck(16)
I32Const(0xdead_beefu32 as i32)
LocalSet(0x0800_0000)
Return
};
assert!(verify(code.clone()).is_err());
assert_eq!(execute(code), Err(TrapCode::StackOverflow));
}
#[test]
fn local_tee_beyond_the_stack_traps_instead_of_writing_out_of_bounds() {
let code = instruction_set! {
StackCheck(16)
I32Const(0xdead_beefu32 as i32)
LocalTee(0x0800_0000)
Drop
Return
};
assert!(verify(code.clone()).is_err());
assert_eq!(execute(code), Err(TrapCode::StackOverflow));
}
#[test]
fn dropping_below_the_stack_base_traps() {
let code = instruction_set! {
StackCheck(16)
Drop
Return
};
assert_eq!(execute(code), Err(TrapCode::StackOverflow));
}
#[test]
fn bulk_drop_below_the_stack_base_traps() {
let code = instruction_set! {
StackCheck(16)
BulkDrop(0x0800_0000)
Return
};
assert!(verify(code.clone()).is_err());
assert_eq!(execute(code), Err(TrapCode::StackOverflow));
}
#[test]
fn popping_below_the_stack_base_traps() {
let code = instruction_set! {
StackCheck(16)
I32Const(1)
I32Add
Return
};
assert_eq!(execute(code), Err(TrapCode::StackOverflow));
}
#[test]
fn syscall_with_underflowing_params_traps_before_reaching_the_host() {
const EXIT_IDX: u32 = 75;
fn halting_handler(
caller: &mut TypedCaller<'_, Vec<i32>>,
_idx: u32,
params: &[Value],
_result: &mut [Value],
) -> Result<(), TrapCode> {
caller
.data_mut()
.extend(params.iter().map(|p| p.i32().unwrap_or(i32::MIN)));
Err(TrapCode::ExecutionHalted)
}
let mut import_linker = ImportLinker::default();
import_linker.insert_function(
ImportName::new("fluentbase_v1preview", "_exit"),
EXIT_IDX,
SyscallFuelParams::default(),
&[ValType::I32],
&[],
);
let code = instruction_set! {
StackCheck(16)
Call(EXIT_IDX)
Return
};
assert!(
verify(code.clone()).is_ok(),
"verification cannot catch this one"
);
let module = RwasmModuleBuilder::new(code).build();
let mut store = RwasmStore::new(
Arc::new(import_linker),
Vec::<i32>::new(),
halting_handler,
Some(1_000_000),
None,
);
let result = ExecutionEngine::new().execute(&mut store, &module, &[], &mut []);
assert_eq!(result, Err(TrapCode::StackOverflow));
assert!(
store.data().is_empty(),
"the host must not have been called: {:?}",
store.data()
);
}
#[test]
fn a_trap_caused_by_an_underflow_is_reported_as_the_underflow() {
let code = instruction_set! {
StackCheck(16)
I32Const(0)
I32DivU
Return
};
assert_eq!(execute(code), Err(TrapCode::StackOverflow));
}
#[test]
fn pushing_past_the_reserved_stack_window_traps() {
let mut code = instruction_set! { StackCheck(1) };
for _ in 0..(rwasm::N_MAX_STACK_SIZE + 1) {
code.op_i32_const(1);
}
code.op_return();
assert!(verify(code.clone()).is_err());
assert_eq!(execute(code), Err(TrapCode::StackOverflow));
}
#[test]
fn compiled_modules_pass_verification() {
use rwasm::{CompilationConfig, ImportName};
use rwasm_fuel_policy::SyscallFuelParams;
use wasmparser::ValType;
const I32X3: &[ValType] = &[ValType::I32; 3];
let mut import_linker = ImportLinker::default();
for (name, idx, params, results) in [
("_debug_log", 70u32, 2usize, 0usize),
("_input_size", 71, 0, 1),
("_output_size", 72, 0, 1),
("_read", 73, 3, 0),
("_write", 74, 2, 0),
("_exit", 75, 1, 0),
("_read_output", 76, 3, 0),
] {
import_linker.insert_function(
ImportName::new("fluentbase_v1preview", name),
idx,
SyscallFuelParams::default(),
&I32X3[..params],
&I32X3[..results],
);
}
let import_linker = Arc::new(import_linker);
for wasm_binary in [
include_bytes!("assets/nitro-verifier-stack-ub.wasm").as_slice(),
include_bytes!("assets/panic-stack-ub.wasm").as_slice(),
include_bytes!("assets/secp256k1-stack-ub.wasm").as_slice(),
] {
let config = CompilationConfig::default()
.with_entrypoint_name("main".into())
.with_allow_malformed_entrypoint_func_type(true)
.with_import_linker(import_linker.clone());
let (module, _) = RwasmModule::compile(config, wasm_binary).expect("rwasm compiles");
module.verify().expect("compiled module verifies");
}
}