Skip to main content

wasmtime/runtime/vm/sys/unix/
signals.rs

1//! Trap handling on Unix based on POSIX signals.
2
3use 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
11/// Function which may handle custom signals while processing traps.
12pub 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    /// Installs all trap handlers.
25    ///
26    /// # Unsafety
27    ///
28    /// This function is unsafe because it's not safe to call concurrently and
29    /// it's not safe to call if the trap handlers have already been initialized
30    /// for this process.
31    pub unsafe fn new(macos_use_mach_ports: bool) -> TrapHandler {
32        // Either mach ports shouldn't be in use or we shouldn't be on macOS,
33        // otherwise the `machports.rs` module should be used instead.
34        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            // The flags here are relatively careful, and they are...
39            //
40            // SA_SIGINFO gives us access to information like the program
41            // counter from where the fault happened.
42            //
43            // SA_ONSTACK allows us to handle signals on an alternate stack,
44            // so that the handler can run in response to running out of
45            // stack space on the main stack. Rust installs an alternate
46            // stack with sigaltstack, so we rely on that.
47            //
48            // SA_NODEFER allows us to reenter the signal handler if we
49            // crash while handling the signal, and fall through to the
50            // Breakpad handler by testing handlingSegFault.
51            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    // Allow handling OOB with signals on all architectures
74    f(&raw mut PREV_SIGSEGV, libc::SIGSEGV);
75
76    // Handle `unreachable` instructions which execute `ud2` right now
77    f(&raw mut PREV_SIGILL, libc::SIGILL);
78
79    // x86 and s390x use SIGFPE to report division by zero
80    if cfg!(target_arch = "x86_64") || cfg!(target_arch = "s390x") {
81        f(&raw mut PREV_SIGFPE, libc::SIGFPE);
82    }
83
84    // Sometimes we need to handle SIGBUS too:
85    // - On Darwin, guard page accesses are raised as SIGBUS.
86    if cfg!(target_vendor = "apple") || cfg!(target_os = "freebsd") {
87        f(&raw mut PREV_SIGBUS, libc::SIGBUS);
88    }
89
90    // TODO(#1980): x86-32, if we support it, will also need a SIGFPE handler.
91    // TODO(#1173): ARM32, if we support it, will also need a SIGBUS handler.
92}
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                // Restore the previous handler that this signal had.
101                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 our trap handler wasn't currently listed for this process
110                // then that's a problem because we have just corrupted the
111                // signal handler state and don't know how to remove ourselves
112                // from the signal handling state. Inform the user of this and
113                // abort the process.
114                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        // If no wasm code is executing, we don't handle this as a wasm
145        // trap.
146        let info = match info {
147            Some(info) => info,
148            None => return false,
149        };
150
151        // If we hit an exception while handling a previous trap, that's
152        // quite bad, so bail out and let the system handle this
153        // recursive segfault.
154        //
155        // Otherwise flag ourselves as handling a trap, do the trap
156        // handling, and reset our trap handling flag. Then we figure
157        // out what to do based on the result of the trap handling.
158        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        // Figure out what to do based on the result of this handling of
168        // the trap. Note that our sentinel value of 1 means that the
169        // exception was handled by a custom exception handler, so we
170        // keep executing.
171        match test {
172            TrapTest::NotWasm => {
173                if let Some(faulting_addr) = faulting_addr {
174                    let range = unsafe { &info.vm_store_context.get().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    // This signal is not for any compiled wasm code we expect, so we
205    // need to forward the signal to the next handler. If there is no
206    // next handler (SIG_IGN or SIG_DFL), then it's time to crash. To do
207    // this, we set the signal back to its original disposition and
208    // return. This will cause the faulting op to be re-executed which
209    // will crash in the normal way. If there is a next handler, call
210    // it. It will either crash synchronously, fix up the instruction
211    // so that execution can continue and return, or trigger a crash by
212    // returning the signal to it's original disposition and returning.
213    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_select! {
242        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                lr: None,
248            }
249        }
250        all(target_os = "linux", target_arch = "x86") => {
251            let cx = unsafe { &*(cx as *const libc::ucontext_t) };
252            TrapRegisters {
253                pc: cx.uc_mcontext.gregs[libc::REG_EIP as usize] as usize,
254                fp: cx.uc_mcontext.gregs[libc::REG_EBP as usize] as usize,
255                lr: None,
256            }
257        }
258        all(any(target_os = "linux", target_os = "android"), target_arch = "aarch64") => {
259            let cx = unsafe { &*(cx as *const libc::ucontext_t) };
260            TrapRegisters {
261                pc: cx.uc_mcontext.pc as usize,
262                fp: cx.uc_mcontext.regs[29] as usize,
263                lr: None,
264            }
265        }
266        all(target_os = "linux", target_arch = "s390x") => {
267            // On s390x, SIGILL and SIGFPE are delivered with the PSW address
268            // pointing *after* the faulting instruction, while SIGSEGV and
269            // SIGBUS are delivered with the PSW address pointing *to* the
270            // faulting instruction.  To handle this, the code generator registers
271            // any trap that results in one of "late" signals on the last byte
272            // of the instruction, and any trap that results in one of the "early"
273            // signals on the first byte of the instruction (as usual).  This
274            // means we simply need to decrement the reported PSW address by
275            // one in the case of a "late" signal here to ensure we always
276            // correctly find the associated trap handler.
277            let trap_offset = match _signum {
278                libc::SIGILL | libc::SIGFPE => 1,
279                _ => 0,
280            };
281            unsafe {
282                let cx = &*(cx as *const libc::ucontext_t);
283                TrapRegisters {
284                    pc: (cx.uc_mcontext.psw.addr - trap_offset) as usize,
285                    fp: *(cx.uc_mcontext.gregs[15] as *const usize),
286                    // Stack-limit checks in the prologue happen before the frame is
287                    // set up, while the return address is still in %r14.
288                    lr: Some(cx.uc_mcontext.gregs[14] as usize),
289                }
290            }
291        }
292        all(target_vendor = "apple", target_arch = "x86_64") => {
293            unsafe {
294                let cx = &*(cx as *const libc::ucontext_t);
295                TrapRegisters {
296                    pc: (*cx.uc_mcontext).__ss.__rip as usize,
297                    fp: (*cx.uc_mcontext).__ss.__rbp as usize,
298                    lr: None,
299                }
300            }
301        }
302        all(target_vendor = "apple", target_arch = "aarch64") => {
303            unsafe {
304                let cx = &*(cx as *const libc::ucontext_t);
305                TrapRegisters {
306                    pc: (*cx.uc_mcontext).__ss.__pc as usize,
307                    fp: (*cx.uc_mcontext).__ss.__fp as usize,
308                    lr: None,
309                }
310            }
311        }
312        all(target_os = "freebsd", target_arch = "x86_64") => {
313            let cx = unsafe { &*(cx as *const libc::ucontext_t) };
314            TrapRegisters {
315                pc: cx.uc_mcontext.mc_rip as usize,
316                fp: cx.uc_mcontext.mc_rbp as usize,
317                lr: None,
318            }
319        }
320        all(target_os = "linux", target_arch = "riscv64") => {
321            let cx = unsafe { &*(cx as *const libc::ucontext_t) };
322            TrapRegisters {
323                pc: cx.uc_mcontext.__gregs[libc::REG_PC] as usize,
324                fp: cx.uc_mcontext.__gregs[libc::REG_S0] as usize,
325                lr: None,
326            }
327        }
328        all(target_os = "freebsd", target_arch = "aarch64") => {
329            let cx = unsafe { &*(cx as *const libc::ucontext_t) };
330            TrapRegisters {
331                pc: cx.uc_mcontext.mc_gpregs.gp_elr as usize,
332                fp: cx.uc_mcontext.mc_gpregs.gp_x[29] as usize,
333                lr: None,
334            }
335        }
336        all(target_os = "openbsd", target_arch = "x86_64") => {
337            let cx = unsafe { &*(cx as *const libc::ucontext_t) };
338            TrapRegisters {
339                pc: cx.sc_rip as usize,
340                fp: cx.sc_rbp as usize,
341                lr: None,
342            }
343        }
344        all(target_os = "linux", target_arch = "arm") => {
345            let cx = unsafe { &*(cx as *const libc::ucontext_t) };
346            TrapRegisters {
347                pc: cx.uc_mcontext.arm_pc as usize,
348                fp: cx.uc_mcontext.arm_fp as usize,
349                lr: None,
350            }
351        }
352        all(target_os = "redox", target_arch = "x86_64") => {
353            let cx = unsafe { &*(cx as *const libc::ucontext_t) };
354            TrapRegisters {
355                pc: cx.uc_mcontext.rip as usize,
356                fp: cx.uc_mcontext.rbp as usize,
357                lr: None,
358            }
359        }
360        _ => {
361            compile_error!("unsupported platform");
362            panic!();
363        }
364    }
365}
366
367/// Updates the siginfo context stored in `cx` to resume to `handler` upon
368/// resumption while returning from the signal handler.
369unsafe fn store_handler_in_ucontext(cx: *mut libc::c_void, handler: &Handler) {
370    cfg_select! {
371        all(any(target_os = "linux", target_os = "android", target_os = "illumos"), target_arch = "x86_64") => {
372            let cx = unsafe { cx.cast::<libc::ucontext_t>().as_mut().unwrap() };
373            cx.uc_mcontext.gregs[libc::REG_RIP as usize] = handler.pc as _;
374            cx.uc_mcontext.gregs[libc::REG_RSP as usize] = handler.sp as _;
375            cx.uc_mcontext.gregs[libc::REG_RBP as usize] = handler.fp as _;
376            cx.uc_mcontext.gregs[libc::REG_RAX as usize] = 0;
377            cx.uc_mcontext.gregs[libc::REG_RDX as usize] = 0;
378        }
379        all(any(target_os = "linux", target_os = "android"), target_arch = "aarch64") => {
380            let cx = unsafe { cx.cast::<libc::ucontext_t>().as_mut().unwrap() };
381            cx.uc_mcontext.pc = handler.pc as _;
382            cx.uc_mcontext.sp = handler.sp as _;
383            cx.uc_mcontext.regs[29] = handler.fp as _;
384            cx.uc_mcontext.regs[0] = 0;
385            cx.uc_mcontext.regs[1] = 0;
386        }
387        all(target_os = "linux", target_arch = "s390x") => {
388            let cx = unsafe { cx.cast::<libc::ucontext_t>().as_mut().unwrap() };
389            cx.uc_mcontext.psw.addr = handler.pc as _;
390            cx.uc_mcontext.gregs[15] = handler.sp as _;
391            cx.uc_mcontext.gregs[6] = 0;
392            cx.uc_mcontext.gregs[7] = 0;
393        }
394        all(target_vendor = "apple", target_arch = "x86_64") => {
395            unsafe {
396                let cx = cx.cast::<libc::ucontext_t>().as_mut().unwrap();
397                let cx = cx.uc_mcontext.as_mut().unwrap();
398                cx.__ss.__rip = handler.pc as _;
399                cx.__ss.__rsp = handler.sp as _;
400                cx.__ss.__rbp = handler.fp as _;
401                cx.__ss.__rax = 0;
402                cx.__ss.__rdx = 0;
403            }
404        }
405        all(target_vendor = "apple", target_arch = "aarch64") => {
406            unsafe {
407                let cx = cx.cast::<libc::ucontext_t>().as_mut().unwrap();
408                let cx = cx.uc_mcontext.as_mut().unwrap();
409                cx.__ss.__pc = handler.pc as _;
410                cx.__ss.__sp = handler.sp as _;
411                cx.__ss.__fp = handler.fp as _;
412                cx.__ss.__x[0] = 0;
413                cx.__ss.__x[1] = 0;
414            }
415        }
416        all(target_os = "freebsd", target_arch = "x86_64") => {
417            let cx = unsafe { cx.cast::<libc::ucontext_t>().as_mut().unwrap() };
418            cx.uc_mcontext.mc_rip = handler.pc as _;
419            cx.uc_mcontext.mc_rbp = handler.fp as _;
420            cx.uc_mcontext.mc_rsp = handler.sp as _;
421            cx.uc_mcontext.mc_rax = 0;
422            cx.uc_mcontext.mc_rdx = 0;
423        }
424        all(target_os = "freebsd", target_arch = "aarch64") => {
425            let cx = unsafe { cx.cast::<libc::ucontext_t>().as_mut().unwrap() };
426            cx.uc_mcontext.mc_gpregs.gp_elr = handler.pc as _;
427            cx.uc_mcontext.mc_gpregs.gp_sp = handler.sp as _;
428            cx.uc_mcontext.mc_gpregs.gp_x[29] = handler.fp as _;
429            cx.uc_mcontext.mc_gpregs.gp_x[0] = 0;
430            cx.uc_mcontext.mc_gpregs.gp_x[1] = 0;
431        }
432        all(target_os = "openbsd", target_arch = "x86_64") => {
433            let cx = unsafe { cx.cast::<libc::ucontext_t>().as_mut().unwrap() };
434            cx.sc_rip = handler.pc as _;
435            cx.sc_rbp = handler.fp as _;
436            cx.sc_rsp = handler.sp as _;
437            cx.sc_rax = 0;
438            cx.sc_rdx = 0;
439        }
440        all(target_os = "linux", target_arch = "riscv64") => {
441            let cx = unsafe { cx.cast::<libc::ucontext_t>().as_mut().unwrap() };
442            cx.uc_mcontext.__gregs[libc::REG_PC] = handler.pc as _;
443            cx.uc_mcontext.__gregs[libc::REG_S0] = handler.fp as _;
444            cx.uc_mcontext.__gregs[libc::REG_SP] = handler.sp as _;
445            cx.uc_mcontext.__gregs[libc::REG_A0] = 0;
446            cx.uc_mcontext.__gregs[libc::REG_A0 + 1] = 0;
447        }
448        all(target_os = "redox", target_arch = "x86_64") => {
449            let cx = unsafe { cx.cast::<libc::ucontext_t>().as_mut().unwrap() };
450            cx.uc_mcontext.rip = handler.pc as _;
451            cx.uc_mcontext.rbp = handler.fp as _;
452            cx.uc_mcontext.rsp = handler.sp as _;
453            cx.uc_mcontext.rax = 0;
454            cx.uc_mcontext.rdx = 0;
455        }
456        _ => {
457            compile_error!("unsupported platform");
458            panic!();
459        }
460    }
461}
462
463/// A function for registering a custom alternate signal stack (sigaltstack).
464///
465/// Rust's libstd installs an alternate stack with size `SIGSTKSZ`, which is not
466/// always large enough for our signal handling code. Override it by creating
467/// and registering our own alternate stack that is large enough and has a guard
468/// page.
469///
470/// Note that one might reasonably ask why do this at all? Why not remove
471/// `SA_ONSTACK` from our signal handlers entirely? The basic reason for that is
472/// because we want to print a message on stack overflow. The Rust standard
473/// library will print this message by default and by us overriding the
474/// `SIGSEGV` handler above we're now sharing responsibility for that as well.
475/// We must have `SA_ONSTACK` to even attempt to being able to printing this
476/// message, and so we leave it turned on. Wasmtime will determine a stack
477/// overflow fault isn't caused by wasm and then forward to libstd's signal
478/// handler which will actually print-and-abort.
479///
480/// Another reasonable question might be why we need to increase the size of the
481/// sigaltstack at all? This is something which we may want to reconsider in the
482/// future. For now it helps keep debug builds working which consume more stack
483/// when handling normal wasm out-of-bounds and faults. Perhaps in the future we
484/// could optimize this more or maybe even do something clever like lazily
485/// allocate the sigaltstack on the fault itself. (e.g. trampoline from a tiny
486/// stack to the "big stack" during a wasm fault or something like that)
487#[cold]
488pub fn lazy_per_thread_init() {
489    // This is a load-bearing requirement to keep address-sanitizer working and
490    // prevent crashes during fuzzing. The general idea here is that we skip the
491    // sigaltstack setup below entirely on asan builds, aka fuzzing. The exact
492    // reason for this is not entirely known, but the closest guess we have at
493    // this time is something like:
494    //
495    // * ASAN builds intercept mmap/munmap to keep track of what's going on.
496    // * The sigaltstack below registers a TLS destructor for when the current
497    //   thread exits to deallocate the stack.
498    // * ASAN looks to also have TLS destructors for its own internal state.
499    // * The current assumption is that the order of these TLS destructors can
500    //   cause corruption in ASAN state where if we run after asan's destructor
501    //   it may intercept munmap and then asan doesn't know it's been
502    //   de-initialized yet.
503    //
504    // The reproduction of this involved a standalone project built with
505    // `-Zsanitizer=address` where internally it would spawn two threads. Each
506    // thread would build a "hello world" module and then one of the threads
507    // would execute a noop exported function. If this was run thousands of
508    // times in a loop in the same process it would eventually crash under asan.
509    //
510    // It's notably not quite so simple as frobbing TLS destructors. There's
511    // clearly something else going on with ASAN state internally which we don't
512    // fully understand at this time. An attempt to make a standalone C++
513    // reproduction, for example, was not successful. In lieu of that the best
514    // we have for now is to disable our custom and larger sigaltstack in asan
515    // builds.
516    //
517    // The exact source was
518    // https://gist.github.com/alexcrichton/6815a5d57a3c5ca94a8d816a9fcc91af for
519    // future reference if necessary.
520    if cfg!(asan) {
521        return;
522    }
523
524    // This thread local is purely used to register a `Stack` to get deallocated
525    // when the thread exists. Otherwise this function is only ever called at
526    // most once per-thread.
527    std::thread_local! {
528        static STACK: RefCell<Option<Stack>> = const { RefCell::new(None) };
529    }
530
531    /// The size of the sigaltstack (not including the guard, which will be
532    /// added). Make this large enough to run our signal handlers.
533    ///
534    /// The main current requirement of the signal handler in terms of stack
535    /// space is that `malloc`/`realloc` are called to create a `Backtrace` of
536    /// wasm frames.
537    ///
538    /// Historically this was 16k. Turns out jemalloc requires more than 16k of
539    /// stack space in debug mode, so this was bumped to 64k.
540    const MIN_STACK_SIZE: usize = 64 * 4096;
541
542    struct Stack {
543        mmap_ptr: *mut libc::c_void,
544        mmap_size: usize,
545    }
546
547    return STACK.with(|s| {
548        *s.borrow_mut() = unsafe { allocate_sigaltstack() };
549    });
550
551    unsafe fn allocate_sigaltstack() -> Option<Stack> {
552        // Check to see if the existing sigaltstack, if it exists, is big
553        // enough. If so we don't need to allocate our own.
554        let mut old_stack = unsafe { mem::zeroed() };
555        let r = unsafe { libc::sigaltstack(ptr::null(), &mut old_stack) };
556        assert_eq!(
557            r,
558            0,
559            "learning about sigaltstack failed: {}",
560            io::Error::last_os_error()
561        );
562        if old_stack.ss_flags & libc::SS_DISABLE == 0 && old_stack.ss_size >= MIN_STACK_SIZE {
563            return None;
564        }
565
566        // ... but failing that we need to allocate our own, so do all that
567        // here.
568        let page_size = crate::runtime::vm::host_page_size();
569        let guard_size = page_size;
570        let alloc_size = guard_size + MIN_STACK_SIZE;
571
572        let ptr = unsafe {
573            rustix::mm::mmap_anonymous(
574                null_mut(),
575                alloc_size,
576                rustix::mm::ProtFlags::empty(),
577                rustix::mm::MapFlags::PRIVATE,
578            )
579            .expect("failed to allocate memory for sigaltstack")
580        };
581
582        // Prepare the stack with readable/writable memory and then register it
583        // with `sigaltstack`.
584        let stack_ptr = (ptr as usize + guard_size) as *mut std::ffi::c_void;
585        unsafe {
586            rustix::mm::mprotect(
587                stack_ptr,
588                MIN_STACK_SIZE,
589                rustix::mm::MprotectFlags::READ | rustix::mm::MprotectFlags::WRITE,
590            )
591            .expect("mprotect to configure memory for sigaltstack failed");
592        }
593        let new_stack = libc::stack_t {
594            ss_sp: stack_ptr,
595            ss_flags: 0,
596            ss_size: MIN_STACK_SIZE,
597        };
598        let r = unsafe { libc::sigaltstack(&new_stack, ptr::null_mut()) };
599        assert_eq!(
600            r,
601            0,
602            "registering new sigaltstack failed: {}",
603            io::Error::last_os_error()
604        );
605
606        Some(Stack {
607            mmap_ptr: ptr,
608            mmap_size: alloc_size,
609        })
610    }
611
612    impl Drop for Stack {
613        fn drop(&mut self) {
614            unsafe {
615                // Before unmapping our memory make sure that it's no longer
616                // listed as our sigaltstack. While it's pretty unlikely we'll
617                // fault during thread teardown we don't want to make things
618                // worse by then additionally using an unmapped stack.
619                //
620                // Also this seems to help with pre-LLVM-23 asan configuration,
621                // see #13857 for some more details.
622                let new_stack = libc::stack_t {
623                    ss_sp: ptr::null_mut(),
624                    ss_flags: libc::SS_DISABLE,
625                    ss_size: MIN_STACK_SIZE,
626                };
627                let r = libc::sigaltstack(&new_stack, ptr::null_mut());
628                debug_assert_eq!(
629                    r,
630                    0,
631                    "registering new sigaltstack failed: {}",
632                    io::Error::last_os_error()
633                );
634
635                // Deallocate the stack memory now that nothing should be using
636                // it.
637                let r = rustix::mm::munmap(self.mmap_ptr, self.mmap_size);
638                debug_assert!(r.is_ok(), "munmap failed during thread shutdown");
639            }
640        }
641    }
642}