wasmtime/runtime/vm/sys/unix/
signals.rs1use crate::prelude::*;
4use crate::runtime::vm::traphandlers::{TrapRegisters, TrapTest, tls};
5use std::cell::RefCell;
6use std::io;
7use std::mem;
8use std::ptr::{self, null_mut};
9use wasmtime_unwinder::Handler;
10
11pub type SignalHandler =
13 Box<dyn Fn(libc::c_int, *const libc::siginfo_t, *const libc::c_void) -> bool + Send + Sync>;
14
15const UNINIT_SIGACTION: libc::sigaction = unsafe { mem::zeroed() };
16static mut PREV_SIGSEGV: libc::sigaction = UNINIT_SIGACTION;
17static mut PREV_SIGBUS: libc::sigaction = UNINIT_SIGACTION;
18static mut PREV_SIGILL: libc::sigaction = UNINIT_SIGACTION;
19static mut PREV_SIGFPE: libc::sigaction = UNINIT_SIGACTION;
20
21pub struct TrapHandler;
22
23impl TrapHandler {
24 pub unsafe fn new(macos_use_mach_ports: bool) -> TrapHandler {
32 assert!(!macos_use_mach_ports || !cfg!(target_vendor = "apple"));
35
36 foreach_handler(|slot, signal| {
37 let mut handler: libc::sigaction = unsafe { mem::zeroed() };
38 handler.sa_flags = libc::SA_SIGINFO | libc::SA_NODEFER | libc::SA_ONSTACK;
52 handler.sa_sigaction = (trap_handler as *const ()).addr();
53 unsafe {
54 libc::sigemptyset(&mut handler.sa_mask);
55 if libc::sigaction(signal, &handler, slot) != 0 {
56 panic!(
57 "unable to install signal handler: {}",
58 io::Error::last_os_error(),
59 );
60 }
61 }
62 });
63
64 TrapHandler
65 }
66
67 pub fn validate_config(&self, macos_use_mach_ports: bool) {
68 assert!(!macos_use_mach_ports || !cfg!(target_vendor = "apple"));
69 }
70}
71
72fn foreach_handler(mut f: impl FnMut(*mut libc::sigaction, i32)) {
73 f(&raw mut PREV_SIGSEGV, libc::SIGSEGV);
75
76 f(&raw mut PREV_SIGILL, libc::SIGILL);
78
79 if cfg!(target_arch = "x86_64") || cfg!(target_arch = "s390x") {
81 f(&raw mut PREV_SIGFPE, libc::SIGFPE);
82 }
83
84 if cfg!(target_vendor = "apple") || cfg!(target_os = "freebsd") {
87 f(&raw mut PREV_SIGBUS, libc::SIGBUS);
88 }
89
90 }
93
94impl Drop for TrapHandler {
95 fn drop(&mut self) {
96 unsafe {
97 foreach_handler(|slot, signal| {
98 let mut prev: libc::sigaction = mem::zeroed();
99
100 if libc::sigaction(signal, slot, &mut prev) != 0 {
102 eprintln!(
103 "unable to reinstall signal handler: {}",
104 io::Error::last_os_error(),
105 );
106 libc::abort();
107 }
108
109 if prev.sa_sigaction != (trap_handler as *const ()).addr() {
115 eprintln!(
116 "
117Wasmtime's signal handler was not the last signal handler to be installed
118in the process so it's not certain how to unload signal handlers. In this
119situation the Engine::unload_process_handlers API is not applicable and requires
120perhaps initializing libraries in a different order. The process will be aborted
121now.
122"
123 );
124 libc::abort();
125 }
126 });
127 }
128 }
129}
130
131unsafe extern "C" fn trap_handler(
132 signum: libc::c_int,
133 siginfo: *mut libc::siginfo_t,
134 context: *mut libc::c_void,
135) {
136 let previous = match signum {
137 libc::SIGSEGV => &raw const PREV_SIGSEGV,
138 libc::SIGBUS => &raw const PREV_SIGBUS,
139 libc::SIGFPE => &raw const PREV_SIGFPE,
140 libc::SIGILL => &raw const PREV_SIGILL,
141 _ => panic!("unknown signal: {signum}"),
142 };
143 let handled = tls::with(|info| {
144 let info = match info {
147 Some(info) => info,
148 None => return false,
149 };
150
151 let faulting_addr = match signum {
159 libc::SIGSEGV | libc::SIGBUS => unsafe { Some((*siginfo).si_addr() as usize) },
160 _ => None,
161 };
162 let regs = unsafe { get_trap_registers(context, signum) };
163 let test = info.test_if_trap(regs, faulting_addr, |handler| {
164 handler(signum, siginfo, context)
165 });
166
167 match test {
172 TrapTest::NotWasm => {
173 if let Some(faulting_addr) = faulting_addr {
174 let range = unsafe { &info.vm_store_context.as_ref().async_guard_range };
175 if range.start.addr() <= faulting_addr && faulting_addr < range.end.addr() {
176 abort_stack_overflow();
177 }
178 }
179 false
180 }
181 TrapTest::HandledByEmbedder => true,
182 TrapTest::Trap(handler) => {
183 unsafe {
184 store_handler_in_ucontext(context, &handler);
185 }
186 true
187 }
188 }
189 });
190
191 if handled {
192 return;
193 }
194
195 unsafe { delegate_signal_to_previous_handler(previous, signum, siginfo, context) }
196}
197
198pub unsafe fn delegate_signal_to_previous_handler(
199 previous: *const libc::sigaction,
200 signum: libc::c_int,
201 siginfo: *mut libc::siginfo_t,
202 context: *mut libc::c_void,
203) {
204 unsafe {
214 let previous = *previous;
215 if previous.sa_flags & libc::SA_SIGINFO != 0 {
216 mem::transmute::<
217 usize,
218 extern "C" fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void),
219 >(previous.sa_sigaction)(signum, siginfo, context)
220 } else if previous.sa_sigaction == libc::SIG_DFL || previous.sa_sigaction == libc::SIG_IGN {
221 libc::sigaction(signum, &previous as *const _, ptr::null_mut());
222 } else {
223 mem::transmute::<usize, extern "C" fn(libc::c_int)>(previous.sa_sigaction)(signum)
224 }
225 }
226}
227
228pub fn abort_stack_overflow() -> ! {
229 unsafe {
230 let msg = "execution on async fiber has overflowed its stack";
231 libc::write(libc::STDERR_FILENO, msg.as_ptr().cast(), msg.len());
232 libc::abort();
233 }
234}
235
236#[allow(
237 clippy::cast_possible_truncation,
238 reason = "too fiddly to handle and wouldn't help much anyway"
239)]
240unsafe fn get_trap_registers(cx: *mut libc::c_void, _signum: libc::c_int) -> TrapRegisters {
241 cfg_if::cfg_if! {
242 if #[cfg(all(any(target_os = "linux", target_os = "android", target_os = "illumos"), target_arch = "x86_64"))] {
243 let cx = unsafe { &*(cx as *const libc::ucontext_t) };
244 TrapRegisters {
245 pc: cx.uc_mcontext.gregs[libc::REG_RIP as usize] as usize,
246 fp: cx.uc_mcontext.gregs[libc::REG_RBP as usize] as usize,
247 }
248 } else if #[cfg(all(target_os = "linux", target_arch = "x86"))] {
249 let cx = unsafe { &*(cx as *const libc::ucontext_t) };
250 TrapRegisters {
251 pc: cx.uc_mcontext.gregs[libc::REG_EIP as usize] as usize,
252 fp: cx.uc_mcontext.gregs[libc::REG_EBP as usize] as usize,
253 }
254 } else if #[cfg(all(any(target_os = "linux", target_os = "android"), target_arch = "aarch64"))] {
255 let cx = unsafe { &*(cx as *const libc::ucontext_t) };
256 TrapRegisters {
257 pc: cx.uc_mcontext.pc as usize,
258 fp: cx.uc_mcontext.regs[29] as usize,
259 }
260 } else if #[cfg(all(target_os = "linux", target_arch = "s390x"))] {
261 let trap_offset = match _signum {
272 libc::SIGILL | libc::SIGFPE => 1,
273 _ => 0,
274 };
275 unsafe {
276 let cx = &*(cx as *const libc::ucontext_t);
277 TrapRegisters {
278 pc: (cx.uc_mcontext.psw.addr - trap_offset) as usize,
279 fp: *(cx.uc_mcontext.gregs[15] as *const usize),
280 }
281 }
282 } else if #[cfg(all(target_vendor = "apple", target_arch = "x86_64"))] {
283 unsafe {
284 let cx = &*(cx as *const libc::ucontext_t);
285 TrapRegisters {
286 pc: (*cx.uc_mcontext).__ss.__rip as usize,
287 fp: (*cx.uc_mcontext).__ss.__rbp as usize,
288 }
289 }
290 } else if #[cfg(all(target_vendor = "apple", target_arch = "aarch64"))] {
291 unsafe {
292 let cx = &*(cx as *const libc::ucontext_t);
293 TrapRegisters {
294 pc: (*cx.uc_mcontext).__ss.__pc as usize,
295 fp: (*cx.uc_mcontext).__ss.__fp as usize,
296 }
297 }
298 } else if #[cfg(all(target_os = "freebsd", target_arch = "x86_64"))] {
299 let cx = unsafe { &*(cx as *const libc::ucontext_t) };
300 TrapRegisters {
301 pc: cx.uc_mcontext.mc_rip as usize,
302 fp: cx.uc_mcontext.mc_rbp as usize,
303 }
304 } else if #[cfg(all(target_os = "linux", target_arch = "riscv64"))] {
305 let cx = unsafe { &*(cx as *const libc::ucontext_t) };
306 TrapRegisters {
307 pc: cx.uc_mcontext.__gregs[libc::REG_PC] as usize,
308 fp: cx.uc_mcontext.__gregs[libc::REG_S0] as usize,
309 }
310 } else if #[cfg(all(target_os = "freebsd", target_arch = "aarch64"))] {
311 let cx = unsafe { &*(cx as *const libc::mcontext_t) };
312 TrapRegisters {
313 pc: cx.mc_gpregs.gp_elr as usize,
314 fp: cx.mc_gpregs.gp_x[29] as usize,
315 }
316 } else if #[cfg(all(target_os = "openbsd", target_arch = "x86_64"))] {
317 let cx = unsafe { &*(cx as *const libc::ucontext_t) };
318 TrapRegisters {
319 pc: cx.sc_rip as usize,
320 fp: cx.sc_rbp as usize,
321 }
322 } else if #[cfg(all(target_os = "linux", target_arch = "arm"))] {
323 let cx = unsafe { &*(cx as *const libc::ucontext_t) };
324 TrapRegisters {
325 pc: cx.uc_mcontext.arm_pc as usize,
326 fp: cx.uc_mcontext.arm_fp as usize,
327 }
328 } else {
329 compile_error!("unsupported platform");
330 panic!();
331 }
332 }
333}
334
335unsafe fn store_handler_in_ucontext(cx: *mut libc::c_void, handler: &Handler) {
338 cfg_if::cfg_if! {
339 if #[cfg(all(any(target_os = "linux", target_os = "android", target_os = "illumos"), target_arch = "x86_64"))] {
340 let cx = unsafe { cx.cast::<libc::ucontext_t>().as_mut().unwrap() };
341 cx.uc_mcontext.gregs[libc::REG_RIP as usize] = handler.pc as _;
342 cx.uc_mcontext.gregs[libc::REG_RSP as usize] = handler.sp as _;
343 cx.uc_mcontext.gregs[libc::REG_RBP as usize] = handler.fp as _;
344 cx.uc_mcontext.gregs[libc::REG_RAX as usize] = 0;
345 cx.uc_mcontext.gregs[libc::REG_RDX as usize] = 0;
346 } else if #[cfg(all(any(target_os = "linux", target_os = "android"), target_arch = "aarch64"))] {
347 let cx = unsafe { cx.cast::<libc::ucontext_t>().as_mut().unwrap() };
348 cx.uc_mcontext.pc = handler.pc as _;
349 cx.uc_mcontext.sp = handler.sp as _;
350 cx.uc_mcontext.regs[29] = handler.fp as _;
351 cx.uc_mcontext.regs[0] = 0;
352 cx.uc_mcontext.regs[1] = 0;
353 } else if #[cfg(all(target_os = "linux", target_arch = "s390x"))] {
354 let cx = unsafe { cx.cast::<libc::ucontext_t>().as_mut().unwrap() };
355 cx.uc_mcontext.psw.addr = handler.pc as _;
356 cx.uc_mcontext.gregs[15] = handler.sp as _;
357 cx.uc_mcontext.gregs[6] = 0;
358 cx.uc_mcontext.gregs[7] = 0;
359 } else if #[cfg(all(target_vendor = "apple", target_arch = "x86_64"))] {
360 unsafe {
361 let cx = cx.cast::<libc::ucontext_t>().as_mut().unwrap();
362 let cx = cx.uc_mcontext.as_mut().unwrap();
363 cx.__ss.__rip = handler.pc as _;
364 cx.__ss.__rsp = handler.sp as _;
365 cx.__ss.__rbp = handler.fp as _;
366 cx.__ss.__rax = 0;
367 cx.__ss.__rdx = 0;
368 }
369 } else if #[cfg(all(target_vendor = "apple", target_arch = "aarch64"))] {
370 unsafe {
371 let cx = cx.cast::<libc::ucontext_t>().as_mut().unwrap();
372 let cx = cx.uc_mcontext.as_mut().unwrap();
373 cx.__ss.__pc = handler.pc as _;
374 cx.__ss.__sp = handler.sp as _;
375 cx.__ss.__fp = handler.fp as _;
376 cx.__ss.__x[0] = 0;
377 cx.__ss.__x[1] = 0;
378 }
379 } else if #[cfg(all(target_os = "freebsd", target_arch = "x86_64"))] {
380 let cx = unsafe { cx.cast::<libc::ucontext_t>().as_mut().unwrap() };
381 cx.uc_mcontext.mc_rip = handler.pc as _;
382 cx.uc_mcontext.mc_rbp = handler.fp as _;
383 cx.uc_mcontext.mc_rsp = handler.sp as _;
384 cx.uc_mcontext.mc_rax = 0;
385 cx.uc_mcontext.mc_rdx = 0;
386 } else if #[cfg(all(target_os = "openbsd", target_arch = "x86_64"))] {
387 let cx = unsafe { cx.cast::<libc::ucontext_t>().as_mut().unwrap() };
388 cx.sc_rip = handler.pc as _;
389 cx.sc_rbp = handler.fp as _;
390 cx.sc_rsp = handler.sp as _;
391 cx.sc_rax = 0;
392 cx.sc_rdx = 0;
393 } else if #[cfg(all(target_os = "linux", target_arch = "riscv64"))] {
394 let cx = unsafe { cx.cast::<libc::ucontext_t>().as_mut().unwrap() };
395 cx.uc_mcontext.__gregs[libc::REG_PC] = handler.pc as _;
396 cx.uc_mcontext.__gregs[libc::REG_S0] = handler.fp as _;
397 cx.uc_mcontext.__gregs[libc::REG_SP] = handler.sp as _;
398 cx.uc_mcontext.__gregs[libc::REG_A0] = 0;
399 cx.uc_mcontext.__gregs[libc::REG_A0 + 1] = 0;
400 } else {
401 compile_error!("unsupported platform");
402 panic!();
403 }
404 }
405}
406
407#[cold]
432pub fn lazy_per_thread_init() {
433 if cfg!(asan) {
465 return;
466 }
467
468 std::thread_local! {
472 static STACK: RefCell<Option<Stack>> = const { RefCell::new(None) };
473 }
474
475 const MIN_STACK_SIZE: usize = 64 * 4096;
485
486 struct Stack {
487 mmap_ptr: *mut libc::c_void,
488 mmap_size: usize,
489 }
490
491 return STACK.with(|s| {
492 *s.borrow_mut() = unsafe { allocate_sigaltstack() };
493 });
494
495 unsafe fn allocate_sigaltstack() -> Option<Stack> {
496 let mut old_stack = unsafe { mem::zeroed() };
499 let r = unsafe { libc::sigaltstack(ptr::null(), &mut old_stack) };
500 assert_eq!(
501 r,
502 0,
503 "learning about sigaltstack failed: {}",
504 io::Error::last_os_error()
505 );
506 if old_stack.ss_flags & libc::SS_DISABLE == 0 && old_stack.ss_size >= MIN_STACK_SIZE {
507 return None;
508 }
509
510 let page_size = crate::runtime::vm::host_page_size();
513 let guard_size = page_size;
514 let alloc_size = guard_size + MIN_STACK_SIZE;
515
516 let ptr = unsafe {
517 rustix::mm::mmap_anonymous(
518 null_mut(),
519 alloc_size,
520 rustix::mm::ProtFlags::empty(),
521 rustix::mm::MapFlags::PRIVATE,
522 )
523 .expect("failed to allocate memory for sigaltstack")
524 };
525
526 let stack_ptr = (ptr as usize + guard_size) as *mut std::ffi::c_void;
529 unsafe {
530 rustix::mm::mprotect(
531 stack_ptr,
532 MIN_STACK_SIZE,
533 rustix::mm::MprotectFlags::READ | rustix::mm::MprotectFlags::WRITE,
534 )
535 .expect("mprotect to configure memory for sigaltstack failed");
536 }
537 let new_stack = libc::stack_t {
538 ss_sp: stack_ptr,
539 ss_flags: 0,
540 ss_size: MIN_STACK_SIZE,
541 };
542 let r = unsafe { libc::sigaltstack(&new_stack, ptr::null_mut()) };
543 assert_eq!(
544 r,
545 0,
546 "registering new sigaltstack failed: {}",
547 io::Error::last_os_error()
548 );
549
550 Some(Stack {
551 mmap_ptr: ptr,
552 mmap_size: alloc_size,
553 })
554 }
555
556 impl Drop for Stack {
557 fn drop(&mut self) {
558 unsafe {
559 let r = rustix::mm::munmap(self.mmap_ptr, self.mmap_size);
561 debug_assert!(r.is_ok(), "munmap failed during thread shutdown");
562 }
563 }
564 }
565}