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