Skip to main content

std/sys/pal/unix/
stack_overflow.rs

1#![cfg_attr(test, allow(dead_code))]
2#![forbid(unsafe_op_in_unsafe_fn)]
3
4pub use self::imp::init;
5use self::imp::{drop_handler, make_handler};
6
7pub struct Handler {
8    data: *mut libc::c_void,
9}
10
11impl Handler {
12    pub unsafe fn new() -> Handler {
13        make_handler(false)
14    }
15
16    fn null() -> Handler {
17        Handler { data: crate::ptr::null_mut() }
18    }
19}
20
21impl Drop for Handler {
22    fn drop(&mut self) {
23        unsafe {
24            drop_handler(self.data);
25        }
26    }
27}
28
29#[cfg(all(
30    not(miri),
31    any(
32        target_os = "linux",
33        target_os = "freebsd",
34        target_os = "hurd",
35        target_os = "macos",
36        target_os = "netbsd",
37        target_os = "openbsd",
38        target_os = "solaris",
39        target_os = "illumos",
40    ),
41))]
42mod thread_info;
43
44// miri doesn't model signals nor stack overflows and this code has some
45// synchronization properties that we don't want to expose to user code,
46// hence we disable it on miri.
47#[cfg(all(
48    not(miri),
49    any(
50        target_os = "linux",
51        target_os = "freebsd",
52        target_os = "hurd",
53        target_os = "macos",
54        target_os = "netbsd",
55        target_os = "openbsd",
56        target_os = "solaris",
57        target_os = "illumos",
58    )
59))]
60mod imp {
61    use libc::{
62        MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK,
63        SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t,
64    };
65    #[cfg(not(all(target_os = "linux", target_env = "gnu")))]
66    use libc::{mmap as mmap64, mprotect, munmap};
67    #[cfg(all(target_os = "linux", target_env = "gnu"))]
68    use libc::{mmap64, mprotect, munmap};
69
70    use super::Handler;
71    use super::thread_info::{delete_current_info, set_current_info, with_current_info};
72    use crate::ops::Range;
73    use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering};
74    use crate::sys::pal::unix::conf;
75    use crate::{io, mem, ptr};
76
77    /// Signal handler for the SIGSEGV and SIGBUS handlers.
78    ///
79    /// We've got guard pages (unmapped pages) at the end of every thread's
80    /// stack, so if a thread ends up running into the guard page it'll trigger
81    /// this handler. We want to detect these cases and print out a helpful error
82    /// saying that the stack has overflowed. All other signals, however, should
83    /// go back to what they were originally supposed to do.
84    ///
85    /// This handler currently exists purely to print an informative message
86    /// whenever a thread overflows its stack. We then abort to exit and
87    /// indicate a crash, but to avoid a misleading SIGSEGV that might lead
88    /// users to believe that unsafe code has accessed an invalid pointer; the
89    /// SIGSEGV encountered when overflowing the stack is expected and
90    /// well-defined.
91    ///
92    /// If this is not a stack overflow, the handler un-registers itself and
93    /// then returns (to allow the original signal to be delivered again).
94    /// Returning from this kind of signal handler is technically not defined
95    /// to work when reading the POSIX spec strictly, but in practice it turns
96    /// out many large systems and all implementations allow returning from a
97    /// signal handler to work. For a more detailed explanation see the
98    /// comments on #26458.
99    ///
100    /// # Safety
101    /// Rust doesn't call this, it *gets called* by the kernel, which we expect
102    /// to provide valid parameters. Apart from that, this function does not
103    /// have any other preconditions.
104    unsafe extern "C" fn signal_handler(
105        signum: libc::c_int,
106        info: *mut libc::siginfo_t,
107        _data: *mut libc::c_void,
108    ) {
109        // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`.
110        let fault_addr = unsafe { (*info).si_addr().addr() };
111
112        // `with_current_info` expects that the process aborts after it is
113        // called. If the signal was not caused by a memory access, this might
114        // not be true. We detect this by noticing that the `si_addr` field is
115        // zero if the signal is synthetic.
116        if fault_addr != 0 {
117            with_current_info(|thread_info| {
118                // If the faulting address is within the guard page, then we print a
119                // message saying so and abort.
120                if let Some(thread_info) = thread_info
121                    && thread_info.guard_page_range.contains(&fault_addr)
122                {
123                    // Hey you! Yes, you modifying the stack overflow message!
124                    // Please make sure that all functions called here are
125                    // actually async-signal-safe. If they're not, try retrieving
126                    // the information beforehand and storing it in `ThreadInfo`.
127                    // Thank you!
128                    // - says Jonas after having had to watch his carefully
129                    //   written code get made unsound again.
130                    let tid = thread_info.tid;
131                    let name = thread_info.name.as_deref().unwrap_or("<unknown>");
132                    rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n");
133                    rtabort!("stack overflow");
134                }
135            })
136        }
137
138        // Unregister ourselves by reverting back to the default behavior.
139        // SAFETY: assuming all platforms define struct sigaction as "zero-initializable"
140        let mut action: sigaction = unsafe { mem::zeroed() };
141        action.sa_sigaction = SIG_DFL;
142        // SAFETY: pray this is a well-behaved POSIX implementation of fn sigaction
143        unsafe { sigaction(signum, &action, ptr::null_mut()) };
144
145        // See comment above for why this function returns.
146    }
147
148    static PAGE_SIZE: Atomic<usize> = AtomicUsize::new(0);
149    // Store a pointer to the allocation for the main thread's altstack so that
150    // tools like valgrind don't complain about a leaked unreachable allocation.
151    //
152    // If the main thread exits, the process will terminate so there's no use in
153    // freeing resources. It also means that the altstack is still installed
154    // while TLS destructors are run on the main thread (c.f. #111272).
155    static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut());
156    static NEED_ALTSTACK: Atomic<bool> = AtomicBool::new(false);
157
158    /// # Safety
159    /// Must be called only once, on the main thread, during program startup.
160    pub unsafe fn init() {
161        PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed);
162
163        // SAFETY:
164        // This is only called on the main thread, and since it is still early
165        // in the programs lifetime there is (almost) certainly enough stack
166        // space left to install the guard page.
167        let mut guard_page_range = unsafe { install_main_guard() };
168
169        // Even for panic=immediate-abort, installing the guard pages is important for soundness.
170        // That said, we do not care about giving nice stackoverflow messages via our custom
171        // signal handler, just exit early and let the user enjoy the segfault.
172        if cfg!(panic = "immediate-abort") {
173            return;
174        }
175
176        // SAFETY: C structures are always zero-initializable.
177        let mut action: sigaction = unsafe { mem::zeroed() };
178        for &signal in &[SIGSEGV, SIGBUS] {
179            // SAFETY: just fetches the current signal handler into action
180            unsafe { sigaction(signal, ptr::null_mut(), &mut action) };
181            // We assume that overriding the signal handler is always safe,
182            // which might conflict with certain libraries that rely on a
183            // specific signal behaviour. To prevent problems, we only
184            // override the handler if it has not been set yet.
185            if action.sa_sigaction == SIG_DFL {
186                if !NEED_ALTSTACK.load(Ordering::Relaxed) {
187                    // haven't set up our sigaltstack yet
188                    NEED_ALTSTACK.store(true, Ordering::Release);
189                    let handler = make_handler(true);
190                    MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed);
191                    mem::forget(handler);
192
193                    if let Some(guard_page_range) = guard_page_range.take() {
194                        set_current_info(guard_page_range);
195                    }
196                }
197
198                action.sa_flags = SA_SIGINFO | SA_ONSTACK;
199                action.sa_sigaction = signal_handler
200                    as unsafe extern "C" fn(i32, *mut libc::siginfo_t, *mut libc::c_void)
201                    as sighandler_t;
202                // SAFETY:
203                // `&action` describes a valid `sigaction` and `signal_handler`
204                // is safe to use as a signal handler for `SIGSEGV` and `SIGBUS`.
205                unsafe { sigaction(signal, &action, ptr::null_mut()) };
206            }
207        }
208    }
209
210    fn get_stack() -> libc::stack_t {
211        // OpenBSD requires this flag for stack mapping
212        // otherwise the said mapping will fail as a no-op on most systems
213        // and has a different meaning on FreeBSD
214        #[cfg(any(
215            target_os = "openbsd",
216            target_os = "netbsd",
217            target_os = "linux",
218            target_os = "dragonfly",
219        ))]
220        let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK;
221        #[cfg(not(any(
222            target_os = "openbsd",
223            target_os = "netbsd",
224            target_os = "linux",
225            target_os = "dragonfly",
226        )))]
227        let flags = MAP_PRIVATE | MAP_ANON;
228
229        let sigstack_size = sigstack_size();
230        let page_size = PAGE_SIZE.load(Ordering::Relaxed);
231
232        // SAFETY: this does not unmap any existing pages.
233        let stackp = unsafe {
234            mmap64(ptr::null_mut(), sigstack_size + page_size, PROT_READ | PROT_WRITE, flags, -1, 0)
235        };
236        if stackp == MAP_FAILED {
237            panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error());
238        }
239        // SAFETY: this only affects the memory we just allocated.
240        let guard_result = unsafe { libc::mprotect(stackp, page_size, PROT_NONE) };
241        if guard_result != 0 {
242            panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error());
243        }
244        // SAFETY:
245        // The region was allocated with a larger size than `page_size`, so this
246        // addition is within bounds.
247        let stackp = unsafe { stackp.add(page_size) };
248
249        libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size }
250    }
251
252    pub fn make_handler(main_thread: bool) -> Handler {
253        if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) {
254            return Handler::null();
255        }
256
257        if !main_thread {
258            if let Some(guard_page_range) = current_guard() {
259                set_current_info(guard_page_range);
260            }
261        }
262
263        // Load the current alternate signal stack to see if we need to install
264        // our own.
265        //
266        // SAFETY: C structures are always zero-initializable.
267        let mut stack = unsafe { mem::zeroed() };
268        // SAFETY: `&mut stack` is valid for writing a `stack_t`.
269        unsafe { sigaltstack(ptr::null(), &mut stack) };
270
271        // Configure alternate signal stack, if one is not already set.
272        if stack.ss_flags & SS_DISABLE != 0 {
273            let stack = get_stack();
274            // SAFETY:
275            // `stack_t` is a freshly allocated stack that's not used anywhere
276            // else. It contains a guard page, so stack overflows in signal
277            // handlers will not cause undefined behaviour. We must make the
278            // fundamental runtime assumption that it is safe to install an
279            // alternate signal stack if there is none currently installed.
280            // This might conflict with foreign libraries that use the existence
281            // of an alternate signal stack as indication that certain runtime
282            // initialisation by the library has been performed (e.g. old
283            // versions of `std` assumed that certain thread-locals were already
284            // accessed and thus initialized in the thread if the stack overflow
285            // signal was successfully delivered). Such assumptions in other
286            // libraries are fundamentally flawed, so we pay no regard to them.
287            unsafe { sigaltstack(&stack, ptr::null_mut()) };
288            Handler { data: stack.ss_sp as *mut libc::c_void }
289        } else {
290            Handler::null()
291        }
292    }
293
294    /// # Safety
295    /// Must only be called with a pointer returned by `make_handler`, and only
296    /// once per `Handler`.
297    pub unsafe fn drop_handler(data: *mut libc::c_void) {
298        if !data.is_null() {
299            let sigstack_size = sigstack_size();
300            let page_size = PAGE_SIZE.load(Ordering::Relaxed);
301            let disabling_stack = libc::stack_t {
302                ss_sp: ptr::null_mut(),
303                ss_flags: SS_DISABLE,
304                // Workaround for bug in macOS implementation of sigaltstack
305                // UNIX2003 which returns ENOMEM when disabling a stack while
306                // passing ss_size smaller than MINSIGSTKSZ. According to POSIX
307                // both ss_sp and ss_size should be ignored in this case.
308                ss_size: sigstack_size,
309            };
310            // SAFETY:
311            // We assume that disabling the alternate signal stack is always
312            // sound, even if the current alternate signal stack is not the one
313            // we installed in `make_handler`. Any stack overflows from this
314            // point on will abort the program when the kernel tries to write
315            // the signal information to the guard page.
316            //
317            // FIXME: detect if the stack has changed, and only uninstall if it hasn't.
318            unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) };
319            // The stack returned by `get_stack` is part of a mapping that
320            // started one page earlier, so walk back a page and unmap from
321            // there.
322            //
323            // SAFETY:
324            // This allocation was created by us in `get_stack` and, as the
325            // alternate signal stack is now disabled, is no longer in use.
326            unsafe { munmap(data.sub(page_size), sigstack_size + page_size) };
327        }
328
329        delete_current_info();
330    }
331
332    /// Modern kernels on modern hardware can have dynamic signal stack sizes.
333    #[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))]
334    fn sigstack_size() -> usize {
335        // SAFETY: `getauxval` is always safe to call.
336        let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) };
337        // If getauxval couldn't find the entry, it returns 0,
338        // so take the higher of the "constant" and auxval.
339        // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ
340        libc::SIGSTKSZ.max(dynamic_sigstksz as _)
341    }
342
343    /// Not all OS support hardware where this is needed.
344    #[cfg(not(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc"))))]
345    fn sigstack_size() -> usize {
346        libc::SIGSTKSZ
347    }
348
349    #[cfg(any(target_os = "solaris", target_os = "illumos"))]
350    fn get_stack_start() -> Option<*mut libc::c_void> {
351        // SAFETY: C types are always zero-initializable.
352        let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() };
353        // SAFETY:
354        // The pointer is valid for writing a `stack_t`.
355        assert_eq!(unsafe { libc::stack_getbounds(&mut current_stack) }, 0);
356        Some(current_stack.ss_sp)
357    }
358
359    #[cfg(target_os = "macos")]
360    fn get_stack_start() -> Option<*mut libc::c_void> {
361        // SAFETY: always safe to call.
362        let th = unsafe { libc::pthread_self() };
363        // SAFETY: `th` is a valid `pthread_t`.
364        unsafe {
365            let stackptr = libc::pthread_get_stackaddr_np(th);
366            let stacksize = libc::pthread_get_stacksize_np(th);
367            Some(stackptr.map_addr(|addr| addr - stacksize))
368        }
369    }
370
371    #[cfg(target_os = "openbsd")]
372    fn get_stack_start() -> Option<*mut libc::c_void> {
373        // SAFETY: C types are always zero-initializable.
374        let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() };
375        // SAFETY:
376        // * calling `pthread_self` is always valid and returns a valid `pthread_t`.
377        // * `&mut current_stack` is coerced to a pointer that is valid for writing
378        //   a `stack_t`.
379        assert_eq!(
380            unsafe { libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack) },
381            0
382        );
383
384        let stack_ptr = current_stack.ss_sp;
385        // SAFETY: this is always safe to call.
386        let stackaddr = if unsafe { libc::pthread_main_np() } == 1 {
387            // main thread
388            stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed)
389        } else {
390            // new thread
391            stack_ptr.addr() - current_stack.ss_size
392        };
393        Some(stack_ptr.with_addr(stackaddr))
394    }
395
396    #[cfg(any(
397        target_os = "android",
398        target_os = "freebsd",
399        target_os = "netbsd",
400        target_os = "hurd",
401        target_os = "linux",
402        target_os = "l4re"
403    ))]
404    fn get_stack_start() -> Option<*mut libc::c_void> {
405        use crate::pin::pin;
406        use crate::sys::helpers::COpaque;
407
408        let mut ret = None;
409        let mut attr: COpaque<libc::pthread_attr_t> = COpaque::uninit();
410        if !cfg!(target_os = "freebsd") {
411            attr = COpaque::zeroed();
412        }
413        let attr = pin!(attr);
414        // FIXME(pin-ergonomics): remove the next line.
415        let attr = attr.into_ref();
416
417        // SAFETY:
418        // The attributes object has not been initialized yet and will not be
419        // moved until destroyed.
420        #[cfg(target_os = "freebsd")]
421        assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0);
422        // SAFETY:
423        // * calling `pthread_self` is always valid and returns a valid `pthread_t`
424        // * `attr` is an initialized attribute object that can be written to.
425        #[cfg(target_os = "freebsd")]
426        let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) };
427        // SAFETY:
428        // * calling `pthread_self` is always valid and returns a valid `pthread_t`
429        // * `attr` can be written to, and will be initialized by this call.
430        #[cfg(not(target_os = "freebsd"))]
431        let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) };
432        if e == 0 {
433            let mut stackaddr = crate::ptr::null_mut();
434            let mut stacksize = 0;
435            // SAFETY:
436            // `attr` is an initialized attribute object and both the pointers
437            // are valid for writing.
438            assert_eq!(
439                unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackaddr, &mut stacksize) },
440                0
441            );
442            ret = Some(stackaddr);
443        }
444        if e == 0 || cfg!(target_os = "freebsd") {
445            // SAFETY:
446            // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or
447            // by `pthread_attr_get_np`, and is not used after this point.
448            assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0);
449        }
450        ret
451    }
452
453    fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> {
454        let stackptr = get_stack_start()?;
455        let stackaddr = stackptr.addr();
456
457        // Ensure stackaddr is page aligned! A parent process might
458        // have reset RLIMIT_STACK to be non-page aligned. The
459        // pthread_attr_getstack() reports the usable stack area
460        // stackaddr < stackaddr + stacksize, so if stackaddr is not
461        // page-aligned, calculate the fix such that stackaddr <
462        // new_page_aligned_stackaddr < stackaddr + stacksize
463        let remainder = stackaddr % page_size;
464        Some(if remainder == 0 {
465            stackptr
466        } else {
467            stackptr.with_addr(stackaddr + page_size - remainder)
468        })
469    }
470
471    /// # Safety
472    /// This function must only be called from the main thread, and there must
473    /// be sufficient stack space remaining to place a stack guard.
474    unsafe fn install_main_guard() -> Option<Range<usize>> {
475        let page_size = PAGE_SIZE.load(Ordering::Relaxed);
476
477        // this way someone on any unix-y OS can check that all these compile
478        if cfg!(all(target_os = "linux", not(target_env = "musl"))) {
479            install_main_guard_linux(page_size)
480        } else if cfg!(all(target_os = "linux", target_env = "musl")) {
481            install_main_guard_linux_musl(page_size)
482        } else if cfg!(target_os = "freebsd") {
483            #[cfg(not(target_os = "freebsd"))]
484            return None;
485            // The FreeBSD code cannot be checked on non-BSDs.
486            #[cfg(target_os = "freebsd")]
487            install_main_guard_freebsd(page_size)
488        } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) {
489            install_main_guard_bsds(page_size)
490        } else {
491            // SAFETY: guaranteed by caller.
492            unsafe { install_main_guard_default(page_size) }
493        }
494    }
495
496    fn install_main_guard_linux(page_size: usize) -> Option<Range<usize>> {
497        // See the corresponding conditional in init().
498        // Avoid stack_start_aligned, which makes slow syscalls to read /proc/self/maps
499        if cfg!(panic = "immediate-abort") {
500            return None;
501        }
502        // Linux doesn't allocate the whole stack right away, and
503        // the kernel has its own stack-guard mechanism to fault
504        // when growing too close to an existing mapping. If we map
505        // our own guard, then the kernel starts enforcing a rather
506        // large gap above that, rendering much of the possible
507        // stack space useless. See #43052.
508        //
509        // Instead, we'll just note where we expect rlimit to start
510        // faulting, so our handler can report "stack overflow", and
511        // trust that the kernel's own stack guard will work.
512        let stackptr = stack_start_aligned(page_size)?;
513        let stackaddr = stackptr.addr();
514        Some(stackaddr - page_size..stackaddr)
515    }
516
517    fn install_main_guard_linux_musl(_page_size: usize) -> Option<Range<usize>> {
518        // For the main thread, the musl's pthread_attr_getstack
519        // returns the current stack size, rather than maximum size
520        // it can eventually grow to. It cannot be used to determine
521        // the position of kernel's stack guard.
522        None
523    }
524
525    #[cfg(target_os = "freebsd")]
526    fn install_main_guard_freebsd(page_size: usize) -> Option<Range<usize>> {
527        // See the corresponding conditional in install_main_guard_linux().
528        if cfg!(panic = "immediate-abort") {
529            return None;
530        }
531        // FreeBSD's stack autogrows, and optionally includes a guard page
532        // at the bottom. If we try to remap the bottom of the stack
533        // ourselves, FreeBSD's guard page moves upwards. So we'll just use
534        // the builtin guard page.
535        let stackptr = stack_start_aligned(page_size)?;
536        let guardaddr = stackptr.addr();
537        // Technically the number of guard pages is tunable and controlled
538        // by the security.bsd.stack_guard_page sysctl.
539        // By default it is 1, checking once is enough since it is
540        // a boot time config value.
541        // FIXME(joboet): this function is only called once, remove the caching.
542        static PAGES: crate::sync::OnceLock<usize> = crate::sync::OnceLock::new();
543
544        let pages = PAGES.get_or_init(|| {
545            let mut guard: usize = 0;
546            let mut size = size_of_val(&guard);
547            let oid = c"security.bsd.stack_guard_page";
548
549            let r = unsafe {
550                libc::sysctlbyname(
551                    oid.as_ptr(),
552                    (&raw mut guard).cast(),
553                    &raw mut size,
554                    ptr::null_mut(),
555                    0,
556                )
557            };
558            if r == 0 { guard } else { 1 }
559        });
560        Some(guardaddr..guardaddr + pages * page_size)
561    }
562
563    fn install_main_guard_bsds(page_size: usize) -> Option<Range<usize>> {
564        // See the corresponding conditional in install_main_guard_linux().
565        if cfg!(panic = "immediate-abort") {
566            return None;
567        }
568        // OpenBSD stack already includes a guard page, and stack is
569        // immutable.
570        // NetBSD stack includes the guard page.
571        //
572        // We'll just note where we expect rlimit to start
573        // faulting, so our handler can report "stack overflow", and
574        // trust that the kernel's own stack guard will work.
575        let stackptr = stack_start_aligned(page_size)?;
576        let stackaddr = stackptr.addr();
577        Some(stackaddr - page_size..stackaddr)
578    }
579
580    /// # Safety
581    /// This function must only be called from the main thread, and there must
582    /// be sufficient stack space remaining to place a stack guard.
583    unsafe fn install_main_guard_default(page_size: usize) -> Option<Range<usize>> {
584        // Reallocate the last page of the stack.
585        // This ensures SIGBUS will be raised on
586        // stack overflow.
587        // Systems which enforce strict PAX MPROTECT do not allow
588        // to mprotect() a mapping with less restrictive permissions
589        // than the initial mmap() used, so we mmap() here with
590        // read/write permissions and only then mprotect() it to
591        // no permissions at all. See issue #50313.
592        let stackptr = stack_start_aligned(page_size)?;
593        // SAFETY:
594        // The memory region from `stackptr..stackptr + page_size` belongs to
595        // the current thread's stack, and the caller has asserted that there
596        // is sufficient stack space, which means that this will not overwrite
597        // any existing allocations.
598        let result = unsafe {
599            mmap64(
600                stackptr,
601                page_size,
602                PROT_READ | PROT_WRITE,
603                MAP_PRIVATE | MAP_ANON | MAP_FIXED,
604                -1,
605                0,
606            )
607        };
608        if result != stackptr || result == MAP_FAILED {
609            panic!("failed to allocate a guard page: {}", io::Error::last_os_error());
610        }
611
612        // SAFETY:
613        // Since this function is only called on the main thread, the stack will
614        // not be reused until program exit, so the runtime will never observe
615        // that part of the stack has been made unusable in this way.
616        let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) };
617        if result != 0 {
618            panic!("failed to protect the guard page: {}", io::Error::last_os_error());
619        }
620
621        let guardaddr = stackptr.addr();
622
623        Some(guardaddr..guardaddr + page_size)
624    }
625
626    #[cfg(any(
627        target_os = "macos",
628        target_os = "openbsd",
629        target_os = "solaris",
630        target_os = "illumos",
631    ))]
632    fn current_guard() -> Option<Range<usize>> {
633        let stackptr = get_stack_start()?;
634        let stackaddr = stackptr.addr();
635        Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr)
636    }
637
638    #[cfg(any(
639        target_os = "android",
640        target_os = "freebsd",
641        target_os = "hurd",
642        target_os = "linux",
643        target_os = "netbsd",
644        target_os = "l4re"
645    ))]
646    fn current_guard() -> Option<Range<usize>> {
647        use crate::pin::pin;
648        use crate::sys::helpers::COpaque;
649
650        let mut ret = None;
651
652        let mut attr: COpaque<libc::pthread_attr_t> = COpaque::uninit();
653        if !cfg!(target_os = "freebsd") {
654            attr = COpaque::zeroed();
655        }
656        let attr = pin!(attr);
657        // FIXME(pin-ergonomics): remove the next line.
658        let attr = attr.into_ref();
659
660        // SAFETY:
661        // The attributes object has not been initialized yet and will not be
662        // moved until destroyed.
663        #[cfg(target_os = "freebsd")]
664        assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0);
665        // SAFETY:
666        // * calling `pthread_self` is always valid and returns a valid `pthread_t`
667        // * `attr` is an initialized attribute object that can be written to.
668        #[cfg(target_os = "freebsd")]
669        let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) };
670        // SAFETY:
671        // * calling `pthread_self` is always valid and returns a valid `pthread_t`
672        // * `attr` can be written to, and will be initialized by this call.
673        #[cfg(not(target_os = "freebsd"))]
674        let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) };
675        if e == 0 {
676            let mut guardsize = 0;
677            // SAFETY:
678            // `attr` is an initialized attribute object and the pointer is valid
679            // for writing.
680            assert_eq!(unsafe { libc::pthread_attr_getguardsize(attr.get(), &mut guardsize) }, 0);
681            if guardsize == 0 {
682                if cfg!(all(target_os = "linux", target_env = "musl")) {
683                    // musl versions before 1.1.19 always reported guard
684                    // size obtained from pthread_attr_get_np as zero.
685                    // Use page size as a fallback.
686                    guardsize = PAGE_SIZE.load(Ordering::Relaxed);
687                } else {
688                    panic!("there is no guard page");
689                }
690            }
691            let mut stackptr = crate::ptr::null_mut::<libc::c_void>();
692            let mut size = 0;
693            // SAFETY:
694            // `attr` is an initialized attribute object and both the pointers
695            // are valid for writing.
696            assert_eq!(
697                unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackptr, &mut size) },
698                0
699            );
700
701            let stackaddr = stackptr.addr();
702            ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) {
703                Some(stackaddr - guardsize..stackaddr)
704            } else if cfg!(all(target_os = "linux", target_env = "musl")) {
705                Some(stackaddr - guardsize..stackaddr)
706            } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")))
707            {
708                // glibc used to include the guard area within the stack, as noted in the BUGS
709                // section of `man pthread_attr_getguardsize`. This has been corrected starting
710                // with glibc 2.27, and in some distro backports, so the guard is now placed at the
711                // end (below) the stack. There's no easy way for us to know which we have at
712                // runtime, so we'll just match any fault in the range right above or below the
713                // stack base to call that fault a stack overflow.
714                Some(stackaddr - guardsize..stackaddr + guardsize)
715            } else {
716                Some(stackaddr..stackaddr + guardsize)
717            };
718        }
719        if e == 0 || cfg!(target_os = "freebsd") {
720            // SAFETY:
721            // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or
722            // by `pthread_attr_get_np`, and is not used after this point.
723            assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0);
724        }
725        ret
726    }
727}
728
729// This is intentionally not enabled on iOS/tvOS/watchOS/visionOS, as it uses
730// several symbols that might lead to rejections from the App Store, namely
731// `sigaction`, `sigaltstack`, `sysctlbyname`, `mmap`, `munmap` and `mprotect`.
732//
733// This might be overly cautious, though it is also what Swift does (and they
734// usually have fewer qualms about forwards compatibility, since the runtime
735// is shipped with the OS):
736// <https://github.com/apple/swift/blob/swift-5.10-RELEASE/stdlib/public/runtime/CrashHandlerMacOS.cpp>
737#[cfg(any(
738    miri,
739    not(any(
740        target_os = "linux",
741        target_os = "freebsd",
742        target_os = "hurd",
743        target_os = "macos",
744        target_os = "netbsd",
745        target_os = "openbsd",
746        target_os = "solaris",
747        target_os = "illumos",
748        target_os = "cygwin",
749    ))
750))]
751mod imp {
752    pub unsafe fn init() {}
753
754    pub fn make_handler(_main_thread: bool) -> super::Handler {
755        super::Handler::null()
756    }
757
758    pub unsafe fn drop_handler(_data: *mut libc::c_void) {}
759}
760
761#[cfg(target_os = "cygwin")]
762mod imp {
763    mod c {
764        pub type PVECTORED_EXCEPTION_HANDLER =
765            Option<unsafe extern "system" fn(exceptioninfo: *mut EXCEPTION_POINTERS) -> i32>;
766        pub type NTSTATUS = i32;
767        pub type BOOL = i32;
768
769        unsafe extern "system" {
770            pub fn AddVectoredExceptionHandler(
771                first: u32,
772                handler: PVECTORED_EXCEPTION_HANDLER,
773            ) -> *mut core::ffi::c_void;
774            pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL;
775        }
776
777        pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _;
778        pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32;
779
780        #[repr(C)]
781        #[derive(Clone, Copy)]
782        pub struct EXCEPTION_POINTERS {
783            pub ExceptionRecord: *mut EXCEPTION_RECORD,
784            // We don't need this field here
785            // pub Context: *mut CONTEXT,
786        }
787        #[repr(C)]
788        #[derive(Clone, Copy)]
789        pub struct EXCEPTION_RECORD {
790            pub ExceptionCode: NTSTATUS,
791            pub ExceptionFlags: u32,
792            pub ExceptionRecord: *mut EXCEPTION_RECORD,
793            pub ExceptionAddress: *mut core::ffi::c_void,
794            pub NumberParameters: u32,
795            pub ExceptionInformation: [usize; 15],
796        }
797    }
798
799    /// Reserve stack space for use in stack overflow exceptions.
800    fn reserve_stack() {
801        let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) };
802        // Reserving stack space is not critical so we allow it to fail in the released build of libstd.
803        // We still use debug assert here so that CI will test that we haven't made a mistake calling the function.
804        debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling");
805    }
806
807    unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 {
808        // SAFETY: It's up to the caller (which in this case is the OS) to ensure that `ExceptionInfo` is valid.
809        unsafe {
810            let rec = &(*(*ExceptionInfo).ExceptionRecord);
811            let code = rec.ExceptionCode;
812
813            if code == c::EXCEPTION_STACK_OVERFLOW {
814                crate::thread::with_current_name(|name| {
815                    let name = name.unwrap_or("<unknown>");
816                    let tid = crate::thread::current_os_id();
817                    rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n");
818                });
819            }
820            c::EXCEPTION_CONTINUE_SEARCH
821        }
822    }
823
824    pub unsafe fn init() {
825        // SAFETY: `vectored_handler` has the correct ABI and is safe to call during exception handling.
826        unsafe {
827            let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler));
828            // Similar to the above, adding the stack overflow handler is allowed to fail
829            // but a debug assert is used so CI will still test that it normally works.
830            debug_assert!(!result.is_null(), "failed to install exception handler");
831        }
832        // Set the thread stack guarantee for the main thread.
833        reserve_stack();
834    }
835
836    pub fn make_handler(main_thread: bool) -> super::Handler {
837        if !main_thread {
838            reserve_stack();
839        }
840        super::Handler::null()
841    }
842
843    pub unsafe fn drop_handler(_data: *mut libc::c_void) {}
844}