From 232a7a9a26fae4ff1fd927099c40af2abed99995 Mon Sep 17 00:00:00 2001 From: joboet Date: Wed, 9 Sep 2026 14:48:12 +0200 Subject: [PATCH 1/4] std: refactor UNIX stack overflow code (create files) --- .../guard_page.rs} | 0 .../pal/unix/stack_overflow/handler_cygwin.rs | 844 ++++++++++++++++++ .../pal/unix/stack_overflow/handler_none.rs | 844 ++++++++++++++++++ .../pal/unix/stack_overflow/handler_signal.rs | 844 ++++++++++++++++++ .../src/sys/pal/unix/stack_overflow/mod.rs | 844 ++++++++++++++++++ 5 files changed, 3376 insertions(+) rename library/std/src/sys/pal/unix/{stack_overflow.rs => stack_overflow/guard_page.rs} (100%) create mode 100644 library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs create mode 100644 library/std/src/sys/pal/unix/stack_overflow/handler_none.rs create mode 100644 library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs create mode 100644 library/std/src/sys/pal/unix/stack_overflow/mod.rs diff --git a/library/std/src/sys/pal/unix/stack_overflow.rs b/library/std/src/sys/pal/unix/stack_overflow/guard_page.rs similarity index 100% rename from library/std/src/sys/pal/unix/stack_overflow.rs rename to library/std/src/sys/pal/unix/stack_overflow/guard_page.rs diff --git a/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs b/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs new file mode 100644 index 0000000000000..5604e3e6dbf42 --- /dev/null +++ b/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs @@ -0,0 +1,844 @@ +#![cfg_attr(test, allow(dead_code))] +#![forbid(unsafe_op_in_unsafe_fn)] + +pub use self::imp::init; +use self::imp::{drop_handler, make_handler}; + +pub struct Handler { + data: *mut libc::c_void, +} + +impl Handler { + pub unsafe fn new() -> Handler { + make_handler(false) + } + + fn null() -> Handler { + Handler { data: crate::ptr::null_mut() } + } +} + +impl Drop for Handler { + fn drop(&mut self) { + unsafe { + drop_handler(self.data); + } + } +} + +#[cfg(all( + not(miri), + any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ), +))] +mod thread_info; + +// miri doesn't model signals nor stack overflows and this code has some +// synchronization properties that we don't want to expose to user code, +// hence we disable it on miri. +#[cfg(all( + not(miri), + any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ) +))] +mod imp { + use libc::{ + MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK, + SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t, + }; + #[cfg(not(all(target_os = "linux", target_env = "gnu")))] + use libc::{mmap as mmap64, mprotect, munmap}; + #[cfg(all(target_os = "linux", target_env = "gnu"))] + use libc::{mmap64, mprotect, munmap}; + + use super::Handler; + use super::thread_info::{delete_current_info, set_current_info, with_current_info}; + use crate::ops::Range; + use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering}; + use crate::sys::pal::unix::conf; + use crate::{io, mem, ptr}; + + /// Signal handler for the SIGSEGV and SIGBUS handlers. + /// + /// We've got guard pages (unmapped pages) at the end of every thread's + /// stack, so if a thread ends up running into the guard page it'll trigger + /// this handler. We want to detect these cases and print out a helpful error + /// saying that the stack has overflowed. All other signals, however, should + /// go back to what they were originally supposed to do. + /// + /// This handler currently exists purely to print an informative message + /// whenever a thread overflows its stack. We then abort to exit and + /// indicate a crash, but to avoid a misleading SIGSEGV that might lead + /// users to believe that unsafe code has accessed an invalid pointer; the + /// SIGSEGV encountered when overflowing the stack is expected and + /// well-defined. + /// + /// If this is not a stack overflow, the handler un-registers itself and + /// then returns (to allow the original signal to be delivered again). + /// Returning from this kind of signal handler is technically not defined + /// to work when reading the POSIX spec strictly, but in practice it turns + /// out many large systems and all implementations allow returning from a + /// signal handler to work. For a more detailed explanation see the + /// comments on #26458. + /// + /// # Safety + /// Rust doesn't call this, it *gets called* by the kernel, which we expect + /// to provide valid parameters. Apart from that, this function does not + /// have any other preconditions. + unsafe extern "C" fn signal_handler( + signum: libc::c_int, + info: *mut libc::siginfo_t, + _data: *mut libc::c_void, + ) { + // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`. + let fault_addr = unsafe { (*info).si_addr().addr() }; + + // `with_current_info` expects that the process aborts after it is + // called. If the signal was not caused by a memory access, this might + // not be true. We detect this by noticing that the `si_addr` field is + // zero if the signal is synthetic. + if fault_addr != 0 { + with_current_info(|thread_info| { + // If the faulting address is within the guard page, then we print a + // message saying so and abort. + if let Some(thread_info) = thread_info + && thread_info.guard_page_range.contains(&fault_addr) + { + // Hey you! Yes, you modifying the stack overflow message! + // Please make sure that all functions called here are + // actually async-signal-safe. If they're not, try retrieving + // the information beforehand and storing it in `ThreadInfo`. + // Thank you! + // - says Jonas after having had to watch his carefully + // written code get made unsound again. + let tid = thread_info.tid; + let name = thread_info.name.as_deref().unwrap_or(""); + rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); + rtabort!("stack overflow"); + } + }) + } + + // Unregister ourselves by reverting back to the default behavior. + // SAFETY: assuming all platforms define struct sigaction as "zero-initializable" + let mut action: sigaction = unsafe { mem::zeroed() }; + action.sa_sigaction = SIG_DFL; + // SAFETY: pray this is a well-behaved POSIX implementation of fn sigaction + unsafe { sigaction(signum, &action, ptr::null_mut()) }; + + // See comment above for why this function returns. + } + + static PAGE_SIZE: Atomic = AtomicUsize::new(0); + // Store a pointer to the allocation for the main thread's altstack so that + // tools like valgrind don't complain about a leaked unreachable allocation. + // + // If the main thread exits, the process will terminate so there's no use in + // freeing resources. It also means that the altstack is still installed + // while TLS destructors are run on the main thread (c.f. #111272). + static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut()); + static NEED_ALTSTACK: Atomic = AtomicBool::new(false); + + /// # Safety + /// Must be called only once, on the main thread, during program startup. + pub unsafe fn init() { + PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed); + + // SAFETY: + // This is only called on the main thread, and since it is still early + // in the programs lifetime there is (almost) certainly enough stack + // space left to install the guard page. + let mut guard_page_range = unsafe { install_main_guard() }; + + // Even for panic=immediate-abort, installing the guard pages is important for soundness. + // That said, we do not care about giving nice stackoverflow messages via our custom + // signal handler, just exit early and let the user enjoy the segfault. + if cfg!(panic = "immediate-abort") { + return; + } + + // SAFETY: C structures are always zero-initializable. + let mut action: sigaction = unsafe { mem::zeroed() }; + for &signal in &[SIGSEGV, SIGBUS] { + // SAFETY: just fetches the current signal handler into action + unsafe { sigaction(signal, ptr::null_mut(), &mut action) }; + // We assume that overriding the signal handler is always safe, + // which might conflict with certain libraries that rely on a + // specific signal behaviour. To prevent problems, we only + // override the handler if it has not been set yet. + if action.sa_sigaction == SIG_DFL { + if !NEED_ALTSTACK.load(Ordering::Relaxed) { + // haven't set up our sigaltstack yet + NEED_ALTSTACK.store(true, Ordering::Release); + let handler = make_handler(true); + MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed); + mem::forget(handler); + + if let Some(guard_page_range) = guard_page_range.take() { + set_current_info(guard_page_range); + } + } + + action.sa_flags = SA_SIGINFO | SA_ONSTACK; + action.sa_sigaction = signal_handler + as unsafe extern "C" fn(i32, *mut libc::siginfo_t, *mut libc::c_void) + as sighandler_t; + // SAFETY: + // `&action` describes a valid `sigaction` and `signal_handler` + // is safe to use as a signal handler for `SIGSEGV` and `SIGBUS`. + unsafe { sigaction(signal, &action, ptr::null_mut()) }; + } + } + } + + fn get_stack() -> libc::stack_t { + // OpenBSD requires this flag for stack mapping + // otherwise the said mapping will fail as a no-op on most systems + // and has a different meaning on FreeBSD + #[cfg(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "linux", + target_os = "dragonfly", + ))] + let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK; + #[cfg(not(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "linux", + target_os = "dragonfly", + )))] + let flags = MAP_PRIVATE | MAP_ANON; + + let sigstack_size = sigstack_size(); + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + + // SAFETY: this does not unmap any existing pages. + let stackp = unsafe { + mmap64(ptr::null_mut(), sigstack_size + page_size, PROT_READ | PROT_WRITE, flags, -1, 0) + }; + if stackp == MAP_FAILED { + panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error()); + } + // SAFETY: this only affects the memory we just allocated. + let guard_result = unsafe { libc::mprotect(stackp, page_size, PROT_NONE) }; + if guard_result != 0 { + panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error()); + } + // SAFETY: + // The region was allocated with a larger size than `page_size`, so this + // addition is within bounds. + let stackp = unsafe { stackp.add(page_size) }; + + libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size } + } + + pub fn make_handler(main_thread: bool) -> Handler { + if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) { + return Handler::null(); + } + + if !main_thread { + if let Some(guard_page_range) = current_guard() { + set_current_info(guard_page_range); + } + } + + // Load the current alternate signal stack to see if we need to install + // our own. + // + // SAFETY: C structures are always zero-initializable. + let mut stack = unsafe { mem::zeroed() }; + // SAFETY: `&mut stack` is valid for writing a `stack_t`. + unsafe { sigaltstack(ptr::null(), &mut stack) }; + + // Configure alternate signal stack, if one is not already set. + if stack.ss_flags & SS_DISABLE != 0 { + let stack = get_stack(); + // SAFETY: + // `stack_t` is a freshly allocated stack that's not used anywhere + // else. It contains a guard page, so stack overflows in signal + // handlers will not cause undefined behaviour. We must make the + // fundamental runtime assumption that it is safe to install an + // alternate signal stack if there is none currently installed. + // This might conflict with foreign libraries that use the existence + // of an alternate signal stack as indication that certain runtime + // initialisation by the library has been performed (e.g. old + // versions of `std` assumed that certain thread-locals were already + // accessed and thus initialized in the thread if the stack overflow + // signal was successfully delivered). Such assumptions in other + // libraries are fundamentally flawed, so we pay no regard to them. + unsafe { sigaltstack(&stack, ptr::null_mut()) }; + Handler { data: stack.ss_sp as *mut libc::c_void } + } else { + Handler::null() + } + } + + /// # Safety + /// Must only be called with a pointer returned by `make_handler`, and only + /// once per `Handler`. + pub unsafe fn drop_handler(data: *mut libc::c_void) { + if !data.is_null() { + let sigstack_size = sigstack_size(); + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + let disabling_stack = libc::stack_t { + ss_sp: ptr::null_mut(), + ss_flags: SS_DISABLE, + // Workaround for bug in macOS implementation of sigaltstack + // UNIX2003 which returns ENOMEM when disabling a stack while + // passing ss_size smaller than MINSIGSTKSZ. According to POSIX + // both ss_sp and ss_size should be ignored in this case. + ss_size: sigstack_size, + }; + // SAFETY: + // We assume that disabling the alternate signal stack is always + // sound, even if the current alternate signal stack is not the one + // we installed in `make_handler`. Any stack overflows from this + // point on will abort the program when the kernel tries to write + // the signal information to the guard page. + // + // FIXME: detect if the stack has changed, and only uninstall if it hasn't. + unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) }; + // The stack returned by `get_stack` is part of a mapping that + // started one page earlier, so walk back a page and unmap from + // there. + // + // SAFETY: + // This allocation was created by us in `get_stack` and, as the + // alternate signal stack is now disabled, is no longer in use. + unsafe { munmap(data.sub(page_size), sigstack_size + page_size) }; + } + + delete_current_info(); + } + + /// Modern kernels on modern hardware can have dynamic signal stack sizes. + #[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] + fn sigstack_size() -> usize { + // SAFETY: `getauxval` is always safe to call. + let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) }; + // If getauxval couldn't find the entry, it returns 0, + // so take the higher of the "constant" and auxval. + // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ + libc::SIGSTKSZ.max(dynamic_sigstksz as _) + } + + /// Not all OS support hardware where this is needed. + #[cfg(not(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc"))))] + fn sigstack_size() -> usize { + libc::SIGSTKSZ + } + + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: C types are always zero-initializable. + let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; + // SAFETY: + // The pointer is valid for writing a `stack_t`. + assert_eq!(unsafe { libc::stack_getbounds(&mut current_stack) }, 0); + Some(current_stack.ss_sp) + } + + #[cfg(target_os = "macos")] + fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: always safe to call. + let th = unsafe { libc::pthread_self() }; + // SAFETY: `th` is a valid `pthread_t`. + unsafe { + let stackptr = libc::pthread_get_stackaddr_np(th); + let stacksize = libc::pthread_get_stacksize_np(th); + Some(stackptr.map_addr(|addr| addr - stacksize)) + } + } + + #[cfg(target_os = "openbsd")] + fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: C types are always zero-initializable. + let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t`. + // * `&mut current_stack` is coerced to a pointer that is valid for writing + // a `stack_t`. + assert_eq!( + unsafe { libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack) }, + 0 + ); + + let stack_ptr = current_stack.ss_sp; + // SAFETY: this is always safe to call. + let stackaddr = if unsafe { libc::pthread_main_np() } == 1 { + // main thread + stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed) + } else { + // new thread + stack_ptr.addr() - current_stack.ss_size + }; + Some(stack_ptr.with_addr(stackaddr)) + } + + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", + target_os = "hurd", + target_os = "linux", + target_os = "l4re" + ))] + fn get_stack_start() -> Option<*mut libc::c_void> { + use crate::pin::pin; + use crate::sys::helpers::COpaque; + + let mut ret = None; + let mut attr: COpaque = COpaque::uninit(); + if !cfg!(target_os = "freebsd") { + attr = COpaque::zeroed(); + } + let attr = pin!(attr); + // FIXME(pin-ergonomics): remove the next line. + let attr = attr.into_ref(); + + // SAFETY: + // The attributes object has not been initialized yet and will not be + // moved until destroyed. + #[cfg(target_os = "freebsd")] + assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` is an initialized attribute object that can be written to. + #[cfg(target_os = "freebsd")] + let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` can be written to, and will be initialized by this call. + #[cfg(not(target_os = "freebsd"))] + let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; + if e == 0 { + let mut stackaddr = crate::ptr::null_mut(); + let mut stacksize = 0; + // SAFETY: + // `attr` is an initialized attribute object and both the pointers + // are valid for writing. + assert_eq!( + unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackaddr, &mut stacksize) }, + 0 + ); + ret = Some(stackaddr); + } + if e == 0 || cfg!(target_os = "freebsd") { + // SAFETY: + // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or + // by `pthread_attr_get_np`, and is not used after this point. + assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); + } + ret + } + + fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> { + let stackptr = get_stack_start()?; + let stackaddr = stackptr.addr(); + + // Ensure stackaddr is page aligned! A parent process might + // have reset RLIMIT_STACK to be non-page aligned. The + // pthread_attr_getstack() reports the usable stack area + // stackaddr < stackaddr + stacksize, so if stackaddr is not + // page-aligned, calculate the fix such that stackaddr < + // new_page_aligned_stackaddr < stackaddr + stacksize + let remainder = stackaddr % page_size; + Some(if remainder == 0 { + stackptr + } else { + stackptr.with_addr(stackaddr + page_size - remainder) + }) + } + + /// # Safety + /// This function must only be called from the main thread, and there must + /// be sufficient stack space remaining to place a stack guard. + unsafe fn install_main_guard() -> Option> { + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + + // this way someone on any unix-y OS can check that all these compile + if cfg!(all(target_os = "linux", not(target_env = "musl"))) { + install_main_guard_linux(page_size) + } else if cfg!(all(target_os = "linux", target_env = "musl")) { + install_main_guard_linux_musl(page_size) + } else if cfg!(target_os = "freebsd") { + #[cfg(not(target_os = "freebsd"))] + return None; + // The FreeBSD code cannot be checked on non-BSDs. + #[cfg(target_os = "freebsd")] + install_main_guard_freebsd(page_size) + } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) { + install_main_guard_bsds(page_size) + } else { + // SAFETY: guaranteed by caller. + unsafe { install_main_guard_default(page_size) } + } + } + + fn install_main_guard_linux(page_size: usize) -> Option> { + // See the corresponding conditional in init(). + // Avoid stack_start_aligned, which makes slow syscalls to read /proc/self/maps + if cfg!(panic = "immediate-abort") { + return None; + } + // Linux doesn't allocate the whole stack right away, and + // the kernel has its own stack-guard mechanism to fault + // when growing too close to an existing mapping. If we map + // our own guard, then the kernel starts enforcing a rather + // large gap above that, rendering much of the possible + // stack space useless. See #43052. + // + // Instead, we'll just note where we expect rlimit to start + // faulting, so our handler can report "stack overflow", and + // trust that the kernel's own stack guard will work. + let stackptr = stack_start_aligned(page_size)?; + let stackaddr = stackptr.addr(); + Some(stackaddr - page_size..stackaddr) + } + + fn install_main_guard_linux_musl(_page_size: usize) -> Option> { + // For the main thread, the musl's pthread_attr_getstack + // returns the current stack size, rather than maximum size + // it can eventually grow to. It cannot be used to determine + // the position of kernel's stack guard. + None + } + + #[cfg(target_os = "freebsd")] + fn install_main_guard_freebsd(page_size: usize) -> Option> { + // See the corresponding conditional in install_main_guard_linux(). + if cfg!(panic = "immediate-abort") { + return None; + } + // FreeBSD's stack autogrows, and optionally includes a guard page + // at the bottom. If we try to remap the bottom of the stack + // ourselves, FreeBSD's guard page moves upwards. So we'll just use + // the builtin guard page. + let stackptr = stack_start_aligned(page_size)?; + let guardaddr = stackptr.addr(); + // Technically the number of guard pages is tunable and controlled + // by the security.bsd.stack_guard_page sysctl. + // By default it is 1, checking once is enough since it is + // a boot time config value. + // FIXME(joboet): this function is only called once, remove the caching. + static PAGES: crate::sync::OnceLock = crate::sync::OnceLock::new(); + + let pages = PAGES.get_or_init(|| { + let mut guard: usize = 0; + let mut size = size_of_val(&guard); + let oid = c"security.bsd.stack_guard_page"; + + let r = unsafe { + libc::sysctlbyname( + oid.as_ptr(), + (&raw mut guard).cast(), + &raw mut size, + ptr::null_mut(), + 0, + ) + }; + if r == 0 { guard } else { 1 } + }); + Some(guardaddr..guardaddr + pages * page_size) + } + + fn install_main_guard_bsds(page_size: usize) -> Option> { + // See the corresponding conditional in install_main_guard_linux(). + if cfg!(panic = "immediate-abort") { + return None; + } + // OpenBSD stack already includes a guard page, and stack is + // immutable. + // NetBSD stack includes the guard page. + // + // We'll just note where we expect rlimit to start + // faulting, so our handler can report "stack overflow", and + // trust that the kernel's own stack guard will work. + let stackptr = stack_start_aligned(page_size)?; + let stackaddr = stackptr.addr(); + Some(stackaddr - page_size..stackaddr) + } + + /// # Safety + /// This function must only be called from the main thread, and there must + /// be sufficient stack space remaining to place a stack guard. + unsafe fn install_main_guard_default(page_size: usize) -> Option> { + // Reallocate the last page of the stack. + // This ensures SIGBUS will be raised on + // stack overflow. + // Systems which enforce strict PAX MPROTECT do not allow + // to mprotect() a mapping with less restrictive permissions + // than the initial mmap() used, so we mmap() here with + // read/write permissions and only then mprotect() it to + // no permissions at all. See issue #50313. + let stackptr = stack_start_aligned(page_size)?; + // SAFETY: + // The memory region from `stackptr..stackptr + page_size` belongs to + // the current thread's stack, and the caller has asserted that there + // is sufficient stack space, which means that this will not overwrite + // any existing allocations. + let result = unsafe { + mmap64( + stackptr, + page_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, + -1, + 0, + ) + }; + if result != stackptr || result == MAP_FAILED { + panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); + } + + // SAFETY: + // Since this function is only called on the main thread, the stack will + // not be reused until program exit, so the runtime will never observe + // that part of the stack has been made unusable in this way. + let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) }; + if result != 0 { + panic!("failed to protect the guard page: {}", io::Error::last_os_error()); + } + + let guardaddr = stackptr.addr(); + + Some(guardaddr..guardaddr + page_size) + } + + #[cfg(any( + target_os = "macos", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ))] + fn current_guard() -> Option> { + let stackptr = get_stack_start()?; + let stackaddr = stackptr.addr(); + Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr) + } + + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "hurd", + target_os = "linux", + target_os = "netbsd", + target_os = "l4re" + ))] + fn current_guard() -> Option> { + use crate::pin::pin; + use crate::sys::helpers::COpaque; + + let mut ret = None; + + let mut attr: COpaque = COpaque::uninit(); + if !cfg!(target_os = "freebsd") { + attr = COpaque::zeroed(); + } + let attr = pin!(attr); + // FIXME(pin-ergonomics): remove the next line. + let attr = attr.into_ref(); + + // SAFETY: + // The attributes object has not been initialized yet and will not be + // moved until destroyed. + #[cfg(target_os = "freebsd")] + assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` is an initialized attribute object that can be written to. + #[cfg(target_os = "freebsd")] + let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` can be written to, and will be initialized by this call. + #[cfg(not(target_os = "freebsd"))] + let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; + if e == 0 { + let mut guardsize = 0; + // SAFETY: + // `attr` is an initialized attribute object and the pointer is valid + // for writing. + assert_eq!(unsafe { libc::pthread_attr_getguardsize(attr.get(), &mut guardsize) }, 0); + if guardsize == 0 { + if cfg!(all(target_os = "linux", target_env = "musl")) { + // musl versions before 1.1.19 always reported guard + // size obtained from pthread_attr_get_np as zero. + // Use page size as a fallback. + guardsize = PAGE_SIZE.load(Ordering::Relaxed); + } else { + panic!("there is no guard page"); + } + } + let mut stackptr = crate::ptr::null_mut::(); + let mut size = 0; + // SAFETY: + // `attr` is an initialized attribute object and both the pointers + // are valid for writing. + assert_eq!( + unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackptr, &mut size) }, + 0 + ); + + let stackaddr = stackptr.addr(); + ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) { + Some(stackaddr - guardsize..stackaddr) + } else if cfg!(all(target_os = "linux", target_env = "musl")) { + Some(stackaddr - guardsize..stackaddr) + } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc"))) + { + // glibc used to include the guard area within the stack, as noted in the BUGS + // section of `man pthread_attr_getguardsize`. This has been corrected starting + // with glibc 2.27, and in some distro backports, so the guard is now placed at the + // end (below) the stack. There's no easy way for us to know which we have at + // runtime, so we'll just match any fault in the range right above or below the + // stack base to call that fault a stack overflow. + Some(stackaddr - guardsize..stackaddr + guardsize) + } else { + Some(stackaddr..stackaddr + guardsize) + }; + } + if e == 0 || cfg!(target_os = "freebsd") { + // SAFETY: + // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or + // by `pthread_attr_get_np`, and is not used after this point. + assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); + } + ret + } +} + +// This is intentionally not enabled on iOS/tvOS/watchOS/visionOS, as it uses +// several symbols that might lead to rejections from the App Store, namely +// `sigaction`, `sigaltstack`, `sysctlbyname`, `mmap`, `munmap` and `mprotect`. +// +// This might be overly cautious, though it is also what Swift does (and they +// usually have fewer qualms about forwards compatibility, since the runtime +// is shipped with the OS): +// +#[cfg(any( + miri, + not(any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + target_os = "cygwin", + )) +))] +mod imp { + pub unsafe fn init() {} + + pub fn make_handler(_main_thread: bool) -> super::Handler { + super::Handler::null() + } + + pub unsafe fn drop_handler(_data: *mut libc::c_void) {} +} + +#[cfg(target_os = "cygwin")] +mod imp { + mod c { + pub type PVECTORED_EXCEPTION_HANDLER = + Option i32>; + pub type NTSTATUS = i32; + pub type BOOL = i32; + + unsafe extern "system" { + pub fn AddVectoredExceptionHandler( + first: u32, + handler: PVECTORED_EXCEPTION_HANDLER, + ) -> *mut core::ffi::c_void; + pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL; + } + + pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _; + pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32; + + #[repr(C)] + #[derive(Clone, Copy)] + pub struct EXCEPTION_POINTERS { + pub ExceptionRecord: *mut EXCEPTION_RECORD, + // We don't need this field here + // pub Context: *mut CONTEXT, + } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct EXCEPTION_RECORD { + pub ExceptionCode: NTSTATUS, + pub ExceptionFlags: u32, + pub ExceptionRecord: *mut EXCEPTION_RECORD, + pub ExceptionAddress: *mut core::ffi::c_void, + pub NumberParameters: u32, + pub ExceptionInformation: [usize; 15], + } + } + + /// Reserve stack space for use in stack overflow exceptions. + fn reserve_stack() { + let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) }; + // Reserving stack space is not critical so we allow it to fail in the released build of libstd. + // We still use debug assert here so that CI will test that we haven't made a mistake calling the function. + debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling"); + } + + unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 { + // SAFETY: It's up to the caller (which in this case is the OS) to ensure that `ExceptionInfo` is valid. + unsafe { + let rec = &(*(*ExceptionInfo).ExceptionRecord); + let code = rec.ExceptionCode; + + if code == c::EXCEPTION_STACK_OVERFLOW { + crate::thread::with_current_name(|name| { + let name = name.unwrap_or(""); + let tid = crate::thread::current_os_id(); + rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); + }); + } + c::EXCEPTION_CONTINUE_SEARCH + } + } + + pub unsafe fn init() { + // SAFETY: `vectored_handler` has the correct ABI and is safe to call during exception handling. + unsafe { + let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler)); + // Similar to the above, adding the stack overflow handler is allowed to fail + // but a debug assert is used so CI will still test that it normally works. + debug_assert!(!result.is_null(), "failed to install exception handler"); + } + // Set the thread stack guarantee for the main thread. + reserve_stack(); + } + + pub fn make_handler(main_thread: bool) -> super::Handler { + if !main_thread { + reserve_stack(); + } + super::Handler::null() + } + + pub unsafe fn drop_handler(_data: *mut libc::c_void) {} +} diff --git a/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs b/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs new file mode 100644 index 0000000000000..5604e3e6dbf42 --- /dev/null +++ b/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs @@ -0,0 +1,844 @@ +#![cfg_attr(test, allow(dead_code))] +#![forbid(unsafe_op_in_unsafe_fn)] + +pub use self::imp::init; +use self::imp::{drop_handler, make_handler}; + +pub struct Handler { + data: *mut libc::c_void, +} + +impl Handler { + pub unsafe fn new() -> Handler { + make_handler(false) + } + + fn null() -> Handler { + Handler { data: crate::ptr::null_mut() } + } +} + +impl Drop for Handler { + fn drop(&mut self) { + unsafe { + drop_handler(self.data); + } + } +} + +#[cfg(all( + not(miri), + any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ), +))] +mod thread_info; + +// miri doesn't model signals nor stack overflows and this code has some +// synchronization properties that we don't want to expose to user code, +// hence we disable it on miri. +#[cfg(all( + not(miri), + any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ) +))] +mod imp { + use libc::{ + MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK, + SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t, + }; + #[cfg(not(all(target_os = "linux", target_env = "gnu")))] + use libc::{mmap as mmap64, mprotect, munmap}; + #[cfg(all(target_os = "linux", target_env = "gnu"))] + use libc::{mmap64, mprotect, munmap}; + + use super::Handler; + use super::thread_info::{delete_current_info, set_current_info, with_current_info}; + use crate::ops::Range; + use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering}; + use crate::sys::pal::unix::conf; + use crate::{io, mem, ptr}; + + /// Signal handler for the SIGSEGV and SIGBUS handlers. + /// + /// We've got guard pages (unmapped pages) at the end of every thread's + /// stack, so if a thread ends up running into the guard page it'll trigger + /// this handler. We want to detect these cases and print out a helpful error + /// saying that the stack has overflowed. All other signals, however, should + /// go back to what they were originally supposed to do. + /// + /// This handler currently exists purely to print an informative message + /// whenever a thread overflows its stack. We then abort to exit and + /// indicate a crash, but to avoid a misleading SIGSEGV that might lead + /// users to believe that unsafe code has accessed an invalid pointer; the + /// SIGSEGV encountered when overflowing the stack is expected and + /// well-defined. + /// + /// If this is not a stack overflow, the handler un-registers itself and + /// then returns (to allow the original signal to be delivered again). + /// Returning from this kind of signal handler is technically not defined + /// to work when reading the POSIX spec strictly, but in practice it turns + /// out many large systems and all implementations allow returning from a + /// signal handler to work. For a more detailed explanation see the + /// comments on #26458. + /// + /// # Safety + /// Rust doesn't call this, it *gets called* by the kernel, which we expect + /// to provide valid parameters. Apart from that, this function does not + /// have any other preconditions. + unsafe extern "C" fn signal_handler( + signum: libc::c_int, + info: *mut libc::siginfo_t, + _data: *mut libc::c_void, + ) { + // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`. + let fault_addr = unsafe { (*info).si_addr().addr() }; + + // `with_current_info` expects that the process aborts after it is + // called. If the signal was not caused by a memory access, this might + // not be true. We detect this by noticing that the `si_addr` field is + // zero if the signal is synthetic. + if fault_addr != 0 { + with_current_info(|thread_info| { + // If the faulting address is within the guard page, then we print a + // message saying so and abort. + if let Some(thread_info) = thread_info + && thread_info.guard_page_range.contains(&fault_addr) + { + // Hey you! Yes, you modifying the stack overflow message! + // Please make sure that all functions called here are + // actually async-signal-safe. If they're not, try retrieving + // the information beforehand and storing it in `ThreadInfo`. + // Thank you! + // - says Jonas after having had to watch his carefully + // written code get made unsound again. + let tid = thread_info.tid; + let name = thread_info.name.as_deref().unwrap_or(""); + rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); + rtabort!("stack overflow"); + } + }) + } + + // Unregister ourselves by reverting back to the default behavior. + // SAFETY: assuming all platforms define struct sigaction as "zero-initializable" + let mut action: sigaction = unsafe { mem::zeroed() }; + action.sa_sigaction = SIG_DFL; + // SAFETY: pray this is a well-behaved POSIX implementation of fn sigaction + unsafe { sigaction(signum, &action, ptr::null_mut()) }; + + // See comment above for why this function returns. + } + + static PAGE_SIZE: Atomic = AtomicUsize::new(0); + // Store a pointer to the allocation for the main thread's altstack so that + // tools like valgrind don't complain about a leaked unreachable allocation. + // + // If the main thread exits, the process will terminate so there's no use in + // freeing resources. It also means that the altstack is still installed + // while TLS destructors are run on the main thread (c.f. #111272). + static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut()); + static NEED_ALTSTACK: Atomic = AtomicBool::new(false); + + /// # Safety + /// Must be called only once, on the main thread, during program startup. + pub unsafe fn init() { + PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed); + + // SAFETY: + // This is only called on the main thread, and since it is still early + // in the programs lifetime there is (almost) certainly enough stack + // space left to install the guard page. + let mut guard_page_range = unsafe { install_main_guard() }; + + // Even for panic=immediate-abort, installing the guard pages is important for soundness. + // That said, we do not care about giving nice stackoverflow messages via our custom + // signal handler, just exit early and let the user enjoy the segfault. + if cfg!(panic = "immediate-abort") { + return; + } + + // SAFETY: C structures are always zero-initializable. + let mut action: sigaction = unsafe { mem::zeroed() }; + for &signal in &[SIGSEGV, SIGBUS] { + // SAFETY: just fetches the current signal handler into action + unsafe { sigaction(signal, ptr::null_mut(), &mut action) }; + // We assume that overriding the signal handler is always safe, + // which might conflict with certain libraries that rely on a + // specific signal behaviour. To prevent problems, we only + // override the handler if it has not been set yet. + if action.sa_sigaction == SIG_DFL { + if !NEED_ALTSTACK.load(Ordering::Relaxed) { + // haven't set up our sigaltstack yet + NEED_ALTSTACK.store(true, Ordering::Release); + let handler = make_handler(true); + MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed); + mem::forget(handler); + + if let Some(guard_page_range) = guard_page_range.take() { + set_current_info(guard_page_range); + } + } + + action.sa_flags = SA_SIGINFO | SA_ONSTACK; + action.sa_sigaction = signal_handler + as unsafe extern "C" fn(i32, *mut libc::siginfo_t, *mut libc::c_void) + as sighandler_t; + // SAFETY: + // `&action` describes a valid `sigaction` and `signal_handler` + // is safe to use as a signal handler for `SIGSEGV` and `SIGBUS`. + unsafe { sigaction(signal, &action, ptr::null_mut()) }; + } + } + } + + fn get_stack() -> libc::stack_t { + // OpenBSD requires this flag for stack mapping + // otherwise the said mapping will fail as a no-op on most systems + // and has a different meaning on FreeBSD + #[cfg(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "linux", + target_os = "dragonfly", + ))] + let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK; + #[cfg(not(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "linux", + target_os = "dragonfly", + )))] + let flags = MAP_PRIVATE | MAP_ANON; + + let sigstack_size = sigstack_size(); + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + + // SAFETY: this does not unmap any existing pages. + let stackp = unsafe { + mmap64(ptr::null_mut(), sigstack_size + page_size, PROT_READ | PROT_WRITE, flags, -1, 0) + }; + if stackp == MAP_FAILED { + panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error()); + } + // SAFETY: this only affects the memory we just allocated. + let guard_result = unsafe { libc::mprotect(stackp, page_size, PROT_NONE) }; + if guard_result != 0 { + panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error()); + } + // SAFETY: + // The region was allocated with a larger size than `page_size`, so this + // addition is within bounds. + let stackp = unsafe { stackp.add(page_size) }; + + libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size } + } + + pub fn make_handler(main_thread: bool) -> Handler { + if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) { + return Handler::null(); + } + + if !main_thread { + if let Some(guard_page_range) = current_guard() { + set_current_info(guard_page_range); + } + } + + // Load the current alternate signal stack to see if we need to install + // our own. + // + // SAFETY: C structures are always zero-initializable. + let mut stack = unsafe { mem::zeroed() }; + // SAFETY: `&mut stack` is valid for writing a `stack_t`. + unsafe { sigaltstack(ptr::null(), &mut stack) }; + + // Configure alternate signal stack, if one is not already set. + if stack.ss_flags & SS_DISABLE != 0 { + let stack = get_stack(); + // SAFETY: + // `stack_t` is a freshly allocated stack that's not used anywhere + // else. It contains a guard page, so stack overflows in signal + // handlers will not cause undefined behaviour. We must make the + // fundamental runtime assumption that it is safe to install an + // alternate signal stack if there is none currently installed. + // This might conflict with foreign libraries that use the existence + // of an alternate signal stack as indication that certain runtime + // initialisation by the library has been performed (e.g. old + // versions of `std` assumed that certain thread-locals were already + // accessed and thus initialized in the thread if the stack overflow + // signal was successfully delivered). Such assumptions in other + // libraries are fundamentally flawed, so we pay no regard to them. + unsafe { sigaltstack(&stack, ptr::null_mut()) }; + Handler { data: stack.ss_sp as *mut libc::c_void } + } else { + Handler::null() + } + } + + /// # Safety + /// Must only be called with a pointer returned by `make_handler`, and only + /// once per `Handler`. + pub unsafe fn drop_handler(data: *mut libc::c_void) { + if !data.is_null() { + let sigstack_size = sigstack_size(); + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + let disabling_stack = libc::stack_t { + ss_sp: ptr::null_mut(), + ss_flags: SS_DISABLE, + // Workaround for bug in macOS implementation of sigaltstack + // UNIX2003 which returns ENOMEM when disabling a stack while + // passing ss_size smaller than MINSIGSTKSZ. According to POSIX + // both ss_sp and ss_size should be ignored in this case. + ss_size: sigstack_size, + }; + // SAFETY: + // We assume that disabling the alternate signal stack is always + // sound, even if the current alternate signal stack is not the one + // we installed in `make_handler`. Any stack overflows from this + // point on will abort the program when the kernel tries to write + // the signal information to the guard page. + // + // FIXME: detect if the stack has changed, and only uninstall if it hasn't. + unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) }; + // The stack returned by `get_stack` is part of a mapping that + // started one page earlier, so walk back a page and unmap from + // there. + // + // SAFETY: + // This allocation was created by us in `get_stack` and, as the + // alternate signal stack is now disabled, is no longer in use. + unsafe { munmap(data.sub(page_size), sigstack_size + page_size) }; + } + + delete_current_info(); + } + + /// Modern kernels on modern hardware can have dynamic signal stack sizes. + #[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] + fn sigstack_size() -> usize { + // SAFETY: `getauxval` is always safe to call. + let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) }; + // If getauxval couldn't find the entry, it returns 0, + // so take the higher of the "constant" and auxval. + // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ + libc::SIGSTKSZ.max(dynamic_sigstksz as _) + } + + /// Not all OS support hardware where this is needed. + #[cfg(not(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc"))))] + fn sigstack_size() -> usize { + libc::SIGSTKSZ + } + + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: C types are always zero-initializable. + let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; + // SAFETY: + // The pointer is valid for writing a `stack_t`. + assert_eq!(unsafe { libc::stack_getbounds(&mut current_stack) }, 0); + Some(current_stack.ss_sp) + } + + #[cfg(target_os = "macos")] + fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: always safe to call. + let th = unsafe { libc::pthread_self() }; + // SAFETY: `th` is a valid `pthread_t`. + unsafe { + let stackptr = libc::pthread_get_stackaddr_np(th); + let stacksize = libc::pthread_get_stacksize_np(th); + Some(stackptr.map_addr(|addr| addr - stacksize)) + } + } + + #[cfg(target_os = "openbsd")] + fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: C types are always zero-initializable. + let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t`. + // * `&mut current_stack` is coerced to a pointer that is valid for writing + // a `stack_t`. + assert_eq!( + unsafe { libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack) }, + 0 + ); + + let stack_ptr = current_stack.ss_sp; + // SAFETY: this is always safe to call. + let stackaddr = if unsafe { libc::pthread_main_np() } == 1 { + // main thread + stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed) + } else { + // new thread + stack_ptr.addr() - current_stack.ss_size + }; + Some(stack_ptr.with_addr(stackaddr)) + } + + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", + target_os = "hurd", + target_os = "linux", + target_os = "l4re" + ))] + fn get_stack_start() -> Option<*mut libc::c_void> { + use crate::pin::pin; + use crate::sys::helpers::COpaque; + + let mut ret = None; + let mut attr: COpaque = COpaque::uninit(); + if !cfg!(target_os = "freebsd") { + attr = COpaque::zeroed(); + } + let attr = pin!(attr); + // FIXME(pin-ergonomics): remove the next line. + let attr = attr.into_ref(); + + // SAFETY: + // The attributes object has not been initialized yet and will not be + // moved until destroyed. + #[cfg(target_os = "freebsd")] + assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` is an initialized attribute object that can be written to. + #[cfg(target_os = "freebsd")] + let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` can be written to, and will be initialized by this call. + #[cfg(not(target_os = "freebsd"))] + let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; + if e == 0 { + let mut stackaddr = crate::ptr::null_mut(); + let mut stacksize = 0; + // SAFETY: + // `attr` is an initialized attribute object and both the pointers + // are valid for writing. + assert_eq!( + unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackaddr, &mut stacksize) }, + 0 + ); + ret = Some(stackaddr); + } + if e == 0 || cfg!(target_os = "freebsd") { + // SAFETY: + // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or + // by `pthread_attr_get_np`, and is not used after this point. + assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); + } + ret + } + + fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> { + let stackptr = get_stack_start()?; + let stackaddr = stackptr.addr(); + + // Ensure stackaddr is page aligned! A parent process might + // have reset RLIMIT_STACK to be non-page aligned. The + // pthread_attr_getstack() reports the usable stack area + // stackaddr < stackaddr + stacksize, so if stackaddr is not + // page-aligned, calculate the fix such that stackaddr < + // new_page_aligned_stackaddr < stackaddr + stacksize + let remainder = stackaddr % page_size; + Some(if remainder == 0 { + stackptr + } else { + stackptr.with_addr(stackaddr + page_size - remainder) + }) + } + + /// # Safety + /// This function must only be called from the main thread, and there must + /// be sufficient stack space remaining to place a stack guard. + unsafe fn install_main_guard() -> Option> { + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + + // this way someone on any unix-y OS can check that all these compile + if cfg!(all(target_os = "linux", not(target_env = "musl"))) { + install_main_guard_linux(page_size) + } else if cfg!(all(target_os = "linux", target_env = "musl")) { + install_main_guard_linux_musl(page_size) + } else if cfg!(target_os = "freebsd") { + #[cfg(not(target_os = "freebsd"))] + return None; + // The FreeBSD code cannot be checked on non-BSDs. + #[cfg(target_os = "freebsd")] + install_main_guard_freebsd(page_size) + } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) { + install_main_guard_bsds(page_size) + } else { + // SAFETY: guaranteed by caller. + unsafe { install_main_guard_default(page_size) } + } + } + + fn install_main_guard_linux(page_size: usize) -> Option> { + // See the corresponding conditional in init(). + // Avoid stack_start_aligned, which makes slow syscalls to read /proc/self/maps + if cfg!(panic = "immediate-abort") { + return None; + } + // Linux doesn't allocate the whole stack right away, and + // the kernel has its own stack-guard mechanism to fault + // when growing too close to an existing mapping. If we map + // our own guard, then the kernel starts enforcing a rather + // large gap above that, rendering much of the possible + // stack space useless. See #43052. + // + // Instead, we'll just note where we expect rlimit to start + // faulting, so our handler can report "stack overflow", and + // trust that the kernel's own stack guard will work. + let stackptr = stack_start_aligned(page_size)?; + let stackaddr = stackptr.addr(); + Some(stackaddr - page_size..stackaddr) + } + + fn install_main_guard_linux_musl(_page_size: usize) -> Option> { + // For the main thread, the musl's pthread_attr_getstack + // returns the current stack size, rather than maximum size + // it can eventually grow to. It cannot be used to determine + // the position of kernel's stack guard. + None + } + + #[cfg(target_os = "freebsd")] + fn install_main_guard_freebsd(page_size: usize) -> Option> { + // See the corresponding conditional in install_main_guard_linux(). + if cfg!(panic = "immediate-abort") { + return None; + } + // FreeBSD's stack autogrows, and optionally includes a guard page + // at the bottom. If we try to remap the bottom of the stack + // ourselves, FreeBSD's guard page moves upwards. So we'll just use + // the builtin guard page. + let stackptr = stack_start_aligned(page_size)?; + let guardaddr = stackptr.addr(); + // Technically the number of guard pages is tunable and controlled + // by the security.bsd.stack_guard_page sysctl. + // By default it is 1, checking once is enough since it is + // a boot time config value. + // FIXME(joboet): this function is only called once, remove the caching. + static PAGES: crate::sync::OnceLock = crate::sync::OnceLock::new(); + + let pages = PAGES.get_or_init(|| { + let mut guard: usize = 0; + let mut size = size_of_val(&guard); + let oid = c"security.bsd.stack_guard_page"; + + let r = unsafe { + libc::sysctlbyname( + oid.as_ptr(), + (&raw mut guard).cast(), + &raw mut size, + ptr::null_mut(), + 0, + ) + }; + if r == 0 { guard } else { 1 } + }); + Some(guardaddr..guardaddr + pages * page_size) + } + + fn install_main_guard_bsds(page_size: usize) -> Option> { + // See the corresponding conditional in install_main_guard_linux(). + if cfg!(panic = "immediate-abort") { + return None; + } + // OpenBSD stack already includes a guard page, and stack is + // immutable. + // NetBSD stack includes the guard page. + // + // We'll just note where we expect rlimit to start + // faulting, so our handler can report "stack overflow", and + // trust that the kernel's own stack guard will work. + let stackptr = stack_start_aligned(page_size)?; + let stackaddr = stackptr.addr(); + Some(stackaddr - page_size..stackaddr) + } + + /// # Safety + /// This function must only be called from the main thread, and there must + /// be sufficient stack space remaining to place a stack guard. + unsafe fn install_main_guard_default(page_size: usize) -> Option> { + // Reallocate the last page of the stack. + // This ensures SIGBUS will be raised on + // stack overflow. + // Systems which enforce strict PAX MPROTECT do not allow + // to mprotect() a mapping with less restrictive permissions + // than the initial mmap() used, so we mmap() here with + // read/write permissions and only then mprotect() it to + // no permissions at all. See issue #50313. + let stackptr = stack_start_aligned(page_size)?; + // SAFETY: + // The memory region from `stackptr..stackptr + page_size` belongs to + // the current thread's stack, and the caller has asserted that there + // is sufficient stack space, which means that this will not overwrite + // any existing allocations. + let result = unsafe { + mmap64( + stackptr, + page_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, + -1, + 0, + ) + }; + if result != stackptr || result == MAP_FAILED { + panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); + } + + // SAFETY: + // Since this function is only called on the main thread, the stack will + // not be reused until program exit, so the runtime will never observe + // that part of the stack has been made unusable in this way. + let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) }; + if result != 0 { + panic!("failed to protect the guard page: {}", io::Error::last_os_error()); + } + + let guardaddr = stackptr.addr(); + + Some(guardaddr..guardaddr + page_size) + } + + #[cfg(any( + target_os = "macos", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ))] + fn current_guard() -> Option> { + let stackptr = get_stack_start()?; + let stackaddr = stackptr.addr(); + Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr) + } + + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "hurd", + target_os = "linux", + target_os = "netbsd", + target_os = "l4re" + ))] + fn current_guard() -> Option> { + use crate::pin::pin; + use crate::sys::helpers::COpaque; + + let mut ret = None; + + let mut attr: COpaque = COpaque::uninit(); + if !cfg!(target_os = "freebsd") { + attr = COpaque::zeroed(); + } + let attr = pin!(attr); + // FIXME(pin-ergonomics): remove the next line. + let attr = attr.into_ref(); + + // SAFETY: + // The attributes object has not been initialized yet and will not be + // moved until destroyed. + #[cfg(target_os = "freebsd")] + assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` is an initialized attribute object that can be written to. + #[cfg(target_os = "freebsd")] + let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` can be written to, and will be initialized by this call. + #[cfg(not(target_os = "freebsd"))] + let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; + if e == 0 { + let mut guardsize = 0; + // SAFETY: + // `attr` is an initialized attribute object and the pointer is valid + // for writing. + assert_eq!(unsafe { libc::pthread_attr_getguardsize(attr.get(), &mut guardsize) }, 0); + if guardsize == 0 { + if cfg!(all(target_os = "linux", target_env = "musl")) { + // musl versions before 1.1.19 always reported guard + // size obtained from pthread_attr_get_np as zero. + // Use page size as a fallback. + guardsize = PAGE_SIZE.load(Ordering::Relaxed); + } else { + panic!("there is no guard page"); + } + } + let mut stackptr = crate::ptr::null_mut::(); + let mut size = 0; + // SAFETY: + // `attr` is an initialized attribute object and both the pointers + // are valid for writing. + assert_eq!( + unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackptr, &mut size) }, + 0 + ); + + let stackaddr = stackptr.addr(); + ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) { + Some(stackaddr - guardsize..stackaddr) + } else if cfg!(all(target_os = "linux", target_env = "musl")) { + Some(stackaddr - guardsize..stackaddr) + } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc"))) + { + // glibc used to include the guard area within the stack, as noted in the BUGS + // section of `man pthread_attr_getguardsize`. This has been corrected starting + // with glibc 2.27, and in some distro backports, so the guard is now placed at the + // end (below) the stack. There's no easy way for us to know which we have at + // runtime, so we'll just match any fault in the range right above or below the + // stack base to call that fault a stack overflow. + Some(stackaddr - guardsize..stackaddr + guardsize) + } else { + Some(stackaddr..stackaddr + guardsize) + }; + } + if e == 0 || cfg!(target_os = "freebsd") { + // SAFETY: + // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or + // by `pthread_attr_get_np`, and is not used after this point. + assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); + } + ret + } +} + +// This is intentionally not enabled on iOS/tvOS/watchOS/visionOS, as it uses +// several symbols that might lead to rejections from the App Store, namely +// `sigaction`, `sigaltstack`, `sysctlbyname`, `mmap`, `munmap` and `mprotect`. +// +// This might be overly cautious, though it is also what Swift does (and they +// usually have fewer qualms about forwards compatibility, since the runtime +// is shipped with the OS): +// +#[cfg(any( + miri, + not(any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + target_os = "cygwin", + )) +))] +mod imp { + pub unsafe fn init() {} + + pub fn make_handler(_main_thread: bool) -> super::Handler { + super::Handler::null() + } + + pub unsafe fn drop_handler(_data: *mut libc::c_void) {} +} + +#[cfg(target_os = "cygwin")] +mod imp { + mod c { + pub type PVECTORED_EXCEPTION_HANDLER = + Option i32>; + pub type NTSTATUS = i32; + pub type BOOL = i32; + + unsafe extern "system" { + pub fn AddVectoredExceptionHandler( + first: u32, + handler: PVECTORED_EXCEPTION_HANDLER, + ) -> *mut core::ffi::c_void; + pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL; + } + + pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _; + pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32; + + #[repr(C)] + #[derive(Clone, Copy)] + pub struct EXCEPTION_POINTERS { + pub ExceptionRecord: *mut EXCEPTION_RECORD, + // We don't need this field here + // pub Context: *mut CONTEXT, + } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct EXCEPTION_RECORD { + pub ExceptionCode: NTSTATUS, + pub ExceptionFlags: u32, + pub ExceptionRecord: *mut EXCEPTION_RECORD, + pub ExceptionAddress: *mut core::ffi::c_void, + pub NumberParameters: u32, + pub ExceptionInformation: [usize; 15], + } + } + + /// Reserve stack space for use in stack overflow exceptions. + fn reserve_stack() { + let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) }; + // Reserving stack space is not critical so we allow it to fail in the released build of libstd. + // We still use debug assert here so that CI will test that we haven't made a mistake calling the function. + debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling"); + } + + unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 { + // SAFETY: It's up to the caller (which in this case is the OS) to ensure that `ExceptionInfo` is valid. + unsafe { + let rec = &(*(*ExceptionInfo).ExceptionRecord); + let code = rec.ExceptionCode; + + if code == c::EXCEPTION_STACK_OVERFLOW { + crate::thread::with_current_name(|name| { + let name = name.unwrap_or(""); + let tid = crate::thread::current_os_id(); + rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); + }); + } + c::EXCEPTION_CONTINUE_SEARCH + } + } + + pub unsafe fn init() { + // SAFETY: `vectored_handler` has the correct ABI and is safe to call during exception handling. + unsafe { + let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler)); + // Similar to the above, adding the stack overflow handler is allowed to fail + // but a debug assert is used so CI will still test that it normally works. + debug_assert!(!result.is_null(), "failed to install exception handler"); + } + // Set the thread stack guarantee for the main thread. + reserve_stack(); + } + + pub fn make_handler(main_thread: bool) -> super::Handler { + if !main_thread { + reserve_stack(); + } + super::Handler::null() + } + + pub unsafe fn drop_handler(_data: *mut libc::c_void) {} +} diff --git a/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs b/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs new file mode 100644 index 0000000000000..5604e3e6dbf42 --- /dev/null +++ b/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs @@ -0,0 +1,844 @@ +#![cfg_attr(test, allow(dead_code))] +#![forbid(unsafe_op_in_unsafe_fn)] + +pub use self::imp::init; +use self::imp::{drop_handler, make_handler}; + +pub struct Handler { + data: *mut libc::c_void, +} + +impl Handler { + pub unsafe fn new() -> Handler { + make_handler(false) + } + + fn null() -> Handler { + Handler { data: crate::ptr::null_mut() } + } +} + +impl Drop for Handler { + fn drop(&mut self) { + unsafe { + drop_handler(self.data); + } + } +} + +#[cfg(all( + not(miri), + any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ), +))] +mod thread_info; + +// miri doesn't model signals nor stack overflows and this code has some +// synchronization properties that we don't want to expose to user code, +// hence we disable it on miri. +#[cfg(all( + not(miri), + any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ) +))] +mod imp { + use libc::{ + MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK, + SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t, + }; + #[cfg(not(all(target_os = "linux", target_env = "gnu")))] + use libc::{mmap as mmap64, mprotect, munmap}; + #[cfg(all(target_os = "linux", target_env = "gnu"))] + use libc::{mmap64, mprotect, munmap}; + + use super::Handler; + use super::thread_info::{delete_current_info, set_current_info, with_current_info}; + use crate::ops::Range; + use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering}; + use crate::sys::pal::unix::conf; + use crate::{io, mem, ptr}; + + /// Signal handler for the SIGSEGV and SIGBUS handlers. + /// + /// We've got guard pages (unmapped pages) at the end of every thread's + /// stack, so if a thread ends up running into the guard page it'll trigger + /// this handler. We want to detect these cases and print out a helpful error + /// saying that the stack has overflowed. All other signals, however, should + /// go back to what they were originally supposed to do. + /// + /// This handler currently exists purely to print an informative message + /// whenever a thread overflows its stack. We then abort to exit and + /// indicate a crash, but to avoid a misleading SIGSEGV that might lead + /// users to believe that unsafe code has accessed an invalid pointer; the + /// SIGSEGV encountered when overflowing the stack is expected and + /// well-defined. + /// + /// If this is not a stack overflow, the handler un-registers itself and + /// then returns (to allow the original signal to be delivered again). + /// Returning from this kind of signal handler is technically not defined + /// to work when reading the POSIX spec strictly, but in practice it turns + /// out many large systems and all implementations allow returning from a + /// signal handler to work. For a more detailed explanation see the + /// comments on #26458. + /// + /// # Safety + /// Rust doesn't call this, it *gets called* by the kernel, which we expect + /// to provide valid parameters. Apart from that, this function does not + /// have any other preconditions. + unsafe extern "C" fn signal_handler( + signum: libc::c_int, + info: *mut libc::siginfo_t, + _data: *mut libc::c_void, + ) { + // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`. + let fault_addr = unsafe { (*info).si_addr().addr() }; + + // `with_current_info` expects that the process aborts after it is + // called. If the signal was not caused by a memory access, this might + // not be true. We detect this by noticing that the `si_addr` field is + // zero if the signal is synthetic. + if fault_addr != 0 { + with_current_info(|thread_info| { + // If the faulting address is within the guard page, then we print a + // message saying so and abort. + if let Some(thread_info) = thread_info + && thread_info.guard_page_range.contains(&fault_addr) + { + // Hey you! Yes, you modifying the stack overflow message! + // Please make sure that all functions called here are + // actually async-signal-safe. If they're not, try retrieving + // the information beforehand and storing it in `ThreadInfo`. + // Thank you! + // - says Jonas after having had to watch his carefully + // written code get made unsound again. + let tid = thread_info.tid; + let name = thread_info.name.as_deref().unwrap_or(""); + rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); + rtabort!("stack overflow"); + } + }) + } + + // Unregister ourselves by reverting back to the default behavior. + // SAFETY: assuming all platforms define struct sigaction as "zero-initializable" + let mut action: sigaction = unsafe { mem::zeroed() }; + action.sa_sigaction = SIG_DFL; + // SAFETY: pray this is a well-behaved POSIX implementation of fn sigaction + unsafe { sigaction(signum, &action, ptr::null_mut()) }; + + // See comment above for why this function returns. + } + + static PAGE_SIZE: Atomic = AtomicUsize::new(0); + // Store a pointer to the allocation for the main thread's altstack so that + // tools like valgrind don't complain about a leaked unreachable allocation. + // + // If the main thread exits, the process will terminate so there's no use in + // freeing resources. It also means that the altstack is still installed + // while TLS destructors are run on the main thread (c.f. #111272). + static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut()); + static NEED_ALTSTACK: Atomic = AtomicBool::new(false); + + /// # Safety + /// Must be called only once, on the main thread, during program startup. + pub unsafe fn init() { + PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed); + + // SAFETY: + // This is only called on the main thread, and since it is still early + // in the programs lifetime there is (almost) certainly enough stack + // space left to install the guard page. + let mut guard_page_range = unsafe { install_main_guard() }; + + // Even for panic=immediate-abort, installing the guard pages is important for soundness. + // That said, we do not care about giving nice stackoverflow messages via our custom + // signal handler, just exit early and let the user enjoy the segfault. + if cfg!(panic = "immediate-abort") { + return; + } + + // SAFETY: C structures are always zero-initializable. + let mut action: sigaction = unsafe { mem::zeroed() }; + for &signal in &[SIGSEGV, SIGBUS] { + // SAFETY: just fetches the current signal handler into action + unsafe { sigaction(signal, ptr::null_mut(), &mut action) }; + // We assume that overriding the signal handler is always safe, + // which might conflict with certain libraries that rely on a + // specific signal behaviour. To prevent problems, we only + // override the handler if it has not been set yet. + if action.sa_sigaction == SIG_DFL { + if !NEED_ALTSTACK.load(Ordering::Relaxed) { + // haven't set up our sigaltstack yet + NEED_ALTSTACK.store(true, Ordering::Release); + let handler = make_handler(true); + MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed); + mem::forget(handler); + + if let Some(guard_page_range) = guard_page_range.take() { + set_current_info(guard_page_range); + } + } + + action.sa_flags = SA_SIGINFO | SA_ONSTACK; + action.sa_sigaction = signal_handler + as unsafe extern "C" fn(i32, *mut libc::siginfo_t, *mut libc::c_void) + as sighandler_t; + // SAFETY: + // `&action` describes a valid `sigaction` and `signal_handler` + // is safe to use as a signal handler for `SIGSEGV` and `SIGBUS`. + unsafe { sigaction(signal, &action, ptr::null_mut()) }; + } + } + } + + fn get_stack() -> libc::stack_t { + // OpenBSD requires this flag for stack mapping + // otherwise the said mapping will fail as a no-op on most systems + // and has a different meaning on FreeBSD + #[cfg(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "linux", + target_os = "dragonfly", + ))] + let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK; + #[cfg(not(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "linux", + target_os = "dragonfly", + )))] + let flags = MAP_PRIVATE | MAP_ANON; + + let sigstack_size = sigstack_size(); + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + + // SAFETY: this does not unmap any existing pages. + let stackp = unsafe { + mmap64(ptr::null_mut(), sigstack_size + page_size, PROT_READ | PROT_WRITE, flags, -1, 0) + }; + if stackp == MAP_FAILED { + panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error()); + } + // SAFETY: this only affects the memory we just allocated. + let guard_result = unsafe { libc::mprotect(stackp, page_size, PROT_NONE) }; + if guard_result != 0 { + panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error()); + } + // SAFETY: + // The region was allocated with a larger size than `page_size`, so this + // addition is within bounds. + let stackp = unsafe { stackp.add(page_size) }; + + libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size } + } + + pub fn make_handler(main_thread: bool) -> Handler { + if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) { + return Handler::null(); + } + + if !main_thread { + if let Some(guard_page_range) = current_guard() { + set_current_info(guard_page_range); + } + } + + // Load the current alternate signal stack to see if we need to install + // our own. + // + // SAFETY: C structures are always zero-initializable. + let mut stack = unsafe { mem::zeroed() }; + // SAFETY: `&mut stack` is valid for writing a `stack_t`. + unsafe { sigaltstack(ptr::null(), &mut stack) }; + + // Configure alternate signal stack, if one is not already set. + if stack.ss_flags & SS_DISABLE != 0 { + let stack = get_stack(); + // SAFETY: + // `stack_t` is a freshly allocated stack that's not used anywhere + // else. It contains a guard page, so stack overflows in signal + // handlers will not cause undefined behaviour. We must make the + // fundamental runtime assumption that it is safe to install an + // alternate signal stack if there is none currently installed. + // This might conflict with foreign libraries that use the existence + // of an alternate signal stack as indication that certain runtime + // initialisation by the library has been performed (e.g. old + // versions of `std` assumed that certain thread-locals were already + // accessed and thus initialized in the thread if the stack overflow + // signal was successfully delivered). Such assumptions in other + // libraries are fundamentally flawed, so we pay no regard to them. + unsafe { sigaltstack(&stack, ptr::null_mut()) }; + Handler { data: stack.ss_sp as *mut libc::c_void } + } else { + Handler::null() + } + } + + /// # Safety + /// Must only be called with a pointer returned by `make_handler`, and only + /// once per `Handler`. + pub unsafe fn drop_handler(data: *mut libc::c_void) { + if !data.is_null() { + let sigstack_size = sigstack_size(); + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + let disabling_stack = libc::stack_t { + ss_sp: ptr::null_mut(), + ss_flags: SS_DISABLE, + // Workaround for bug in macOS implementation of sigaltstack + // UNIX2003 which returns ENOMEM when disabling a stack while + // passing ss_size smaller than MINSIGSTKSZ. According to POSIX + // both ss_sp and ss_size should be ignored in this case. + ss_size: sigstack_size, + }; + // SAFETY: + // We assume that disabling the alternate signal stack is always + // sound, even if the current alternate signal stack is not the one + // we installed in `make_handler`. Any stack overflows from this + // point on will abort the program when the kernel tries to write + // the signal information to the guard page. + // + // FIXME: detect if the stack has changed, and only uninstall if it hasn't. + unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) }; + // The stack returned by `get_stack` is part of a mapping that + // started one page earlier, so walk back a page and unmap from + // there. + // + // SAFETY: + // This allocation was created by us in `get_stack` and, as the + // alternate signal stack is now disabled, is no longer in use. + unsafe { munmap(data.sub(page_size), sigstack_size + page_size) }; + } + + delete_current_info(); + } + + /// Modern kernels on modern hardware can have dynamic signal stack sizes. + #[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] + fn sigstack_size() -> usize { + // SAFETY: `getauxval` is always safe to call. + let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) }; + // If getauxval couldn't find the entry, it returns 0, + // so take the higher of the "constant" and auxval. + // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ + libc::SIGSTKSZ.max(dynamic_sigstksz as _) + } + + /// Not all OS support hardware where this is needed. + #[cfg(not(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc"))))] + fn sigstack_size() -> usize { + libc::SIGSTKSZ + } + + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: C types are always zero-initializable. + let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; + // SAFETY: + // The pointer is valid for writing a `stack_t`. + assert_eq!(unsafe { libc::stack_getbounds(&mut current_stack) }, 0); + Some(current_stack.ss_sp) + } + + #[cfg(target_os = "macos")] + fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: always safe to call. + let th = unsafe { libc::pthread_self() }; + // SAFETY: `th` is a valid `pthread_t`. + unsafe { + let stackptr = libc::pthread_get_stackaddr_np(th); + let stacksize = libc::pthread_get_stacksize_np(th); + Some(stackptr.map_addr(|addr| addr - stacksize)) + } + } + + #[cfg(target_os = "openbsd")] + fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: C types are always zero-initializable. + let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t`. + // * `&mut current_stack` is coerced to a pointer that is valid for writing + // a `stack_t`. + assert_eq!( + unsafe { libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack) }, + 0 + ); + + let stack_ptr = current_stack.ss_sp; + // SAFETY: this is always safe to call. + let stackaddr = if unsafe { libc::pthread_main_np() } == 1 { + // main thread + stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed) + } else { + // new thread + stack_ptr.addr() - current_stack.ss_size + }; + Some(stack_ptr.with_addr(stackaddr)) + } + + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", + target_os = "hurd", + target_os = "linux", + target_os = "l4re" + ))] + fn get_stack_start() -> Option<*mut libc::c_void> { + use crate::pin::pin; + use crate::sys::helpers::COpaque; + + let mut ret = None; + let mut attr: COpaque = COpaque::uninit(); + if !cfg!(target_os = "freebsd") { + attr = COpaque::zeroed(); + } + let attr = pin!(attr); + // FIXME(pin-ergonomics): remove the next line. + let attr = attr.into_ref(); + + // SAFETY: + // The attributes object has not been initialized yet and will not be + // moved until destroyed. + #[cfg(target_os = "freebsd")] + assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` is an initialized attribute object that can be written to. + #[cfg(target_os = "freebsd")] + let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` can be written to, and will be initialized by this call. + #[cfg(not(target_os = "freebsd"))] + let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; + if e == 0 { + let mut stackaddr = crate::ptr::null_mut(); + let mut stacksize = 0; + // SAFETY: + // `attr` is an initialized attribute object and both the pointers + // are valid for writing. + assert_eq!( + unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackaddr, &mut stacksize) }, + 0 + ); + ret = Some(stackaddr); + } + if e == 0 || cfg!(target_os = "freebsd") { + // SAFETY: + // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or + // by `pthread_attr_get_np`, and is not used after this point. + assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); + } + ret + } + + fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> { + let stackptr = get_stack_start()?; + let stackaddr = stackptr.addr(); + + // Ensure stackaddr is page aligned! A parent process might + // have reset RLIMIT_STACK to be non-page aligned. The + // pthread_attr_getstack() reports the usable stack area + // stackaddr < stackaddr + stacksize, so if stackaddr is not + // page-aligned, calculate the fix such that stackaddr < + // new_page_aligned_stackaddr < stackaddr + stacksize + let remainder = stackaddr % page_size; + Some(if remainder == 0 { + stackptr + } else { + stackptr.with_addr(stackaddr + page_size - remainder) + }) + } + + /// # Safety + /// This function must only be called from the main thread, and there must + /// be sufficient stack space remaining to place a stack guard. + unsafe fn install_main_guard() -> Option> { + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + + // this way someone on any unix-y OS can check that all these compile + if cfg!(all(target_os = "linux", not(target_env = "musl"))) { + install_main_guard_linux(page_size) + } else if cfg!(all(target_os = "linux", target_env = "musl")) { + install_main_guard_linux_musl(page_size) + } else if cfg!(target_os = "freebsd") { + #[cfg(not(target_os = "freebsd"))] + return None; + // The FreeBSD code cannot be checked on non-BSDs. + #[cfg(target_os = "freebsd")] + install_main_guard_freebsd(page_size) + } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) { + install_main_guard_bsds(page_size) + } else { + // SAFETY: guaranteed by caller. + unsafe { install_main_guard_default(page_size) } + } + } + + fn install_main_guard_linux(page_size: usize) -> Option> { + // See the corresponding conditional in init(). + // Avoid stack_start_aligned, which makes slow syscalls to read /proc/self/maps + if cfg!(panic = "immediate-abort") { + return None; + } + // Linux doesn't allocate the whole stack right away, and + // the kernel has its own stack-guard mechanism to fault + // when growing too close to an existing mapping. If we map + // our own guard, then the kernel starts enforcing a rather + // large gap above that, rendering much of the possible + // stack space useless. See #43052. + // + // Instead, we'll just note where we expect rlimit to start + // faulting, so our handler can report "stack overflow", and + // trust that the kernel's own stack guard will work. + let stackptr = stack_start_aligned(page_size)?; + let stackaddr = stackptr.addr(); + Some(stackaddr - page_size..stackaddr) + } + + fn install_main_guard_linux_musl(_page_size: usize) -> Option> { + // For the main thread, the musl's pthread_attr_getstack + // returns the current stack size, rather than maximum size + // it can eventually grow to. It cannot be used to determine + // the position of kernel's stack guard. + None + } + + #[cfg(target_os = "freebsd")] + fn install_main_guard_freebsd(page_size: usize) -> Option> { + // See the corresponding conditional in install_main_guard_linux(). + if cfg!(panic = "immediate-abort") { + return None; + } + // FreeBSD's stack autogrows, and optionally includes a guard page + // at the bottom. If we try to remap the bottom of the stack + // ourselves, FreeBSD's guard page moves upwards. So we'll just use + // the builtin guard page. + let stackptr = stack_start_aligned(page_size)?; + let guardaddr = stackptr.addr(); + // Technically the number of guard pages is tunable and controlled + // by the security.bsd.stack_guard_page sysctl. + // By default it is 1, checking once is enough since it is + // a boot time config value. + // FIXME(joboet): this function is only called once, remove the caching. + static PAGES: crate::sync::OnceLock = crate::sync::OnceLock::new(); + + let pages = PAGES.get_or_init(|| { + let mut guard: usize = 0; + let mut size = size_of_val(&guard); + let oid = c"security.bsd.stack_guard_page"; + + let r = unsafe { + libc::sysctlbyname( + oid.as_ptr(), + (&raw mut guard).cast(), + &raw mut size, + ptr::null_mut(), + 0, + ) + }; + if r == 0 { guard } else { 1 } + }); + Some(guardaddr..guardaddr + pages * page_size) + } + + fn install_main_guard_bsds(page_size: usize) -> Option> { + // See the corresponding conditional in install_main_guard_linux(). + if cfg!(panic = "immediate-abort") { + return None; + } + // OpenBSD stack already includes a guard page, and stack is + // immutable. + // NetBSD stack includes the guard page. + // + // We'll just note where we expect rlimit to start + // faulting, so our handler can report "stack overflow", and + // trust that the kernel's own stack guard will work. + let stackptr = stack_start_aligned(page_size)?; + let stackaddr = stackptr.addr(); + Some(stackaddr - page_size..stackaddr) + } + + /// # Safety + /// This function must only be called from the main thread, and there must + /// be sufficient stack space remaining to place a stack guard. + unsafe fn install_main_guard_default(page_size: usize) -> Option> { + // Reallocate the last page of the stack. + // This ensures SIGBUS will be raised on + // stack overflow. + // Systems which enforce strict PAX MPROTECT do not allow + // to mprotect() a mapping with less restrictive permissions + // than the initial mmap() used, so we mmap() here with + // read/write permissions and only then mprotect() it to + // no permissions at all. See issue #50313. + let stackptr = stack_start_aligned(page_size)?; + // SAFETY: + // The memory region from `stackptr..stackptr + page_size` belongs to + // the current thread's stack, and the caller has asserted that there + // is sufficient stack space, which means that this will not overwrite + // any existing allocations. + let result = unsafe { + mmap64( + stackptr, + page_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, + -1, + 0, + ) + }; + if result != stackptr || result == MAP_FAILED { + panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); + } + + // SAFETY: + // Since this function is only called on the main thread, the stack will + // not be reused until program exit, so the runtime will never observe + // that part of the stack has been made unusable in this way. + let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) }; + if result != 0 { + panic!("failed to protect the guard page: {}", io::Error::last_os_error()); + } + + let guardaddr = stackptr.addr(); + + Some(guardaddr..guardaddr + page_size) + } + + #[cfg(any( + target_os = "macos", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ))] + fn current_guard() -> Option> { + let stackptr = get_stack_start()?; + let stackaddr = stackptr.addr(); + Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr) + } + + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "hurd", + target_os = "linux", + target_os = "netbsd", + target_os = "l4re" + ))] + fn current_guard() -> Option> { + use crate::pin::pin; + use crate::sys::helpers::COpaque; + + let mut ret = None; + + let mut attr: COpaque = COpaque::uninit(); + if !cfg!(target_os = "freebsd") { + attr = COpaque::zeroed(); + } + let attr = pin!(attr); + // FIXME(pin-ergonomics): remove the next line. + let attr = attr.into_ref(); + + // SAFETY: + // The attributes object has not been initialized yet and will not be + // moved until destroyed. + #[cfg(target_os = "freebsd")] + assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` is an initialized attribute object that can be written to. + #[cfg(target_os = "freebsd")] + let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` can be written to, and will be initialized by this call. + #[cfg(not(target_os = "freebsd"))] + let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; + if e == 0 { + let mut guardsize = 0; + // SAFETY: + // `attr` is an initialized attribute object and the pointer is valid + // for writing. + assert_eq!(unsafe { libc::pthread_attr_getguardsize(attr.get(), &mut guardsize) }, 0); + if guardsize == 0 { + if cfg!(all(target_os = "linux", target_env = "musl")) { + // musl versions before 1.1.19 always reported guard + // size obtained from pthread_attr_get_np as zero. + // Use page size as a fallback. + guardsize = PAGE_SIZE.load(Ordering::Relaxed); + } else { + panic!("there is no guard page"); + } + } + let mut stackptr = crate::ptr::null_mut::(); + let mut size = 0; + // SAFETY: + // `attr` is an initialized attribute object and both the pointers + // are valid for writing. + assert_eq!( + unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackptr, &mut size) }, + 0 + ); + + let stackaddr = stackptr.addr(); + ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) { + Some(stackaddr - guardsize..stackaddr) + } else if cfg!(all(target_os = "linux", target_env = "musl")) { + Some(stackaddr - guardsize..stackaddr) + } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc"))) + { + // glibc used to include the guard area within the stack, as noted in the BUGS + // section of `man pthread_attr_getguardsize`. This has been corrected starting + // with glibc 2.27, and in some distro backports, so the guard is now placed at the + // end (below) the stack. There's no easy way for us to know which we have at + // runtime, so we'll just match any fault in the range right above or below the + // stack base to call that fault a stack overflow. + Some(stackaddr - guardsize..stackaddr + guardsize) + } else { + Some(stackaddr..stackaddr + guardsize) + }; + } + if e == 0 || cfg!(target_os = "freebsd") { + // SAFETY: + // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or + // by `pthread_attr_get_np`, and is not used after this point. + assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); + } + ret + } +} + +// This is intentionally not enabled on iOS/tvOS/watchOS/visionOS, as it uses +// several symbols that might lead to rejections from the App Store, namely +// `sigaction`, `sigaltstack`, `sysctlbyname`, `mmap`, `munmap` and `mprotect`. +// +// This might be overly cautious, though it is also what Swift does (and they +// usually have fewer qualms about forwards compatibility, since the runtime +// is shipped with the OS): +// +#[cfg(any( + miri, + not(any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + target_os = "cygwin", + )) +))] +mod imp { + pub unsafe fn init() {} + + pub fn make_handler(_main_thread: bool) -> super::Handler { + super::Handler::null() + } + + pub unsafe fn drop_handler(_data: *mut libc::c_void) {} +} + +#[cfg(target_os = "cygwin")] +mod imp { + mod c { + pub type PVECTORED_EXCEPTION_HANDLER = + Option i32>; + pub type NTSTATUS = i32; + pub type BOOL = i32; + + unsafe extern "system" { + pub fn AddVectoredExceptionHandler( + first: u32, + handler: PVECTORED_EXCEPTION_HANDLER, + ) -> *mut core::ffi::c_void; + pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL; + } + + pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _; + pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32; + + #[repr(C)] + #[derive(Clone, Copy)] + pub struct EXCEPTION_POINTERS { + pub ExceptionRecord: *mut EXCEPTION_RECORD, + // We don't need this field here + // pub Context: *mut CONTEXT, + } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct EXCEPTION_RECORD { + pub ExceptionCode: NTSTATUS, + pub ExceptionFlags: u32, + pub ExceptionRecord: *mut EXCEPTION_RECORD, + pub ExceptionAddress: *mut core::ffi::c_void, + pub NumberParameters: u32, + pub ExceptionInformation: [usize; 15], + } + } + + /// Reserve stack space for use in stack overflow exceptions. + fn reserve_stack() { + let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) }; + // Reserving stack space is not critical so we allow it to fail in the released build of libstd. + // We still use debug assert here so that CI will test that we haven't made a mistake calling the function. + debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling"); + } + + unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 { + // SAFETY: It's up to the caller (which in this case is the OS) to ensure that `ExceptionInfo` is valid. + unsafe { + let rec = &(*(*ExceptionInfo).ExceptionRecord); + let code = rec.ExceptionCode; + + if code == c::EXCEPTION_STACK_OVERFLOW { + crate::thread::with_current_name(|name| { + let name = name.unwrap_or(""); + let tid = crate::thread::current_os_id(); + rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); + }); + } + c::EXCEPTION_CONTINUE_SEARCH + } + } + + pub unsafe fn init() { + // SAFETY: `vectored_handler` has the correct ABI and is safe to call during exception handling. + unsafe { + let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler)); + // Similar to the above, adding the stack overflow handler is allowed to fail + // but a debug assert is used so CI will still test that it normally works. + debug_assert!(!result.is_null(), "failed to install exception handler"); + } + // Set the thread stack guarantee for the main thread. + reserve_stack(); + } + + pub fn make_handler(main_thread: bool) -> super::Handler { + if !main_thread { + reserve_stack(); + } + super::Handler::null() + } + + pub unsafe fn drop_handler(_data: *mut libc::c_void) {} +} diff --git a/library/std/src/sys/pal/unix/stack_overflow/mod.rs b/library/std/src/sys/pal/unix/stack_overflow/mod.rs new file mode 100644 index 0000000000000..5604e3e6dbf42 --- /dev/null +++ b/library/std/src/sys/pal/unix/stack_overflow/mod.rs @@ -0,0 +1,844 @@ +#![cfg_attr(test, allow(dead_code))] +#![forbid(unsafe_op_in_unsafe_fn)] + +pub use self::imp::init; +use self::imp::{drop_handler, make_handler}; + +pub struct Handler { + data: *mut libc::c_void, +} + +impl Handler { + pub unsafe fn new() -> Handler { + make_handler(false) + } + + fn null() -> Handler { + Handler { data: crate::ptr::null_mut() } + } +} + +impl Drop for Handler { + fn drop(&mut self) { + unsafe { + drop_handler(self.data); + } + } +} + +#[cfg(all( + not(miri), + any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ), +))] +mod thread_info; + +// miri doesn't model signals nor stack overflows and this code has some +// synchronization properties that we don't want to expose to user code, +// hence we disable it on miri. +#[cfg(all( + not(miri), + any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ) +))] +mod imp { + use libc::{ + MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK, + SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t, + }; + #[cfg(not(all(target_os = "linux", target_env = "gnu")))] + use libc::{mmap as mmap64, mprotect, munmap}; + #[cfg(all(target_os = "linux", target_env = "gnu"))] + use libc::{mmap64, mprotect, munmap}; + + use super::Handler; + use super::thread_info::{delete_current_info, set_current_info, with_current_info}; + use crate::ops::Range; + use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering}; + use crate::sys::pal::unix::conf; + use crate::{io, mem, ptr}; + + /// Signal handler for the SIGSEGV and SIGBUS handlers. + /// + /// We've got guard pages (unmapped pages) at the end of every thread's + /// stack, so if a thread ends up running into the guard page it'll trigger + /// this handler. We want to detect these cases and print out a helpful error + /// saying that the stack has overflowed. All other signals, however, should + /// go back to what they were originally supposed to do. + /// + /// This handler currently exists purely to print an informative message + /// whenever a thread overflows its stack. We then abort to exit and + /// indicate a crash, but to avoid a misleading SIGSEGV that might lead + /// users to believe that unsafe code has accessed an invalid pointer; the + /// SIGSEGV encountered when overflowing the stack is expected and + /// well-defined. + /// + /// If this is not a stack overflow, the handler un-registers itself and + /// then returns (to allow the original signal to be delivered again). + /// Returning from this kind of signal handler is technically not defined + /// to work when reading the POSIX spec strictly, but in practice it turns + /// out many large systems and all implementations allow returning from a + /// signal handler to work. For a more detailed explanation see the + /// comments on #26458. + /// + /// # Safety + /// Rust doesn't call this, it *gets called* by the kernel, which we expect + /// to provide valid parameters. Apart from that, this function does not + /// have any other preconditions. + unsafe extern "C" fn signal_handler( + signum: libc::c_int, + info: *mut libc::siginfo_t, + _data: *mut libc::c_void, + ) { + // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`. + let fault_addr = unsafe { (*info).si_addr().addr() }; + + // `with_current_info` expects that the process aborts after it is + // called. If the signal was not caused by a memory access, this might + // not be true. We detect this by noticing that the `si_addr` field is + // zero if the signal is synthetic. + if fault_addr != 0 { + with_current_info(|thread_info| { + // If the faulting address is within the guard page, then we print a + // message saying so and abort. + if let Some(thread_info) = thread_info + && thread_info.guard_page_range.contains(&fault_addr) + { + // Hey you! Yes, you modifying the stack overflow message! + // Please make sure that all functions called here are + // actually async-signal-safe. If they're not, try retrieving + // the information beforehand and storing it in `ThreadInfo`. + // Thank you! + // - says Jonas after having had to watch his carefully + // written code get made unsound again. + let tid = thread_info.tid; + let name = thread_info.name.as_deref().unwrap_or(""); + rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); + rtabort!("stack overflow"); + } + }) + } + + // Unregister ourselves by reverting back to the default behavior. + // SAFETY: assuming all platforms define struct sigaction as "zero-initializable" + let mut action: sigaction = unsafe { mem::zeroed() }; + action.sa_sigaction = SIG_DFL; + // SAFETY: pray this is a well-behaved POSIX implementation of fn sigaction + unsafe { sigaction(signum, &action, ptr::null_mut()) }; + + // See comment above for why this function returns. + } + + static PAGE_SIZE: Atomic = AtomicUsize::new(0); + // Store a pointer to the allocation for the main thread's altstack so that + // tools like valgrind don't complain about a leaked unreachable allocation. + // + // If the main thread exits, the process will terminate so there's no use in + // freeing resources. It also means that the altstack is still installed + // while TLS destructors are run on the main thread (c.f. #111272). + static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut()); + static NEED_ALTSTACK: Atomic = AtomicBool::new(false); + + /// # Safety + /// Must be called only once, on the main thread, during program startup. + pub unsafe fn init() { + PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed); + + // SAFETY: + // This is only called on the main thread, and since it is still early + // in the programs lifetime there is (almost) certainly enough stack + // space left to install the guard page. + let mut guard_page_range = unsafe { install_main_guard() }; + + // Even for panic=immediate-abort, installing the guard pages is important for soundness. + // That said, we do not care about giving nice stackoverflow messages via our custom + // signal handler, just exit early and let the user enjoy the segfault. + if cfg!(panic = "immediate-abort") { + return; + } + + // SAFETY: C structures are always zero-initializable. + let mut action: sigaction = unsafe { mem::zeroed() }; + for &signal in &[SIGSEGV, SIGBUS] { + // SAFETY: just fetches the current signal handler into action + unsafe { sigaction(signal, ptr::null_mut(), &mut action) }; + // We assume that overriding the signal handler is always safe, + // which might conflict with certain libraries that rely on a + // specific signal behaviour. To prevent problems, we only + // override the handler if it has not been set yet. + if action.sa_sigaction == SIG_DFL { + if !NEED_ALTSTACK.load(Ordering::Relaxed) { + // haven't set up our sigaltstack yet + NEED_ALTSTACK.store(true, Ordering::Release); + let handler = make_handler(true); + MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed); + mem::forget(handler); + + if let Some(guard_page_range) = guard_page_range.take() { + set_current_info(guard_page_range); + } + } + + action.sa_flags = SA_SIGINFO | SA_ONSTACK; + action.sa_sigaction = signal_handler + as unsafe extern "C" fn(i32, *mut libc::siginfo_t, *mut libc::c_void) + as sighandler_t; + // SAFETY: + // `&action` describes a valid `sigaction` and `signal_handler` + // is safe to use as a signal handler for `SIGSEGV` and `SIGBUS`. + unsafe { sigaction(signal, &action, ptr::null_mut()) }; + } + } + } + + fn get_stack() -> libc::stack_t { + // OpenBSD requires this flag for stack mapping + // otherwise the said mapping will fail as a no-op on most systems + // and has a different meaning on FreeBSD + #[cfg(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "linux", + target_os = "dragonfly", + ))] + let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK; + #[cfg(not(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "linux", + target_os = "dragonfly", + )))] + let flags = MAP_PRIVATE | MAP_ANON; + + let sigstack_size = sigstack_size(); + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + + // SAFETY: this does not unmap any existing pages. + let stackp = unsafe { + mmap64(ptr::null_mut(), sigstack_size + page_size, PROT_READ | PROT_WRITE, flags, -1, 0) + }; + if stackp == MAP_FAILED { + panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error()); + } + // SAFETY: this only affects the memory we just allocated. + let guard_result = unsafe { libc::mprotect(stackp, page_size, PROT_NONE) }; + if guard_result != 0 { + panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error()); + } + // SAFETY: + // The region was allocated with a larger size than `page_size`, so this + // addition is within bounds. + let stackp = unsafe { stackp.add(page_size) }; + + libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size } + } + + pub fn make_handler(main_thread: bool) -> Handler { + if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) { + return Handler::null(); + } + + if !main_thread { + if let Some(guard_page_range) = current_guard() { + set_current_info(guard_page_range); + } + } + + // Load the current alternate signal stack to see if we need to install + // our own. + // + // SAFETY: C structures are always zero-initializable. + let mut stack = unsafe { mem::zeroed() }; + // SAFETY: `&mut stack` is valid for writing a `stack_t`. + unsafe { sigaltstack(ptr::null(), &mut stack) }; + + // Configure alternate signal stack, if one is not already set. + if stack.ss_flags & SS_DISABLE != 0 { + let stack = get_stack(); + // SAFETY: + // `stack_t` is a freshly allocated stack that's not used anywhere + // else. It contains a guard page, so stack overflows in signal + // handlers will not cause undefined behaviour. We must make the + // fundamental runtime assumption that it is safe to install an + // alternate signal stack if there is none currently installed. + // This might conflict with foreign libraries that use the existence + // of an alternate signal stack as indication that certain runtime + // initialisation by the library has been performed (e.g. old + // versions of `std` assumed that certain thread-locals were already + // accessed and thus initialized in the thread if the stack overflow + // signal was successfully delivered). Such assumptions in other + // libraries are fundamentally flawed, so we pay no regard to them. + unsafe { sigaltstack(&stack, ptr::null_mut()) }; + Handler { data: stack.ss_sp as *mut libc::c_void } + } else { + Handler::null() + } + } + + /// # Safety + /// Must only be called with a pointer returned by `make_handler`, and only + /// once per `Handler`. + pub unsafe fn drop_handler(data: *mut libc::c_void) { + if !data.is_null() { + let sigstack_size = sigstack_size(); + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + let disabling_stack = libc::stack_t { + ss_sp: ptr::null_mut(), + ss_flags: SS_DISABLE, + // Workaround for bug in macOS implementation of sigaltstack + // UNIX2003 which returns ENOMEM when disabling a stack while + // passing ss_size smaller than MINSIGSTKSZ. According to POSIX + // both ss_sp and ss_size should be ignored in this case. + ss_size: sigstack_size, + }; + // SAFETY: + // We assume that disabling the alternate signal stack is always + // sound, even if the current alternate signal stack is not the one + // we installed in `make_handler`. Any stack overflows from this + // point on will abort the program when the kernel tries to write + // the signal information to the guard page. + // + // FIXME: detect if the stack has changed, and only uninstall if it hasn't. + unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) }; + // The stack returned by `get_stack` is part of a mapping that + // started one page earlier, so walk back a page and unmap from + // there. + // + // SAFETY: + // This allocation was created by us in `get_stack` and, as the + // alternate signal stack is now disabled, is no longer in use. + unsafe { munmap(data.sub(page_size), sigstack_size + page_size) }; + } + + delete_current_info(); + } + + /// Modern kernels on modern hardware can have dynamic signal stack sizes. + #[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] + fn sigstack_size() -> usize { + // SAFETY: `getauxval` is always safe to call. + let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) }; + // If getauxval couldn't find the entry, it returns 0, + // so take the higher of the "constant" and auxval. + // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ + libc::SIGSTKSZ.max(dynamic_sigstksz as _) + } + + /// Not all OS support hardware where this is needed. + #[cfg(not(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc"))))] + fn sigstack_size() -> usize { + libc::SIGSTKSZ + } + + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: C types are always zero-initializable. + let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; + // SAFETY: + // The pointer is valid for writing a `stack_t`. + assert_eq!(unsafe { libc::stack_getbounds(&mut current_stack) }, 0); + Some(current_stack.ss_sp) + } + + #[cfg(target_os = "macos")] + fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: always safe to call. + let th = unsafe { libc::pthread_self() }; + // SAFETY: `th` is a valid `pthread_t`. + unsafe { + let stackptr = libc::pthread_get_stackaddr_np(th); + let stacksize = libc::pthread_get_stacksize_np(th); + Some(stackptr.map_addr(|addr| addr - stacksize)) + } + } + + #[cfg(target_os = "openbsd")] + fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: C types are always zero-initializable. + let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t`. + // * `&mut current_stack` is coerced to a pointer that is valid for writing + // a `stack_t`. + assert_eq!( + unsafe { libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack) }, + 0 + ); + + let stack_ptr = current_stack.ss_sp; + // SAFETY: this is always safe to call. + let stackaddr = if unsafe { libc::pthread_main_np() } == 1 { + // main thread + stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed) + } else { + // new thread + stack_ptr.addr() - current_stack.ss_size + }; + Some(stack_ptr.with_addr(stackaddr)) + } + + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", + target_os = "hurd", + target_os = "linux", + target_os = "l4re" + ))] + fn get_stack_start() -> Option<*mut libc::c_void> { + use crate::pin::pin; + use crate::sys::helpers::COpaque; + + let mut ret = None; + let mut attr: COpaque = COpaque::uninit(); + if !cfg!(target_os = "freebsd") { + attr = COpaque::zeroed(); + } + let attr = pin!(attr); + // FIXME(pin-ergonomics): remove the next line. + let attr = attr.into_ref(); + + // SAFETY: + // The attributes object has not been initialized yet and will not be + // moved until destroyed. + #[cfg(target_os = "freebsd")] + assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` is an initialized attribute object that can be written to. + #[cfg(target_os = "freebsd")] + let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` can be written to, and will be initialized by this call. + #[cfg(not(target_os = "freebsd"))] + let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; + if e == 0 { + let mut stackaddr = crate::ptr::null_mut(); + let mut stacksize = 0; + // SAFETY: + // `attr` is an initialized attribute object and both the pointers + // are valid for writing. + assert_eq!( + unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackaddr, &mut stacksize) }, + 0 + ); + ret = Some(stackaddr); + } + if e == 0 || cfg!(target_os = "freebsd") { + // SAFETY: + // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or + // by `pthread_attr_get_np`, and is not used after this point. + assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); + } + ret + } + + fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> { + let stackptr = get_stack_start()?; + let stackaddr = stackptr.addr(); + + // Ensure stackaddr is page aligned! A parent process might + // have reset RLIMIT_STACK to be non-page aligned. The + // pthread_attr_getstack() reports the usable stack area + // stackaddr < stackaddr + stacksize, so if stackaddr is not + // page-aligned, calculate the fix such that stackaddr < + // new_page_aligned_stackaddr < stackaddr + stacksize + let remainder = stackaddr % page_size; + Some(if remainder == 0 { + stackptr + } else { + stackptr.with_addr(stackaddr + page_size - remainder) + }) + } + + /// # Safety + /// This function must only be called from the main thread, and there must + /// be sufficient stack space remaining to place a stack guard. + unsafe fn install_main_guard() -> Option> { + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + + // this way someone on any unix-y OS can check that all these compile + if cfg!(all(target_os = "linux", not(target_env = "musl"))) { + install_main_guard_linux(page_size) + } else if cfg!(all(target_os = "linux", target_env = "musl")) { + install_main_guard_linux_musl(page_size) + } else if cfg!(target_os = "freebsd") { + #[cfg(not(target_os = "freebsd"))] + return None; + // The FreeBSD code cannot be checked on non-BSDs. + #[cfg(target_os = "freebsd")] + install_main_guard_freebsd(page_size) + } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) { + install_main_guard_bsds(page_size) + } else { + // SAFETY: guaranteed by caller. + unsafe { install_main_guard_default(page_size) } + } + } + + fn install_main_guard_linux(page_size: usize) -> Option> { + // See the corresponding conditional in init(). + // Avoid stack_start_aligned, which makes slow syscalls to read /proc/self/maps + if cfg!(panic = "immediate-abort") { + return None; + } + // Linux doesn't allocate the whole stack right away, and + // the kernel has its own stack-guard mechanism to fault + // when growing too close to an existing mapping. If we map + // our own guard, then the kernel starts enforcing a rather + // large gap above that, rendering much of the possible + // stack space useless. See #43052. + // + // Instead, we'll just note where we expect rlimit to start + // faulting, so our handler can report "stack overflow", and + // trust that the kernel's own stack guard will work. + let stackptr = stack_start_aligned(page_size)?; + let stackaddr = stackptr.addr(); + Some(stackaddr - page_size..stackaddr) + } + + fn install_main_guard_linux_musl(_page_size: usize) -> Option> { + // For the main thread, the musl's pthread_attr_getstack + // returns the current stack size, rather than maximum size + // it can eventually grow to. It cannot be used to determine + // the position of kernel's stack guard. + None + } + + #[cfg(target_os = "freebsd")] + fn install_main_guard_freebsd(page_size: usize) -> Option> { + // See the corresponding conditional in install_main_guard_linux(). + if cfg!(panic = "immediate-abort") { + return None; + } + // FreeBSD's stack autogrows, and optionally includes a guard page + // at the bottom. If we try to remap the bottom of the stack + // ourselves, FreeBSD's guard page moves upwards. So we'll just use + // the builtin guard page. + let stackptr = stack_start_aligned(page_size)?; + let guardaddr = stackptr.addr(); + // Technically the number of guard pages is tunable and controlled + // by the security.bsd.stack_guard_page sysctl. + // By default it is 1, checking once is enough since it is + // a boot time config value. + // FIXME(joboet): this function is only called once, remove the caching. + static PAGES: crate::sync::OnceLock = crate::sync::OnceLock::new(); + + let pages = PAGES.get_or_init(|| { + let mut guard: usize = 0; + let mut size = size_of_val(&guard); + let oid = c"security.bsd.stack_guard_page"; + + let r = unsafe { + libc::sysctlbyname( + oid.as_ptr(), + (&raw mut guard).cast(), + &raw mut size, + ptr::null_mut(), + 0, + ) + }; + if r == 0 { guard } else { 1 } + }); + Some(guardaddr..guardaddr + pages * page_size) + } + + fn install_main_guard_bsds(page_size: usize) -> Option> { + // See the corresponding conditional in install_main_guard_linux(). + if cfg!(panic = "immediate-abort") { + return None; + } + // OpenBSD stack already includes a guard page, and stack is + // immutable. + // NetBSD stack includes the guard page. + // + // We'll just note where we expect rlimit to start + // faulting, so our handler can report "stack overflow", and + // trust that the kernel's own stack guard will work. + let stackptr = stack_start_aligned(page_size)?; + let stackaddr = stackptr.addr(); + Some(stackaddr - page_size..stackaddr) + } + + /// # Safety + /// This function must only be called from the main thread, and there must + /// be sufficient stack space remaining to place a stack guard. + unsafe fn install_main_guard_default(page_size: usize) -> Option> { + // Reallocate the last page of the stack. + // This ensures SIGBUS will be raised on + // stack overflow. + // Systems which enforce strict PAX MPROTECT do not allow + // to mprotect() a mapping with less restrictive permissions + // than the initial mmap() used, so we mmap() here with + // read/write permissions and only then mprotect() it to + // no permissions at all. See issue #50313. + let stackptr = stack_start_aligned(page_size)?; + // SAFETY: + // The memory region from `stackptr..stackptr + page_size` belongs to + // the current thread's stack, and the caller has asserted that there + // is sufficient stack space, which means that this will not overwrite + // any existing allocations. + let result = unsafe { + mmap64( + stackptr, + page_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, + -1, + 0, + ) + }; + if result != stackptr || result == MAP_FAILED { + panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); + } + + // SAFETY: + // Since this function is only called on the main thread, the stack will + // not be reused until program exit, so the runtime will never observe + // that part of the stack has been made unusable in this way. + let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) }; + if result != 0 { + panic!("failed to protect the guard page: {}", io::Error::last_os_error()); + } + + let guardaddr = stackptr.addr(); + + Some(guardaddr..guardaddr + page_size) + } + + #[cfg(any( + target_os = "macos", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ))] + fn current_guard() -> Option> { + let stackptr = get_stack_start()?; + let stackaddr = stackptr.addr(); + Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr) + } + + #[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "hurd", + target_os = "linux", + target_os = "netbsd", + target_os = "l4re" + ))] + fn current_guard() -> Option> { + use crate::pin::pin; + use crate::sys::helpers::COpaque; + + let mut ret = None; + + let mut attr: COpaque = COpaque::uninit(); + if !cfg!(target_os = "freebsd") { + attr = COpaque::zeroed(); + } + let attr = pin!(attr); + // FIXME(pin-ergonomics): remove the next line. + let attr = attr.into_ref(); + + // SAFETY: + // The attributes object has not been initialized yet and will not be + // moved until destroyed. + #[cfg(target_os = "freebsd")] + assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` is an initialized attribute object that can be written to. + #[cfg(target_os = "freebsd")] + let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` can be written to, and will be initialized by this call. + #[cfg(not(target_os = "freebsd"))] + let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; + if e == 0 { + let mut guardsize = 0; + // SAFETY: + // `attr` is an initialized attribute object and the pointer is valid + // for writing. + assert_eq!(unsafe { libc::pthread_attr_getguardsize(attr.get(), &mut guardsize) }, 0); + if guardsize == 0 { + if cfg!(all(target_os = "linux", target_env = "musl")) { + // musl versions before 1.1.19 always reported guard + // size obtained from pthread_attr_get_np as zero. + // Use page size as a fallback. + guardsize = PAGE_SIZE.load(Ordering::Relaxed); + } else { + panic!("there is no guard page"); + } + } + let mut stackptr = crate::ptr::null_mut::(); + let mut size = 0; + // SAFETY: + // `attr` is an initialized attribute object and both the pointers + // are valid for writing. + assert_eq!( + unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackptr, &mut size) }, + 0 + ); + + let stackaddr = stackptr.addr(); + ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) { + Some(stackaddr - guardsize..stackaddr) + } else if cfg!(all(target_os = "linux", target_env = "musl")) { + Some(stackaddr - guardsize..stackaddr) + } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc"))) + { + // glibc used to include the guard area within the stack, as noted in the BUGS + // section of `man pthread_attr_getguardsize`. This has been corrected starting + // with glibc 2.27, and in some distro backports, so the guard is now placed at the + // end (below) the stack. There's no easy way for us to know which we have at + // runtime, so we'll just match any fault in the range right above or below the + // stack base to call that fault a stack overflow. + Some(stackaddr - guardsize..stackaddr + guardsize) + } else { + Some(stackaddr..stackaddr + guardsize) + }; + } + if e == 0 || cfg!(target_os = "freebsd") { + // SAFETY: + // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or + // by `pthread_attr_get_np`, and is not used after this point. + assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); + } + ret + } +} + +// This is intentionally not enabled on iOS/tvOS/watchOS/visionOS, as it uses +// several symbols that might lead to rejections from the App Store, namely +// `sigaction`, `sigaltstack`, `sysctlbyname`, `mmap`, `munmap` and `mprotect`. +// +// This might be overly cautious, though it is also what Swift does (and they +// usually have fewer qualms about forwards compatibility, since the runtime +// is shipped with the OS): +// +#[cfg(any( + miri, + not(any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + target_os = "cygwin", + )) +))] +mod imp { + pub unsafe fn init() {} + + pub fn make_handler(_main_thread: bool) -> super::Handler { + super::Handler::null() + } + + pub unsafe fn drop_handler(_data: *mut libc::c_void) {} +} + +#[cfg(target_os = "cygwin")] +mod imp { + mod c { + pub type PVECTORED_EXCEPTION_HANDLER = + Option i32>; + pub type NTSTATUS = i32; + pub type BOOL = i32; + + unsafe extern "system" { + pub fn AddVectoredExceptionHandler( + first: u32, + handler: PVECTORED_EXCEPTION_HANDLER, + ) -> *mut core::ffi::c_void; + pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL; + } + + pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _; + pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32; + + #[repr(C)] + #[derive(Clone, Copy)] + pub struct EXCEPTION_POINTERS { + pub ExceptionRecord: *mut EXCEPTION_RECORD, + // We don't need this field here + // pub Context: *mut CONTEXT, + } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct EXCEPTION_RECORD { + pub ExceptionCode: NTSTATUS, + pub ExceptionFlags: u32, + pub ExceptionRecord: *mut EXCEPTION_RECORD, + pub ExceptionAddress: *mut core::ffi::c_void, + pub NumberParameters: u32, + pub ExceptionInformation: [usize; 15], + } + } + + /// Reserve stack space for use in stack overflow exceptions. + fn reserve_stack() { + let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) }; + // Reserving stack space is not critical so we allow it to fail in the released build of libstd. + // We still use debug assert here so that CI will test that we haven't made a mistake calling the function. + debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling"); + } + + unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 { + // SAFETY: It's up to the caller (which in this case is the OS) to ensure that `ExceptionInfo` is valid. + unsafe { + let rec = &(*(*ExceptionInfo).ExceptionRecord); + let code = rec.ExceptionCode; + + if code == c::EXCEPTION_STACK_OVERFLOW { + crate::thread::with_current_name(|name| { + let name = name.unwrap_or(""); + let tid = crate::thread::current_os_id(); + rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); + }); + } + c::EXCEPTION_CONTINUE_SEARCH + } + } + + pub unsafe fn init() { + // SAFETY: `vectored_handler` has the correct ABI and is safe to call during exception handling. + unsafe { + let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler)); + // Similar to the above, adding the stack overflow handler is allowed to fail + // but a debug assert is used so CI will still test that it normally works. + debug_assert!(!result.is_null(), "failed to install exception handler"); + } + // Set the thread stack guarantee for the main thread. + reserve_stack(); + } + + pub fn make_handler(main_thread: bool) -> super::Handler { + if !main_thread { + reserve_stack(); + } + super::Handler::null() + } + + pub unsafe fn drop_handler(_data: *mut libc::c_void) {} +} From 0333a0ff1aa2b41263830a4a99a472439414b97d Mon Sep 17 00:00:00 2001 From: joboet Date: Wed, 9 Sep 2026 14:53:33 +0200 Subject: [PATCH 2/4] std: refactor UNIX stack overflow code (delete unused) --- .../sys/pal/unix/stack_overflow/guard_page.rs | 466 ---------- .../pal/unix/stack_overflow/handler_cygwin.rs | 763 ---------------- .../pal/unix/stack_overflow/handler_none.rs | 837 ------------------ .../pal/unix/stack_overflow/handler_signal.rs | 557 ------------ .../src/sys/pal/unix/stack_overflow/mod.rs | 763 +--------------- 5 files changed, 3 insertions(+), 3383 deletions(-) diff --git a/library/std/src/sys/pal/unix/stack_overflow/guard_page.rs b/library/std/src/sys/pal/unix/stack_overflow/guard_page.rs index 5604e3e6dbf42..12a87269870fc 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/guard_page.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/guard_page.rs @@ -1,351 +1,3 @@ -#![cfg_attr(test, allow(dead_code))] -#![forbid(unsafe_op_in_unsafe_fn)] - -pub use self::imp::init; -use self::imp::{drop_handler, make_handler}; - -pub struct Handler { - data: *mut libc::c_void, -} - -impl Handler { - pub unsafe fn new() -> Handler { - make_handler(false) - } - - fn null() -> Handler { - Handler { data: crate::ptr::null_mut() } - } -} - -impl Drop for Handler { - fn drop(&mut self) { - unsafe { - drop_handler(self.data); - } - } -} - -#[cfg(all( - not(miri), - any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ), -))] -mod thread_info; - -// miri doesn't model signals nor stack overflows and this code has some -// synchronization properties that we don't want to expose to user code, -// hence we disable it on miri. -#[cfg(all( - not(miri), - any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ) -))] -mod imp { - use libc::{ - MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK, - SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t, - }; - #[cfg(not(all(target_os = "linux", target_env = "gnu")))] - use libc::{mmap as mmap64, mprotect, munmap}; - #[cfg(all(target_os = "linux", target_env = "gnu"))] - use libc::{mmap64, mprotect, munmap}; - - use super::Handler; - use super::thread_info::{delete_current_info, set_current_info, with_current_info}; - use crate::ops::Range; - use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering}; - use crate::sys::pal::unix::conf; - use crate::{io, mem, ptr}; - - /// Signal handler for the SIGSEGV and SIGBUS handlers. - /// - /// We've got guard pages (unmapped pages) at the end of every thread's - /// stack, so if a thread ends up running into the guard page it'll trigger - /// this handler. We want to detect these cases and print out a helpful error - /// saying that the stack has overflowed. All other signals, however, should - /// go back to what they were originally supposed to do. - /// - /// This handler currently exists purely to print an informative message - /// whenever a thread overflows its stack. We then abort to exit and - /// indicate a crash, but to avoid a misleading SIGSEGV that might lead - /// users to believe that unsafe code has accessed an invalid pointer; the - /// SIGSEGV encountered when overflowing the stack is expected and - /// well-defined. - /// - /// If this is not a stack overflow, the handler un-registers itself and - /// then returns (to allow the original signal to be delivered again). - /// Returning from this kind of signal handler is technically not defined - /// to work when reading the POSIX spec strictly, but in practice it turns - /// out many large systems and all implementations allow returning from a - /// signal handler to work. For a more detailed explanation see the - /// comments on #26458. - /// - /// # Safety - /// Rust doesn't call this, it *gets called* by the kernel, which we expect - /// to provide valid parameters. Apart from that, this function does not - /// have any other preconditions. - unsafe extern "C" fn signal_handler( - signum: libc::c_int, - info: *mut libc::siginfo_t, - _data: *mut libc::c_void, - ) { - // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`. - let fault_addr = unsafe { (*info).si_addr().addr() }; - - // `with_current_info` expects that the process aborts after it is - // called. If the signal was not caused by a memory access, this might - // not be true. We detect this by noticing that the `si_addr` field is - // zero if the signal is synthetic. - if fault_addr != 0 { - with_current_info(|thread_info| { - // If the faulting address is within the guard page, then we print a - // message saying so and abort. - if let Some(thread_info) = thread_info - && thread_info.guard_page_range.contains(&fault_addr) - { - // Hey you! Yes, you modifying the stack overflow message! - // Please make sure that all functions called here are - // actually async-signal-safe. If they're not, try retrieving - // the information beforehand and storing it in `ThreadInfo`. - // Thank you! - // - says Jonas after having had to watch his carefully - // written code get made unsound again. - let tid = thread_info.tid; - let name = thread_info.name.as_deref().unwrap_or(""); - rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); - rtabort!("stack overflow"); - } - }) - } - - // Unregister ourselves by reverting back to the default behavior. - // SAFETY: assuming all platforms define struct sigaction as "zero-initializable" - let mut action: sigaction = unsafe { mem::zeroed() }; - action.sa_sigaction = SIG_DFL; - // SAFETY: pray this is a well-behaved POSIX implementation of fn sigaction - unsafe { sigaction(signum, &action, ptr::null_mut()) }; - - // See comment above for why this function returns. - } - - static PAGE_SIZE: Atomic = AtomicUsize::new(0); - // Store a pointer to the allocation for the main thread's altstack so that - // tools like valgrind don't complain about a leaked unreachable allocation. - // - // If the main thread exits, the process will terminate so there's no use in - // freeing resources. It also means that the altstack is still installed - // while TLS destructors are run on the main thread (c.f. #111272). - static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut()); - static NEED_ALTSTACK: Atomic = AtomicBool::new(false); - - /// # Safety - /// Must be called only once, on the main thread, during program startup. - pub unsafe fn init() { - PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed); - - // SAFETY: - // This is only called on the main thread, and since it is still early - // in the programs lifetime there is (almost) certainly enough stack - // space left to install the guard page. - let mut guard_page_range = unsafe { install_main_guard() }; - - // Even for panic=immediate-abort, installing the guard pages is important for soundness. - // That said, we do not care about giving nice stackoverflow messages via our custom - // signal handler, just exit early and let the user enjoy the segfault. - if cfg!(panic = "immediate-abort") { - return; - } - - // SAFETY: C structures are always zero-initializable. - let mut action: sigaction = unsafe { mem::zeroed() }; - for &signal in &[SIGSEGV, SIGBUS] { - // SAFETY: just fetches the current signal handler into action - unsafe { sigaction(signal, ptr::null_mut(), &mut action) }; - // We assume that overriding the signal handler is always safe, - // which might conflict with certain libraries that rely on a - // specific signal behaviour. To prevent problems, we only - // override the handler if it has not been set yet. - if action.sa_sigaction == SIG_DFL { - if !NEED_ALTSTACK.load(Ordering::Relaxed) { - // haven't set up our sigaltstack yet - NEED_ALTSTACK.store(true, Ordering::Release); - let handler = make_handler(true); - MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed); - mem::forget(handler); - - if let Some(guard_page_range) = guard_page_range.take() { - set_current_info(guard_page_range); - } - } - - action.sa_flags = SA_SIGINFO | SA_ONSTACK; - action.sa_sigaction = signal_handler - as unsafe extern "C" fn(i32, *mut libc::siginfo_t, *mut libc::c_void) - as sighandler_t; - // SAFETY: - // `&action` describes a valid `sigaction` and `signal_handler` - // is safe to use as a signal handler for `SIGSEGV` and `SIGBUS`. - unsafe { sigaction(signal, &action, ptr::null_mut()) }; - } - } - } - - fn get_stack() -> libc::stack_t { - // OpenBSD requires this flag for stack mapping - // otherwise the said mapping will fail as a no-op on most systems - // and has a different meaning on FreeBSD - #[cfg(any( - target_os = "openbsd", - target_os = "netbsd", - target_os = "linux", - target_os = "dragonfly", - ))] - let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK; - #[cfg(not(any( - target_os = "openbsd", - target_os = "netbsd", - target_os = "linux", - target_os = "dragonfly", - )))] - let flags = MAP_PRIVATE | MAP_ANON; - - let sigstack_size = sigstack_size(); - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - - // SAFETY: this does not unmap any existing pages. - let stackp = unsafe { - mmap64(ptr::null_mut(), sigstack_size + page_size, PROT_READ | PROT_WRITE, flags, -1, 0) - }; - if stackp == MAP_FAILED { - panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error()); - } - // SAFETY: this only affects the memory we just allocated. - let guard_result = unsafe { libc::mprotect(stackp, page_size, PROT_NONE) }; - if guard_result != 0 { - panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error()); - } - // SAFETY: - // The region was allocated with a larger size than `page_size`, so this - // addition is within bounds. - let stackp = unsafe { stackp.add(page_size) }; - - libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size } - } - - pub fn make_handler(main_thread: bool) -> Handler { - if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) { - return Handler::null(); - } - - if !main_thread { - if let Some(guard_page_range) = current_guard() { - set_current_info(guard_page_range); - } - } - - // Load the current alternate signal stack to see if we need to install - // our own. - // - // SAFETY: C structures are always zero-initializable. - let mut stack = unsafe { mem::zeroed() }; - // SAFETY: `&mut stack` is valid for writing a `stack_t`. - unsafe { sigaltstack(ptr::null(), &mut stack) }; - - // Configure alternate signal stack, if one is not already set. - if stack.ss_flags & SS_DISABLE != 0 { - let stack = get_stack(); - // SAFETY: - // `stack_t` is a freshly allocated stack that's not used anywhere - // else. It contains a guard page, so stack overflows in signal - // handlers will not cause undefined behaviour. We must make the - // fundamental runtime assumption that it is safe to install an - // alternate signal stack if there is none currently installed. - // This might conflict with foreign libraries that use the existence - // of an alternate signal stack as indication that certain runtime - // initialisation by the library has been performed (e.g. old - // versions of `std` assumed that certain thread-locals were already - // accessed and thus initialized in the thread if the stack overflow - // signal was successfully delivered). Such assumptions in other - // libraries are fundamentally flawed, so we pay no regard to them. - unsafe { sigaltstack(&stack, ptr::null_mut()) }; - Handler { data: stack.ss_sp as *mut libc::c_void } - } else { - Handler::null() - } - } - - /// # Safety - /// Must only be called with a pointer returned by `make_handler`, and only - /// once per `Handler`. - pub unsafe fn drop_handler(data: *mut libc::c_void) { - if !data.is_null() { - let sigstack_size = sigstack_size(); - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - let disabling_stack = libc::stack_t { - ss_sp: ptr::null_mut(), - ss_flags: SS_DISABLE, - // Workaround for bug in macOS implementation of sigaltstack - // UNIX2003 which returns ENOMEM when disabling a stack while - // passing ss_size smaller than MINSIGSTKSZ. According to POSIX - // both ss_sp and ss_size should be ignored in this case. - ss_size: sigstack_size, - }; - // SAFETY: - // We assume that disabling the alternate signal stack is always - // sound, even if the current alternate signal stack is not the one - // we installed in `make_handler`. Any stack overflows from this - // point on will abort the program when the kernel tries to write - // the signal information to the guard page. - // - // FIXME: detect if the stack has changed, and only uninstall if it hasn't. - unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) }; - // The stack returned by `get_stack` is part of a mapping that - // started one page earlier, so walk back a page and unmap from - // there. - // - // SAFETY: - // This allocation was created by us in `get_stack` and, as the - // alternate signal stack is now disabled, is no longer in use. - unsafe { munmap(data.sub(page_size), sigstack_size + page_size) }; - } - - delete_current_info(); - } - - /// Modern kernels on modern hardware can have dynamic signal stack sizes. - #[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] - fn sigstack_size() -> usize { - // SAFETY: `getauxval` is always safe to call. - let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) }; - // If getauxval couldn't find the entry, it returns 0, - // so take the higher of the "constant" and auxval. - // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ - libc::SIGSTKSZ.max(dynamic_sigstksz as _) - } - - /// Not all OS support hardware where this is needed. - #[cfg(not(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc"))))] - fn sigstack_size() -> usize { - libc::SIGSTKSZ - } - #[cfg(any(target_os = "solaris", target_os = "illumos"))] fn get_stack_start() -> Option<*mut libc::c_void> { // SAFETY: C types are always zero-initializable. @@ -724,121 +376,3 @@ mod imp { } ret } -} - -// This is intentionally not enabled on iOS/tvOS/watchOS/visionOS, as it uses -// several symbols that might lead to rejections from the App Store, namely -// `sigaction`, `sigaltstack`, `sysctlbyname`, `mmap`, `munmap` and `mprotect`. -// -// This might be overly cautious, though it is also what Swift does (and they -// usually have fewer qualms about forwards compatibility, since the runtime -// is shipped with the OS): -// -#[cfg(any( - miri, - not(any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - target_os = "cygwin", - )) -))] -mod imp { - pub unsafe fn init() {} - - pub fn make_handler(_main_thread: bool) -> super::Handler { - super::Handler::null() - } - - pub unsafe fn drop_handler(_data: *mut libc::c_void) {} -} - -#[cfg(target_os = "cygwin")] -mod imp { - mod c { - pub type PVECTORED_EXCEPTION_HANDLER = - Option i32>; - pub type NTSTATUS = i32; - pub type BOOL = i32; - - unsafe extern "system" { - pub fn AddVectoredExceptionHandler( - first: u32, - handler: PVECTORED_EXCEPTION_HANDLER, - ) -> *mut core::ffi::c_void; - pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL; - } - - pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _; - pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32; - - #[repr(C)] - #[derive(Clone, Copy)] - pub struct EXCEPTION_POINTERS { - pub ExceptionRecord: *mut EXCEPTION_RECORD, - // We don't need this field here - // pub Context: *mut CONTEXT, - } - #[repr(C)] - #[derive(Clone, Copy)] - pub struct EXCEPTION_RECORD { - pub ExceptionCode: NTSTATUS, - pub ExceptionFlags: u32, - pub ExceptionRecord: *mut EXCEPTION_RECORD, - pub ExceptionAddress: *mut core::ffi::c_void, - pub NumberParameters: u32, - pub ExceptionInformation: [usize; 15], - } - } - - /// Reserve stack space for use in stack overflow exceptions. - fn reserve_stack() { - let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) }; - // Reserving stack space is not critical so we allow it to fail in the released build of libstd. - // We still use debug assert here so that CI will test that we haven't made a mistake calling the function. - debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling"); - } - - unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 { - // SAFETY: It's up to the caller (which in this case is the OS) to ensure that `ExceptionInfo` is valid. - unsafe { - let rec = &(*(*ExceptionInfo).ExceptionRecord); - let code = rec.ExceptionCode; - - if code == c::EXCEPTION_STACK_OVERFLOW { - crate::thread::with_current_name(|name| { - let name = name.unwrap_or(""); - let tid = crate::thread::current_os_id(); - rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); - }); - } - c::EXCEPTION_CONTINUE_SEARCH - } - } - - pub unsafe fn init() { - // SAFETY: `vectored_handler` has the correct ABI and is safe to call during exception handling. - unsafe { - let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler)); - // Similar to the above, adding the stack overflow handler is allowed to fail - // but a debug assert is used so CI will still test that it normally works. - debug_assert!(!result.is_null(), "failed to install exception handler"); - } - // Set the thread stack guarantee for the main thread. - reserve_stack(); - } - - pub fn make_handler(main_thread: bool) -> super::Handler { - if !main_thread { - reserve_stack(); - } - super::Handler::null() - } - - pub unsafe fn drop_handler(_data: *mut libc::c_void) {} -} diff --git a/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs b/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs index 5604e3e6dbf42..089bd2c95073d 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs @@ -1,765 +1,3 @@ -#![cfg_attr(test, allow(dead_code))] -#![forbid(unsafe_op_in_unsafe_fn)] - -pub use self::imp::init; -use self::imp::{drop_handler, make_handler}; - -pub struct Handler { - data: *mut libc::c_void, -} - -impl Handler { - pub unsafe fn new() -> Handler { - make_handler(false) - } - - fn null() -> Handler { - Handler { data: crate::ptr::null_mut() } - } -} - -impl Drop for Handler { - fn drop(&mut self) { - unsafe { - drop_handler(self.data); - } - } -} - -#[cfg(all( - not(miri), - any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ), -))] -mod thread_info; - -// miri doesn't model signals nor stack overflows and this code has some -// synchronization properties that we don't want to expose to user code, -// hence we disable it on miri. -#[cfg(all( - not(miri), - any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ) -))] -mod imp { - use libc::{ - MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK, - SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t, - }; - #[cfg(not(all(target_os = "linux", target_env = "gnu")))] - use libc::{mmap as mmap64, mprotect, munmap}; - #[cfg(all(target_os = "linux", target_env = "gnu"))] - use libc::{mmap64, mprotect, munmap}; - - use super::Handler; - use super::thread_info::{delete_current_info, set_current_info, with_current_info}; - use crate::ops::Range; - use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering}; - use crate::sys::pal::unix::conf; - use crate::{io, mem, ptr}; - - /// Signal handler for the SIGSEGV and SIGBUS handlers. - /// - /// We've got guard pages (unmapped pages) at the end of every thread's - /// stack, so if a thread ends up running into the guard page it'll trigger - /// this handler. We want to detect these cases and print out a helpful error - /// saying that the stack has overflowed. All other signals, however, should - /// go back to what they were originally supposed to do. - /// - /// This handler currently exists purely to print an informative message - /// whenever a thread overflows its stack. We then abort to exit and - /// indicate a crash, but to avoid a misleading SIGSEGV that might lead - /// users to believe that unsafe code has accessed an invalid pointer; the - /// SIGSEGV encountered when overflowing the stack is expected and - /// well-defined. - /// - /// If this is not a stack overflow, the handler un-registers itself and - /// then returns (to allow the original signal to be delivered again). - /// Returning from this kind of signal handler is technically not defined - /// to work when reading the POSIX spec strictly, but in practice it turns - /// out many large systems and all implementations allow returning from a - /// signal handler to work. For a more detailed explanation see the - /// comments on #26458. - /// - /// # Safety - /// Rust doesn't call this, it *gets called* by the kernel, which we expect - /// to provide valid parameters. Apart from that, this function does not - /// have any other preconditions. - unsafe extern "C" fn signal_handler( - signum: libc::c_int, - info: *mut libc::siginfo_t, - _data: *mut libc::c_void, - ) { - // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`. - let fault_addr = unsafe { (*info).si_addr().addr() }; - - // `with_current_info` expects that the process aborts after it is - // called. If the signal was not caused by a memory access, this might - // not be true. We detect this by noticing that the `si_addr` field is - // zero if the signal is synthetic. - if fault_addr != 0 { - with_current_info(|thread_info| { - // If the faulting address is within the guard page, then we print a - // message saying so and abort. - if let Some(thread_info) = thread_info - && thread_info.guard_page_range.contains(&fault_addr) - { - // Hey you! Yes, you modifying the stack overflow message! - // Please make sure that all functions called here are - // actually async-signal-safe. If they're not, try retrieving - // the information beforehand and storing it in `ThreadInfo`. - // Thank you! - // - says Jonas after having had to watch his carefully - // written code get made unsound again. - let tid = thread_info.tid; - let name = thread_info.name.as_deref().unwrap_or(""); - rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); - rtabort!("stack overflow"); - } - }) - } - - // Unregister ourselves by reverting back to the default behavior. - // SAFETY: assuming all platforms define struct sigaction as "zero-initializable" - let mut action: sigaction = unsafe { mem::zeroed() }; - action.sa_sigaction = SIG_DFL; - // SAFETY: pray this is a well-behaved POSIX implementation of fn sigaction - unsafe { sigaction(signum, &action, ptr::null_mut()) }; - - // See comment above for why this function returns. - } - - static PAGE_SIZE: Atomic = AtomicUsize::new(0); - // Store a pointer to the allocation for the main thread's altstack so that - // tools like valgrind don't complain about a leaked unreachable allocation. - // - // If the main thread exits, the process will terminate so there's no use in - // freeing resources. It also means that the altstack is still installed - // while TLS destructors are run on the main thread (c.f. #111272). - static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut()); - static NEED_ALTSTACK: Atomic = AtomicBool::new(false); - - /// # Safety - /// Must be called only once, on the main thread, during program startup. - pub unsafe fn init() { - PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed); - - // SAFETY: - // This is only called on the main thread, and since it is still early - // in the programs lifetime there is (almost) certainly enough stack - // space left to install the guard page. - let mut guard_page_range = unsafe { install_main_guard() }; - - // Even for panic=immediate-abort, installing the guard pages is important for soundness. - // That said, we do not care about giving nice stackoverflow messages via our custom - // signal handler, just exit early and let the user enjoy the segfault. - if cfg!(panic = "immediate-abort") { - return; - } - - // SAFETY: C structures are always zero-initializable. - let mut action: sigaction = unsafe { mem::zeroed() }; - for &signal in &[SIGSEGV, SIGBUS] { - // SAFETY: just fetches the current signal handler into action - unsafe { sigaction(signal, ptr::null_mut(), &mut action) }; - // We assume that overriding the signal handler is always safe, - // which might conflict with certain libraries that rely on a - // specific signal behaviour. To prevent problems, we only - // override the handler if it has not been set yet. - if action.sa_sigaction == SIG_DFL { - if !NEED_ALTSTACK.load(Ordering::Relaxed) { - // haven't set up our sigaltstack yet - NEED_ALTSTACK.store(true, Ordering::Release); - let handler = make_handler(true); - MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed); - mem::forget(handler); - - if let Some(guard_page_range) = guard_page_range.take() { - set_current_info(guard_page_range); - } - } - - action.sa_flags = SA_SIGINFO | SA_ONSTACK; - action.sa_sigaction = signal_handler - as unsafe extern "C" fn(i32, *mut libc::siginfo_t, *mut libc::c_void) - as sighandler_t; - // SAFETY: - // `&action` describes a valid `sigaction` and `signal_handler` - // is safe to use as a signal handler for `SIGSEGV` and `SIGBUS`. - unsafe { sigaction(signal, &action, ptr::null_mut()) }; - } - } - } - - fn get_stack() -> libc::stack_t { - // OpenBSD requires this flag for stack mapping - // otherwise the said mapping will fail as a no-op on most systems - // and has a different meaning on FreeBSD - #[cfg(any( - target_os = "openbsd", - target_os = "netbsd", - target_os = "linux", - target_os = "dragonfly", - ))] - let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK; - #[cfg(not(any( - target_os = "openbsd", - target_os = "netbsd", - target_os = "linux", - target_os = "dragonfly", - )))] - let flags = MAP_PRIVATE | MAP_ANON; - - let sigstack_size = sigstack_size(); - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - - // SAFETY: this does not unmap any existing pages. - let stackp = unsafe { - mmap64(ptr::null_mut(), sigstack_size + page_size, PROT_READ | PROT_WRITE, flags, -1, 0) - }; - if stackp == MAP_FAILED { - panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error()); - } - // SAFETY: this only affects the memory we just allocated. - let guard_result = unsafe { libc::mprotect(stackp, page_size, PROT_NONE) }; - if guard_result != 0 { - panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error()); - } - // SAFETY: - // The region was allocated with a larger size than `page_size`, so this - // addition is within bounds. - let stackp = unsafe { stackp.add(page_size) }; - - libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size } - } - - pub fn make_handler(main_thread: bool) -> Handler { - if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) { - return Handler::null(); - } - - if !main_thread { - if let Some(guard_page_range) = current_guard() { - set_current_info(guard_page_range); - } - } - - // Load the current alternate signal stack to see if we need to install - // our own. - // - // SAFETY: C structures are always zero-initializable. - let mut stack = unsafe { mem::zeroed() }; - // SAFETY: `&mut stack` is valid for writing a `stack_t`. - unsafe { sigaltstack(ptr::null(), &mut stack) }; - - // Configure alternate signal stack, if one is not already set. - if stack.ss_flags & SS_DISABLE != 0 { - let stack = get_stack(); - // SAFETY: - // `stack_t` is a freshly allocated stack that's not used anywhere - // else. It contains a guard page, so stack overflows in signal - // handlers will not cause undefined behaviour. We must make the - // fundamental runtime assumption that it is safe to install an - // alternate signal stack if there is none currently installed. - // This might conflict with foreign libraries that use the existence - // of an alternate signal stack as indication that certain runtime - // initialisation by the library has been performed (e.g. old - // versions of `std` assumed that certain thread-locals were already - // accessed and thus initialized in the thread if the stack overflow - // signal was successfully delivered). Such assumptions in other - // libraries are fundamentally flawed, so we pay no regard to them. - unsafe { sigaltstack(&stack, ptr::null_mut()) }; - Handler { data: stack.ss_sp as *mut libc::c_void } - } else { - Handler::null() - } - } - - /// # Safety - /// Must only be called with a pointer returned by `make_handler`, and only - /// once per `Handler`. - pub unsafe fn drop_handler(data: *mut libc::c_void) { - if !data.is_null() { - let sigstack_size = sigstack_size(); - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - let disabling_stack = libc::stack_t { - ss_sp: ptr::null_mut(), - ss_flags: SS_DISABLE, - // Workaround for bug in macOS implementation of sigaltstack - // UNIX2003 which returns ENOMEM when disabling a stack while - // passing ss_size smaller than MINSIGSTKSZ. According to POSIX - // both ss_sp and ss_size should be ignored in this case. - ss_size: sigstack_size, - }; - // SAFETY: - // We assume that disabling the alternate signal stack is always - // sound, even if the current alternate signal stack is not the one - // we installed in `make_handler`. Any stack overflows from this - // point on will abort the program when the kernel tries to write - // the signal information to the guard page. - // - // FIXME: detect if the stack has changed, and only uninstall if it hasn't. - unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) }; - // The stack returned by `get_stack` is part of a mapping that - // started one page earlier, so walk back a page and unmap from - // there. - // - // SAFETY: - // This allocation was created by us in `get_stack` and, as the - // alternate signal stack is now disabled, is no longer in use. - unsafe { munmap(data.sub(page_size), sigstack_size + page_size) }; - } - - delete_current_info(); - } - - /// Modern kernels on modern hardware can have dynamic signal stack sizes. - #[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] - fn sigstack_size() -> usize { - // SAFETY: `getauxval` is always safe to call. - let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) }; - // If getauxval couldn't find the entry, it returns 0, - // so take the higher of the "constant" and auxval. - // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ - libc::SIGSTKSZ.max(dynamic_sigstksz as _) - } - - /// Not all OS support hardware where this is needed. - #[cfg(not(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc"))))] - fn sigstack_size() -> usize { - libc::SIGSTKSZ - } - - #[cfg(any(target_os = "solaris", target_os = "illumos"))] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: C types are always zero-initializable. - let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; - // SAFETY: - // The pointer is valid for writing a `stack_t`. - assert_eq!(unsafe { libc::stack_getbounds(&mut current_stack) }, 0); - Some(current_stack.ss_sp) - } - - #[cfg(target_os = "macos")] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: always safe to call. - let th = unsafe { libc::pthread_self() }; - // SAFETY: `th` is a valid `pthread_t`. - unsafe { - let stackptr = libc::pthread_get_stackaddr_np(th); - let stacksize = libc::pthread_get_stacksize_np(th); - Some(stackptr.map_addr(|addr| addr - stacksize)) - } - } - - #[cfg(target_os = "openbsd")] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: C types are always zero-initializable. - let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t`. - // * `&mut current_stack` is coerced to a pointer that is valid for writing - // a `stack_t`. - assert_eq!( - unsafe { libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack) }, - 0 - ); - - let stack_ptr = current_stack.ss_sp; - // SAFETY: this is always safe to call. - let stackaddr = if unsafe { libc::pthread_main_np() } == 1 { - // main thread - stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed) - } else { - // new thread - stack_ptr.addr() - current_stack.ss_size - }; - Some(stack_ptr.with_addr(stackaddr)) - } - - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "netbsd", - target_os = "hurd", - target_os = "linux", - target_os = "l4re" - ))] - fn get_stack_start() -> Option<*mut libc::c_void> { - use crate::pin::pin; - use crate::sys::helpers::COpaque; - - let mut ret = None; - let mut attr: COpaque = COpaque::uninit(); - if !cfg!(target_os = "freebsd") { - attr = COpaque::zeroed(); - } - let attr = pin!(attr); - // FIXME(pin-ergonomics): remove the next line. - let attr = attr.into_ref(); - - // SAFETY: - // The attributes object has not been initialized yet and will not be - // moved until destroyed. - #[cfg(target_os = "freebsd")] - assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` is an initialized attribute object that can be written to. - #[cfg(target_os = "freebsd")] - let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` can be written to, and will be initialized by this call. - #[cfg(not(target_os = "freebsd"))] - let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; - if e == 0 { - let mut stackaddr = crate::ptr::null_mut(); - let mut stacksize = 0; - // SAFETY: - // `attr` is an initialized attribute object and both the pointers - // are valid for writing. - assert_eq!( - unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackaddr, &mut stacksize) }, - 0 - ); - ret = Some(stackaddr); - } - if e == 0 || cfg!(target_os = "freebsd") { - // SAFETY: - // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or - // by `pthread_attr_get_np`, and is not used after this point. - assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); - } - ret - } - - fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> { - let stackptr = get_stack_start()?; - let stackaddr = stackptr.addr(); - - // Ensure stackaddr is page aligned! A parent process might - // have reset RLIMIT_STACK to be non-page aligned. The - // pthread_attr_getstack() reports the usable stack area - // stackaddr < stackaddr + stacksize, so if stackaddr is not - // page-aligned, calculate the fix such that stackaddr < - // new_page_aligned_stackaddr < stackaddr + stacksize - let remainder = stackaddr % page_size; - Some(if remainder == 0 { - stackptr - } else { - stackptr.with_addr(stackaddr + page_size - remainder) - }) - } - - /// # Safety - /// This function must only be called from the main thread, and there must - /// be sufficient stack space remaining to place a stack guard. - unsafe fn install_main_guard() -> Option> { - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - - // this way someone on any unix-y OS can check that all these compile - if cfg!(all(target_os = "linux", not(target_env = "musl"))) { - install_main_guard_linux(page_size) - } else if cfg!(all(target_os = "linux", target_env = "musl")) { - install_main_guard_linux_musl(page_size) - } else if cfg!(target_os = "freebsd") { - #[cfg(not(target_os = "freebsd"))] - return None; - // The FreeBSD code cannot be checked on non-BSDs. - #[cfg(target_os = "freebsd")] - install_main_guard_freebsd(page_size) - } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) { - install_main_guard_bsds(page_size) - } else { - // SAFETY: guaranteed by caller. - unsafe { install_main_guard_default(page_size) } - } - } - - fn install_main_guard_linux(page_size: usize) -> Option> { - // See the corresponding conditional in init(). - // Avoid stack_start_aligned, which makes slow syscalls to read /proc/self/maps - if cfg!(panic = "immediate-abort") { - return None; - } - // Linux doesn't allocate the whole stack right away, and - // the kernel has its own stack-guard mechanism to fault - // when growing too close to an existing mapping. If we map - // our own guard, then the kernel starts enforcing a rather - // large gap above that, rendering much of the possible - // stack space useless. See #43052. - // - // Instead, we'll just note where we expect rlimit to start - // faulting, so our handler can report "stack overflow", and - // trust that the kernel's own stack guard will work. - let stackptr = stack_start_aligned(page_size)?; - let stackaddr = stackptr.addr(); - Some(stackaddr - page_size..stackaddr) - } - - fn install_main_guard_linux_musl(_page_size: usize) -> Option> { - // For the main thread, the musl's pthread_attr_getstack - // returns the current stack size, rather than maximum size - // it can eventually grow to. It cannot be used to determine - // the position of kernel's stack guard. - None - } - - #[cfg(target_os = "freebsd")] - fn install_main_guard_freebsd(page_size: usize) -> Option> { - // See the corresponding conditional in install_main_guard_linux(). - if cfg!(panic = "immediate-abort") { - return None; - } - // FreeBSD's stack autogrows, and optionally includes a guard page - // at the bottom. If we try to remap the bottom of the stack - // ourselves, FreeBSD's guard page moves upwards. So we'll just use - // the builtin guard page. - let stackptr = stack_start_aligned(page_size)?; - let guardaddr = stackptr.addr(); - // Technically the number of guard pages is tunable and controlled - // by the security.bsd.stack_guard_page sysctl. - // By default it is 1, checking once is enough since it is - // a boot time config value. - // FIXME(joboet): this function is only called once, remove the caching. - static PAGES: crate::sync::OnceLock = crate::sync::OnceLock::new(); - - let pages = PAGES.get_or_init(|| { - let mut guard: usize = 0; - let mut size = size_of_val(&guard); - let oid = c"security.bsd.stack_guard_page"; - - let r = unsafe { - libc::sysctlbyname( - oid.as_ptr(), - (&raw mut guard).cast(), - &raw mut size, - ptr::null_mut(), - 0, - ) - }; - if r == 0 { guard } else { 1 } - }); - Some(guardaddr..guardaddr + pages * page_size) - } - - fn install_main_guard_bsds(page_size: usize) -> Option> { - // See the corresponding conditional in install_main_guard_linux(). - if cfg!(panic = "immediate-abort") { - return None; - } - // OpenBSD stack already includes a guard page, and stack is - // immutable. - // NetBSD stack includes the guard page. - // - // We'll just note where we expect rlimit to start - // faulting, so our handler can report "stack overflow", and - // trust that the kernel's own stack guard will work. - let stackptr = stack_start_aligned(page_size)?; - let stackaddr = stackptr.addr(); - Some(stackaddr - page_size..stackaddr) - } - - /// # Safety - /// This function must only be called from the main thread, and there must - /// be sufficient stack space remaining to place a stack guard. - unsafe fn install_main_guard_default(page_size: usize) -> Option> { - // Reallocate the last page of the stack. - // This ensures SIGBUS will be raised on - // stack overflow. - // Systems which enforce strict PAX MPROTECT do not allow - // to mprotect() a mapping with less restrictive permissions - // than the initial mmap() used, so we mmap() here with - // read/write permissions and only then mprotect() it to - // no permissions at all. See issue #50313. - let stackptr = stack_start_aligned(page_size)?; - // SAFETY: - // The memory region from `stackptr..stackptr + page_size` belongs to - // the current thread's stack, and the caller has asserted that there - // is sufficient stack space, which means that this will not overwrite - // any existing allocations. - let result = unsafe { - mmap64( - stackptr, - page_size, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON | MAP_FIXED, - -1, - 0, - ) - }; - if result != stackptr || result == MAP_FAILED { - panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); - } - - // SAFETY: - // Since this function is only called on the main thread, the stack will - // not be reused until program exit, so the runtime will never observe - // that part of the stack has been made unusable in this way. - let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) }; - if result != 0 { - panic!("failed to protect the guard page: {}", io::Error::last_os_error()); - } - - let guardaddr = stackptr.addr(); - - Some(guardaddr..guardaddr + page_size) - } - - #[cfg(any( - target_os = "macos", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ))] - fn current_guard() -> Option> { - let stackptr = get_stack_start()?; - let stackaddr = stackptr.addr(); - Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr) - } - - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "hurd", - target_os = "linux", - target_os = "netbsd", - target_os = "l4re" - ))] - fn current_guard() -> Option> { - use crate::pin::pin; - use crate::sys::helpers::COpaque; - - let mut ret = None; - - let mut attr: COpaque = COpaque::uninit(); - if !cfg!(target_os = "freebsd") { - attr = COpaque::zeroed(); - } - let attr = pin!(attr); - // FIXME(pin-ergonomics): remove the next line. - let attr = attr.into_ref(); - - // SAFETY: - // The attributes object has not been initialized yet and will not be - // moved until destroyed. - #[cfg(target_os = "freebsd")] - assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` is an initialized attribute object that can be written to. - #[cfg(target_os = "freebsd")] - let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` can be written to, and will be initialized by this call. - #[cfg(not(target_os = "freebsd"))] - let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; - if e == 0 { - let mut guardsize = 0; - // SAFETY: - // `attr` is an initialized attribute object and the pointer is valid - // for writing. - assert_eq!(unsafe { libc::pthread_attr_getguardsize(attr.get(), &mut guardsize) }, 0); - if guardsize == 0 { - if cfg!(all(target_os = "linux", target_env = "musl")) { - // musl versions before 1.1.19 always reported guard - // size obtained from pthread_attr_get_np as zero. - // Use page size as a fallback. - guardsize = PAGE_SIZE.load(Ordering::Relaxed); - } else { - panic!("there is no guard page"); - } - } - let mut stackptr = crate::ptr::null_mut::(); - let mut size = 0; - // SAFETY: - // `attr` is an initialized attribute object and both the pointers - // are valid for writing. - assert_eq!( - unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackptr, &mut size) }, - 0 - ); - - let stackaddr = stackptr.addr(); - ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) { - Some(stackaddr - guardsize..stackaddr) - } else if cfg!(all(target_os = "linux", target_env = "musl")) { - Some(stackaddr - guardsize..stackaddr) - } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc"))) - { - // glibc used to include the guard area within the stack, as noted in the BUGS - // section of `man pthread_attr_getguardsize`. This has been corrected starting - // with glibc 2.27, and in some distro backports, so the guard is now placed at the - // end (below) the stack. There's no easy way for us to know which we have at - // runtime, so we'll just match any fault in the range right above or below the - // stack base to call that fault a stack overflow. - Some(stackaddr - guardsize..stackaddr + guardsize) - } else { - Some(stackaddr..stackaddr + guardsize) - }; - } - if e == 0 || cfg!(target_os = "freebsd") { - // SAFETY: - // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or - // by `pthread_attr_get_np`, and is not used after this point. - assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); - } - ret - } -} - -// This is intentionally not enabled on iOS/tvOS/watchOS/visionOS, as it uses -// several symbols that might lead to rejections from the App Store, namely -// `sigaction`, `sigaltstack`, `sysctlbyname`, `mmap`, `munmap` and `mprotect`. -// -// This might be overly cautious, though it is also what Swift does (and they -// usually have fewer qualms about forwards compatibility, since the runtime -// is shipped with the OS): -// -#[cfg(any( - miri, - not(any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - target_os = "cygwin", - )) -))] -mod imp { - pub unsafe fn init() {} - - pub fn make_handler(_main_thread: bool) -> super::Handler { - super::Handler::null() - } - - pub unsafe fn drop_handler(_data: *mut libc::c_void) {} -} - -#[cfg(target_os = "cygwin")] -mod imp { mod c { pub type PVECTORED_EXCEPTION_HANDLER = Option i32>; @@ -841,4 +79,3 @@ mod imp { } pub unsafe fn drop_handler(_data: *mut libc::c_void) {} -} diff --git a/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs b/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs index 5604e3e6dbf42..f765090ffff7d 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs @@ -1,754 +1,3 @@ -#![cfg_attr(test, allow(dead_code))] -#![forbid(unsafe_op_in_unsafe_fn)] - -pub use self::imp::init; -use self::imp::{drop_handler, make_handler}; - -pub struct Handler { - data: *mut libc::c_void, -} - -impl Handler { - pub unsafe fn new() -> Handler { - make_handler(false) - } - - fn null() -> Handler { - Handler { data: crate::ptr::null_mut() } - } -} - -impl Drop for Handler { - fn drop(&mut self) { - unsafe { - drop_handler(self.data); - } - } -} - -#[cfg(all( - not(miri), - any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ), -))] -mod thread_info; - -// miri doesn't model signals nor stack overflows and this code has some -// synchronization properties that we don't want to expose to user code, -// hence we disable it on miri. -#[cfg(all( - not(miri), - any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ) -))] -mod imp { - use libc::{ - MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK, - SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t, - }; - #[cfg(not(all(target_os = "linux", target_env = "gnu")))] - use libc::{mmap as mmap64, mprotect, munmap}; - #[cfg(all(target_os = "linux", target_env = "gnu"))] - use libc::{mmap64, mprotect, munmap}; - - use super::Handler; - use super::thread_info::{delete_current_info, set_current_info, with_current_info}; - use crate::ops::Range; - use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering}; - use crate::sys::pal::unix::conf; - use crate::{io, mem, ptr}; - - /// Signal handler for the SIGSEGV and SIGBUS handlers. - /// - /// We've got guard pages (unmapped pages) at the end of every thread's - /// stack, so if a thread ends up running into the guard page it'll trigger - /// this handler. We want to detect these cases and print out a helpful error - /// saying that the stack has overflowed. All other signals, however, should - /// go back to what they were originally supposed to do. - /// - /// This handler currently exists purely to print an informative message - /// whenever a thread overflows its stack. We then abort to exit and - /// indicate a crash, but to avoid a misleading SIGSEGV that might lead - /// users to believe that unsafe code has accessed an invalid pointer; the - /// SIGSEGV encountered when overflowing the stack is expected and - /// well-defined. - /// - /// If this is not a stack overflow, the handler un-registers itself and - /// then returns (to allow the original signal to be delivered again). - /// Returning from this kind of signal handler is technically not defined - /// to work when reading the POSIX spec strictly, but in practice it turns - /// out many large systems and all implementations allow returning from a - /// signal handler to work. For a more detailed explanation see the - /// comments on #26458. - /// - /// # Safety - /// Rust doesn't call this, it *gets called* by the kernel, which we expect - /// to provide valid parameters. Apart from that, this function does not - /// have any other preconditions. - unsafe extern "C" fn signal_handler( - signum: libc::c_int, - info: *mut libc::siginfo_t, - _data: *mut libc::c_void, - ) { - // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`. - let fault_addr = unsafe { (*info).si_addr().addr() }; - - // `with_current_info` expects that the process aborts after it is - // called. If the signal was not caused by a memory access, this might - // not be true. We detect this by noticing that the `si_addr` field is - // zero if the signal is synthetic. - if fault_addr != 0 { - with_current_info(|thread_info| { - // If the faulting address is within the guard page, then we print a - // message saying so and abort. - if let Some(thread_info) = thread_info - && thread_info.guard_page_range.contains(&fault_addr) - { - // Hey you! Yes, you modifying the stack overflow message! - // Please make sure that all functions called here are - // actually async-signal-safe. If they're not, try retrieving - // the information beforehand and storing it in `ThreadInfo`. - // Thank you! - // - says Jonas after having had to watch his carefully - // written code get made unsound again. - let tid = thread_info.tid; - let name = thread_info.name.as_deref().unwrap_or(""); - rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); - rtabort!("stack overflow"); - } - }) - } - - // Unregister ourselves by reverting back to the default behavior. - // SAFETY: assuming all platforms define struct sigaction as "zero-initializable" - let mut action: sigaction = unsafe { mem::zeroed() }; - action.sa_sigaction = SIG_DFL; - // SAFETY: pray this is a well-behaved POSIX implementation of fn sigaction - unsafe { sigaction(signum, &action, ptr::null_mut()) }; - - // See comment above for why this function returns. - } - - static PAGE_SIZE: Atomic = AtomicUsize::new(0); - // Store a pointer to the allocation for the main thread's altstack so that - // tools like valgrind don't complain about a leaked unreachable allocation. - // - // If the main thread exits, the process will terminate so there's no use in - // freeing resources. It also means that the altstack is still installed - // while TLS destructors are run on the main thread (c.f. #111272). - static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut()); - static NEED_ALTSTACK: Atomic = AtomicBool::new(false); - - /// # Safety - /// Must be called only once, on the main thread, during program startup. - pub unsafe fn init() { - PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed); - - // SAFETY: - // This is only called on the main thread, and since it is still early - // in the programs lifetime there is (almost) certainly enough stack - // space left to install the guard page. - let mut guard_page_range = unsafe { install_main_guard() }; - - // Even for panic=immediate-abort, installing the guard pages is important for soundness. - // That said, we do not care about giving nice stackoverflow messages via our custom - // signal handler, just exit early and let the user enjoy the segfault. - if cfg!(panic = "immediate-abort") { - return; - } - - // SAFETY: C structures are always zero-initializable. - let mut action: sigaction = unsafe { mem::zeroed() }; - for &signal in &[SIGSEGV, SIGBUS] { - // SAFETY: just fetches the current signal handler into action - unsafe { sigaction(signal, ptr::null_mut(), &mut action) }; - // We assume that overriding the signal handler is always safe, - // which might conflict with certain libraries that rely on a - // specific signal behaviour. To prevent problems, we only - // override the handler if it has not been set yet. - if action.sa_sigaction == SIG_DFL { - if !NEED_ALTSTACK.load(Ordering::Relaxed) { - // haven't set up our sigaltstack yet - NEED_ALTSTACK.store(true, Ordering::Release); - let handler = make_handler(true); - MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed); - mem::forget(handler); - - if let Some(guard_page_range) = guard_page_range.take() { - set_current_info(guard_page_range); - } - } - - action.sa_flags = SA_SIGINFO | SA_ONSTACK; - action.sa_sigaction = signal_handler - as unsafe extern "C" fn(i32, *mut libc::siginfo_t, *mut libc::c_void) - as sighandler_t; - // SAFETY: - // `&action` describes a valid `sigaction` and `signal_handler` - // is safe to use as a signal handler for `SIGSEGV` and `SIGBUS`. - unsafe { sigaction(signal, &action, ptr::null_mut()) }; - } - } - } - - fn get_stack() -> libc::stack_t { - // OpenBSD requires this flag for stack mapping - // otherwise the said mapping will fail as a no-op on most systems - // and has a different meaning on FreeBSD - #[cfg(any( - target_os = "openbsd", - target_os = "netbsd", - target_os = "linux", - target_os = "dragonfly", - ))] - let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK; - #[cfg(not(any( - target_os = "openbsd", - target_os = "netbsd", - target_os = "linux", - target_os = "dragonfly", - )))] - let flags = MAP_PRIVATE | MAP_ANON; - - let sigstack_size = sigstack_size(); - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - - // SAFETY: this does not unmap any existing pages. - let stackp = unsafe { - mmap64(ptr::null_mut(), sigstack_size + page_size, PROT_READ | PROT_WRITE, flags, -1, 0) - }; - if stackp == MAP_FAILED { - panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error()); - } - // SAFETY: this only affects the memory we just allocated. - let guard_result = unsafe { libc::mprotect(stackp, page_size, PROT_NONE) }; - if guard_result != 0 { - panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error()); - } - // SAFETY: - // The region was allocated with a larger size than `page_size`, so this - // addition is within bounds. - let stackp = unsafe { stackp.add(page_size) }; - - libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size } - } - - pub fn make_handler(main_thread: bool) -> Handler { - if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) { - return Handler::null(); - } - - if !main_thread { - if let Some(guard_page_range) = current_guard() { - set_current_info(guard_page_range); - } - } - - // Load the current alternate signal stack to see if we need to install - // our own. - // - // SAFETY: C structures are always zero-initializable. - let mut stack = unsafe { mem::zeroed() }; - // SAFETY: `&mut stack` is valid for writing a `stack_t`. - unsafe { sigaltstack(ptr::null(), &mut stack) }; - - // Configure alternate signal stack, if one is not already set. - if stack.ss_flags & SS_DISABLE != 0 { - let stack = get_stack(); - // SAFETY: - // `stack_t` is a freshly allocated stack that's not used anywhere - // else. It contains a guard page, so stack overflows in signal - // handlers will not cause undefined behaviour. We must make the - // fundamental runtime assumption that it is safe to install an - // alternate signal stack if there is none currently installed. - // This might conflict with foreign libraries that use the existence - // of an alternate signal stack as indication that certain runtime - // initialisation by the library has been performed (e.g. old - // versions of `std` assumed that certain thread-locals were already - // accessed and thus initialized in the thread if the stack overflow - // signal was successfully delivered). Such assumptions in other - // libraries are fundamentally flawed, so we pay no regard to them. - unsafe { sigaltstack(&stack, ptr::null_mut()) }; - Handler { data: stack.ss_sp as *mut libc::c_void } - } else { - Handler::null() - } - } - - /// # Safety - /// Must only be called with a pointer returned by `make_handler`, and only - /// once per `Handler`. - pub unsafe fn drop_handler(data: *mut libc::c_void) { - if !data.is_null() { - let sigstack_size = sigstack_size(); - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - let disabling_stack = libc::stack_t { - ss_sp: ptr::null_mut(), - ss_flags: SS_DISABLE, - // Workaround for bug in macOS implementation of sigaltstack - // UNIX2003 which returns ENOMEM when disabling a stack while - // passing ss_size smaller than MINSIGSTKSZ. According to POSIX - // both ss_sp and ss_size should be ignored in this case. - ss_size: sigstack_size, - }; - // SAFETY: - // We assume that disabling the alternate signal stack is always - // sound, even if the current alternate signal stack is not the one - // we installed in `make_handler`. Any stack overflows from this - // point on will abort the program when the kernel tries to write - // the signal information to the guard page. - // - // FIXME: detect if the stack has changed, and only uninstall if it hasn't. - unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) }; - // The stack returned by `get_stack` is part of a mapping that - // started one page earlier, so walk back a page and unmap from - // there. - // - // SAFETY: - // This allocation was created by us in `get_stack` and, as the - // alternate signal stack is now disabled, is no longer in use. - unsafe { munmap(data.sub(page_size), sigstack_size + page_size) }; - } - - delete_current_info(); - } - - /// Modern kernels on modern hardware can have dynamic signal stack sizes. - #[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] - fn sigstack_size() -> usize { - // SAFETY: `getauxval` is always safe to call. - let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) }; - // If getauxval couldn't find the entry, it returns 0, - // so take the higher of the "constant" and auxval. - // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ - libc::SIGSTKSZ.max(dynamic_sigstksz as _) - } - - /// Not all OS support hardware where this is needed. - #[cfg(not(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc"))))] - fn sigstack_size() -> usize { - libc::SIGSTKSZ - } - - #[cfg(any(target_os = "solaris", target_os = "illumos"))] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: C types are always zero-initializable. - let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; - // SAFETY: - // The pointer is valid for writing a `stack_t`. - assert_eq!(unsafe { libc::stack_getbounds(&mut current_stack) }, 0); - Some(current_stack.ss_sp) - } - - #[cfg(target_os = "macos")] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: always safe to call. - let th = unsafe { libc::pthread_self() }; - // SAFETY: `th` is a valid `pthread_t`. - unsafe { - let stackptr = libc::pthread_get_stackaddr_np(th); - let stacksize = libc::pthread_get_stacksize_np(th); - Some(stackptr.map_addr(|addr| addr - stacksize)) - } - } - - #[cfg(target_os = "openbsd")] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: C types are always zero-initializable. - let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t`. - // * `&mut current_stack` is coerced to a pointer that is valid for writing - // a `stack_t`. - assert_eq!( - unsafe { libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack) }, - 0 - ); - - let stack_ptr = current_stack.ss_sp; - // SAFETY: this is always safe to call. - let stackaddr = if unsafe { libc::pthread_main_np() } == 1 { - // main thread - stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed) - } else { - // new thread - stack_ptr.addr() - current_stack.ss_size - }; - Some(stack_ptr.with_addr(stackaddr)) - } - - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "netbsd", - target_os = "hurd", - target_os = "linux", - target_os = "l4re" - ))] - fn get_stack_start() -> Option<*mut libc::c_void> { - use crate::pin::pin; - use crate::sys::helpers::COpaque; - - let mut ret = None; - let mut attr: COpaque = COpaque::uninit(); - if !cfg!(target_os = "freebsd") { - attr = COpaque::zeroed(); - } - let attr = pin!(attr); - // FIXME(pin-ergonomics): remove the next line. - let attr = attr.into_ref(); - - // SAFETY: - // The attributes object has not been initialized yet and will not be - // moved until destroyed. - #[cfg(target_os = "freebsd")] - assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` is an initialized attribute object that can be written to. - #[cfg(target_os = "freebsd")] - let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` can be written to, and will be initialized by this call. - #[cfg(not(target_os = "freebsd"))] - let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; - if e == 0 { - let mut stackaddr = crate::ptr::null_mut(); - let mut stacksize = 0; - // SAFETY: - // `attr` is an initialized attribute object and both the pointers - // are valid for writing. - assert_eq!( - unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackaddr, &mut stacksize) }, - 0 - ); - ret = Some(stackaddr); - } - if e == 0 || cfg!(target_os = "freebsd") { - // SAFETY: - // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or - // by `pthread_attr_get_np`, and is not used after this point. - assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); - } - ret - } - - fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> { - let stackptr = get_stack_start()?; - let stackaddr = stackptr.addr(); - - // Ensure stackaddr is page aligned! A parent process might - // have reset RLIMIT_STACK to be non-page aligned. The - // pthread_attr_getstack() reports the usable stack area - // stackaddr < stackaddr + stacksize, so if stackaddr is not - // page-aligned, calculate the fix such that stackaddr < - // new_page_aligned_stackaddr < stackaddr + stacksize - let remainder = stackaddr % page_size; - Some(if remainder == 0 { - stackptr - } else { - stackptr.with_addr(stackaddr + page_size - remainder) - }) - } - - /// # Safety - /// This function must only be called from the main thread, and there must - /// be sufficient stack space remaining to place a stack guard. - unsafe fn install_main_guard() -> Option> { - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - - // this way someone on any unix-y OS can check that all these compile - if cfg!(all(target_os = "linux", not(target_env = "musl"))) { - install_main_guard_linux(page_size) - } else if cfg!(all(target_os = "linux", target_env = "musl")) { - install_main_guard_linux_musl(page_size) - } else if cfg!(target_os = "freebsd") { - #[cfg(not(target_os = "freebsd"))] - return None; - // The FreeBSD code cannot be checked on non-BSDs. - #[cfg(target_os = "freebsd")] - install_main_guard_freebsd(page_size) - } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) { - install_main_guard_bsds(page_size) - } else { - // SAFETY: guaranteed by caller. - unsafe { install_main_guard_default(page_size) } - } - } - - fn install_main_guard_linux(page_size: usize) -> Option> { - // See the corresponding conditional in init(). - // Avoid stack_start_aligned, which makes slow syscalls to read /proc/self/maps - if cfg!(panic = "immediate-abort") { - return None; - } - // Linux doesn't allocate the whole stack right away, and - // the kernel has its own stack-guard mechanism to fault - // when growing too close to an existing mapping. If we map - // our own guard, then the kernel starts enforcing a rather - // large gap above that, rendering much of the possible - // stack space useless. See #43052. - // - // Instead, we'll just note where we expect rlimit to start - // faulting, so our handler can report "stack overflow", and - // trust that the kernel's own stack guard will work. - let stackptr = stack_start_aligned(page_size)?; - let stackaddr = stackptr.addr(); - Some(stackaddr - page_size..stackaddr) - } - - fn install_main_guard_linux_musl(_page_size: usize) -> Option> { - // For the main thread, the musl's pthread_attr_getstack - // returns the current stack size, rather than maximum size - // it can eventually grow to. It cannot be used to determine - // the position of kernel's stack guard. - None - } - - #[cfg(target_os = "freebsd")] - fn install_main_guard_freebsd(page_size: usize) -> Option> { - // See the corresponding conditional in install_main_guard_linux(). - if cfg!(panic = "immediate-abort") { - return None; - } - // FreeBSD's stack autogrows, and optionally includes a guard page - // at the bottom. If we try to remap the bottom of the stack - // ourselves, FreeBSD's guard page moves upwards. So we'll just use - // the builtin guard page. - let stackptr = stack_start_aligned(page_size)?; - let guardaddr = stackptr.addr(); - // Technically the number of guard pages is tunable and controlled - // by the security.bsd.stack_guard_page sysctl. - // By default it is 1, checking once is enough since it is - // a boot time config value. - // FIXME(joboet): this function is only called once, remove the caching. - static PAGES: crate::sync::OnceLock = crate::sync::OnceLock::new(); - - let pages = PAGES.get_or_init(|| { - let mut guard: usize = 0; - let mut size = size_of_val(&guard); - let oid = c"security.bsd.stack_guard_page"; - - let r = unsafe { - libc::sysctlbyname( - oid.as_ptr(), - (&raw mut guard).cast(), - &raw mut size, - ptr::null_mut(), - 0, - ) - }; - if r == 0 { guard } else { 1 } - }); - Some(guardaddr..guardaddr + pages * page_size) - } - - fn install_main_guard_bsds(page_size: usize) -> Option> { - // See the corresponding conditional in install_main_guard_linux(). - if cfg!(panic = "immediate-abort") { - return None; - } - // OpenBSD stack already includes a guard page, and stack is - // immutable. - // NetBSD stack includes the guard page. - // - // We'll just note where we expect rlimit to start - // faulting, so our handler can report "stack overflow", and - // trust that the kernel's own stack guard will work. - let stackptr = stack_start_aligned(page_size)?; - let stackaddr = stackptr.addr(); - Some(stackaddr - page_size..stackaddr) - } - - /// # Safety - /// This function must only be called from the main thread, and there must - /// be sufficient stack space remaining to place a stack guard. - unsafe fn install_main_guard_default(page_size: usize) -> Option> { - // Reallocate the last page of the stack. - // This ensures SIGBUS will be raised on - // stack overflow. - // Systems which enforce strict PAX MPROTECT do not allow - // to mprotect() a mapping with less restrictive permissions - // than the initial mmap() used, so we mmap() here with - // read/write permissions and only then mprotect() it to - // no permissions at all. See issue #50313. - let stackptr = stack_start_aligned(page_size)?; - // SAFETY: - // The memory region from `stackptr..stackptr + page_size` belongs to - // the current thread's stack, and the caller has asserted that there - // is sufficient stack space, which means that this will not overwrite - // any existing allocations. - let result = unsafe { - mmap64( - stackptr, - page_size, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON | MAP_FIXED, - -1, - 0, - ) - }; - if result != stackptr || result == MAP_FAILED { - panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); - } - - // SAFETY: - // Since this function is only called on the main thread, the stack will - // not be reused until program exit, so the runtime will never observe - // that part of the stack has been made unusable in this way. - let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) }; - if result != 0 { - panic!("failed to protect the guard page: {}", io::Error::last_os_error()); - } - - let guardaddr = stackptr.addr(); - - Some(guardaddr..guardaddr + page_size) - } - - #[cfg(any( - target_os = "macos", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ))] - fn current_guard() -> Option> { - let stackptr = get_stack_start()?; - let stackaddr = stackptr.addr(); - Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr) - } - - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "hurd", - target_os = "linux", - target_os = "netbsd", - target_os = "l4re" - ))] - fn current_guard() -> Option> { - use crate::pin::pin; - use crate::sys::helpers::COpaque; - - let mut ret = None; - - let mut attr: COpaque = COpaque::uninit(); - if !cfg!(target_os = "freebsd") { - attr = COpaque::zeroed(); - } - let attr = pin!(attr); - // FIXME(pin-ergonomics): remove the next line. - let attr = attr.into_ref(); - - // SAFETY: - // The attributes object has not been initialized yet and will not be - // moved until destroyed. - #[cfg(target_os = "freebsd")] - assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` is an initialized attribute object that can be written to. - #[cfg(target_os = "freebsd")] - let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` can be written to, and will be initialized by this call. - #[cfg(not(target_os = "freebsd"))] - let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; - if e == 0 { - let mut guardsize = 0; - // SAFETY: - // `attr` is an initialized attribute object and the pointer is valid - // for writing. - assert_eq!(unsafe { libc::pthread_attr_getguardsize(attr.get(), &mut guardsize) }, 0); - if guardsize == 0 { - if cfg!(all(target_os = "linux", target_env = "musl")) { - // musl versions before 1.1.19 always reported guard - // size obtained from pthread_attr_get_np as zero. - // Use page size as a fallback. - guardsize = PAGE_SIZE.load(Ordering::Relaxed); - } else { - panic!("there is no guard page"); - } - } - let mut stackptr = crate::ptr::null_mut::(); - let mut size = 0; - // SAFETY: - // `attr` is an initialized attribute object and both the pointers - // are valid for writing. - assert_eq!( - unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackptr, &mut size) }, - 0 - ); - - let stackaddr = stackptr.addr(); - ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) { - Some(stackaddr - guardsize..stackaddr) - } else if cfg!(all(target_os = "linux", target_env = "musl")) { - Some(stackaddr - guardsize..stackaddr) - } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc"))) - { - // glibc used to include the guard area within the stack, as noted in the BUGS - // section of `man pthread_attr_getguardsize`. This has been corrected starting - // with glibc 2.27, and in some distro backports, so the guard is now placed at the - // end (below) the stack. There's no easy way for us to know which we have at - // runtime, so we'll just match any fault in the range right above or below the - // stack base to call that fault a stack overflow. - Some(stackaddr - guardsize..stackaddr + guardsize) - } else { - Some(stackaddr..stackaddr + guardsize) - }; - } - if e == 0 || cfg!(target_os = "freebsd") { - // SAFETY: - // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or - // by `pthread_attr_get_np`, and is not used after this point. - assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); - } - ret - } -} - -// This is intentionally not enabled on iOS/tvOS/watchOS/visionOS, as it uses -// several symbols that might lead to rejections from the App Store, namely -// `sigaction`, `sigaltstack`, `sysctlbyname`, `mmap`, `munmap` and `mprotect`. -// -// This might be overly cautious, though it is also what Swift does (and they -// usually have fewer qualms about forwards compatibility, since the runtime -// is shipped with the OS): -// -#[cfg(any( - miri, - not(any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - target_os = "cygwin", - )) -))] -mod imp { pub unsafe fn init() {} pub fn make_handler(_main_thread: bool) -> super::Handler { @@ -756,89 +5,3 @@ mod imp { } pub unsafe fn drop_handler(_data: *mut libc::c_void) {} -} - -#[cfg(target_os = "cygwin")] -mod imp { - mod c { - pub type PVECTORED_EXCEPTION_HANDLER = - Option i32>; - pub type NTSTATUS = i32; - pub type BOOL = i32; - - unsafe extern "system" { - pub fn AddVectoredExceptionHandler( - first: u32, - handler: PVECTORED_EXCEPTION_HANDLER, - ) -> *mut core::ffi::c_void; - pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL; - } - - pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _; - pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32; - - #[repr(C)] - #[derive(Clone, Copy)] - pub struct EXCEPTION_POINTERS { - pub ExceptionRecord: *mut EXCEPTION_RECORD, - // We don't need this field here - // pub Context: *mut CONTEXT, - } - #[repr(C)] - #[derive(Clone, Copy)] - pub struct EXCEPTION_RECORD { - pub ExceptionCode: NTSTATUS, - pub ExceptionFlags: u32, - pub ExceptionRecord: *mut EXCEPTION_RECORD, - pub ExceptionAddress: *mut core::ffi::c_void, - pub NumberParameters: u32, - pub ExceptionInformation: [usize; 15], - } - } - - /// Reserve stack space for use in stack overflow exceptions. - fn reserve_stack() { - let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) }; - // Reserving stack space is not critical so we allow it to fail in the released build of libstd. - // We still use debug assert here so that CI will test that we haven't made a mistake calling the function. - debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling"); - } - - unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 { - // SAFETY: It's up to the caller (which in this case is the OS) to ensure that `ExceptionInfo` is valid. - unsafe { - let rec = &(*(*ExceptionInfo).ExceptionRecord); - let code = rec.ExceptionCode; - - if code == c::EXCEPTION_STACK_OVERFLOW { - crate::thread::with_current_name(|name| { - let name = name.unwrap_or(""); - let tid = crate::thread::current_os_id(); - rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); - }); - } - c::EXCEPTION_CONTINUE_SEARCH - } - } - - pub unsafe fn init() { - // SAFETY: `vectored_handler` has the correct ABI and is safe to call during exception handling. - unsafe { - let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler)); - // Similar to the above, adding the stack overflow handler is allowed to fail - // but a debug assert is used so CI will still test that it normally works. - debug_assert!(!result.is_null(), "failed to install exception handler"); - } - // Set the thread stack guarantee for the main thread. - reserve_stack(); - } - - pub fn make_handler(main_thread: bool) -> super::Handler { - if !main_thread { - reserve_stack(); - } - super::Handler::null() - } - - pub unsafe fn drop_handler(_data: *mut libc::c_void) {} -} diff --git a/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs b/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs index 5604e3e6dbf42..8b4240810768b 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs @@ -1,63 +1,3 @@ -#![cfg_attr(test, allow(dead_code))] -#![forbid(unsafe_op_in_unsafe_fn)] - -pub use self::imp::init; -use self::imp::{drop_handler, make_handler}; - -pub struct Handler { - data: *mut libc::c_void, -} - -impl Handler { - pub unsafe fn new() -> Handler { - make_handler(false) - } - - fn null() -> Handler { - Handler { data: crate::ptr::null_mut() } - } -} - -impl Drop for Handler { - fn drop(&mut self) { - unsafe { - drop_handler(self.data); - } - } -} - -#[cfg(all( - not(miri), - any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ), -))] -mod thread_info; - -// miri doesn't model signals nor stack overflows and this code has some -// synchronization properties that we don't want to expose to user code, -// hence we disable it on miri. -#[cfg(all( - not(miri), - any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ) -))] -mod imp { use libc::{ MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK, SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t, @@ -345,500 +285,3 @@ mod imp { fn sigstack_size() -> usize { libc::SIGSTKSZ } - - #[cfg(any(target_os = "solaris", target_os = "illumos"))] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: C types are always zero-initializable. - let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; - // SAFETY: - // The pointer is valid for writing a `stack_t`. - assert_eq!(unsafe { libc::stack_getbounds(&mut current_stack) }, 0); - Some(current_stack.ss_sp) - } - - #[cfg(target_os = "macos")] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: always safe to call. - let th = unsafe { libc::pthread_self() }; - // SAFETY: `th` is a valid `pthread_t`. - unsafe { - let stackptr = libc::pthread_get_stackaddr_np(th); - let stacksize = libc::pthread_get_stacksize_np(th); - Some(stackptr.map_addr(|addr| addr - stacksize)) - } - } - - #[cfg(target_os = "openbsd")] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: C types are always zero-initializable. - let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t`. - // * `&mut current_stack` is coerced to a pointer that is valid for writing - // a `stack_t`. - assert_eq!( - unsafe { libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack) }, - 0 - ); - - let stack_ptr = current_stack.ss_sp; - // SAFETY: this is always safe to call. - let stackaddr = if unsafe { libc::pthread_main_np() } == 1 { - // main thread - stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed) - } else { - // new thread - stack_ptr.addr() - current_stack.ss_size - }; - Some(stack_ptr.with_addr(stackaddr)) - } - - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "netbsd", - target_os = "hurd", - target_os = "linux", - target_os = "l4re" - ))] - fn get_stack_start() -> Option<*mut libc::c_void> { - use crate::pin::pin; - use crate::sys::helpers::COpaque; - - let mut ret = None; - let mut attr: COpaque = COpaque::uninit(); - if !cfg!(target_os = "freebsd") { - attr = COpaque::zeroed(); - } - let attr = pin!(attr); - // FIXME(pin-ergonomics): remove the next line. - let attr = attr.into_ref(); - - // SAFETY: - // The attributes object has not been initialized yet and will not be - // moved until destroyed. - #[cfg(target_os = "freebsd")] - assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` is an initialized attribute object that can be written to. - #[cfg(target_os = "freebsd")] - let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` can be written to, and will be initialized by this call. - #[cfg(not(target_os = "freebsd"))] - let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; - if e == 0 { - let mut stackaddr = crate::ptr::null_mut(); - let mut stacksize = 0; - // SAFETY: - // `attr` is an initialized attribute object and both the pointers - // are valid for writing. - assert_eq!( - unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackaddr, &mut stacksize) }, - 0 - ); - ret = Some(stackaddr); - } - if e == 0 || cfg!(target_os = "freebsd") { - // SAFETY: - // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or - // by `pthread_attr_get_np`, and is not used after this point. - assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); - } - ret - } - - fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> { - let stackptr = get_stack_start()?; - let stackaddr = stackptr.addr(); - - // Ensure stackaddr is page aligned! A parent process might - // have reset RLIMIT_STACK to be non-page aligned. The - // pthread_attr_getstack() reports the usable stack area - // stackaddr < stackaddr + stacksize, so if stackaddr is not - // page-aligned, calculate the fix such that stackaddr < - // new_page_aligned_stackaddr < stackaddr + stacksize - let remainder = stackaddr % page_size; - Some(if remainder == 0 { - stackptr - } else { - stackptr.with_addr(stackaddr + page_size - remainder) - }) - } - - /// # Safety - /// This function must only be called from the main thread, and there must - /// be sufficient stack space remaining to place a stack guard. - unsafe fn install_main_guard() -> Option> { - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - - // this way someone on any unix-y OS can check that all these compile - if cfg!(all(target_os = "linux", not(target_env = "musl"))) { - install_main_guard_linux(page_size) - } else if cfg!(all(target_os = "linux", target_env = "musl")) { - install_main_guard_linux_musl(page_size) - } else if cfg!(target_os = "freebsd") { - #[cfg(not(target_os = "freebsd"))] - return None; - // The FreeBSD code cannot be checked on non-BSDs. - #[cfg(target_os = "freebsd")] - install_main_guard_freebsd(page_size) - } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) { - install_main_guard_bsds(page_size) - } else { - // SAFETY: guaranteed by caller. - unsafe { install_main_guard_default(page_size) } - } - } - - fn install_main_guard_linux(page_size: usize) -> Option> { - // See the corresponding conditional in init(). - // Avoid stack_start_aligned, which makes slow syscalls to read /proc/self/maps - if cfg!(panic = "immediate-abort") { - return None; - } - // Linux doesn't allocate the whole stack right away, and - // the kernel has its own stack-guard mechanism to fault - // when growing too close to an existing mapping. If we map - // our own guard, then the kernel starts enforcing a rather - // large gap above that, rendering much of the possible - // stack space useless. See #43052. - // - // Instead, we'll just note where we expect rlimit to start - // faulting, so our handler can report "stack overflow", and - // trust that the kernel's own stack guard will work. - let stackptr = stack_start_aligned(page_size)?; - let stackaddr = stackptr.addr(); - Some(stackaddr - page_size..stackaddr) - } - - fn install_main_guard_linux_musl(_page_size: usize) -> Option> { - // For the main thread, the musl's pthread_attr_getstack - // returns the current stack size, rather than maximum size - // it can eventually grow to. It cannot be used to determine - // the position of kernel's stack guard. - None - } - - #[cfg(target_os = "freebsd")] - fn install_main_guard_freebsd(page_size: usize) -> Option> { - // See the corresponding conditional in install_main_guard_linux(). - if cfg!(panic = "immediate-abort") { - return None; - } - // FreeBSD's stack autogrows, and optionally includes a guard page - // at the bottom. If we try to remap the bottom of the stack - // ourselves, FreeBSD's guard page moves upwards. So we'll just use - // the builtin guard page. - let stackptr = stack_start_aligned(page_size)?; - let guardaddr = stackptr.addr(); - // Technically the number of guard pages is tunable and controlled - // by the security.bsd.stack_guard_page sysctl. - // By default it is 1, checking once is enough since it is - // a boot time config value. - // FIXME(joboet): this function is only called once, remove the caching. - static PAGES: crate::sync::OnceLock = crate::sync::OnceLock::new(); - - let pages = PAGES.get_or_init(|| { - let mut guard: usize = 0; - let mut size = size_of_val(&guard); - let oid = c"security.bsd.stack_guard_page"; - - let r = unsafe { - libc::sysctlbyname( - oid.as_ptr(), - (&raw mut guard).cast(), - &raw mut size, - ptr::null_mut(), - 0, - ) - }; - if r == 0 { guard } else { 1 } - }); - Some(guardaddr..guardaddr + pages * page_size) - } - - fn install_main_guard_bsds(page_size: usize) -> Option> { - // See the corresponding conditional in install_main_guard_linux(). - if cfg!(panic = "immediate-abort") { - return None; - } - // OpenBSD stack already includes a guard page, and stack is - // immutable. - // NetBSD stack includes the guard page. - // - // We'll just note where we expect rlimit to start - // faulting, so our handler can report "stack overflow", and - // trust that the kernel's own stack guard will work. - let stackptr = stack_start_aligned(page_size)?; - let stackaddr = stackptr.addr(); - Some(stackaddr - page_size..stackaddr) - } - - /// # Safety - /// This function must only be called from the main thread, and there must - /// be sufficient stack space remaining to place a stack guard. - unsafe fn install_main_guard_default(page_size: usize) -> Option> { - // Reallocate the last page of the stack. - // This ensures SIGBUS will be raised on - // stack overflow. - // Systems which enforce strict PAX MPROTECT do not allow - // to mprotect() a mapping with less restrictive permissions - // than the initial mmap() used, so we mmap() here with - // read/write permissions and only then mprotect() it to - // no permissions at all. See issue #50313. - let stackptr = stack_start_aligned(page_size)?; - // SAFETY: - // The memory region from `stackptr..stackptr + page_size` belongs to - // the current thread's stack, and the caller has asserted that there - // is sufficient stack space, which means that this will not overwrite - // any existing allocations. - let result = unsafe { - mmap64( - stackptr, - page_size, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON | MAP_FIXED, - -1, - 0, - ) - }; - if result != stackptr || result == MAP_FAILED { - panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); - } - - // SAFETY: - // Since this function is only called on the main thread, the stack will - // not be reused until program exit, so the runtime will never observe - // that part of the stack has been made unusable in this way. - let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) }; - if result != 0 { - panic!("failed to protect the guard page: {}", io::Error::last_os_error()); - } - - let guardaddr = stackptr.addr(); - - Some(guardaddr..guardaddr + page_size) - } - - #[cfg(any( - target_os = "macos", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ))] - fn current_guard() -> Option> { - let stackptr = get_stack_start()?; - let stackaddr = stackptr.addr(); - Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr) - } - - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "hurd", - target_os = "linux", - target_os = "netbsd", - target_os = "l4re" - ))] - fn current_guard() -> Option> { - use crate::pin::pin; - use crate::sys::helpers::COpaque; - - let mut ret = None; - - let mut attr: COpaque = COpaque::uninit(); - if !cfg!(target_os = "freebsd") { - attr = COpaque::zeroed(); - } - let attr = pin!(attr); - // FIXME(pin-ergonomics): remove the next line. - let attr = attr.into_ref(); - - // SAFETY: - // The attributes object has not been initialized yet and will not be - // moved until destroyed. - #[cfg(target_os = "freebsd")] - assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` is an initialized attribute object that can be written to. - #[cfg(target_os = "freebsd")] - let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` can be written to, and will be initialized by this call. - #[cfg(not(target_os = "freebsd"))] - let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; - if e == 0 { - let mut guardsize = 0; - // SAFETY: - // `attr` is an initialized attribute object and the pointer is valid - // for writing. - assert_eq!(unsafe { libc::pthread_attr_getguardsize(attr.get(), &mut guardsize) }, 0); - if guardsize == 0 { - if cfg!(all(target_os = "linux", target_env = "musl")) { - // musl versions before 1.1.19 always reported guard - // size obtained from pthread_attr_get_np as zero. - // Use page size as a fallback. - guardsize = PAGE_SIZE.load(Ordering::Relaxed); - } else { - panic!("there is no guard page"); - } - } - let mut stackptr = crate::ptr::null_mut::(); - let mut size = 0; - // SAFETY: - // `attr` is an initialized attribute object and both the pointers - // are valid for writing. - assert_eq!( - unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackptr, &mut size) }, - 0 - ); - - let stackaddr = stackptr.addr(); - ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) { - Some(stackaddr - guardsize..stackaddr) - } else if cfg!(all(target_os = "linux", target_env = "musl")) { - Some(stackaddr - guardsize..stackaddr) - } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc"))) - { - // glibc used to include the guard area within the stack, as noted in the BUGS - // section of `man pthread_attr_getguardsize`. This has been corrected starting - // with glibc 2.27, and in some distro backports, so the guard is now placed at the - // end (below) the stack. There's no easy way for us to know which we have at - // runtime, so we'll just match any fault in the range right above or below the - // stack base to call that fault a stack overflow. - Some(stackaddr - guardsize..stackaddr + guardsize) - } else { - Some(stackaddr..stackaddr + guardsize) - }; - } - if e == 0 || cfg!(target_os = "freebsd") { - // SAFETY: - // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or - // by `pthread_attr_get_np`, and is not used after this point. - assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); - } - ret - } -} - -// This is intentionally not enabled on iOS/tvOS/watchOS/visionOS, as it uses -// several symbols that might lead to rejections from the App Store, namely -// `sigaction`, `sigaltstack`, `sysctlbyname`, `mmap`, `munmap` and `mprotect`. -// -// This might be overly cautious, though it is also what Swift does (and they -// usually have fewer qualms about forwards compatibility, since the runtime -// is shipped with the OS): -// -#[cfg(any( - miri, - not(any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - target_os = "cygwin", - )) -))] -mod imp { - pub unsafe fn init() {} - - pub fn make_handler(_main_thread: bool) -> super::Handler { - super::Handler::null() - } - - pub unsafe fn drop_handler(_data: *mut libc::c_void) {} -} - -#[cfg(target_os = "cygwin")] -mod imp { - mod c { - pub type PVECTORED_EXCEPTION_HANDLER = - Option i32>; - pub type NTSTATUS = i32; - pub type BOOL = i32; - - unsafe extern "system" { - pub fn AddVectoredExceptionHandler( - first: u32, - handler: PVECTORED_EXCEPTION_HANDLER, - ) -> *mut core::ffi::c_void; - pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL; - } - - pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _; - pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32; - - #[repr(C)] - #[derive(Clone, Copy)] - pub struct EXCEPTION_POINTERS { - pub ExceptionRecord: *mut EXCEPTION_RECORD, - // We don't need this field here - // pub Context: *mut CONTEXT, - } - #[repr(C)] - #[derive(Clone, Copy)] - pub struct EXCEPTION_RECORD { - pub ExceptionCode: NTSTATUS, - pub ExceptionFlags: u32, - pub ExceptionRecord: *mut EXCEPTION_RECORD, - pub ExceptionAddress: *mut core::ffi::c_void, - pub NumberParameters: u32, - pub ExceptionInformation: [usize; 15], - } - } - - /// Reserve stack space for use in stack overflow exceptions. - fn reserve_stack() { - let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) }; - // Reserving stack space is not critical so we allow it to fail in the released build of libstd. - // We still use debug assert here so that CI will test that we haven't made a mistake calling the function. - debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling"); - } - - unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 { - // SAFETY: It's up to the caller (which in this case is the OS) to ensure that `ExceptionInfo` is valid. - unsafe { - let rec = &(*(*ExceptionInfo).ExceptionRecord); - let code = rec.ExceptionCode; - - if code == c::EXCEPTION_STACK_OVERFLOW { - crate::thread::with_current_name(|name| { - let name = name.unwrap_or(""); - let tid = crate::thread::current_os_id(); - rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); - }); - } - c::EXCEPTION_CONTINUE_SEARCH - } - } - - pub unsafe fn init() { - // SAFETY: `vectored_handler` has the correct ABI and is safe to call during exception handling. - unsafe { - let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler)); - // Similar to the above, adding the stack overflow handler is allowed to fail - // but a debug assert is used so CI will still test that it normally works. - debug_assert!(!result.is_null(), "failed to install exception handler"); - } - // Set the thread stack guarantee for the main thread. - reserve_stack(); - } - - pub fn make_handler(main_thread: bool) -> super::Handler { - if !main_thread { - reserve_stack(); - } - super::Handler::null() - } - - pub unsafe fn drop_handler(_data: *mut libc::c_void) {} -} diff --git a/library/std/src/sys/pal/unix/stack_overflow/mod.rs b/library/std/src/sys/pal/unix/stack_overflow/mod.rs index 5604e3e6dbf42..d87ae102f2da2 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/mod.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/mod.rs @@ -57,674 +57,7 @@ mod thread_info; target_os = "illumos", ) ))] -mod imp { - use libc::{ - MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK, - SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t, - }; - #[cfg(not(all(target_os = "linux", target_env = "gnu")))] - use libc::{mmap as mmap64, mprotect, munmap}; - #[cfg(all(target_os = "linux", target_env = "gnu"))] - use libc::{mmap64, mprotect, munmap}; - - use super::Handler; - use super::thread_info::{delete_current_info, set_current_info, with_current_info}; - use crate::ops::Range; - use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering}; - use crate::sys::pal::unix::conf; - use crate::{io, mem, ptr}; - - /// Signal handler for the SIGSEGV and SIGBUS handlers. - /// - /// We've got guard pages (unmapped pages) at the end of every thread's - /// stack, so if a thread ends up running into the guard page it'll trigger - /// this handler. We want to detect these cases and print out a helpful error - /// saying that the stack has overflowed. All other signals, however, should - /// go back to what they were originally supposed to do. - /// - /// This handler currently exists purely to print an informative message - /// whenever a thread overflows its stack. We then abort to exit and - /// indicate a crash, but to avoid a misleading SIGSEGV that might lead - /// users to believe that unsafe code has accessed an invalid pointer; the - /// SIGSEGV encountered when overflowing the stack is expected and - /// well-defined. - /// - /// If this is not a stack overflow, the handler un-registers itself and - /// then returns (to allow the original signal to be delivered again). - /// Returning from this kind of signal handler is technically not defined - /// to work when reading the POSIX spec strictly, but in practice it turns - /// out many large systems and all implementations allow returning from a - /// signal handler to work. For a more detailed explanation see the - /// comments on #26458. - /// - /// # Safety - /// Rust doesn't call this, it *gets called* by the kernel, which we expect - /// to provide valid parameters. Apart from that, this function does not - /// have any other preconditions. - unsafe extern "C" fn signal_handler( - signum: libc::c_int, - info: *mut libc::siginfo_t, - _data: *mut libc::c_void, - ) { - // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`. - let fault_addr = unsafe { (*info).si_addr().addr() }; - - // `with_current_info` expects that the process aborts after it is - // called. If the signal was not caused by a memory access, this might - // not be true. We detect this by noticing that the `si_addr` field is - // zero if the signal is synthetic. - if fault_addr != 0 { - with_current_info(|thread_info| { - // If the faulting address is within the guard page, then we print a - // message saying so and abort. - if let Some(thread_info) = thread_info - && thread_info.guard_page_range.contains(&fault_addr) - { - // Hey you! Yes, you modifying the stack overflow message! - // Please make sure that all functions called here are - // actually async-signal-safe. If they're not, try retrieving - // the information beforehand and storing it in `ThreadInfo`. - // Thank you! - // - says Jonas after having had to watch his carefully - // written code get made unsound again. - let tid = thread_info.tid; - let name = thread_info.name.as_deref().unwrap_or(""); - rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); - rtabort!("stack overflow"); - } - }) - } - - // Unregister ourselves by reverting back to the default behavior. - // SAFETY: assuming all platforms define struct sigaction as "zero-initializable" - let mut action: sigaction = unsafe { mem::zeroed() }; - action.sa_sigaction = SIG_DFL; - // SAFETY: pray this is a well-behaved POSIX implementation of fn sigaction - unsafe { sigaction(signum, &action, ptr::null_mut()) }; - - // See comment above for why this function returns. - } - - static PAGE_SIZE: Atomic = AtomicUsize::new(0); - // Store a pointer to the allocation for the main thread's altstack so that - // tools like valgrind don't complain about a leaked unreachable allocation. - // - // If the main thread exits, the process will terminate so there's no use in - // freeing resources. It also means that the altstack is still installed - // while TLS destructors are run on the main thread (c.f. #111272). - static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut()); - static NEED_ALTSTACK: Atomic = AtomicBool::new(false); - - /// # Safety - /// Must be called only once, on the main thread, during program startup. - pub unsafe fn init() { - PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed); - - // SAFETY: - // This is only called on the main thread, and since it is still early - // in the programs lifetime there is (almost) certainly enough stack - // space left to install the guard page. - let mut guard_page_range = unsafe { install_main_guard() }; - - // Even for panic=immediate-abort, installing the guard pages is important for soundness. - // That said, we do not care about giving nice stackoverflow messages via our custom - // signal handler, just exit early and let the user enjoy the segfault. - if cfg!(panic = "immediate-abort") { - return; - } - - // SAFETY: C structures are always zero-initializable. - let mut action: sigaction = unsafe { mem::zeroed() }; - for &signal in &[SIGSEGV, SIGBUS] { - // SAFETY: just fetches the current signal handler into action - unsafe { sigaction(signal, ptr::null_mut(), &mut action) }; - // We assume that overriding the signal handler is always safe, - // which might conflict with certain libraries that rely on a - // specific signal behaviour. To prevent problems, we only - // override the handler if it has not been set yet. - if action.sa_sigaction == SIG_DFL { - if !NEED_ALTSTACK.load(Ordering::Relaxed) { - // haven't set up our sigaltstack yet - NEED_ALTSTACK.store(true, Ordering::Release); - let handler = make_handler(true); - MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed); - mem::forget(handler); - - if let Some(guard_page_range) = guard_page_range.take() { - set_current_info(guard_page_range); - } - } - - action.sa_flags = SA_SIGINFO | SA_ONSTACK; - action.sa_sigaction = signal_handler - as unsafe extern "C" fn(i32, *mut libc::siginfo_t, *mut libc::c_void) - as sighandler_t; - // SAFETY: - // `&action` describes a valid `sigaction` and `signal_handler` - // is safe to use as a signal handler for `SIGSEGV` and `SIGBUS`. - unsafe { sigaction(signal, &action, ptr::null_mut()) }; - } - } - } - - fn get_stack() -> libc::stack_t { - // OpenBSD requires this flag for stack mapping - // otherwise the said mapping will fail as a no-op on most systems - // and has a different meaning on FreeBSD - #[cfg(any( - target_os = "openbsd", - target_os = "netbsd", - target_os = "linux", - target_os = "dragonfly", - ))] - let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK; - #[cfg(not(any( - target_os = "openbsd", - target_os = "netbsd", - target_os = "linux", - target_os = "dragonfly", - )))] - let flags = MAP_PRIVATE | MAP_ANON; - - let sigstack_size = sigstack_size(); - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - - // SAFETY: this does not unmap any existing pages. - let stackp = unsafe { - mmap64(ptr::null_mut(), sigstack_size + page_size, PROT_READ | PROT_WRITE, flags, -1, 0) - }; - if stackp == MAP_FAILED { - panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error()); - } - // SAFETY: this only affects the memory we just allocated. - let guard_result = unsafe { libc::mprotect(stackp, page_size, PROT_NONE) }; - if guard_result != 0 { - panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error()); - } - // SAFETY: - // The region was allocated with a larger size than `page_size`, so this - // addition is within bounds. - let stackp = unsafe { stackp.add(page_size) }; - - libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size } - } - - pub fn make_handler(main_thread: bool) -> Handler { - if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) { - return Handler::null(); - } - - if !main_thread { - if let Some(guard_page_range) = current_guard() { - set_current_info(guard_page_range); - } - } - - // Load the current alternate signal stack to see if we need to install - // our own. - // - // SAFETY: C structures are always zero-initializable. - let mut stack = unsafe { mem::zeroed() }; - // SAFETY: `&mut stack` is valid for writing a `stack_t`. - unsafe { sigaltstack(ptr::null(), &mut stack) }; - - // Configure alternate signal stack, if one is not already set. - if stack.ss_flags & SS_DISABLE != 0 { - let stack = get_stack(); - // SAFETY: - // `stack_t` is a freshly allocated stack that's not used anywhere - // else. It contains a guard page, so stack overflows in signal - // handlers will not cause undefined behaviour. We must make the - // fundamental runtime assumption that it is safe to install an - // alternate signal stack if there is none currently installed. - // This might conflict with foreign libraries that use the existence - // of an alternate signal stack as indication that certain runtime - // initialisation by the library has been performed (e.g. old - // versions of `std` assumed that certain thread-locals were already - // accessed and thus initialized in the thread if the stack overflow - // signal was successfully delivered). Such assumptions in other - // libraries are fundamentally flawed, so we pay no regard to them. - unsafe { sigaltstack(&stack, ptr::null_mut()) }; - Handler { data: stack.ss_sp as *mut libc::c_void } - } else { - Handler::null() - } - } - - /// # Safety - /// Must only be called with a pointer returned by `make_handler`, and only - /// once per `Handler`. - pub unsafe fn drop_handler(data: *mut libc::c_void) { - if !data.is_null() { - let sigstack_size = sigstack_size(); - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - let disabling_stack = libc::stack_t { - ss_sp: ptr::null_mut(), - ss_flags: SS_DISABLE, - // Workaround for bug in macOS implementation of sigaltstack - // UNIX2003 which returns ENOMEM when disabling a stack while - // passing ss_size smaller than MINSIGSTKSZ. According to POSIX - // both ss_sp and ss_size should be ignored in this case. - ss_size: sigstack_size, - }; - // SAFETY: - // We assume that disabling the alternate signal stack is always - // sound, even if the current alternate signal stack is not the one - // we installed in `make_handler`. Any stack overflows from this - // point on will abort the program when the kernel tries to write - // the signal information to the guard page. - // - // FIXME: detect if the stack has changed, and only uninstall if it hasn't. - unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) }; - // The stack returned by `get_stack` is part of a mapping that - // started one page earlier, so walk back a page and unmap from - // there. - // - // SAFETY: - // This allocation was created by us in `get_stack` and, as the - // alternate signal stack is now disabled, is no longer in use. - unsafe { munmap(data.sub(page_size), sigstack_size + page_size) }; - } - - delete_current_info(); - } - - /// Modern kernels on modern hardware can have dynamic signal stack sizes. - #[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] - fn sigstack_size() -> usize { - // SAFETY: `getauxval` is always safe to call. - let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) }; - // If getauxval couldn't find the entry, it returns 0, - // so take the higher of the "constant" and auxval. - // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ - libc::SIGSTKSZ.max(dynamic_sigstksz as _) - } - - /// Not all OS support hardware where this is needed. - #[cfg(not(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc"))))] - fn sigstack_size() -> usize { - libc::SIGSTKSZ - } - - #[cfg(any(target_os = "solaris", target_os = "illumos"))] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: C types are always zero-initializable. - let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; - // SAFETY: - // The pointer is valid for writing a `stack_t`. - assert_eq!(unsafe { libc::stack_getbounds(&mut current_stack) }, 0); - Some(current_stack.ss_sp) - } - - #[cfg(target_os = "macos")] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: always safe to call. - let th = unsafe { libc::pthread_self() }; - // SAFETY: `th` is a valid `pthread_t`. - unsafe { - let stackptr = libc::pthread_get_stackaddr_np(th); - let stacksize = libc::pthread_get_stacksize_np(th); - Some(stackptr.map_addr(|addr| addr - stacksize)) - } - } - - #[cfg(target_os = "openbsd")] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: C types are always zero-initializable. - let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t`. - // * `&mut current_stack` is coerced to a pointer that is valid for writing - // a `stack_t`. - assert_eq!( - unsafe { libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack) }, - 0 - ); - - let stack_ptr = current_stack.ss_sp; - // SAFETY: this is always safe to call. - let stackaddr = if unsafe { libc::pthread_main_np() } == 1 { - // main thread - stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed) - } else { - // new thread - stack_ptr.addr() - current_stack.ss_size - }; - Some(stack_ptr.with_addr(stackaddr)) - } - - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "netbsd", - target_os = "hurd", - target_os = "linux", - target_os = "l4re" - ))] - fn get_stack_start() -> Option<*mut libc::c_void> { - use crate::pin::pin; - use crate::sys::helpers::COpaque; - - let mut ret = None; - let mut attr: COpaque = COpaque::uninit(); - if !cfg!(target_os = "freebsd") { - attr = COpaque::zeroed(); - } - let attr = pin!(attr); - // FIXME(pin-ergonomics): remove the next line. - let attr = attr.into_ref(); - - // SAFETY: - // The attributes object has not been initialized yet and will not be - // moved until destroyed. - #[cfg(target_os = "freebsd")] - assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` is an initialized attribute object that can be written to. - #[cfg(target_os = "freebsd")] - let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` can be written to, and will be initialized by this call. - #[cfg(not(target_os = "freebsd"))] - let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; - if e == 0 { - let mut stackaddr = crate::ptr::null_mut(); - let mut stacksize = 0; - // SAFETY: - // `attr` is an initialized attribute object and both the pointers - // are valid for writing. - assert_eq!( - unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackaddr, &mut stacksize) }, - 0 - ); - ret = Some(stackaddr); - } - if e == 0 || cfg!(target_os = "freebsd") { - // SAFETY: - // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or - // by `pthread_attr_get_np`, and is not used after this point. - assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); - } - ret - } - - fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> { - let stackptr = get_stack_start()?; - let stackaddr = stackptr.addr(); - - // Ensure stackaddr is page aligned! A parent process might - // have reset RLIMIT_STACK to be non-page aligned. The - // pthread_attr_getstack() reports the usable stack area - // stackaddr < stackaddr + stacksize, so if stackaddr is not - // page-aligned, calculate the fix such that stackaddr < - // new_page_aligned_stackaddr < stackaddr + stacksize - let remainder = stackaddr % page_size; - Some(if remainder == 0 { - stackptr - } else { - stackptr.with_addr(stackaddr + page_size - remainder) - }) - } - - /// # Safety - /// This function must only be called from the main thread, and there must - /// be sufficient stack space remaining to place a stack guard. - unsafe fn install_main_guard() -> Option> { - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - - // this way someone on any unix-y OS can check that all these compile - if cfg!(all(target_os = "linux", not(target_env = "musl"))) { - install_main_guard_linux(page_size) - } else if cfg!(all(target_os = "linux", target_env = "musl")) { - install_main_guard_linux_musl(page_size) - } else if cfg!(target_os = "freebsd") { - #[cfg(not(target_os = "freebsd"))] - return None; - // The FreeBSD code cannot be checked on non-BSDs. - #[cfg(target_os = "freebsd")] - install_main_guard_freebsd(page_size) - } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) { - install_main_guard_bsds(page_size) - } else { - // SAFETY: guaranteed by caller. - unsafe { install_main_guard_default(page_size) } - } - } - - fn install_main_guard_linux(page_size: usize) -> Option> { - // See the corresponding conditional in init(). - // Avoid stack_start_aligned, which makes slow syscalls to read /proc/self/maps - if cfg!(panic = "immediate-abort") { - return None; - } - // Linux doesn't allocate the whole stack right away, and - // the kernel has its own stack-guard mechanism to fault - // when growing too close to an existing mapping. If we map - // our own guard, then the kernel starts enforcing a rather - // large gap above that, rendering much of the possible - // stack space useless. See #43052. - // - // Instead, we'll just note where we expect rlimit to start - // faulting, so our handler can report "stack overflow", and - // trust that the kernel's own stack guard will work. - let stackptr = stack_start_aligned(page_size)?; - let stackaddr = stackptr.addr(); - Some(stackaddr - page_size..stackaddr) - } - - fn install_main_guard_linux_musl(_page_size: usize) -> Option> { - // For the main thread, the musl's pthread_attr_getstack - // returns the current stack size, rather than maximum size - // it can eventually grow to. It cannot be used to determine - // the position of kernel's stack guard. - None - } - - #[cfg(target_os = "freebsd")] - fn install_main_guard_freebsd(page_size: usize) -> Option> { - // See the corresponding conditional in install_main_guard_linux(). - if cfg!(panic = "immediate-abort") { - return None; - } - // FreeBSD's stack autogrows, and optionally includes a guard page - // at the bottom. If we try to remap the bottom of the stack - // ourselves, FreeBSD's guard page moves upwards. So we'll just use - // the builtin guard page. - let stackptr = stack_start_aligned(page_size)?; - let guardaddr = stackptr.addr(); - // Technically the number of guard pages is tunable and controlled - // by the security.bsd.stack_guard_page sysctl. - // By default it is 1, checking once is enough since it is - // a boot time config value. - // FIXME(joboet): this function is only called once, remove the caching. - static PAGES: crate::sync::OnceLock = crate::sync::OnceLock::new(); - - let pages = PAGES.get_or_init(|| { - let mut guard: usize = 0; - let mut size = size_of_val(&guard); - let oid = c"security.bsd.stack_guard_page"; - - let r = unsafe { - libc::sysctlbyname( - oid.as_ptr(), - (&raw mut guard).cast(), - &raw mut size, - ptr::null_mut(), - 0, - ) - }; - if r == 0 { guard } else { 1 } - }); - Some(guardaddr..guardaddr + pages * page_size) - } - - fn install_main_guard_bsds(page_size: usize) -> Option> { - // See the corresponding conditional in install_main_guard_linux(). - if cfg!(panic = "immediate-abort") { - return None; - } - // OpenBSD stack already includes a guard page, and stack is - // immutable. - // NetBSD stack includes the guard page. - // - // We'll just note where we expect rlimit to start - // faulting, so our handler can report "stack overflow", and - // trust that the kernel's own stack guard will work. - let stackptr = stack_start_aligned(page_size)?; - let stackaddr = stackptr.addr(); - Some(stackaddr - page_size..stackaddr) - } - - /// # Safety - /// This function must only be called from the main thread, and there must - /// be sufficient stack space remaining to place a stack guard. - unsafe fn install_main_guard_default(page_size: usize) -> Option> { - // Reallocate the last page of the stack. - // This ensures SIGBUS will be raised on - // stack overflow. - // Systems which enforce strict PAX MPROTECT do not allow - // to mprotect() a mapping with less restrictive permissions - // than the initial mmap() used, so we mmap() here with - // read/write permissions and only then mprotect() it to - // no permissions at all. See issue #50313. - let stackptr = stack_start_aligned(page_size)?; - // SAFETY: - // The memory region from `stackptr..stackptr + page_size` belongs to - // the current thread's stack, and the caller has asserted that there - // is sufficient stack space, which means that this will not overwrite - // any existing allocations. - let result = unsafe { - mmap64( - stackptr, - page_size, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON | MAP_FIXED, - -1, - 0, - ) - }; - if result != stackptr || result == MAP_FAILED { - panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); - } - - // SAFETY: - // Since this function is only called on the main thread, the stack will - // not be reused until program exit, so the runtime will never observe - // that part of the stack has been made unusable in this way. - let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) }; - if result != 0 { - panic!("failed to protect the guard page: {}", io::Error::last_os_error()); - } - - let guardaddr = stackptr.addr(); - - Some(guardaddr..guardaddr + page_size) - } - - #[cfg(any( - target_os = "macos", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ))] - fn current_guard() -> Option> { - let stackptr = get_stack_start()?; - let stackaddr = stackptr.addr(); - Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr) - } - - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "hurd", - target_os = "linux", - target_os = "netbsd", - target_os = "l4re" - ))] - fn current_guard() -> Option> { - use crate::pin::pin; - use crate::sys::helpers::COpaque; - - let mut ret = None; - - let mut attr: COpaque = COpaque::uninit(); - if !cfg!(target_os = "freebsd") { - attr = COpaque::zeroed(); - } - let attr = pin!(attr); - // FIXME(pin-ergonomics): remove the next line. - let attr = attr.into_ref(); - - // SAFETY: - // The attributes object has not been initialized yet and will not be - // moved until destroyed. - #[cfg(target_os = "freebsd")] - assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` is an initialized attribute object that can be written to. - #[cfg(target_os = "freebsd")] - let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` can be written to, and will be initialized by this call. - #[cfg(not(target_os = "freebsd"))] - let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; - if e == 0 { - let mut guardsize = 0; - // SAFETY: - // `attr` is an initialized attribute object and the pointer is valid - // for writing. - assert_eq!(unsafe { libc::pthread_attr_getguardsize(attr.get(), &mut guardsize) }, 0); - if guardsize == 0 { - if cfg!(all(target_os = "linux", target_env = "musl")) { - // musl versions before 1.1.19 always reported guard - // size obtained from pthread_attr_get_np as zero. - // Use page size as a fallback. - guardsize = PAGE_SIZE.load(Ordering::Relaxed); - } else { - panic!("there is no guard page"); - } - } - let mut stackptr = crate::ptr::null_mut::(); - let mut size = 0; - // SAFETY: - // `attr` is an initialized attribute object and both the pointers - // are valid for writing. - assert_eq!( - unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackptr, &mut size) }, - 0 - ); - - let stackaddr = stackptr.addr(); - ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) { - Some(stackaddr - guardsize..stackaddr) - } else if cfg!(all(target_os = "linux", target_env = "musl")) { - Some(stackaddr - guardsize..stackaddr) - } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc"))) - { - // glibc used to include the guard area within the stack, as noted in the BUGS - // section of `man pthread_attr_getguardsize`. This has been corrected starting - // with glibc 2.27, and in some distro backports, so the guard is now placed at the - // end (below) the stack. There's no easy way for us to know which we have at - // runtime, so we'll just match any fault in the range right above or below the - // stack base to call that fault a stack overflow. - Some(stackaddr - guardsize..stackaddr + guardsize) - } else { - Some(stackaddr..stackaddr + guardsize) - }; - } - if e == 0 || cfg!(target_os = "freebsd") { - // SAFETY: - // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or - // by `pthread_attr_get_np`, and is not used after this point. - assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); - } - ret - } -} +mod imp; // This is intentionally not enabled on iOS/tvOS/watchOS/visionOS, as it uses // several symbols that might lead to rejections from the App Store, namely @@ -748,97 +81,7 @@ mod imp { target_os = "cygwin", )) ))] -mod imp { - pub unsafe fn init() {} - - pub fn make_handler(_main_thread: bool) -> super::Handler { - super::Handler::null() - } - - pub unsafe fn drop_handler(_data: *mut libc::c_void) {} -} +mod imp; #[cfg(target_os = "cygwin")] -mod imp { - mod c { - pub type PVECTORED_EXCEPTION_HANDLER = - Option i32>; - pub type NTSTATUS = i32; - pub type BOOL = i32; - - unsafe extern "system" { - pub fn AddVectoredExceptionHandler( - first: u32, - handler: PVECTORED_EXCEPTION_HANDLER, - ) -> *mut core::ffi::c_void; - pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL; - } - - pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _; - pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32; - - #[repr(C)] - #[derive(Clone, Copy)] - pub struct EXCEPTION_POINTERS { - pub ExceptionRecord: *mut EXCEPTION_RECORD, - // We don't need this field here - // pub Context: *mut CONTEXT, - } - #[repr(C)] - #[derive(Clone, Copy)] - pub struct EXCEPTION_RECORD { - pub ExceptionCode: NTSTATUS, - pub ExceptionFlags: u32, - pub ExceptionRecord: *mut EXCEPTION_RECORD, - pub ExceptionAddress: *mut core::ffi::c_void, - pub NumberParameters: u32, - pub ExceptionInformation: [usize; 15], - } - } - - /// Reserve stack space for use in stack overflow exceptions. - fn reserve_stack() { - let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) }; - // Reserving stack space is not critical so we allow it to fail in the released build of libstd. - // We still use debug assert here so that CI will test that we haven't made a mistake calling the function. - debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling"); - } - - unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 { - // SAFETY: It's up to the caller (which in this case is the OS) to ensure that `ExceptionInfo` is valid. - unsafe { - let rec = &(*(*ExceptionInfo).ExceptionRecord); - let code = rec.ExceptionCode; - - if code == c::EXCEPTION_STACK_OVERFLOW { - crate::thread::with_current_name(|name| { - let name = name.unwrap_or(""); - let tid = crate::thread::current_os_id(); - rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); - }); - } - c::EXCEPTION_CONTINUE_SEARCH - } - } - - pub unsafe fn init() { - // SAFETY: `vectored_handler` has the correct ABI and is safe to call during exception handling. - unsafe { - let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler)); - // Similar to the above, adding the stack overflow handler is allowed to fail - // but a debug assert is used so CI will still test that it normally works. - debug_assert!(!result.is_null(), "failed to install exception handler"); - } - // Set the thread stack guarantee for the main thread. - reserve_stack(); - } - - pub fn make_handler(main_thread: bool) -> super::Handler { - if !main_thread { - reserve_stack(); - } - super::Handler::null() - } - - pub unsafe fn drop_handler(_data: *mut libc::c_void) {} -} +mod imp; From cec6074a46160ce6bdfcf942d4e30e7f21374fb1 Mon Sep 17 00:00:00 2001 From: joboet Date: Wed, 9 Sep 2026 14:54:15 +0200 Subject: [PATCH 3/4] std: refactor UNIX stack overflow code (reformat) --- .../sys/pal/unix/stack_overflow/guard_page.rs | 671 +++++++++--------- .../pal/unix/stack_overflow/handler_cygwin.rs | 134 ++-- .../pal/unix/stack_overflow/handler_none.rs | 10 +- .../pal/unix/stack_overflow/handler_signal.rs | 514 +++++++------- 4 files changed, 661 insertions(+), 668 deletions(-) diff --git a/library/std/src/sys/pal/unix/stack_overflow/guard_page.rs b/library/std/src/sys/pal/unix/stack_overflow/guard_page.rs index 12a87269870fc..777a02f4b38bb 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/guard_page.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/guard_page.rs @@ -1,378 +1,371 @@ - #[cfg(any(target_os = "solaris", target_os = "illumos"))] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: C types are always zero-initializable. - let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; - // SAFETY: - // The pointer is valid for writing a `stack_t`. - assert_eq!(unsafe { libc::stack_getbounds(&mut current_stack) }, 0); - Some(current_stack.ss_sp) +#[cfg(any(target_os = "solaris", target_os = "illumos"))] +fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: C types are always zero-initializable. + let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; + // SAFETY: + // The pointer is valid for writing a `stack_t`. + assert_eq!(unsafe { libc::stack_getbounds(&mut current_stack) }, 0); + Some(current_stack.ss_sp) +} + +#[cfg(target_os = "macos")] +fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: always safe to call. + let th = unsafe { libc::pthread_self() }; + // SAFETY: `th` is a valid `pthread_t`. + unsafe { + let stackptr = libc::pthread_get_stackaddr_np(th); + let stacksize = libc::pthread_get_stacksize_np(th); + Some(stackptr.map_addr(|addr| addr - stacksize)) } +} - #[cfg(target_os = "macos")] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: always safe to call. - let th = unsafe { libc::pthread_self() }; - // SAFETY: `th` is a valid `pthread_t`. - unsafe { - let stackptr = libc::pthread_get_stackaddr_np(th); - let stacksize = libc::pthread_get_stacksize_np(th); - Some(stackptr.map_addr(|addr| addr - stacksize)) - } +#[cfg(target_os = "openbsd")] +fn get_stack_start() -> Option<*mut libc::c_void> { + // SAFETY: C types are always zero-initializable. + let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t`. + // * `&mut current_stack` is coerced to a pointer that is valid for writing + // a `stack_t`. + assert_eq!(unsafe { libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack) }, 0); + + let stack_ptr = current_stack.ss_sp; + // SAFETY: this is always safe to call. + let stackaddr = if unsafe { libc::pthread_main_np() } == 1 { + // main thread + stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed) + } else { + // new thread + stack_ptr.addr() - current_stack.ss_size + }; + Some(stack_ptr.with_addr(stackaddr)) +} + +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "netbsd", + target_os = "hurd", + target_os = "linux", + target_os = "l4re" +))] +fn get_stack_start() -> Option<*mut libc::c_void> { + use crate::pin::pin; + use crate::sys::helpers::COpaque; + + let mut ret = None; + let mut attr: COpaque = COpaque::uninit(); + if !cfg!(target_os = "freebsd") { + attr = COpaque::zeroed(); } + let attr = pin!(attr); + // FIXME(pin-ergonomics): remove the next line. + let attr = attr.into_ref(); - #[cfg(target_os = "openbsd")] - fn get_stack_start() -> Option<*mut libc::c_void> { - // SAFETY: C types are always zero-initializable. - let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; + // SAFETY: + // The attributes object has not been initialized yet and will not be + // moved until destroyed. + #[cfg(target_os = "freebsd")] + assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` is an initialized attribute object that can be written to. + #[cfg(target_os = "freebsd")] + let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` can be written to, and will be initialized by this call. + #[cfg(not(target_os = "freebsd"))] + let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; + if e == 0 { + let mut stackaddr = crate::ptr::null_mut(); + let mut stacksize = 0; // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t`. - // * `&mut current_stack` is coerced to a pointer that is valid for writing - // a `stack_t`. + // `attr` is an initialized attribute object and both the pointers + // are valid for writing. assert_eq!( - unsafe { libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack) }, + unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackaddr, &mut stacksize) }, 0 ); - - let stack_ptr = current_stack.ss_sp; - // SAFETY: this is always safe to call. - let stackaddr = if unsafe { libc::pthread_main_np() } == 1 { - // main thread - stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed) - } else { - // new thread - stack_ptr.addr() - current_stack.ss_size - }; - Some(stack_ptr.with_addr(stackaddr)) + ret = Some(stackaddr); } - - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "netbsd", - target_os = "hurd", - target_os = "linux", - target_os = "l4re" - ))] - fn get_stack_start() -> Option<*mut libc::c_void> { - use crate::pin::pin; - use crate::sys::helpers::COpaque; - - let mut ret = None; - let mut attr: COpaque = COpaque::uninit(); - if !cfg!(target_os = "freebsd") { - attr = COpaque::zeroed(); - } - let attr = pin!(attr); - // FIXME(pin-ergonomics): remove the next line. - let attr = attr.into_ref(); - - // SAFETY: - // The attributes object has not been initialized yet and will not be - // moved until destroyed. - #[cfg(target_os = "freebsd")] - assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` is an initialized attribute object that can be written to. - #[cfg(target_os = "freebsd")] - let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; + if e == 0 || cfg!(target_os = "freebsd") { // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` can be written to, and will be initialized by this call. - #[cfg(not(target_os = "freebsd"))] - let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; - if e == 0 { - let mut stackaddr = crate::ptr::null_mut(); - let mut stacksize = 0; - // SAFETY: - // `attr` is an initialized attribute object and both the pointers - // are valid for writing. - assert_eq!( - unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackaddr, &mut stacksize) }, - 0 - ); - ret = Some(stackaddr); - } - if e == 0 || cfg!(target_os = "freebsd") { - // SAFETY: - // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or - // by `pthread_attr_get_np`, and is not used after this point. - assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); - } - ret + // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or + // by `pthread_attr_get_np`, and is not used after this point. + assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); } + ret +} - fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> { - let stackptr = get_stack_start()?; - let stackaddr = stackptr.addr(); +fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> { + let stackptr = get_stack_start()?; + let stackaddr = stackptr.addr(); - // Ensure stackaddr is page aligned! A parent process might - // have reset RLIMIT_STACK to be non-page aligned. The - // pthread_attr_getstack() reports the usable stack area - // stackaddr < stackaddr + stacksize, so if stackaddr is not - // page-aligned, calculate the fix such that stackaddr < - // new_page_aligned_stackaddr < stackaddr + stacksize - let remainder = stackaddr % page_size; - Some(if remainder == 0 { - stackptr - } else { - stackptr.with_addr(stackaddr + page_size - remainder) - }) - } + // Ensure stackaddr is page aligned! A parent process might + // have reset RLIMIT_STACK to be non-page aligned. The + // pthread_attr_getstack() reports the usable stack area + // stackaddr < stackaddr + stacksize, so if stackaddr is not + // page-aligned, calculate the fix such that stackaddr < + // new_page_aligned_stackaddr < stackaddr + stacksize + let remainder = stackaddr % page_size; + Some(if remainder == 0 { + stackptr + } else { + stackptr.with_addr(stackaddr + page_size - remainder) + }) +} - /// # Safety - /// This function must only be called from the main thread, and there must - /// be sufficient stack space remaining to place a stack guard. - unsafe fn install_main_guard() -> Option> { - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - - // this way someone on any unix-y OS can check that all these compile - if cfg!(all(target_os = "linux", not(target_env = "musl"))) { - install_main_guard_linux(page_size) - } else if cfg!(all(target_os = "linux", target_env = "musl")) { - install_main_guard_linux_musl(page_size) - } else if cfg!(target_os = "freebsd") { - #[cfg(not(target_os = "freebsd"))] - return None; - // The FreeBSD code cannot be checked on non-BSDs. - #[cfg(target_os = "freebsd")] - install_main_guard_freebsd(page_size) - } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) { - install_main_guard_bsds(page_size) - } else { - // SAFETY: guaranteed by caller. - unsafe { install_main_guard_default(page_size) } - } - } +/// # Safety +/// This function must only be called from the main thread, and there must +/// be sufficient stack space remaining to place a stack guard. +unsafe fn install_main_guard() -> Option> { + let page_size = PAGE_SIZE.load(Ordering::Relaxed); - fn install_main_guard_linux(page_size: usize) -> Option> { - // See the corresponding conditional in init(). - // Avoid stack_start_aligned, which makes slow syscalls to read /proc/self/maps - if cfg!(panic = "immediate-abort") { - return None; - } - // Linux doesn't allocate the whole stack right away, and - // the kernel has its own stack-guard mechanism to fault - // when growing too close to an existing mapping. If we map - // our own guard, then the kernel starts enforcing a rather - // large gap above that, rendering much of the possible - // stack space useless. See #43052. - // - // Instead, we'll just note where we expect rlimit to start - // faulting, so our handler can report "stack overflow", and - // trust that the kernel's own stack guard will work. - let stackptr = stack_start_aligned(page_size)?; - let stackaddr = stackptr.addr(); - Some(stackaddr - page_size..stackaddr) + // this way someone on any unix-y OS can check that all these compile + if cfg!(all(target_os = "linux", not(target_env = "musl"))) { + install_main_guard_linux(page_size) + } else if cfg!(all(target_os = "linux", target_env = "musl")) { + install_main_guard_linux_musl(page_size) + } else if cfg!(target_os = "freebsd") { + #[cfg(not(target_os = "freebsd"))] + return None; + // The FreeBSD code cannot be checked on non-BSDs. + #[cfg(target_os = "freebsd")] + install_main_guard_freebsd(page_size) + } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) { + install_main_guard_bsds(page_size) + } else { + // SAFETY: guaranteed by caller. + unsafe { install_main_guard_default(page_size) } } +} - fn install_main_guard_linux_musl(_page_size: usize) -> Option> { - // For the main thread, the musl's pthread_attr_getstack - // returns the current stack size, rather than maximum size - // it can eventually grow to. It cannot be used to determine - // the position of kernel's stack guard. - None +fn install_main_guard_linux(page_size: usize) -> Option> { + // See the corresponding conditional in init(). + // Avoid stack_start_aligned, which makes slow syscalls to read /proc/self/maps + if cfg!(panic = "immediate-abort") { + return None; } + // Linux doesn't allocate the whole stack right away, and + // the kernel has its own stack-guard mechanism to fault + // when growing too close to an existing mapping. If we map + // our own guard, then the kernel starts enforcing a rather + // large gap above that, rendering much of the possible + // stack space useless. See #43052. + // + // Instead, we'll just note where we expect rlimit to start + // faulting, so our handler can report "stack overflow", and + // trust that the kernel's own stack guard will work. + let stackptr = stack_start_aligned(page_size)?; + let stackaddr = stackptr.addr(); + Some(stackaddr - page_size..stackaddr) +} - #[cfg(target_os = "freebsd")] - fn install_main_guard_freebsd(page_size: usize) -> Option> { - // See the corresponding conditional in install_main_guard_linux(). - if cfg!(panic = "immediate-abort") { - return None; - } - // FreeBSD's stack autogrows, and optionally includes a guard page - // at the bottom. If we try to remap the bottom of the stack - // ourselves, FreeBSD's guard page moves upwards. So we'll just use - // the builtin guard page. - let stackptr = stack_start_aligned(page_size)?; - let guardaddr = stackptr.addr(); - // Technically the number of guard pages is tunable and controlled - // by the security.bsd.stack_guard_page sysctl. - // By default it is 1, checking once is enough since it is - // a boot time config value. - // FIXME(joboet): this function is only called once, remove the caching. - static PAGES: crate::sync::OnceLock = crate::sync::OnceLock::new(); +fn install_main_guard_linux_musl(_page_size: usize) -> Option> { + // For the main thread, the musl's pthread_attr_getstack + // returns the current stack size, rather than maximum size + // it can eventually grow to. It cannot be used to determine + // the position of kernel's stack guard. + None +} - let pages = PAGES.get_or_init(|| { - let mut guard: usize = 0; - let mut size = size_of_val(&guard); - let oid = c"security.bsd.stack_guard_page"; - - let r = unsafe { - libc::sysctlbyname( - oid.as_ptr(), - (&raw mut guard).cast(), - &raw mut size, - ptr::null_mut(), - 0, - ) - }; - if r == 0 { guard } else { 1 } - }); - Some(guardaddr..guardaddr + pages * page_size) +#[cfg(target_os = "freebsd")] +fn install_main_guard_freebsd(page_size: usize) -> Option> { + // See the corresponding conditional in install_main_guard_linux(). + if cfg!(panic = "immediate-abort") { + return None; } + // FreeBSD's stack autogrows, and optionally includes a guard page + // at the bottom. If we try to remap the bottom of the stack + // ourselves, FreeBSD's guard page moves upwards. So we'll just use + // the builtin guard page. + let stackptr = stack_start_aligned(page_size)?; + let guardaddr = stackptr.addr(); + // Technically the number of guard pages is tunable and controlled + // by the security.bsd.stack_guard_page sysctl. + // By default it is 1, checking once is enough since it is + // a boot time config value. + // FIXME(joboet): this function is only called once, remove the caching. + static PAGES: crate::sync::OnceLock = crate::sync::OnceLock::new(); - fn install_main_guard_bsds(page_size: usize) -> Option> { - // See the corresponding conditional in install_main_guard_linux(). - if cfg!(panic = "immediate-abort") { - return None; - } - // OpenBSD stack already includes a guard page, and stack is - // immutable. - // NetBSD stack includes the guard page. - // - // We'll just note where we expect rlimit to start - // faulting, so our handler can report "stack overflow", and - // trust that the kernel's own stack guard will work. - let stackptr = stack_start_aligned(page_size)?; - let stackaddr = stackptr.addr(); - Some(stackaddr - page_size..stackaddr) - } + let pages = PAGES.get_or_init(|| { + let mut guard: usize = 0; + let mut size = size_of_val(&guard); + let oid = c"security.bsd.stack_guard_page"; - /// # Safety - /// This function must only be called from the main thread, and there must - /// be sufficient stack space remaining to place a stack guard. - unsafe fn install_main_guard_default(page_size: usize) -> Option> { - // Reallocate the last page of the stack. - // This ensures SIGBUS will be raised on - // stack overflow. - // Systems which enforce strict PAX MPROTECT do not allow - // to mprotect() a mapping with less restrictive permissions - // than the initial mmap() used, so we mmap() here with - // read/write permissions and only then mprotect() it to - // no permissions at all. See issue #50313. - let stackptr = stack_start_aligned(page_size)?; - // SAFETY: - // The memory region from `stackptr..stackptr + page_size` belongs to - // the current thread's stack, and the caller has asserted that there - // is sufficient stack space, which means that this will not overwrite - // any existing allocations. - let result = unsafe { - mmap64( - stackptr, - page_size, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON | MAP_FIXED, - -1, + let r = unsafe { + libc::sysctlbyname( + oid.as_ptr(), + (&raw mut guard).cast(), + &raw mut size, + ptr::null_mut(), 0, ) }; - if result != stackptr || result == MAP_FAILED { - panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); - } - - // SAFETY: - // Since this function is only called on the main thread, the stack will - // not be reused until program exit, so the runtime will never observe - // that part of the stack has been made unusable in this way. - let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) }; - if result != 0 { - panic!("failed to protect the guard page: {}", io::Error::last_os_error()); - } + if r == 0 { guard } else { 1 } + }); + Some(guardaddr..guardaddr + pages * page_size) +} - let guardaddr = stackptr.addr(); +fn install_main_guard_bsds(page_size: usize) -> Option> { + // See the corresponding conditional in install_main_guard_linux(). + if cfg!(panic = "immediate-abort") { + return None; + } + // OpenBSD stack already includes a guard page, and stack is + // immutable. + // NetBSD stack includes the guard page. + // + // We'll just note where we expect rlimit to start + // faulting, so our handler can report "stack overflow", and + // trust that the kernel's own stack guard will work. + let stackptr = stack_start_aligned(page_size)?; + let stackaddr = stackptr.addr(); + Some(stackaddr - page_size..stackaddr) +} - Some(guardaddr..guardaddr + page_size) +/// # Safety +/// This function must only be called from the main thread, and there must +/// be sufficient stack space remaining to place a stack guard. +unsafe fn install_main_guard_default(page_size: usize) -> Option> { + // Reallocate the last page of the stack. + // This ensures SIGBUS will be raised on + // stack overflow. + // Systems which enforce strict PAX MPROTECT do not allow + // to mprotect() a mapping with less restrictive permissions + // than the initial mmap() used, so we mmap() here with + // read/write permissions and only then mprotect() it to + // no permissions at all. See issue #50313. + let stackptr = stack_start_aligned(page_size)?; + // SAFETY: + // The memory region from `stackptr..stackptr + page_size` belongs to + // the current thread's stack, and the caller has asserted that there + // is sufficient stack space, which means that this will not overwrite + // any existing allocations. + let result = unsafe { + mmap64( + stackptr, + page_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, + -1, + 0, + ) + }; + if result != stackptr || result == MAP_FAILED { + panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); } - #[cfg(any( - target_os = "macos", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ))] - fn current_guard() -> Option> { - let stackptr = get_stack_start()?; - let stackaddr = stackptr.addr(); - Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr) + // SAFETY: + // Since this function is only called on the main thread, the stack will + // not be reused until program exit, so the runtime will never observe + // that part of the stack has been made unusable in this way. + let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) }; + if result != 0 { + panic!("failed to protect the guard page: {}", io::Error::last_os_error()); } - #[cfg(any( - target_os = "android", - target_os = "freebsd", - target_os = "hurd", - target_os = "linux", - target_os = "netbsd", - target_os = "l4re" - ))] - fn current_guard() -> Option> { - use crate::pin::pin; - use crate::sys::helpers::COpaque; + let guardaddr = stackptr.addr(); - let mut ret = None; + Some(guardaddr..guardaddr + page_size) +} - let mut attr: COpaque = COpaque::uninit(); - if !cfg!(target_os = "freebsd") { - attr = COpaque::zeroed(); - } - let attr = pin!(attr); - // FIXME(pin-ergonomics): remove the next line. - let attr = attr.into_ref(); +#[cfg(any( + target_os = "macos", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", +))] +fn current_guard() -> Option> { + let stackptr = get_stack_start()?; + let stackaddr = stackptr.addr(); + Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr) +} - // SAFETY: - // The attributes object has not been initialized yet and will not be - // moved until destroyed. - #[cfg(target_os = "freebsd")] - assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` is an initialized attribute object that can be written to. - #[cfg(target_os = "freebsd")] - let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; - // SAFETY: - // * calling `pthread_self` is always valid and returns a valid `pthread_t` - // * `attr` can be written to, and will be initialized by this call. - #[cfg(not(target_os = "freebsd"))] - let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; - if e == 0 { - let mut guardsize = 0; - // SAFETY: - // `attr` is an initialized attribute object and the pointer is valid - // for writing. - assert_eq!(unsafe { libc::pthread_attr_getguardsize(attr.get(), &mut guardsize) }, 0); - if guardsize == 0 { - if cfg!(all(target_os = "linux", target_env = "musl")) { - // musl versions before 1.1.19 always reported guard - // size obtained from pthread_attr_get_np as zero. - // Use page size as a fallback. - guardsize = PAGE_SIZE.load(Ordering::Relaxed); - } else { - panic!("there is no guard page"); - } - } - let mut stackptr = crate::ptr::null_mut::(); - let mut size = 0; - // SAFETY: - // `attr` is an initialized attribute object and both the pointers - // are valid for writing. - assert_eq!( - unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackptr, &mut size) }, - 0 - ); +#[cfg(any( + target_os = "android", + target_os = "freebsd", + target_os = "hurd", + target_os = "linux", + target_os = "netbsd", + target_os = "l4re" +))] +fn current_guard() -> Option> { + use crate::pin::pin; + use crate::sys::helpers::COpaque; + + let mut ret = None; - let stackaddr = stackptr.addr(); - ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) { - Some(stackaddr - guardsize..stackaddr) - } else if cfg!(all(target_os = "linux", target_env = "musl")) { - Some(stackaddr - guardsize..stackaddr) - } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc"))) - { - // glibc used to include the guard area within the stack, as noted in the BUGS - // section of `man pthread_attr_getguardsize`. This has been corrected starting - // with glibc 2.27, and in some distro backports, so the guard is now placed at the - // end (below) the stack. There's no easy way for us to know which we have at - // runtime, so we'll just match any fault in the range right above or below the - // stack base to call that fault a stack overflow. - Some(stackaddr - guardsize..stackaddr + guardsize) + let mut attr: COpaque = COpaque::uninit(); + if !cfg!(target_os = "freebsd") { + attr = COpaque::zeroed(); + } + let attr = pin!(attr); + // FIXME(pin-ergonomics): remove the next line. + let attr = attr.into_ref(); + + // SAFETY: + // The attributes object has not been initialized yet and will not be + // moved until destroyed. + #[cfg(target_os = "freebsd")] + assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0); + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` is an initialized attribute object that can be written to. + #[cfg(target_os = "freebsd")] + let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) }; + // SAFETY: + // * calling `pthread_self` is always valid and returns a valid `pthread_t` + // * `attr` can be written to, and will be initialized by this call. + #[cfg(not(target_os = "freebsd"))] + let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) }; + if e == 0 { + let mut guardsize = 0; + // SAFETY: + // `attr` is an initialized attribute object and the pointer is valid + // for writing. + assert_eq!(unsafe { libc::pthread_attr_getguardsize(attr.get(), &mut guardsize) }, 0); + if guardsize == 0 { + if cfg!(all(target_os = "linux", target_env = "musl")) { + // musl versions before 1.1.19 always reported guard + // size obtained from pthread_attr_get_np as zero. + // Use page size as a fallback. + guardsize = PAGE_SIZE.load(Ordering::Relaxed); } else { - Some(stackaddr..stackaddr + guardsize) - }; - } - if e == 0 || cfg!(target_os = "freebsd") { - // SAFETY: - // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or - // by `pthread_attr_get_np`, and is not used after this point. - assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); + panic!("there is no guard page"); + } } - ret + let mut stackptr = crate::ptr::null_mut::(); + let mut size = 0; + // SAFETY: + // `attr` is an initialized attribute object and both the pointers + // are valid for writing. + assert_eq!(unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackptr, &mut size) }, 0); + + let stackaddr = stackptr.addr(); + ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) { + Some(stackaddr - guardsize..stackaddr) + } else if cfg!(all(target_os = "linux", target_env = "musl")) { + Some(stackaddr - guardsize..stackaddr) + } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc"))) { + // glibc used to include the guard area within the stack, as noted in the BUGS + // section of `man pthread_attr_getguardsize`. This has been corrected starting + // with glibc 2.27, and in some distro backports, so the guard is now placed at the + // end (below) the stack. There's no easy way for us to know which we have at + // runtime, so we'll just match any fault in the range right above or below the + // stack base to call that fault a stack overflow. + Some(stackaddr - guardsize..stackaddr + guardsize) + } else { + Some(stackaddr..stackaddr + guardsize) + }; + } + if e == 0 || cfg!(target_os = "freebsd") { + // SAFETY: + // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or + // by `pthread_attr_get_np`, and is not used after this point. + assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0); } + ret +} diff --git a/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs b/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs index 089bd2c95073d..3289efe05b068 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs @@ -1,81 +1,81 @@ - mod c { - pub type PVECTORED_EXCEPTION_HANDLER = - Option i32>; - pub type NTSTATUS = i32; - pub type BOOL = i32; +mod c { + pub type PVECTORED_EXCEPTION_HANDLER = + Option i32>; + pub type NTSTATUS = i32; + pub type BOOL = i32; - unsafe extern "system" { - pub fn AddVectoredExceptionHandler( - first: u32, - handler: PVECTORED_EXCEPTION_HANDLER, - ) -> *mut core::ffi::c_void; - pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL; - } + unsafe extern "system" { + pub fn AddVectoredExceptionHandler( + first: u32, + handler: PVECTORED_EXCEPTION_HANDLER, + ) -> *mut core::ffi::c_void; + pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL; + } - pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _; - pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32; + pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _; + pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32; - #[repr(C)] - #[derive(Clone, Copy)] - pub struct EXCEPTION_POINTERS { - pub ExceptionRecord: *mut EXCEPTION_RECORD, - // We don't need this field here - // pub Context: *mut CONTEXT, - } - #[repr(C)] - #[derive(Clone, Copy)] - pub struct EXCEPTION_RECORD { - pub ExceptionCode: NTSTATUS, - pub ExceptionFlags: u32, - pub ExceptionRecord: *mut EXCEPTION_RECORD, - pub ExceptionAddress: *mut core::ffi::c_void, - pub NumberParameters: u32, - pub ExceptionInformation: [usize; 15], - } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct EXCEPTION_POINTERS { + pub ExceptionRecord: *mut EXCEPTION_RECORD, + // We don't need this field here + // pub Context: *mut CONTEXT, } - - /// Reserve stack space for use in stack overflow exceptions. - fn reserve_stack() { - let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) }; - // Reserving stack space is not critical so we allow it to fail in the released build of libstd. - // We still use debug assert here so that CI will test that we haven't made a mistake calling the function. - debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling"); + #[repr(C)] + #[derive(Clone, Copy)] + pub struct EXCEPTION_RECORD { + pub ExceptionCode: NTSTATUS, + pub ExceptionFlags: u32, + pub ExceptionRecord: *mut EXCEPTION_RECORD, + pub ExceptionAddress: *mut core::ffi::c_void, + pub NumberParameters: u32, + pub ExceptionInformation: [usize; 15], } +} - unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 { - // SAFETY: It's up to the caller (which in this case is the OS) to ensure that `ExceptionInfo` is valid. - unsafe { - let rec = &(*(*ExceptionInfo).ExceptionRecord); - let code = rec.ExceptionCode; +/// Reserve stack space for use in stack overflow exceptions. +fn reserve_stack() { + let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) }; + // Reserving stack space is not critical so we allow it to fail in the released build of libstd. + // We still use debug assert here so that CI will test that we haven't made a mistake calling the function. + debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling"); +} - if code == c::EXCEPTION_STACK_OVERFLOW { - crate::thread::with_current_name(|name| { - let name = name.unwrap_or(""); - let tid = crate::thread::current_os_id(); - rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); - }); - } - c::EXCEPTION_CONTINUE_SEARCH +unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 { + // SAFETY: It's up to the caller (which in this case is the OS) to ensure that `ExceptionInfo` is valid. + unsafe { + let rec = &(*(*ExceptionInfo).ExceptionRecord); + let code = rec.ExceptionCode; + + if code == c::EXCEPTION_STACK_OVERFLOW { + crate::thread::with_current_name(|name| { + let name = name.unwrap_or(""); + let tid = crate::thread::current_os_id(); + rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); + }); } + c::EXCEPTION_CONTINUE_SEARCH } +} - pub unsafe fn init() { - // SAFETY: `vectored_handler` has the correct ABI and is safe to call during exception handling. - unsafe { - let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler)); - // Similar to the above, adding the stack overflow handler is allowed to fail - // but a debug assert is used so CI will still test that it normally works. - debug_assert!(!result.is_null(), "failed to install exception handler"); - } - // Set the thread stack guarantee for the main thread. - reserve_stack(); +pub unsafe fn init() { + // SAFETY: `vectored_handler` has the correct ABI and is safe to call during exception handling. + unsafe { + let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler)); + // Similar to the above, adding the stack overflow handler is allowed to fail + // but a debug assert is used so CI will still test that it normally works. + debug_assert!(!result.is_null(), "failed to install exception handler"); } + // Set the thread stack guarantee for the main thread. + reserve_stack(); +} - pub fn make_handler(main_thread: bool) -> super::Handler { - if !main_thread { - reserve_stack(); - } - super::Handler::null() +pub fn make_handler(main_thread: bool) -> super::Handler { + if !main_thread { + reserve_stack(); } + super::Handler::null() +} - pub unsafe fn drop_handler(_data: *mut libc::c_void) {} +pub unsafe fn drop_handler(_data: *mut libc::c_void) {} diff --git a/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs b/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs index f765090ffff7d..a0e2dfa941867 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs @@ -1,7 +1,7 @@ - pub unsafe fn init() {} +pub unsafe fn init() {} - pub fn make_handler(_main_thread: bool) -> super::Handler { - super::Handler::null() - } +pub fn make_handler(_main_thread: bool) -> super::Handler { + super::Handler::null() +} - pub unsafe fn drop_handler(_data: *mut libc::c_void) {} +pub unsafe fn drop_handler(_data: *mut libc::c_void) {} diff --git a/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs b/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs index 8b4240810768b..d8238f6c924b0 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs @@ -1,287 +1,287 @@ - use libc::{ - MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK, - SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t, - }; - #[cfg(not(all(target_os = "linux", target_env = "gnu")))] - use libc::{mmap as mmap64, mprotect, munmap}; - #[cfg(all(target_os = "linux", target_env = "gnu"))] - use libc::{mmap64, mprotect, munmap}; +use libc::{ + MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK, + SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t, +}; +#[cfg(not(all(target_os = "linux", target_env = "gnu")))] +use libc::{mmap as mmap64, mprotect, munmap}; +#[cfg(all(target_os = "linux", target_env = "gnu"))] +use libc::{mmap64, mprotect, munmap}; - use super::Handler; - use super::thread_info::{delete_current_info, set_current_info, with_current_info}; - use crate::ops::Range; - use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering}; - use crate::sys::pal::unix::conf; - use crate::{io, mem, ptr}; +use super::Handler; +use super::thread_info::{delete_current_info, set_current_info, with_current_info}; +use crate::ops::Range; +use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering}; +use crate::sys::pal::unix::conf; +use crate::{io, mem, ptr}; - /// Signal handler for the SIGSEGV and SIGBUS handlers. - /// - /// We've got guard pages (unmapped pages) at the end of every thread's - /// stack, so if a thread ends up running into the guard page it'll trigger - /// this handler. We want to detect these cases and print out a helpful error - /// saying that the stack has overflowed. All other signals, however, should - /// go back to what they were originally supposed to do. - /// - /// This handler currently exists purely to print an informative message - /// whenever a thread overflows its stack. We then abort to exit and - /// indicate a crash, but to avoid a misleading SIGSEGV that might lead - /// users to believe that unsafe code has accessed an invalid pointer; the - /// SIGSEGV encountered when overflowing the stack is expected and - /// well-defined. - /// - /// If this is not a stack overflow, the handler un-registers itself and - /// then returns (to allow the original signal to be delivered again). - /// Returning from this kind of signal handler is technically not defined - /// to work when reading the POSIX spec strictly, but in practice it turns - /// out many large systems and all implementations allow returning from a - /// signal handler to work. For a more detailed explanation see the - /// comments on #26458. - /// - /// # Safety - /// Rust doesn't call this, it *gets called* by the kernel, which we expect - /// to provide valid parameters. Apart from that, this function does not - /// have any other preconditions. - unsafe extern "C" fn signal_handler( - signum: libc::c_int, - info: *mut libc::siginfo_t, - _data: *mut libc::c_void, - ) { - // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`. - let fault_addr = unsafe { (*info).si_addr().addr() }; +/// Signal handler for the SIGSEGV and SIGBUS handlers. +/// +/// We've got guard pages (unmapped pages) at the end of every thread's +/// stack, so if a thread ends up running into the guard page it'll trigger +/// this handler. We want to detect these cases and print out a helpful error +/// saying that the stack has overflowed. All other signals, however, should +/// go back to what they were originally supposed to do. +/// +/// This handler currently exists purely to print an informative message +/// whenever a thread overflows its stack. We then abort to exit and +/// indicate a crash, but to avoid a misleading SIGSEGV that might lead +/// users to believe that unsafe code has accessed an invalid pointer; the +/// SIGSEGV encountered when overflowing the stack is expected and +/// well-defined. +/// +/// If this is not a stack overflow, the handler un-registers itself and +/// then returns (to allow the original signal to be delivered again). +/// Returning from this kind of signal handler is technically not defined +/// to work when reading the POSIX spec strictly, but in practice it turns +/// out many large systems and all implementations allow returning from a +/// signal handler to work. For a more detailed explanation see the +/// comments on #26458. +/// +/// # Safety +/// Rust doesn't call this, it *gets called* by the kernel, which we expect +/// to provide valid parameters. Apart from that, this function does not +/// have any other preconditions. +unsafe extern "C" fn signal_handler( + signum: libc::c_int, + info: *mut libc::siginfo_t, + _data: *mut libc::c_void, +) { + // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`. + let fault_addr = unsafe { (*info).si_addr().addr() }; - // `with_current_info` expects that the process aborts after it is - // called. If the signal was not caused by a memory access, this might - // not be true. We detect this by noticing that the `si_addr` field is - // zero if the signal is synthetic. - if fault_addr != 0 { - with_current_info(|thread_info| { - // If the faulting address is within the guard page, then we print a - // message saying so and abort. - if let Some(thread_info) = thread_info - && thread_info.guard_page_range.contains(&fault_addr) - { - // Hey you! Yes, you modifying the stack overflow message! - // Please make sure that all functions called here are - // actually async-signal-safe. If they're not, try retrieving - // the information beforehand and storing it in `ThreadInfo`. - // Thank you! - // - says Jonas after having had to watch his carefully - // written code get made unsound again. - let tid = thread_info.tid; - let name = thread_info.name.as_deref().unwrap_or(""); - rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); - rtabort!("stack overflow"); - } - }) - } + // `with_current_info` expects that the process aborts after it is + // called. If the signal was not caused by a memory access, this might + // not be true. We detect this by noticing that the `si_addr` field is + // zero if the signal is synthetic. + if fault_addr != 0 { + with_current_info(|thread_info| { + // If the faulting address is within the guard page, then we print a + // message saying so and abort. + if let Some(thread_info) = thread_info + && thread_info.guard_page_range.contains(&fault_addr) + { + // Hey you! Yes, you modifying the stack overflow message! + // Please make sure that all functions called here are + // actually async-signal-safe. If they're not, try retrieving + // the information beforehand and storing it in `ThreadInfo`. + // Thank you! + // - says Jonas after having had to watch his carefully + // written code get made unsound again. + let tid = thread_info.tid; + let name = thread_info.name.as_deref().unwrap_or(""); + rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n"); + rtabort!("stack overflow"); + } + }) + } - // Unregister ourselves by reverting back to the default behavior. - // SAFETY: assuming all platforms define struct sigaction as "zero-initializable" - let mut action: sigaction = unsafe { mem::zeroed() }; - action.sa_sigaction = SIG_DFL; - // SAFETY: pray this is a well-behaved POSIX implementation of fn sigaction - unsafe { sigaction(signum, &action, ptr::null_mut()) }; + // Unregister ourselves by reverting back to the default behavior. + // SAFETY: assuming all platforms define struct sigaction as "zero-initializable" + let mut action: sigaction = unsafe { mem::zeroed() }; + action.sa_sigaction = SIG_DFL; + // SAFETY: pray this is a well-behaved POSIX implementation of fn sigaction + unsafe { sigaction(signum, &action, ptr::null_mut()) }; - // See comment above for why this function returns. - } + // See comment above for why this function returns. +} - static PAGE_SIZE: Atomic = AtomicUsize::new(0); - // Store a pointer to the allocation for the main thread's altstack so that - // tools like valgrind don't complain about a leaked unreachable allocation. - // - // If the main thread exits, the process will terminate so there's no use in - // freeing resources. It also means that the altstack is still installed - // while TLS destructors are run on the main thread (c.f. #111272). - static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut()); - static NEED_ALTSTACK: Atomic = AtomicBool::new(false); +static PAGE_SIZE: Atomic = AtomicUsize::new(0); +// Store a pointer to the allocation for the main thread's altstack so that +// tools like valgrind don't complain about a leaked unreachable allocation. +// +// If the main thread exits, the process will terminate so there's no use in +// freeing resources. It also means that the altstack is still installed +// while TLS destructors are run on the main thread (c.f. #111272). +static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut()); +static NEED_ALTSTACK: Atomic = AtomicBool::new(false); - /// # Safety - /// Must be called only once, on the main thread, during program startup. - pub unsafe fn init() { - PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed); +/// # Safety +/// Must be called only once, on the main thread, during program startup. +pub unsafe fn init() { + PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed); - // SAFETY: - // This is only called on the main thread, and since it is still early - // in the programs lifetime there is (almost) certainly enough stack - // space left to install the guard page. - let mut guard_page_range = unsafe { install_main_guard() }; + // SAFETY: + // This is only called on the main thread, and since it is still early + // in the programs lifetime there is (almost) certainly enough stack + // space left to install the guard page. + let mut guard_page_range = unsafe { install_main_guard() }; - // Even for panic=immediate-abort, installing the guard pages is important for soundness. - // That said, we do not care about giving nice stackoverflow messages via our custom - // signal handler, just exit early and let the user enjoy the segfault. - if cfg!(panic = "immediate-abort") { - return; - } + // Even for panic=immediate-abort, installing the guard pages is important for soundness. + // That said, we do not care about giving nice stackoverflow messages via our custom + // signal handler, just exit early and let the user enjoy the segfault. + if cfg!(panic = "immediate-abort") { + return; + } - // SAFETY: C structures are always zero-initializable. - let mut action: sigaction = unsafe { mem::zeroed() }; - for &signal in &[SIGSEGV, SIGBUS] { - // SAFETY: just fetches the current signal handler into action - unsafe { sigaction(signal, ptr::null_mut(), &mut action) }; - // We assume that overriding the signal handler is always safe, - // which might conflict with certain libraries that rely on a - // specific signal behaviour. To prevent problems, we only - // override the handler if it has not been set yet. - if action.sa_sigaction == SIG_DFL { - if !NEED_ALTSTACK.load(Ordering::Relaxed) { - // haven't set up our sigaltstack yet - NEED_ALTSTACK.store(true, Ordering::Release); - let handler = make_handler(true); - MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed); - mem::forget(handler); + // SAFETY: C structures are always zero-initializable. + let mut action: sigaction = unsafe { mem::zeroed() }; + for &signal in &[SIGSEGV, SIGBUS] { + // SAFETY: just fetches the current signal handler into action + unsafe { sigaction(signal, ptr::null_mut(), &mut action) }; + // We assume that overriding the signal handler is always safe, + // which might conflict with certain libraries that rely on a + // specific signal behaviour. To prevent problems, we only + // override the handler if it has not been set yet. + if action.sa_sigaction == SIG_DFL { + if !NEED_ALTSTACK.load(Ordering::Relaxed) { + // haven't set up our sigaltstack yet + NEED_ALTSTACK.store(true, Ordering::Release); + let handler = make_handler(true); + MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed); + mem::forget(handler); - if let Some(guard_page_range) = guard_page_range.take() { - set_current_info(guard_page_range); - } + if let Some(guard_page_range) = guard_page_range.take() { + set_current_info(guard_page_range); } - - action.sa_flags = SA_SIGINFO | SA_ONSTACK; - action.sa_sigaction = signal_handler - as unsafe extern "C" fn(i32, *mut libc::siginfo_t, *mut libc::c_void) - as sighandler_t; - // SAFETY: - // `&action` describes a valid `sigaction` and `signal_handler` - // is safe to use as a signal handler for `SIGSEGV` and `SIGBUS`. - unsafe { sigaction(signal, &action, ptr::null_mut()) }; } + + action.sa_flags = SA_SIGINFO | SA_ONSTACK; + action.sa_sigaction = signal_handler + as unsafe extern "C" fn(i32, *mut libc::siginfo_t, *mut libc::c_void) + as sighandler_t; + // SAFETY: + // `&action` describes a valid `sigaction` and `signal_handler` + // is safe to use as a signal handler for `SIGSEGV` and `SIGBUS`. + unsafe { sigaction(signal, &action, ptr::null_mut()) }; } } +} - fn get_stack() -> libc::stack_t { - // OpenBSD requires this flag for stack mapping - // otherwise the said mapping will fail as a no-op on most systems - // and has a different meaning on FreeBSD - #[cfg(any( - target_os = "openbsd", - target_os = "netbsd", - target_os = "linux", - target_os = "dragonfly", - ))] - let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK; - #[cfg(not(any( - target_os = "openbsd", - target_os = "netbsd", - target_os = "linux", - target_os = "dragonfly", - )))] - let flags = MAP_PRIVATE | MAP_ANON; +fn get_stack() -> libc::stack_t { + // OpenBSD requires this flag for stack mapping + // otherwise the said mapping will fail as a no-op on most systems + // and has a different meaning on FreeBSD + #[cfg(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "linux", + target_os = "dragonfly", + ))] + let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK; + #[cfg(not(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "linux", + target_os = "dragonfly", + )))] + let flags = MAP_PRIVATE | MAP_ANON; - let sigstack_size = sigstack_size(); - let page_size = PAGE_SIZE.load(Ordering::Relaxed); + let sigstack_size = sigstack_size(); + let page_size = PAGE_SIZE.load(Ordering::Relaxed); - // SAFETY: this does not unmap any existing pages. - let stackp = unsafe { - mmap64(ptr::null_mut(), sigstack_size + page_size, PROT_READ | PROT_WRITE, flags, -1, 0) - }; - if stackp == MAP_FAILED { - panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error()); - } - // SAFETY: this only affects the memory we just allocated. - let guard_result = unsafe { libc::mprotect(stackp, page_size, PROT_NONE) }; - if guard_result != 0 { - panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error()); - } - // SAFETY: - // The region was allocated with a larger size than `page_size`, so this - // addition is within bounds. - let stackp = unsafe { stackp.add(page_size) }; - - libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size } + // SAFETY: this does not unmap any existing pages. + let stackp = unsafe { + mmap64(ptr::null_mut(), sigstack_size + page_size, PROT_READ | PROT_WRITE, flags, -1, 0) + }; + if stackp == MAP_FAILED { + panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error()); } + // SAFETY: this only affects the memory we just allocated. + let guard_result = unsafe { libc::mprotect(stackp, page_size, PROT_NONE) }; + if guard_result != 0 { + panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error()); + } + // SAFETY: + // The region was allocated with a larger size than `page_size`, so this + // addition is within bounds. + let stackp = unsafe { stackp.add(page_size) }; - pub fn make_handler(main_thread: bool) -> Handler { - if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) { - return Handler::null(); - } - - if !main_thread { - if let Some(guard_page_range) = current_guard() { - set_current_info(guard_page_range); - } - } + libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size } +} - // Load the current alternate signal stack to see if we need to install - // our own. - // - // SAFETY: C structures are always zero-initializable. - let mut stack = unsafe { mem::zeroed() }; - // SAFETY: `&mut stack` is valid for writing a `stack_t`. - unsafe { sigaltstack(ptr::null(), &mut stack) }; +pub fn make_handler(main_thread: bool) -> Handler { + if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) { + return Handler::null(); + } - // Configure alternate signal stack, if one is not already set. - if stack.ss_flags & SS_DISABLE != 0 { - let stack = get_stack(); - // SAFETY: - // `stack_t` is a freshly allocated stack that's not used anywhere - // else. It contains a guard page, so stack overflows in signal - // handlers will not cause undefined behaviour. We must make the - // fundamental runtime assumption that it is safe to install an - // alternate signal stack if there is none currently installed. - // This might conflict with foreign libraries that use the existence - // of an alternate signal stack as indication that certain runtime - // initialisation by the library has been performed (e.g. old - // versions of `std` assumed that certain thread-locals were already - // accessed and thus initialized in the thread if the stack overflow - // signal was successfully delivered). Such assumptions in other - // libraries are fundamentally flawed, so we pay no regard to them. - unsafe { sigaltstack(&stack, ptr::null_mut()) }; - Handler { data: stack.ss_sp as *mut libc::c_void } - } else { - Handler::null() + if !main_thread { + if let Some(guard_page_range) = current_guard() { + set_current_info(guard_page_range); } } - /// # Safety - /// Must only be called with a pointer returned by `make_handler`, and only - /// once per `Handler`. - pub unsafe fn drop_handler(data: *mut libc::c_void) { - if !data.is_null() { - let sigstack_size = sigstack_size(); - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - let disabling_stack = libc::stack_t { - ss_sp: ptr::null_mut(), - ss_flags: SS_DISABLE, - // Workaround for bug in macOS implementation of sigaltstack - // UNIX2003 which returns ENOMEM when disabling a stack while - // passing ss_size smaller than MINSIGSTKSZ. According to POSIX - // both ss_sp and ss_size should be ignored in this case. - ss_size: sigstack_size, - }; - // SAFETY: - // We assume that disabling the alternate signal stack is always - // sound, even if the current alternate signal stack is not the one - // we installed in `make_handler`. Any stack overflows from this - // point on will abort the program when the kernel tries to write - // the signal information to the guard page. - // - // FIXME: detect if the stack has changed, and only uninstall if it hasn't. - unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) }; - // The stack returned by `get_stack` is part of a mapping that - // started one page earlier, so walk back a page and unmap from - // there. - // - // SAFETY: - // This allocation was created by us in `get_stack` and, as the - // alternate signal stack is now disabled, is no longer in use. - unsafe { munmap(data.sub(page_size), sigstack_size + page_size) }; - } + // Load the current alternate signal stack to see if we need to install + // our own. + // + // SAFETY: C structures are always zero-initializable. + let mut stack = unsafe { mem::zeroed() }; + // SAFETY: `&mut stack` is valid for writing a `stack_t`. + unsafe { sigaltstack(ptr::null(), &mut stack) }; - delete_current_info(); + // Configure alternate signal stack, if one is not already set. + if stack.ss_flags & SS_DISABLE != 0 { + let stack = get_stack(); + // SAFETY: + // `stack_t` is a freshly allocated stack that's not used anywhere + // else. It contains a guard page, so stack overflows in signal + // handlers will not cause undefined behaviour. We must make the + // fundamental runtime assumption that it is safe to install an + // alternate signal stack if there is none currently installed. + // This might conflict with foreign libraries that use the existence + // of an alternate signal stack as indication that certain runtime + // initialisation by the library has been performed (e.g. old + // versions of `std` assumed that certain thread-locals were already + // accessed and thus initialized in the thread if the stack overflow + // signal was successfully delivered). Such assumptions in other + // libraries are fundamentally flawed, so we pay no regard to them. + unsafe { sigaltstack(&stack, ptr::null_mut()) }; + Handler { data: stack.ss_sp as *mut libc::c_void } + } else { + Handler::null() } +} - /// Modern kernels on modern hardware can have dynamic signal stack sizes. - #[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] - fn sigstack_size() -> usize { - // SAFETY: `getauxval` is always safe to call. - let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) }; - // If getauxval couldn't find the entry, it returns 0, - // so take the higher of the "constant" and auxval. - // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ - libc::SIGSTKSZ.max(dynamic_sigstksz as _) +/// # Safety +/// Must only be called with a pointer returned by `make_handler`, and only +/// once per `Handler`. +pub unsafe fn drop_handler(data: *mut libc::c_void) { + if !data.is_null() { + let sigstack_size = sigstack_size(); + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + let disabling_stack = libc::stack_t { + ss_sp: ptr::null_mut(), + ss_flags: SS_DISABLE, + // Workaround for bug in macOS implementation of sigaltstack + // UNIX2003 which returns ENOMEM when disabling a stack while + // passing ss_size smaller than MINSIGSTKSZ. According to POSIX + // both ss_sp and ss_size should be ignored in this case. + ss_size: sigstack_size, + }; + // SAFETY: + // We assume that disabling the alternate signal stack is always + // sound, even if the current alternate signal stack is not the one + // we installed in `make_handler`. Any stack overflows from this + // point on will abort the program when the kernel tries to write + // the signal information to the guard page. + // + // FIXME: detect if the stack has changed, and only uninstall if it hasn't. + unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) }; + // The stack returned by `get_stack` is part of a mapping that + // started one page earlier, so walk back a page and unmap from + // there. + // + // SAFETY: + // This allocation was created by us in `get_stack` and, as the + // alternate signal stack is now disabled, is no longer in use. + unsafe { munmap(data.sub(page_size), sigstack_size + page_size) }; } - /// Not all OS support hardware where this is needed. - #[cfg(not(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc"))))] - fn sigstack_size() -> usize { - libc::SIGSTKSZ - } + delete_current_info(); +} + +/// Modern kernels on modern hardware can have dynamic signal stack sizes. +#[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))] +fn sigstack_size() -> usize { + // SAFETY: `getauxval` is always safe to call. + let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) }; + // If getauxval couldn't find the entry, it returns 0, + // so take the higher of the "constant" and auxval. + // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ + libc::SIGSTKSZ.max(dynamic_sigstksz as _) +} + +/// Not all OS support hardware where this is needed. +#[cfg(not(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc"))))] +fn sigstack_size() -> usize { + libc::SIGSTKSZ +} From ae721d84f8047a6f82a39c1459905e7f7ab57f8f Mon Sep 17 00:00:00 2001 From: joboet Date: Fri, 4 Sep 2026 15:44:21 +0200 Subject: [PATCH 4/4] std: refactor UNIX stack overflow code --- .../sys/pal/unix/stack_overflow/guard_page.rs | 186 +++++++++--------- .../pal/unix/stack_overflow/handler_cygwin.rs | 4 +- .../pal/unix/stack_overflow/handler_none.rs | 4 +- .../pal/unix/stack_overflow/handler_signal.rs | 36 ++-- .../src/sys/pal/unix/stack_overflow/mod.rs | 127 ++++++------ 5 files changed, 172 insertions(+), 185 deletions(-) diff --git a/library/std/src/sys/pal/unix/stack_overflow/guard_page.rs b/library/std/src/sys/pal/unix/stack_overflow/guard_page.rs index 777a02f4b38bb..e867713e000d0 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/guard_page.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/guard_page.rs @@ -1,5 +1,7 @@ +use crate::ops::Range; + #[cfg(any(target_os = "solaris", target_os = "illumos"))] -fn get_stack_start() -> Option<*mut libc::c_void> { +fn get_stack_start(_page_size: usize) -> Option<*mut libc::c_void> { // SAFETY: C types are always zero-initializable. let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; // SAFETY: @@ -9,7 +11,7 @@ fn get_stack_start() -> Option<*mut libc::c_void> { } #[cfg(target_os = "macos")] -fn get_stack_start() -> Option<*mut libc::c_void> { +fn get_stack_start(_page_size: usize) -> Option<*mut libc::c_void> { // SAFETY: always safe to call. let th = unsafe { libc::pthread_self() }; // SAFETY: `th` is a valid `pthread_t`. @@ -21,7 +23,7 @@ fn get_stack_start() -> Option<*mut libc::c_void> { } #[cfg(target_os = "openbsd")] -fn get_stack_start() -> Option<*mut libc::c_void> { +fn get_stack_start(page_size: usize) -> Option<*mut libc::c_void> { // SAFETY: C types are always zero-initializable. let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() }; // SAFETY: @@ -34,7 +36,7 @@ fn get_stack_start() -> Option<*mut libc::c_void> { // SAFETY: this is always safe to call. let stackaddr = if unsafe { libc::pthread_main_np() } == 1 { // main thread - stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed) + stack_ptr.addr() - current_stack.ss_size + page_size } else { // new thread stack_ptr.addr() - current_stack.ss_size @@ -43,14 +45,14 @@ fn get_stack_start() -> Option<*mut libc::c_void> { } #[cfg(any( - target_os = "android", + //target_os = "android", (currently unused) target_os = "freebsd", target_os = "netbsd", target_os = "hurd", - target_os = "linux", - target_os = "l4re" + all(target_os = "linux", not(target_env = "musl")), + //target_os = "l4re" (currently unused) ))] -fn get_stack_start() -> Option<*mut libc::c_void> { +fn get_stack_start(_page_size: usize) -> Option<*mut libc::c_void> { use crate::pin::pin; use crate::sys::helpers::COpaque; @@ -99,8 +101,18 @@ fn get_stack_start() -> Option<*mut libc::c_void> { ret } +#[cfg(any( + target_os = "hurd", + target_os = "macos", + target_os = "solaris", + target_os = "illumos", + all(target_os = "linux", not(target_env = "musl")), + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", +))] fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> { - let stackptr = get_stack_start()?; + let stackptr = get_stack_start(page_size)?; let stackaddr = stackptr.addr(); // Ensure stackaddr is page aligned! A parent process might @@ -120,34 +132,60 @@ fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> { /// # Safety /// This function must only be called from the main thread, and there must /// be sufficient stack space remaining to place a stack guard. -unsafe fn install_main_guard() -> Option> { - let page_size = PAGE_SIZE.load(Ordering::Relaxed); +pub unsafe fn install_main_guard() -> Option> { + cfg_select! { + any(target_os = "hurd", target_os = "macos", target_os = "solaris", target_os = "illumos",) => { + use crate::io::Error; - // this way someone on any unix-y OS can check that all these compile - if cfg!(all(target_os = "linux", not(target_env = "musl"))) { - install_main_guard_linux(page_size) - } else if cfg!(all(target_os = "linux", target_env = "musl")) { - install_main_guard_linux_musl(page_size) - } else if cfg!(target_os = "freebsd") { - #[cfg(not(target_os = "freebsd"))] - return None; - // The FreeBSD code cannot be checked on non-BSDs. - #[cfg(target_os = "freebsd")] - install_main_guard_freebsd(page_size) - } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) { - install_main_guard_bsds(page_size) - } else { - // SAFETY: guaranteed by caller. - unsafe { install_main_guard_default(page_size) } + let page_size = crate::sys::pal::conf::page_size(); + + // Reallocate the last page of the stack. + // This ensures SIGBUS will be raised on + // stack overflow. + // Systems which enforce strict PAX MPROTECT do not allow + // to mprotect() a mapping with less restrictive permissions + // than the initial mmap() used, so we mmap() here with + // read/write permissions and only then mprotect() it to + // no permissions at all. See issue #50313. + let stackptr = stack_start_aligned(page_size)?; + // SAFETY: + // The memory region from `stackptr..stackptr + page_size` belongs to + // the current thread's stack, and the caller has asserted that there + // is sufficient stack space, which means that this will not overwrite + // any existing allocations. + let result = unsafe { + libc::mmap( + stackptr, + page_size, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANON | libc::MAP_FIXED, + -1, + 0, + ) + }; + if result != stackptr || result == libc::MAP_FAILED { + panic!("failed to allocate a guard page: {}", Error::last_os_error()); + } + + // SAFETY: + // Since this function is only called on the main thread, the stack will + // not be reused until program exit, so the runtime will never observe + // that part of the stack has been made unusable in this way. + let result = unsafe { libc::mprotect(stackptr, page_size, libc::PROT_NONE) }; + if result != 0 { + panic!("failed to protect the guard page: {}", Error::last_os_error()); + } + + let guardaddr = stackptr.addr(); + + Some(guardaddr..guardaddr + page_size) + } + _ => None, } } -fn install_main_guard_linux(page_size: usize) -> Option> { - // See the corresponding conditional in init(). - // Avoid stack_start_aligned, which makes slow syscalls to read /proc/self/maps - if cfg!(panic = "immediate-abort") { - return None; - } +#[cfg(all(target_os = "linux", not(target_env = "musl")))] +pub fn find_main_guard(page_size: usize) -> Option> { // Linux doesn't allocate the whole stack right away, and // the kernel has its own stack-guard mechanism to fault // when growing too close to an existing mapping. If we map @@ -163,7 +201,8 @@ fn install_main_guard_linux(page_size: usize) -> Option> { Some(stackaddr - page_size..stackaddr) } -fn install_main_guard_linux_musl(_page_size: usize) -> Option> { +#[cfg(all(target_os = "linux", target_env = "musl"))] +pub fn find_main_guard(_page_size: usize) -> Option> { // For the main thread, the musl's pthread_attr_getstack // returns the current stack size, rather than maximum size // it can eventually grow to. It cannot be used to determine @@ -172,11 +211,7 @@ fn install_main_guard_linux_musl(_page_size: usize) -> Option> { } #[cfg(target_os = "freebsd")] -fn install_main_guard_freebsd(page_size: usize) -> Option> { - // See the corresponding conditional in install_main_guard_linux(). - if cfg!(panic = "immediate-abort") { - return None; - } +pub fn find_main_guard(page_size: usize) -> Option> { // FreeBSD's stack autogrows, and optionally includes a guard page // at the bottom. If we try to remap the bottom of the stack // ourselves, FreeBSD's guard page moves upwards. So we'll just use @@ -200,7 +235,7 @@ fn install_main_guard_freebsd(page_size: usize) -> Option> { oid.as_ptr(), (&raw mut guard).cast(), &raw mut size, - ptr::null_mut(), + crate::ptr::null_mut(), 0, ) }; @@ -209,11 +244,8 @@ fn install_main_guard_freebsd(page_size: usize) -> Option> { Some(guardaddr..guardaddr + pages * page_size) } -fn install_main_guard_bsds(page_size: usize) -> Option> { - // See the corresponding conditional in install_main_guard_linux(). - if cfg!(panic = "immediate-abort") { - return None; - } +#[cfg(any(target_os = "netbsd", target_os = "openbsd"))] +pub fn find_main_guard(page_size: usize) -> Option> { // OpenBSD stack already includes a guard page, and stack is // immutable. // NetBSD stack includes the guard page. @@ -226,50 +258,12 @@ fn install_main_guard_bsds(page_size: usize) -> Option> { Some(stackaddr - page_size..stackaddr) } -/// # Safety -/// This function must only be called from the main thread, and there must -/// be sufficient stack space remaining to place a stack guard. -unsafe fn install_main_guard_default(page_size: usize) -> Option> { - // Reallocate the last page of the stack. - // This ensures SIGBUS will be raised on - // stack overflow. - // Systems which enforce strict PAX MPROTECT do not allow - // to mprotect() a mapping with less restrictive permissions - // than the initial mmap() used, so we mmap() here with - // read/write permissions and only then mprotect() it to - // no permissions at all. See issue #50313. - let stackptr = stack_start_aligned(page_size)?; - // SAFETY: - // The memory region from `stackptr..stackptr + page_size` belongs to - // the current thread's stack, and the caller has asserted that there - // is sufficient stack space, which means that this will not overwrite - // any existing allocations. - let result = unsafe { - mmap64( - stackptr, - page_size, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON | MAP_FIXED, - -1, - 0, - ) - }; - if result != stackptr || result == MAP_FAILED { - panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); - } - - // SAFETY: - // Since this function is only called on the main thread, the stack will - // not be reused until program exit, so the runtime will never observe - // that part of the stack has been made unusable in this way. - let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) }; - if result != 0 { - panic!("failed to protect the guard page: {}", io::Error::last_os_error()); - } - - let guardaddr = stackptr.addr(); - - Some(guardaddr..guardaddr + page_size) +#[cfg(any(target_os = "hurd", target_os = "macos", target_os = "solaris", target_os = "illumos",))] +pub fn find_main_guard(_page_size: usize) -> Option> { + // We installed the main thread's guard page ourselves in `install_main_guard`, + // so this function will only be called if that fails, in which case there + // won't be any guard page. + None } #[cfg(any( @@ -278,21 +272,21 @@ unsafe fn install_main_guard_default(page_size: usize) -> Option> { target_os = "solaris", target_os = "illumos", ))] -fn current_guard() -> Option> { - let stackptr = get_stack_start()?; +pub fn current_guard(page_size: usize) -> Option> { + let stackptr = get_stack_start(page_size)?; let stackaddr = stackptr.addr(); - Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr) + Some(stackaddr - page_size..stackaddr) } #[cfg(any( - target_os = "android", + //target_os = "android", (currently unused) target_os = "freebsd", target_os = "hurd", target_os = "linux", target_os = "netbsd", - target_os = "l4re" + //target_os = "l4re" (currently unused) ))] -fn current_guard() -> Option> { +pub fn current_guard(page_size: usize) -> Option> { use crate::pin::pin; use crate::sys::helpers::COpaque; @@ -332,7 +326,7 @@ fn current_guard() -> Option> { // musl versions before 1.1.19 always reported guard // size obtained from pthread_attr_get_np as zero. // Use page size as a fallback. - guardsize = PAGE_SIZE.load(Ordering::Relaxed); + guardsize = page_size; } else { panic!("there is no guard page"); } diff --git a/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs b/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs index 3289efe05b068..093a80dcb4052 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/handler_cygwin.rs @@ -1,3 +1,5 @@ +use crate::ops::Range; + mod c { pub type PVECTORED_EXCEPTION_HANDLER = Option i32>; @@ -59,7 +61,7 @@ unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POIN } } -pub unsafe fn init() { +pub fn init(_guard_page_range: Option>) { // SAFETY: `vectored_handler` has the correct ABI and is safe to call during exception handling. unsafe { let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler)); diff --git a/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs b/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs index a0e2dfa941867..c48bc1ceb8a7d 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/handler_none.rs @@ -1,4 +1,6 @@ -pub unsafe fn init() {} +use crate::ops::Range; + +pub fn init(_guard_page_range: Option>) {} pub fn make_handler(_main_thread: bool) -> super::Handler { super::Handler::null() diff --git a/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs b/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs index d8238f6c924b0..9b355cfef8c83 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/handler_signal.rs @@ -1,6 +1,6 @@ use libc::{ - MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK, - SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t, + MAP_ANON, MAP_FAILED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK, SA_SIGINFO, + SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t, }; #[cfg(not(all(target_os = "linux", target_env = "gnu")))] use libc::{mmap as mmap64, mprotect, munmap}; @@ -95,23 +95,12 @@ static PAGE_SIZE: Atomic = AtomicUsize::new(0); static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut()); static NEED_ALTSTACK: Atomic = AtomicBool::new(false); -/// # Safety -/// Must be called only once, on the main thread, during program startup. -pub unsafe fn init() { - PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed); - - // SAFETY: - // This is only called on the main thread, and since it is still early - // in the programs lifetime there is (almost) certainly enough stack - // space left to install the guard page. - let mut guard_page_range = unsafe { install_main_guard() }; +pub fn init(guard_page_range: Option>) { + let page_size = conf::page_size(); + PAGE_SIZE.store(page_size, Ordering::Relaxed); - // Even for panic=immediate-abort, installing the guard pages is important for soundness. - // That said, we do not care about giving nice stackoverflow messages via our custom - // signal handler, just exit early and let the user enjoy the segfault. - if cfg!(panic = "immediate-abort") { - return; - } + let mut guard_page_range = + guard_page_range.or_else(|| super::guard_page::find_main_guard(page_size)); // SAFETY: C structures are always zero-initializable. let mut action: sigaction = unsafe { mem::zeroed() }; @@ -147,7 +136,7 @@ pub unsafe fn init() { } } -fn get_stack() -> libc::stack_t { +fn get_stack(page_size: usize) -> libc::stack_t { // OpenBSD requires this flag for stack mapping // otherwise the said mapping will fail as a no-op on most systems // and has a different meaning on FreeBSD @@ -167,7 +156,6 @@ fn get_stack() -> libc::stack_t { let flags = MAP_PRIVATE | MAP_ANON; let sigstack_size = sigstack_size(); - let page_size = PAGE_SIZE.load(Ordering::Relaxed); // SAFETY: this does not unmap any existing pages. let stackp = unsafe { @@ -177,7 +165,7 @@ fn get_stack() -> libc::stack_t { panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error()); } // SAFETY: this only affects the memory we just allocated. - let guard_result = unsafe { libc::mprotect(stackp, page_size, PROT_NONE) }; + let guard_result = unsafe { mprotect(stackp, page_size, PROT_NONE) }; if guard_result != 0 { panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error()); } @@ -194,8 +182,10 @@ pub fn make_handler(main_thread: bool) -> Handler { return Handler::null(); } + let page_size = PAGE_SIZE.load(Ordering::Relaxed); + if !main_thread { - if let Some(guard_page_range) = current_guard() { + if let Some(guard_page_range) = super::guard_page::current_guard(page_size) { set_current_info(guard_page_range); } } @@ -210,7 +200,7 @@ pub fn make_handler(main_thread: bool) -> Handler { // Configure alternate signal stack, if one is not already set. if stack.ss_flags & SS_DISABLE != 0 { - let stack = get_stack(); + let stack = get_stack(page_size); // SAFETY: // `stack_t` is a freshly allocated stack that's not used anywhere // else. It contains a guard page, so stack overflows in signal diff --git a/library/std/src/sys/pal/unix/stack_overflow/mod.rs b/library/std/src/sys/pal/unix/stack_overflow/mod.rs index d87ae102f2da2..62bc9640c1436 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/mod.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/mod.rs @@ -1,8 +1,67 @@ #![cfg_attr(test, allow(dead_code))] #![forbid(unsafe_op_in_unsafe_fn)] -pub use self::imp::init; -use self::imp::{drop_handler, make_handler}; +mod guard_page; + +cfg_select! { + any( + target_os = "linux", + target_os = "freebsd", + target_os = "hurd", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "illumos", + ) => { + mod thread_info; + mod handler_signal; + use handler_signal as handler; + } + target_os = "cygwin" => { + mod handler_cygwin; + use handler_cygwin as handler; + } + // This is intentionally not enabled on iOS/tvOS/watchOS/visionOS, as it uses + // several symbols that might lead to rejections from the App Store, namely + // `sigaction`, `sigaltstack`, `sysctlbyname`, `mmap`, `munmap` and `mprotect`. + // + // This might be overly cautious, though it is also what Swift does (and they + // usually have fewer qualms about forwards compatibility, since the runtime + // is shipped with the OS): + // + _ => { + mod handler_none; + use handler_none as handler; + } +} + +/// # Safety +/// Must be called only once, on the main thread, during program startup. +pub unsafe fn init() { + // miri models neither signals, stack overflows nor guard pages. Also, this + // code has some synchronization properties that we don't want to expose to + // user code, hence we disable it on miri. + if cfg!(miri) { + return; + } + + // SAFETY: + // This is only called on the main thread, and since it is still early + // in the programs lifetime there is (almost) certainly enough stack + // space left to install the guard page. + let guard_page_range = unsafe { guard_page::install_main_guard() }; + + // Even for panic=immediate-abort, installing the guard pages is important + // for soundness. That said, we do not care about giving nice stackoverflow + // messages via our custom signal handler, just exit early and let the user + // enjoy the segfault. + if cfg!(panic = "immediate-abort") { + return; + } + + handler::init(guard_page_range); +} pub struct Handler { data: *mut libc::c_void, @@ -10,7 +69,7 @@ pub struct Handler { impl Handler { pub unsafe fn new() -> Handler { - make_handler(false) + handler::make_handler(false) } fn null() -> Handler { @@ -21,67 +80,7 @@ impl Handler { impl Drop for Handler { fn drop(&mut self) { unsafe { - drop_handler(self.data); + handler::drop_handler(self.data); } } } - -#[cfg(all( - not(miri), - any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ), -))] -mod thread_info; - -// miri doesn't model signals nor stack overflows and this code has some -// synchronization properties that we don't want to expose to user code, -// hence we disable it on miri. -#[cfg(all( - not(miri), - any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - ) -))] -mod imp; - -// This is intentionally not enabled on iOS/tvOS/watchOS/visionOS, as it uses -// several symbols that might lead to rejections from the App Store, namely -// `sigaction`, `sigaltstack`, `sysctlbyname`, `mmap`, `munmap` and `mprotect`. -// -// This might be overly cautious, though it is also what Swift does (and they -// usually have fewer qualms about forwards compatibility, since the runtime -// is shipped with the OS): -// -#[cfg(any( - miri, - not(any( - target_os = "linux", - target_os = "freebsd", - target_os = "hurd", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "solaris", - target_os = "illumos", - target_os = "cygwin", - )) -))] -mod imp; - -#[cfg(target_os = "cygwin")] -mod imp;