You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

340 lines
11 KiB

// NB: this is a separate test file because on macOS this should use signals
// instead of Mach Ports and only one can be configured globally per process.
#![cfg(any(target_os = "linux", target_os = "macos"))]
#![cfg(not(miri))]
use rustix::mm::{mprotect, MprotectFlags};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use wasmtime::unix::StoreExt;
use wasmtime::*;
const WAT1: &str = r#"
(module
(func $hostcall_read (import "" "hostcall_read") (result i32))
(func $read (export "read") (result i32)
(i32.load (i32.const 0))
)
(func $read_out_of_bounds (export "read_out_of_bounds") (result i32)
(i32.load
(i32.mul
;; memory size in Wasm pages
(memory.size)
;; Wasm page size
(i32.const 65536)
)
)
)
(func (export "hostcall_read") (result i32)
call $hostcall_read
)
(func $start
(i32.store (i32.const 0) (i32.const 123))
)
(start $start)
(memory (export "memory") 1 4)
)
"#;
const WAT2: &str = r#"
(module
(import "other_module" "read" (func $other_module.read (result i32)))
(func $run (export "run") (result i32)
call $other_module.read)
)
"#;
fn invoke_export(store: &mut Store<()>, instance: Instance, func_name: &str) -> Result<i32> {
let ret = instance
.get_typed_func::<(), i32>(&mut *store, func_name)?
.call(store, ())?;
Ok(ret)
}
// Locate "memory" export, get base address and size and set memory protection to PROT_NONE
fn set_up_memory(store: &mut Store<()>, instance: Instance) -> (usize, usize) {
let mem_export = instance.get_memory(&mut *store, "memory").unwrap();
let base = mem_export.data_ptr(&store);
let length = mem_export.data_size(&store);
// So we can later trigger SIGSEGV by performing a read
unsafe {
mprotect(
base as *mut std::ffi::c_void,
length,
MprotectFlags::empty(),
)
.unwrap();
}
println!("memory: base={base:?}, length={length}");
(base as usize, length)
}
fn handle_sigsegv(
base: usize,
length: usize,
signum: libc::c_int,
siginfo: *const libc::siginfo_t,
) -> bool {
println!("Hello from instance signal handler!");
// SIGSEGV on Linux, SIGBUS on Mac
if libc::SIGSEGV == signum || libc::SIGBUS == signum {
let si_addr: *mut libc::c_void = unsafe { (*siginfo).si_addr() };
// Any signal from within module's memory we handle ourselves
let result = (si_addr as u64) < (base as u64) + (length as u64);
// Remove protections so the execution may resume
unsafe {
mprotect(
base as *mut libc::c_void,
length,
MprotectFlags::READ | MprotectFlags::WRITE,
)
.unwrap();
}
println!("signal handled: {result}");
result
} else {
// Otherwise, we forward to wasmtime's signal handler.
false
}
}
fn make_externs(store: &mut Store<()>, module: &Module) -> Vec<Extern> {
module
.imports()
.map(|import| {
assert_eq!("hostcall_read", import.name());
let func = Func::wrap(&mut *store, {
move |mut caller: Caller<'_, _>| {
let mem = caller.get_export("memory").unwrap().into_memory().unwrap();
let memory = mem.data(&caller);
i32::from_le_bytes(memory[0..4].try_into().unwrap())
}
});
wasmtime::Extern::Func(func)
})
.collect::<Vec<_>>()
}
fn engine() -> Engine {
let mut config = Config::new();
config.macos_use_mach_ports(false);
Engine::new(&config).unwrap()
}
// This test will only succeed if the SIGSEGV signal originating from the
// hostcall can be handled.
#[test]
fn test_custom_signal_handler_single_instance_hostcall() -> Result<()> {
let engine = engine();
let mut store = Store::new(&engine, ());
let module = Module::new(&engine, WAT1)?;
let externs = make_externs(&mut store, &module);
let instance = Instance::new(&mut store, &module, &externs)?;
let (base, length) = set_up_memory(&mut store, instance);
unsafe {
store.set_signal_handler(move |signum, siginfo, _| {
handle_sigsegv(base, length, signum, siginfo)
});
}
println!("calling hostcall_read...");
let result = invoke_export(&mut store, instance, "hostcall_read").unwrap();
assert_eq!(123, result);
Ok(())
}
#[test]
fn test_custom_signal_handler_single_instance() -> Result<()> {
let engine = engine();
let mut store = Store::new(&engine, ());
let module = Module::new(&engine, WAT1)?;
let externs = make_externs(&mut store, &module);
let instance = Instance::new(&mut store, &module, &externs)?;
let (base, length) = set_up_memory(&mut store, instance);
unsafe {
store.set_signal_handler(move |signum, siginfo, _| {
handle_sigsegv(base, length, signum, siginfo)
});
}
// these invoke wasmtime_call_trampoline from action.rs
{
println!("calling read...");
let result = invoke_export(&mut store, instance, "read").expect("read succeeded");
assert_eq!(123, result);
}
{
println!("calling read_out_of_bounds...");
let trap = invoke_export(&mut store, instance, "read_out_of_bounds")
.unwrap_err()
.downcast::<Trap>()?;
assert_eq!(trap, Trap::MemoryOutOfBounds);
}
// these invoke wasmtime_call_trampoline from callable.rs
{
let read_func = instance.get_typed_func::<(), i32>(&mut store, "read")?;
println!("calling read...");
let result = read_func
.call(&mut store, ())
.expect("expected function not to trap");
assert_eq!(123i32, result);
}
{
let read_out_of_bounds_func =
instance.get_typed_func::<(), i32>(&mut store, "read_out_of_bounds")?;
println!("calling read_out_of_bounds...");
let trap = read_out_of_bounds_func
.call(&mut store, ())
.unwrap_err()
.downcast::<Trap>()?;
assert_eq!(trap, Trap::MemoryOutOfBounds);
}
Ok(())
}
#[test]
fn test_custom_signal_handler_multiple_instances() -> Result<()> {
let engine = engine();
let mut store = Store::new(&engine, ());
let module = Module::new(&engine, WAT1)?;
// Set up multiple instances
let externs = make_externs(&mut store, &module);
let instance1 = Instance::new(&mut store, &module, &externs)?;
let instance1_handler_triggered = Arc::new(AtomicBool::new(false));
unsafe {
let (base1, length1) = set_up_memory(&mut store, instance1);
store.set_signal_handler({
let instance1_handler_triggered = instance1_handler_triggered.clone();
move |_signum, _siginfo, _context| {
// Remove protections so the execution may resume
mprotect(
base1 as *mut libc::c_void,
length1,
MprotectFlags::READ | MprotectFlags::WRITE,
)
.unwrap();
instance1_handler_triggered.store(true, Ordering::SeqCst);
println!(
"Hello from instance1 signal handler! {}",
instance1_handler_triggered.load(Ordering::SeqCst)
);
true
}
});
}
// Invoke both instances and trigger both signal handlers
// First instance1
{
let mut exports1 = instance1.exports(&mut store);
assert!(exports1.next().is_some());
drop(exports1);
println!("calling instance1.read...");
let result = invoke_export(&mut store, instance1, "read").expect("read succeeded");
assert_eq!(123, result);
assert_eq!(
instance1_handler_triggered.load(Ordering::SeqCst),
true,
"instance1 signal handler has been triggered"
);
}
let externs = make_externs(&mut store, &module);
let instance2 =
Instance::new(&mut store, &module, &externs).expect("failed to instantiate module");
let instance2_handler_triggered = Arc::new(AtomicBool::new(false));
unsafe {
let (base2, length2) = set_up_memory(&mut store, instance2);
store.set_signal_handler({
let instance2_handler_triggered = instance2_handler_triggered.clone();
move |_signum, _siginfo, _context| {
// Remove protections so the execution may resume
mprotect(
base2 as *mut libc::c_void,
length2,
MprotectFlags::READ | MprotectFlags::WRITE,
)
.unwrap();
instance2_handler_triggered.store(true, Ordering::SeqCst);
println!(
"Hello from instance2 signal handler! {}",
instance2_handler_triggered.load(Ordering::SeqCst)
);
true
}
});
}
// And then instance2
{
let mut exports2 = instance2.exports(&mut store);
assert!(exports2.next().is_some());
drop(exports2);
println!("calling instance2.read...");
let result = invoke_export(&mut store, instance2, "read").expect("read succeeded");
assert_eq!(123, result);
assert_eq!(
instance2_handler_triggered.load(Ordering::SeqCst),
true,
"instance1 signal handler has been triggered"
);
}
Ok(())
}
#[test]
fn test_custom_signal_handler_instance_calling_another_instance() -> Result<()> {
let engine = engine();
let mut store = Store::new(&engine, ());
// instance1 which defines 'read'
let module1 = Module::new(&engine, WAT1)?;
let externs = make_externs(&mut store, &module1);
let instance1 = Instance::new(&mut store, &module1, &externs)?;
let (base1, length1) = set_up_memory(&mut store, instance1);
unsafe {
store.set_signal_handler(move |signum, siginfo, _| {
println!("instance1");
handle_sigsegv(base1, length1, signum, siginfo)
});
}
let mut instance1_exports = instance1.exports(&mut store);
let instance1_read = instance1_exports.next().unwrap().clone().into_extern();
drop(instance1_exports);
// instance2 which calls 'instance1.read'
let module2 = Module::new(&engine, WAT2)?;
let instance2 = Instance::new(&mut store, &module2, &[instance1_read])?;
// since 'instance2.run' calls 'instance1.read' we need to set up the signal handler to handle
// SIGSEGV originating from within the memory of instance1
unsafe {
store.set_signal_handler(move |signum, siginfo, _| {
handle_sigsegv(base1, length1, signum, siginfo)
});
}
println!("calling instance2.run");
let result = invoke_export(&mut store, instance2, "run")?;
assert_eq!(123, result);
Ok(())
}