1 // NB: this is a separate test file because on macOS this should use signals
2 // instead of Mach Ports and only one can be configured globally per process.
3
4 #![cfg(any(target_os = "linux", target_os = "macos"))]
5 #![cfg(not(miri))]
6 #![cfg(has_native_signals)]
7
8 use rustix::mm::{MprotectFlags, mprotect};
9 use std::sync::Arc;
10 use std::sync::atomic::{AtomicBool, Ordering};
11 use wasmtime::unix::StoreExt;
12 use wasmtime::*;
13
14 const WAT1: &str = r#"
15 (module
16 (func $hostcall_read (import "" "hostcall_read") (result i32))
17 (func $read (export "read") (result i32)
18 (i32.load (i32.const 0))
19 )
20 (func $read_out_of_bounds (export "read_out_of_bounds") (result i32)
21 (i32.load
22 (i32.mul
23 ;; memory size in Wasm pages
24 (memory.size)
25 ;; Wasm page size
26 (i32.const 65536)
27 )
28 )
29 )
30 (func (export "hostcall_read") (result i32)
31 call $hostcall_read
32 )
33 (func $start
34 (i32.store (i32.const 0) (i32.const 123))
35 )
36 (start $start)
37 (memory (export "memory") 1 4)
38 )
39 "#;
40
41 const WAT2: &str = r#"
42 (module
43 (import "other_module" "read" (func $other_module.read (result i32)))
44 (func $run (export "run") (result i32)
45 call $other_module.read)
46 )
47 "#;
48
invoke_export(store: &mut Store<()>, instance: Instance, func_name: &str) -> Result<i32>49 fn invoke_export(store: &mut Store<()>, instance: Instance, func_name: &str) -> Result<i32> {
50 let ret = instance
51 .get_typed_func::<(), i32>(&mut *store, func_name)?
52 .call(store, ())?;
53 Ok(ret)
54 }
55
56 // Locate "memory" export, get base address and size and set memory protection to PROT_NONE
set_up_memory(store: &mut Store<()>, instance: Instance) -> (usize, usize)57 fn set_up_memory(store: &mut Store<()>, instance: Instance) -> (usize, usize) {
58 let mem_export = instance.get_memory(&mut *store, "memory").unwrap();
59 let base = mem_export.data_ptr(&store);
60 let length = mem_export.data_size(&store);
61
62 // So we can later trigger SIGSEGV by performing a read
63 unsafe {
64 mprotect(
65 base as *mut std::ffi::c_void,
66 length,
67 MprotectFlags::empty(),
68 )
69 .unwrap();
70 }
71
72 println!("memory: base={base:?}, length={length}");
73
74 (base as usize, length)
75 }
76
handle_sigsegv( base: usize, length: usize, signum: libc::c_int, siginfo: *const libc::siginfo_t, ) -> bool77 fn handle_sigsegv(
78 base: usize,
79 length: usize,
80 signum: libc::c_int,
81 siginfo: *const libc::siginfo_t,
82 ) -> bool {
83 println!("Hello from instance signal handler!");
84 // SIGSEGV on Linux, SIGBUS on Mac
85 if libc::SIGSEGV == signum || libc::SIGBUS == signum {
86 let si_addr: *mut libc::c_void = unsafe { (*siginfo).si_addr() };
87 // Any signal from within module's memory we handle ourselves
88 let result = (si_addr as u64) < (base as u64) + (length as u64);
89 // Remove protections so the execution may resume
90 unsafe {
91 mprotect(
92 base as *mut libc::c_void,
93 length,
94 MprotectFlags::READ | MprotectFlags::WRITE,
95 )
96 .unwrap();
97 }
98 println!("signal handled: {result}");
99 result
100 } else {
101 // Otherwise, we forward to wasmtime's signal handler.
102 false
103 }
104 }
105
make_externs(store: &mut Store<()>, module: &Module) -> Vec<Extern>106 fn make_externs(store: &mut Store<()>, module: &Module) -> Vec<Extern> {
107 module
108 .imports()
109 .map(|import| {
110 assert_eq!("hostcall_read", import.name());
111 let func = Func::wrap(&mut *store, {
112 move |mut caller: Caller<'_, _>| {
113 let mem = caller.get_export("memory").unwrap().into_memory().unwrap();
114 let memory = mem.data(&caller);
115 i32::from_le_bytes(memory[0..4].try_into().unwrap())
116 }
117 });
118 wasmtime::Extern::Func(func)
119 })
120 .collect::<Vec<_>>()
121 }
122
engine() -> Engine123 fn engine() -> Engine {
124 let mut config = Config::new();
125 config.macos_use_mach_ports(false);
126 Engine::new(&config).unwrap()
127 }
128
129 // This test will only succeed if the SIGSEGV signal originating from the
130 // hostcall can be handled.
131 #[test]
test_custom_signal_handler_single_instance_hostcall() -> Result<()>132 fn test_custom_signal_handler_single_instance_hostcall() -> Result<()> {
133 let engine = engine();
134 let mut store = Store::new(&engine, ());
135 let module = Module::new(&engine, WAT1)?;
136
137 let externs = make_externs(&mut store, &module);
138 let instance = Instance::new(&mut store, &module, &externs)?;
139
140 let (base, length) = set_up_memory(&mut store, instance);
141 unsafe {
142 store.set_signal_handler(move |signum, siginfo, _| {
143 handle_sigsegv(base, length, signum, siginfo)
144 });
145 }
146 println!("calling hostcall_read...");
147 let result = invoke_export(&mut store, instance, "hostcall_read").unwrap();
148 assert_eq!(123, result);
149 Ok(())
150 }
151
152 #[test]
test_custom_signal_handler_single_instance() -> Result<()>153 fn test_custom_signal_handler_single_instance() -> Result<()> {
154 let engine = engine();
155 let mut store = Store::new(&engine, ());
156 let module = Module::new(&engine, WAT1)?;
157
158 let externs = make_externs(&mut store, &module);
159 let instance = Instance::new(&mut store, &module, &externs)?;
160
161 let (base, length) = set_up_memory(&mut store, instance);
162 unsafe {
163 store.set_signal_handler(move |signum, siginfo, _| {
164 handle_sigsegv(base, length, signum, siginfo)
165 });
166 }
167
168 // these invoke wasmtime_call_trampoline from action.rs
169 {
170 println!("calling read...");
171 let result = invoke_export(&mut store, instance, "read").expect("read succeeded");
172 assert_eq!(123, result);
173 }
174
175 {
176 println!("calling read_out_of_bounds...");
177 let trap = invoke_export(&mut store, instance, "read_out_of_bounds")
178 .unwrap_err()
179 .downcast::<Trap>()?;
180 assert_eq!(trap, Trap::MemoryOutOfBounds);
181 }
182
183 // these invoke wasmtime_call_trampoline from callable.rs
184 {
185 let read_func = instance.get_typed_func::<(), i32>(&mut store, "read")?;
186 println!("calling read...");
187 let result = read_func
188 .call(&mut store, ())
189 .expect("expected function not to trap");
190 assert_eq!(123i32, result);
191 }
192
193 {
194 let read_out_of_bounds_func =
195 instance.get_typed_func::<(), i32>(&mut store, "read_out_of_bounds")?;
196 println!("calling read_out_of_bounds...");
197 let trap = read_out_of_bounds_func
198 .call(&mut store, ())
199 .unwrap_err()
200 .downcast::<Trap>()?;
201 assert_eq!(trap, Trap::MemoryOutOfBounds);
202 }
203 Ok(())
204 }
205
206 #[test]
test_custom_signal_handler_multiple_instances() -> Result<()>207 fn test_custom_signal_handler_multiple_instances() -> Result<()> {
208 let engine = engine();
209 let mut store = Store::new(&engine, ());
210 let module = Module::new(&engine, WAT1)?;
211
212 // Set up multiple instances
213
214 let externs = make_externs(&mut store, &module);
215 let instance1 = Instance::new(&mut store, &module, &externs)?;
216 let instance1_handler_triggered = Arc::new(AtomicBool::new(false));
217
218 unsafe {
219 let (base1, length1) = set_up_memory(&mut store, instance1);
220
221 store.set_signal_handler({
222 let instance1_handler_triggered = instance1_handler_triggered.clone();
223 move |_signum, _siginfo, _context| {
224 // Remove protections so the execution may resume
225 mprotect(
226 base1 as *mut libc::c_void,
227 length1,
228 MprotectFlags::READ | MprotectFlags::WRITE,
229 )
230 .unwrap();
231 instance1_handler_triggered.store(true, Ordering::SeqCst);
232 println!(
233 "Hello from instance1 signal handler! {}",
234 instance1_handler_triggered.load(Ordering::SeqCst)
235 );
236 true
237 }
238 });
239 }
240
241 // Invoke both instances and trigger both signal handlers
242
243 // First instance1
244 {
245 let mut exports1 = instance1.exports(&mut store);
246 assert!(exports1.next().is_some());
247 drop(exports1);
248
249 println!("calling instance1.read...");
250 let result = invoke_export(&mut store, instance1, "read").expect("read succeeded");
251 assert_eq!(123, result);
252 assert_eq!(
253 instance1_handler_triggered.load(Ordering::SeqCst),
254 true,
255 "instance1 signal handler has been triggered"
256 );
257 }
258
259 let externs = make_externs(&mut store, &module);
260 let instance2 =
261 Instance::new(&mut store, &module, &externs).expect("failed to instantiate module");
262 let instance2_handler_triggered = Arc::new(AtomicBool::new(false));
263
264 unsafe {
265 let (base2, length2) = set_up_memory(&mut store, instance2);
266
267 store.set_signal_handler({
268 let instance2_handler_triggered = instance2_handler_triggered.clone();
269 move |_signum, _siginfo, _context| {
270 // Remove protections so the execution may resume
271 mprotect(
272 base2 as *mut libc::c_void,
273 length2,
274 MprotectFlags::READ | MprotectFlags::WRITE,
275 )
276 .unwrap();
277 instance2_handler_triggered.store(true, Ordering::SeqCst);
278 println!(
279 "Hello from instance2 signal handler! {}",
280 instance2_handler_triggered.load(Ordering::SeqCst)
281 );
282 true
283 }
284 });
285 }
286
287 // And then instance2
288 {
289 let mut exports2 = instance2.exports(&mut store);
290 assert!(exports2.next().is_some());
291 drop(exports2);
292
293 println!("calling instance2.read...");
294 let result = invoke_export(&mut store, instance2, "read").expect("read succeeded");
295 assert_eq!(123, result);
296 assert_eq!(
297 instance2_handler_triggered.load(Ordering::SeqCst),
298 true,
299 "instance1 signal handler has been triggered"
300 );
301 }
302 Ok(())
303 }
304
305 #[test]
test_custom_signal_handler_instance_calling_another_instance() -> Result<()>306 fn test_custom_signal_handler_instance_calling_another_instance() -> Result<()> {
307 let engine = engine();
308 let mut store = Store::new(&engine, ());
309
310 // instance1 which defines 'read'
311 let module1 = Module::new(&engine, WAT1)?;
312 let externs = make_externs(&mut store, &module1);
313 let instance1 = Instance::new(&mut store, &module1, &externs)?;
314 let (base1, length1) = set_up_memory(&mut store, instance1);
315 unsafe {
316 store.set_signal_handler(move |signum, siginfo, _| {
317 println!("instance1");
318 handle_sigsegv(base1, length1, signum, siginfo)
319 });
320 }
321
322 let mut instance1_exports = instance1.exports(&mut store);
323 let instance1_read = instance1_exports.next().unwrap().clone().into_extern();
324 drop(instance1_exports);
325
326 // instance2 which calls 'instance1.read'
327 let module2 = Module::new(&engine, WAT2)?;
328 let instance2 = Instance::new(&mut store, &module2, &[instance1_read])?;
329 // since 'instance2.run' calls 'instance1.read' we need to set up the signal handler to handle
330 // SIGSEGV originating from within the memory of instance1
331 unsafe {
332 store.set_signal_handler(move |signum, siginfo, _| {
333 handle_sigsegv(base1, length1, signum, siginfo)
334 });
335 }
336
337 println!("calling instance2.run");
338 let result = invoke_export(&mut store, instance2, "run")?;
339 assert_eq!(123, result);
340 Ok(())
341 }
342