From 452a7cac6e79b4bff25906e2e45bb820d46f793b Mon Sep 17 00:00:00 2001 From: dianne Date: Tue, 22 Sep 2026 23:53:16 -0700 Subject: [PATCH 01/16] make `RustaceansAreAwesome` satisfy trait bounds --- .../src/traits/dyn_compatibility.rs | 9 ++++ ...tchability-placeholder-satisfies-bounds.rs | 37 ++++++++++++++ .../ice-generics-of-crate-root-152335.rs | 2 + .../ice-generics-of-crate-root-152335.stderr | 50 +++++++++++++++---- .../unhandled-crate-mod-issue-144888.rs | 2 + .../unhandled-crate-mod-issue-144888.stderr | 46 ++++++++++++++--- 6 files changed, 129 insertions(+), 17 deletions(-) create mode 100644 tests/ui/dyn-compatibility/dispatchability-placeholder-satisfies-bounds.rs diff --git a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs index e964c09420b0e..7f05f7e04aadf 100644 --- a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs +++ b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs @@ -749,6 +749,15 @@ fn receiver_is_dispatchable<'tcx>( let trait_predicate = ty::TraitRef::new_from_args(tcx, trait_def_id, args); clauses.push(trait_predicate.upcast(tcx)); + // U satisfies `Trait`'s where-bounds. + clauses.extend( + tcx.clauses_of(trait_def_id) + .instantiate(tcx, args) + .clauses + .into_iter() + .map(Unnormalized::skip_norm_wip), + ); + let meta_sized_predicate = { let meta_sized_did = tcx.require_lang_item(LangItem::MetaSized, DUMMY_SP); ty::TraitRef::new(tcx, meta_sized_did, [unsized_self_ty]) diff --git a/tests/ui/dyn-compatibility/dispatchability-placeholder-satisfies-bounds.rs b/tests/ui/dyn-compatibility/dispatchability-placeholder-satisfies-bounds.rs new file mode 100644 index 0000000000000..421b9a512c1e4 --- /dev/null +++ b/tests/ui/dyn-compatibility/dispatchability-placeholder-satisfies-bounds.rs @@ -0,0 +1,37 @@ +//! Regression tests for . When checking whether +//! method receivers for a trait `Trait` are dynamically dispatchable, we use a placeholder type in +//! place of `dyn Trait` to avoid a cycle (as that would require determining whether `Trait` is dyn- +//! compatible). This placeholder must satisfy `Trait`'s where-bounds to avoid errors in param- +//! environment normalization. +//@ check-pass + +// Test 1 + +use std::ops::Deref; + +trait SignatureToFnPtr { + type Ptr; +} + +trait DerefsToFn +where + (Args, Self::Output): SignatureToFnPtr, + Self: Deref::Ptr>, +{ + type Output; + fn method(&self); +} + +// Test 2 + +trait Associator { + type Point; +} + +trait Marker {} + +trait Trait + ?Sized>: Marker { + fn method(&self); +} + +fn main() {} diff --git a/tests/ui/dyn-compatibility/ice-generics-of-crate-root-152335.rs b/tests/ui/dyn-compatibility/ice-generics-of-crate-root-152335.rs index cfafca68bfd2f..188f63fa94bab 100644 --- a/tests/ui/dyn-compatibility/ice-generics-of-crate-root-152335.rs +++ b/tests/ui/dyn-compatibility/ice-generics-of-crate-root-152335.rs @@ -26,6 +26,8 @@ where //~| ERROR the size for values of type `Self` cannot be known //~| ERROR type mismatch resolving //~| ERROR the size for values of type `Self` cannot be known + //~| ERROR type mismatch resolving + //~| ERROR the size for values of type `RustaceansAreAwesome` cannot be known } trait Mirror { diff --git a/tests/ui/dyn-compatibility/ice-generics-of-crate-root-152335.stderr b/tests/ui/dyn-compatibility/ice-generics-of-crate-root-152335.stderr index b8796f673f4a5..06fd8bced4af1 100644 --- a/tests/ui/dyn-compatibility/ice-generics-of-crate-root-152335.stderr +++ b/tests/ui/dyn-compatibility/ice-generics-of-crate-root-152335.stderr @@ -5,11 +5,29 @@ LL | fn transmute(&self, t: T) -> ::Assoc; | ^ not found in this scope error[E0601]: `main` function not found in crate `ice_generics_of_crate_root_152335` - --> $DIR/ice-generics-of-crate-root-152335.rs:34:57 + --> $DIR/ice-generics-of-crate-root-152335.rs:36:57 | LL | impl> Mirror for T {} | ^ consider adding a `main` function to `$DIR/ice-generics-of-crate-root-152335.rs` +error[E0271]: type mismatch resolving `>::Assoc == RustaceansAreAwesome` + --> $DIR/ice-generics-of-crate-root-152335.rs:23:5 + | +LL | fn transmute(&self, t: T) -> ::Assoc; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `RustaceansAreAwesome`, found type parameter `T` + | + = note: expected type parameter `RustaceansAreAwesome` + found type parameter `T` + = note: a type parameter was expected, but a different one was found; you might be missing a type parameter or trait bound + = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters +note: required for `RustaceansAreAwesome` to implement `Mirror` + --> $DIR/ice-generics-of-crate-root-152335.rs:36:42 + | +LL | impl> Mirror for T {} + | --------- ^^^^^^ ^ + | | + | unsatisfied trait bound introduced here + error[E0271]: type mismatch resolving `>::Assoc == Self` --> $DIR/ice-generics-of-crate-root-152335.rs:23:5 | @@ -21,13 +39,27 @@ LL | fn transmute(&self, t: T) -> ::Assoc; = note: a type parameter was expected, but a different one was found; you might be missing a type parameter or trait bound = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters note: required for `Self` to implement `Mirror` - --> $DIR/ice-generics-of-crate-root-152335.rs:34:42 + --> $DIR/ice-generics-of-crate-root-152335.rs:36:42 | LL | impl> Mirror for T {} | --------- ^^^^^^ ^ | | | unsatisfied trait bound introduced here +error[E0277]: the size for values of type `RustaceansAreAwesome` cannot be known at compilation time + --> $DIR/ice-generics-of-crate-root-152335.rs:23:5 + | +LL | fn transmute(&self, t: T) -> ::Assoc; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time + | +note: required for `RustaceansAreAwesome` to implement `Mirror` + --> $DIR/ice-generics-of-crate-root-152335.rs:36:42 + | +LL | impl> Mirror for T {} + | - ^^^^^^ ^ + | | + | unsatisfied trait bound implicitly introduced here + error[E0277]: the size for values of type `Self` cannot be known at compilation time --> $DIR/ice-generics-of-crate-root-152335.rs:23:5 | @@ -35,7 +67,7 @@ LL | fn transmute(&self, t: T) -> ::Assoc; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time | note: required for `Self` to implement `Mirror` - --> $DIR/ice-generics-of-crate-root-152335.rs:34:42 + --> $DIR/ice-generics-of-crate-root-152335.rs:36:42 | LL | impl> Mirror for T {} | - ^^^^^^ ^ @@ -69,7 +101,7 @@ LL | trait Foo: Super = note: a type parameter was expected, but a different one was found; you might be missing a type parameter or trait bound = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters note: required for `Self` to implement `Mirror` - --> $DIR/ice-generics-of-crate-root-152335.rs:34:42 + --> $DIR/ice-generics-of-crate-root-152335.rs:36:42 | LL | impl> Mirror for T {} | --------- ^^^^^^ ^ @@ -83,7 +115,7 @@ LL | trait Foo: Super | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time | note: required for `Self` to implement `Mirror` - --> $DIR/ice-generics-of-crate-root-152335.rs:34:42 + --> $DIR/ice-generics-of-crate-root-152335.rs:36:42 | LL | impl> Mirror for T {} | - ^^^^^^ ^ @@ -95,7 +127,7 @@ LL | trait Foo: Super + Sized | +++++++ error[E0046]: not all trait items implemented, missing: `Assoc` - --> $DIR/ice-generics-of-crate-root-152335.rs:34:1 + --> $DIR/ice-generics-of-crate-root-152335.rs:36:1 | LL | type Assoc: ?Sized; | ------------------ `Assoc` from trait @@ -120,7 +152,7 @@ LL | fn transmute(&self, t: T) -> ::Assoc; = note: a type parameter was expected, but a different one was found; you might be missing a type parameter or trait bound = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters note: required for `Self` to implement `Mirror` - --> $DIR/ice-generics-of-crate-root-152335.rs:34:42 + --> $DIR/ice-generics-of-crate-root-152335.rs:36:42 | LL | impl> Mirror for T {} | --------- ^^^^^^ ^ @@ -134,7 +166,7 @@ LL | fn transmute(&self, t: T) -> ::Assoc; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time | note: required for `Self` to implement `Mirror` - --> $DIR/ice-generics-of-crate-root-152335.rs:34:42 + --> $DIR/ice-generics-of-crate-root-152335.rs:36:42 | LL | impl> Mirror for T {} | - ^^^^^^ ^ @@ -145,7 +177,7 @@ help: consider further restricting `Self` LL | fn transmute(&self, t: T) -> ::Assoc where Self: Sized; | +++++++++++++++++ -error: aborting due to 10 previous errors +error: aborting due to 12 previous errors Some errors have detailed explanations: E0038, E0046, E0271, E0277, E0405, E0601. For more information about an error, try `rustc --explain E0038`. diff --git a/tests/ui/traits/unhandled-crate-mod-issue-144888.rs b/tests/ui/traits/unhandled-crate-mod-issue-144888.rs index 8c9085ee80b35..41f233398d83b 100644 --- a/tests/ui/traits/unhandled-crate-mod-issue-144888.rs +++ b/tests/ui/traits/unhandled-crate-mod-issue-144888.rs @@ -18,6 +18,8 @@ where //~| ERROR the size for values of type `Self` cannot be known at compilation time //~| ERROR type mismatch resolving //~| ERROR the size for values of type `Self` cannot be known at compilation time + //~| ERROR type mismatch resolving + //~| ERROR the size for values of type `RustaceansAreAwesome` cannot be known at compilation time } trait Mirror { diff --git a/tests/ui/traits/unhandled-crate-mod-issue-144888.stderr b/tests/ui/traits/unhandled-crate-mod-issue-144888.stderr index 3208945a5b732..4cc5b41588efa 100644 --- a/tests/ui/traits/unhandled-crate-mod-issue-144888.stderr +++ b/tests/ui/traits/unhandled-crate-mod-issue-144888.stderr @@ -1,3 +1,19 @@ +error[E0271]: type mismatch resolving `::Assoc == ()` + --> $DIR/unhandled-crate-mod-issue-144888.rs:16:5 + | +LL | fn transmute(&self) {} + | ^^^^^^^^^^^^^^^^^^^ expected `()`, found type parameter `T` + | + = note: expected unit type `()` + found type parameter `T` +note: required for `RustaceansAreAwesome` to implement `Mirror` + --> $DIR/unhandled-crate-mod-issue-144888.rs:29:28 + | +LL | impl> Mirror for T {} + | ---------- ^^^^^^ ^ + | | + | unsatisfied trait bound introduced here + error[E0271]: type mismatch resolving `::Assoc == ()` --> $DIR/unhandled-crate-mod-issue-144888.rs:16:5 | @@ -7,13 +23,27 @@ LL | fn transmute(&self) {} = note: expected unit type `()` found type parameter `T` note: required for `Self` to implement `Mirror` - --> $DIR/unhandled-crate-mod-issue-144888.rs:27:28 + --> $DIR/unhandled-crate-mod-issue-144888.rs:29:28 | LL | impl> Mirror for T {} | ---------- ^^^^^^ ^ | | | unsatisfied trait bound introduced here +error[E0277]: the size for values of type `RustaceansAreAwesome` cannot be known at compilation time + --> $DIR/unhandled-crate-mod-issue-144888.rs:16:5 + | +LL | fn transmute(&self) {} + | ^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time + | +note: required for `RustaceansAreAwesome` to implement `Mirror` + --> $DIR/unhandled-crate-mod-issue-144888.rs:29:28 + | +LL | impl> Mirror for T {} + | - ^^^^^^ ^ + | | + | unsatisfied trait bound implicitly introduced here + error[E0277]: the size for values of type `Self` cannot be known at compilation time --> $DIR/unhandled-crate-mod-issue-144888.rs:16:5 | @@ -21,7 +51,7 @@ LL | fn transmute(&self) {} | ^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time | note: required for `Self` to implement `Mirror` - --> $DIR/unhandled-crate-mod-issue-144888.rs:27:28 + --> $DIR/unhandled-crate-mod-issue-144888.rs:29:28 | LL | impl> Mirror for T {} | - ^^^^^^ ^ @@ -37,7 +67,7 @@ LL | trait Foo: Super = note: expected unit type `()` found type parameter `T` note: required for `Self` to implement `Mirror` - --> $DIR/unhandled-crate-mod-issue-144888.rs:27:28 + --> $DIR/unhandled-crate-mod-issue-144888.rs:29:28 | LL | impl> Mirror for T {} | ---------- ^^^^^^ ^ @@ -51,7 +81,7 @@ LL | trait Foo: Super | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time | note: required for `Self` to implement `Mirror` - --> $DIR/unhandled-crate-mod-issue-144888.rs:27:28 + --> $DIR/unhandled-crate-mod-issue-144888.rs:29:28 | LL | impl> Mirror for T {} | - ^^^^^^ ^ @@ -63,7 +93,7 @@ LL | trait Foo: Super + Sized | +++++++ error[E0046]: not all trait items implemented, missing: `Assoc` - --> $DIR/unhandled-crate-mod-issue-144888.rs:27:1 + --> $DIR/unhandled-crate-mod-issue-144888.rs:29:1 | LL | type Assoc; | ---------- `Assoc` from trait @@ -83,7 +113,7 @@ LL | fn transmute(&self) {} = note: expected unit type `()` found type parameter `T` note: required for `Self` to implement `Mirror` - --> $DIR/unhandled-crate-mod-issue-144888.rs:27:28 + --> $DIR/unhandled-crate-mod-issue-144888.rs:29:28 | LL | impl> Mirror for T {} | ---------- ^^^^^^ ^ @@ -97,7 +127,7 @@ LL | fn transmute(&self) {} | ^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time | note: required for `Self` to implement `Mirror` - --> $DIR/unhandled-crate-mod-issue-144888.rs:27:28 + --> $DIR/unhandled-crate-mod-issue-144888.rs:29:28 | LL | impl> Mirror for T {} | - ^^^^^^ ^ @@ -108,7 +138,7 @@ help: consider further restricting `Self` LL | fn transmute(&self) where Self: Sized {} | +++++++++++++++++ -error: aborting due to 7 previous errors +error: aborting due to 9 previous errors Some errors have detailed explanations: E0046, E0271, E0277. For more information about an error, try `rustc --explain E0046`. From 5695ab858ef53df19d937a072dc2a6acee183219 Mon Sep 17 00:00:00 2001 From: joboet Date: Wed, 23 Sep 2026 13:44:32 +0200 Subject: [PATCH 02/16] std: split stack overflow module (duplicate) --- .../handler_cygwin.rs} | 0 .../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 ++++++++++++++++++ 4 files changed, 2532 insertions(+) rename library/std/src/sys/pal/unix/{stack_overflow.rs => stack_overflow/handler_cygwin.rs} (100%) 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/handler_cygwin.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/handler_cygwin.rs 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 86d18b46ef59a67b514ef59aa80a0705dadb96c0 Mon Sep 17 00:00:00 2001 From: joboet Date: Wed, 23 Sep 2026 13:46:57 +0200 Subject: [PATCH 03/16] std: split stack overflow module (delete) --- .../pal/unix/stack_overflow/handler_cygwin.rs | 763 ---------------- .../pal/unix/stack_overflow/handler_none.rs | 837 ------------------ .../pal/unix/stack_overflow/handler_signal.rs | 178 ---- .../src/sys/pal/unix/stack_overflow/mod.rs | 754 ---------------- 4 files changed, 2532 deletions(-) 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..ee4de76121097 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, @@ -724,121 +664,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/mod.rs b/library/std/src/sys/pal/unix/stack_overflow/mod.rs index 5604e3e6dbf42..f9541e182156c 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/mod.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/mod.rs @@ -58,672 +58,6 @@ mod thread_info; ) ))] 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 @@ -749,96 +83,8 @@ mod imp { )) ))] 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 e449348b8a02e876834a2f152aef1634f22d2904 Mon Sep 17 00:00:00 2001 From: joboet Date: Wed, 23 Sep 2026 13:48:51 +0200 Subject: [PATCH 04/16] std: split stack overflow module (format) --- .../pal/unix/stack_overflow/handler_cygwin.rs | 134 +- .../pal/unix/stack_overflow/handler_none.rs | 10 +- .../pal/unix/stack_overflow/handler_signal.rs | 1223 ++++++++--------- .../src/sys/pal/unix/stack_overflow/mod.rs | 9 +- 4 files changed, 683 insertions(+), 693 deletions(-) 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 ee4de76121097..54e94e0f22578 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,666 +1,659 @@ - 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. +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"); + } + }) } - 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; - } + // 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); - } + // 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()); + 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()) }; } - // 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(); - } +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; - if !main_thread { - if let Some(guard_page_range) = current_guard() { - set_current_info(guard_page_range); - } - } + let sigstack_size = sigstack_size(); + let page_size = PAGE_SIZE.load(Ordering::Relaxed); - // 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: 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) }; - /// # 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) }; - } + libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size } +} - delete_current_info(); +pub fn make_handler(main_thread: bool) -> Handler { + if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) { + return 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 _) + if !main_thread { + if let Some(guard_page_range) = current_guard() { + set_current_info(guard_page_range); + } } - /// 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 + // 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() } +} - #[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 +/// 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: - // The pointer is valid for writing a `stack_t`. - assert_eq!(unsafe { libc::stack_getbounds(&mut current_stack) }, 0); - Some(current_stack.ss_sp) + // 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) }; } - #[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)) - } + 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(); - #[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. + // `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"))] - 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 + 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 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) - }) +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; } - - /// # 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) } - } + // 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; } - - 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) + // 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; } - - 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 + // 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()); } - #[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) + // 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()); } - 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 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 - /// 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 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: - // 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()); + // `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: - // 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(); + // `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); - 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) + 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) + }; } - - #[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(); - + if e == 0 || cfg!(target_os = "freebsd") { // 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 + // `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/mod.rs b/library/std/src/sys/pal/unix/stack_overflow/mod.rs index f9541e182156c..0a070a20d432f 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/mod.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/mod.rs @@ -57,8 +57,7 @@ mod thread_info; target_os = "illumos", ) ))] -mod imp { -} +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 @@ -82,9 +81,7 @@ mod imp { target_os = "cygwin", )) ))] -mod imp { -} +mod imp {} #[cfg(target_os = "cygwin")] -mod imp { -} +mod imp {} From 6387f4b9bcde5a76f05411c9b441396f2458690c Mon Sep 17 00:00:00 2001 From: joboet Date: Wed, 23 Sep 2026 13:56:19 +0200 Subject: [PATCH 05/16] std: split stack overflow module --- .../src/sys/pal/unix/stack_overflow/mod.rs | 100 +++++++----------- 1 file changed, 40 insertions(+), 60 deletions(-) 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 0a070a20d432f..d60fe4b5bbec8 100644 --- a/library/std/src/sys/pal/unix/stack_overflow/mod.rs +++ b/library/std/src/sys/pal/unix/stack_overflow/mod.rs @@ -1,6 +1,46 @@ #![cfg_attr(test, allow(dead_code))] #![forbid(unsafe_op_in_unsafe_fn)] +cfg_select! { + // 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): + // + // + // 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. + 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 handler_signal; + mod thread_info; + use handler_signal as imp; + } + target_os = "cygwin" => { + mod handler_cygwin; + use handler_cygwin as imp; + } + _ => { + mod handler_none; + use handler_none as imp; + } +} + pub use self::imp::init; use self::imp::{drop_handler, make_handler}; @@ -25,63 +65,3 @@ impl Drop for Handler { } } } - -#[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 {} From 188011ac2901b1762e8bb9a3baaf8338cc839e55 Mon Sep 17 00:00:00 2001 From: Raushan kumar Date: Thu, 9 Jul 2026 05:49:29 +0000 Subject: [PATCH 06/16] test(imports): add baseline regression for hidden extern crate suggestion --- .../auxiliary/macro-generated-extern-crate.rs | 3 +++ .../ui/imports/macro-generated-extern-crate.rs | 14 ++++++++++++++ .../macro-generated-extern-crate.stderr | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 tests/ui/imports/auxiliary/macro-generated-extern-crate.rs create mode 100644 tests/ui/imports/macro-generated-extern-crate.rs create mode 100644 tests/ui/imports/macro-generated-extern-crate.stderr diff --git a/tests/ui/imports/auxiliary/macro-generated-extern-crate.rs b/tests/ui/imports/auxiliary/macro-generated-extern-crate.rs new file mode 100644 index 0000000000000..8a2c17266b7a9 --- /dev/null +++ b/tests/ui/imports/auxiliary/macro-generated-extern-crate.rs @@ -0,0 +1,3 @@ +pub trait MyTrait { + fn custom() {} +} diff --git a/tests/ui/imports/macro-generated-extern-crate.rs b/tests/ui/imports/macro-generated-extern-crate.rs new file mode 100644 index 0000000000000..a7b87efdd8fbe --- /dev/null +++ b/tests/ui/imports/macro-generated-extern-crate.rs @@ -0,0 +1,14 @@ +//@ edition: 2021 +//@ aux-build: macro-generated-extern-crate.rs + +const _: () = { + extern crate macro_generated_extern_crate as _my_crate; + impl _my_crate::MyTrait for Local {} +}; + +struct Local; + +fn main() { + Local::custom(); + //~^ ERROR no associated function or constant named `custom` found for struct `Local` +} diff --git a/tests/ui/imports/macro-generated-extern-crate.stderr b/tests/ui/imports/macro-generated-extern-crate.stderr new file mode 100644 index 0000000000000..7c022f715cac3 --- /dev/null +++ b/tests/ui/imports/macro-generated-extern-crate.stderr @@ -0,0 +1,18 @@ +error[E0599]: no associated function or constant named `custom` found for struct `Local` in the current scope + --> $DIR/macro-generated-extern-crate.rs:12:12 + | +LL | struct Local; + | ------------ associated function or constant `custom` not found for this struct +... +LL | Local::custom(); + | ^^^^^^ associated function or constant not found in `Local` + | + = help: items from traits can only be used if the trait is in scope +help: trait `MyTrait` which provides `custom` is implemented but not in scope; perhaps you want to import it + | +LL + use crate::_::_my_crate::MyTrait; + | + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0599`. From 2e4b8e5f17e7ce1bc95e35091eabe530f275a394 Mon Sep 17 00:00:00 2001 From: Raushan kumar Date: Thu, 9 Jul 2026 05:41:41 +0000 Subject: [PATCH 07/16] fix(metadata): demote unnameable `extern crate` items to `ExternCrateSource::Path` Use the `DefPath` to determine whether an `extern crate` item is globally nameable. `extern crate` items nested inside value namespaces cannot be referred to by a stable path outside their enclosing scope. Record them as `ExternCrateSource::Path` instead of `ExternCrateSource::Extern` so later path resolution does not reconstruct unnameable paths. --- compiler/rustc_metadata/src/creader.rs | 18 ++++- .../macro-generated-extern-crate.stderr | 2 +- .../uniform-paths/issue-87932.stderr | 2 +- tests/ui/traits/bound/same-crate-name.rs | 9 --- tests/ui/traits/bound/same-crate-name.stderr | 76 +++++-------------- .../ui/type/type-mismatch-same-crate-name.rs | 13 ++-- .../type/type-mismatch-same-crate-name.stderr | 33 +++----- 7 files changed, 52 insertions(+), 101 deletions(-) diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs index aea843ac0384c..10693448ea685 100644 --- a/compiler/rustc_metadata/src/creader.rs +++ b/compiler/rustc_metadata/src/creader.rs @@ -15,7 +15,7 @@ use rustc_data_structures::sync::{self, FreezeReadGuard, FreezeWriteGuard}; use rustc_data_structures::unord::UnordMap; use rustc_expand::base::SyntaxExtension; use rustc_hir::def_id::{CrateNum, LOCAL_CRATE, LocalDefId, StableCrateId}; -use rustc_hir::definitions::Definitions; +use rustc_hir::definitions::{DefPathData, Definitions}; use rustc_index::IndexVec; use rustc_lint_defs as lint; use rustc_lint_defs::builtin::UNUSED_CRATE_DEPENDENCIES; @@ -1358,14 +1358,24 @@ impl CStore { let cnum = self.resolve_crate(tcx, name, item.span, dep_kind, CrateOrigin::Extern)?; - let path_len = definitions.def_path(def_id).data.len(); + let def_path = definitions.def_path(def_id); + // An extern crate is only globally nameable if every segment in its path + // is in the type namespace (i.e., it is purely nested inside modules). + // If any segment is in the value namespace (e.g. inside a fn or const), + // it is unnameable from outside that block. + let is_unnameable = + def_path.data.iter().any(|d| !matches!(d.data, DefPathData::TypeNs(_))); self.update_extern_crate( cnum, name, ExternCrate { - src: ExternCrateSource::Extern(def_id.to_def_id()), + src: if is_unnameable { + ExternCrateSource::Path + } else { + ExternCrateSource::Extern(def_id.to_def_id()) + }, span: item.span, - path_len, + path_len: if is_unnameable { usize::MAX } else { def_path.data.len() }, dependency_of: LOCAL_CRATE, }, ); diff --git a/tests/ui/imports/macro-generated-extern-crate.stderr b/tests/ui/imports/macro-generated-extern-crate.stderr index 7c022f715cac3..e103b185789ac 100644 --- a/tests/ui/imports/macro-generated-extern-crate.stderr +++ b/tests/ui/imports/macro-generated-extern-crate.stderr @@ -10,7 +10,7 @@ LL | Local::custom(); = help: items from traits can only be used if the trait is in scope help: trait `MyTrait` which provides `custom` is implemented but not in scope; perhaps you want to import it | -LL + use crate::_::_my_crate::MyTrait; +LL + use macro_generated_extern_crate::MyTrait; | error: aborting due to 1 previous error diff --git a/tests/ui/rust-2018/uniform-paths/issue-87932.stderr b/tests/ui/rust-2018/uniform-paths/issue-87932.stderr index c408b747b506f..ff069320f413d 100644 --- a/tests/ui/rust-2018/uniform-paths/issue-87932.stderr +++ b/tests/ui/rust-2018/uniform-paths/issue-87932.stderr @@ -10,7 +10,7 @@ LL | A::deserialize(); = help: items from traits can only be used if the trait is in scope help: trait `Deserialize` which provides `deserialize` is implemented but not in scope; perhaps you want to import it | -LL + use ::deserialize::_a::Deserialize; +LL + use issue_87932_a::Deserialize; | error: aborting due to 1 previous error diff --git a/tests/ui/traits/bound/same-crate-name.rs b/tests/ui/traits/bound/same-crate-name.rs index acd4894612872..5de6620bf4856 100644 --- a/tests/ui/traits/bound/same-crate-name.rs +++ b/tests/ui/traits/bound/same-crate-name.rs @@ -32,15 +32,11 @@ fn main() { extern crate crate_a1 as a; a::try_foo(foo); //~^ ERROR E0277 - //~| NOTE there are multiple different versions of crate `crate_a1` in the dependency graph - //~| HELP you can use `cargo tree` to explore your dependency tree // We don't want to see the "version mismatch" help message here // because `implements_no_traits` has no impl for `Foo` a::try_foo(implements_no_traits); //~^ ERROR E0277 - //~| NOTE there are multiple different versions of crate `crate_a1` in the dependency graph - //~| HELP you can use `cargo tree` to explore your dependency tree // We don't want to see the "version mismatch" help message here // because `other_variant_implements_mismatched_trait` @@ -48,16 +44,11 @@ fn main() { // only for its `` variant. a::try_foo(other_variant_implements_mismatched_trait); //~^ ERROR E0277 - //~| NOTE there are multiple different versions of crate `crate_a1` in the dependency graph - //~| HELP you can use `cargo tree` to explore your dependency tree // We don't want to see the "version mismatch" help message here // because `ImplementsTraitForUsize` only has // impls for the correct trait where the path is not misleading. a::try_foo(other_variant_implements_correct_trait); //~^ ERROR E0277 - //~| HELP the trait `main::a::Bar` is implemented for `ImplementsTraitForUsize` - //~| NOTE there are multiple different versions of crate `crate_a1` in the dependency graph - //~| HELP you can use `cargo tree` to explore your dependency tree } } diff --git a/tests/ui/traits/bound/same-crate-name.stderr b/tests/ui/traits/bound/same-crate-name.stderr index 9e5e110f8624e..27cad715b0738 100644 --- a/tests/ui/traits/bound/same-crate-name.stderr +++ b/tests/ui/traits/bound/same-crate-name.stderr @@ -1,23 +1,13 @@ -error[E0277]: the trait bound `Foo: main::a::Bar` is not satisfied +error[E0277]: the trait bound `Foo: crate_a1::Bar` is not satisfied --> $DIR/same-crate-name.rs:33:20 | LL | a::try_foo(foo); - | ---------- ^^^ the trait `main::a::Bar` is not implemented for `Foo` + | ---------- ^^^ the trait `crate_a1::Bar` is not implemented for `Foo` | | | required by a bound introduced by this call | -note: there are multiple different versions of crate `crate_a1` in the dependency graph - --> $DIR/auxiliary/crate_a1.rs:1:1 - | -LL | pub trait Bar {} - | ^^^^^^^^^^^^^ this is the expected trait - | - ::: $DIR/auxiliary/crate_a2.rs:3:1 - | -LL | pub trait Bar {} - | ------------- this is the found trait - = help: you can use `cargo tree` to explore your dependency tree -help: the trait `main::a::Bar` is implemented for `ImplementsTraitForUsize` + = note: `Foo` implements similarly named trait `crate_a2::Bar`, but not `crate_a1::Bar` +help: the trait `crate_a1::Bar` is implemented for `ImplementsTraitForUsize` --> $DIR/auxiliary/crate_a1.rs:9:1 | LL | impl Bar for ImplementsTraitForUsize {} @@ -28,26 +18,15 @@ note: required by a bound in `try_foo` LL | pub fn try_foo(x: impl Bar) {} | ^^^ required by this bound in `try_foo` -error[E0277]: the trait bound `DoesNotImplementTrait: main::a::Bar` is not satisfied - --> $DIR/same-crate-name.rs:40:20 +error[E0277]: the trait bound `DoesNotImplementTrait: crate_a1::Bar` is not satisfied + --> $DIR/same-crate-name.rs:38:20 | LL | a::try_foo(implements_no_traits); - | ---------- ^^^^^^^^^^^^^^^^^^^^ the trait `main::a::Bar` is not implemented for `DoesNotImplementTrait` + | ---------- ^^^^^^^^^^^^^^^^^^^^ the trait `crate_a1::Bar` is not implemented for `DoesNotImplementTrait` | | | required by a bound introduced by this call | -note: there are multiple different versions of crate `crate_a1` in the dependency graph - --> $DIR/auxiliary/crate_a1.rs:1:1 - | -LL | pub trait Bar {} - | ^^^^^^^^^^^^^ this is the expected trait - | - ::: $DIR/auxiliary/crate_a2.rs:3:1 - | -LL | pub trait Bar {} - | ------------- this is the trait that was imported - = help: you can use `cargo tree` to explore your dependency tree -help: the trait `main::a::Bar` is implemented for `ImplementsTraitForUsize` +help: the trait `crate_a1::Bar` is implemented for `ImplementsTraitForUsize` --> $DIR/auxiliary/crate_a1.rs:9:1 | LL | impl Bar for ImplementsTraitForUsize {} @@ -58,26 +37,16 @@ note: required by a bound in `try_foo` LL | pub fn try_foo(x: impl Bar) {} | ^^^ required by this bound in `try_foo` -error[E0277]: the trait bound `ImplementsWrongTraitConditionally: main::a::Bar` is not satisfied - --> $DIR/same-crate-name.rs:49:20 +error[E0277]: the trait bound `ImplementsWrongTraitConditionally: crate_a1::Bar` is not satisfied + --> $DIR/same-crate-name.rs:45:20 | LL | a::try_foo(other_variant_implements_mismatched_trait); - | ---------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `main::a::Bar` is not implemented for `ImplementsWrongTraitConditionally` + | ---------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `crate_a1::Bar` is not implemented for `ImplementsWrongTraitConditionally` | | | required by a bound introduced by this call | -note: there are multiple different versions of crate `crate_a1` in the dependency graph - --> $DIR/auxiliary/crate_a1.rs:1:1 - | -LL | pub trait Bar {} - | ^^^^^^^^^^^^^ this is the expected trait - | - ::: $DIR/auxiliary/crate_a2.rs:3:1 - | -LL | pub trait Bar {} - | ------------- this is the found trait - = help: you can use `cargo tree` to explore your dependency tree -help: the trait `main::a::Bar` is implemented for `ImplementsTraitForUsize` + = note: `ImplementsWrongTraitConditionally` implements similarly named trait `crate_a2::Bar`, but not `crate_a1::Bar` +help: the trait `crate_a1::Bar` is implemented for `ImplementsTraitForUsize` --> $DIR/auxiliary/crate_a1.rs:9:1 | LL | impl Bar for ImplementsTraitForUsize {} @@ -88,26 +57,15 @@ note: required by a bound in `try_foo` LL | pub fn try_foo(x: impl Bar) {} | ^^^ required by this bound in `try_foo` -error[E0277]: the trait bound `ImplementsTraitForUsize: main::a::Bar` is not satisfied - --> $DIR/same-crate-name.rs:57:20 +error[E0277]: the trait bound `ImplementsTraitForUsize: crate_a1::Bar` is not satisfied + --> $DIR/same-crate-name.rs:51:20 | LL | a::try_foo(other_variant_implements_correct_trait); - | ---------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `main::a::Bar` is not implemented for `ImplementsTraitForUsize` + | ---------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `crate_a1::Bar` is not implemented for `ImplementsTraitForUsize` | | | required by a bound introduced by this call | -note: there are multiple different versions of crate `crate_a1` in the dependency graph - --> $DIR/auxiliary/crate_a1.rs:1:1 - | -LL | pub trait Bar {} - | ^^^^^^^^^^^^^ this is the expected trait - | - ::: $DIR/auxiliary/crate_a2.rs:3:1 - | -LL | pub trait Bar {} - | ------------- this is the trait that was imported - = help: you can use `cargo tree` to explore your dependency tree -help: the trait `main::a::Bar` is implemented for `ImplementsTraitForUsize` +help: the trait `crate_a1::Bar` is implemented for `ImplementsTraitForUsize` --> $DIR/auxiliary/crate_a1.rs:9:1 | LL | impl Bar for ImplementsTraitForUsize {} diff --git a/tests/ui/type/type-mismatch-same-crate-name.rs b/tests/ui/type/type-mismatch-same-crate-name.rs index df6d7aba8fd69..31fd47251660e 100644 --- a/tests/ui/type/type-mismatch-same-crate-name.rs +++ b/tests/ui/type/type-mismatch-same-crate-name.rs @@ -16,17 +16,18 @@ fn main() { extern crate crate_a1 as a; a::try_foo(foo2); //~^ ERROR mismatched types - //~| NOTE expected `main::a::Foo`, found a different `main::a::Foo` + //~| NOTE expected `crate_a1::Foo`, found `crate_a2::Foo` //~| NOTE arguments to this function are incorrect - //~| NOTE there are multiple different versions of crate `crate_a1` in the dependency graph + //~| NOTE `crate_a2::Foo` and `crate_a1::Foo` have similar names, but are actually distinct types + //~| NOTE `crate_a2::Foo` is defined in crate `crate_a2` + //~| NOTE `crate_a1::Foo` is defined in crate `crate_a1` //~| NOTE function defined here - //~| HELP you can use `cargo tree` to explore your dependency tree a::try_bar(bar2); //~^ ERROR mismatched types - //~| NOTE expected trait `main::a::Bar`, found a different trait `main::a::Bar` + //~| NOTE expected trait `crate_a1::Bar`, found trait `crate_a2::Bar` //~| NOTE arguments to this function are incorrect - //~| NOTE there are multiple different versions of crate `crate_a1` in the dependency graph + //~| NOTE expected struct `Box<(dyn crate_a1::Bar + 'static)>` + //~| NOTE found struct `Box` //~| NOTE function defined here - //~| HELP you can use `cargo tree` to explore your dependency tree } } diff --git a/tests/ui/type/type-mismatch-same-crate-name.stderr b/tests/ui/type/type-mismatch-same-crate-name.stderr index 2913b994e9529..71648741c2dd5 100644 --- a/tests/ui/type/type-mismatch-same-crate-name.stderr +++ b/tests/ui/type/type-mismatch-same-crate-name.stderr @@ -2,21 +2,21 @@ error[E0308]: mismatched types --> $DIR/type-mismatch-same-crate-name.rs:17:20 | LL | a::try_foo(foo2); - | ---------- ^^^^ expected `main::a::Foo`, found a different `main::a::Foo` + | ---------- ^^^^ expected `crate_a1::Foo`, found `crate_a2::Foo` | | | arguments to this function are incorrect | -note: there are multiple different versions of crate `crate_a1` in the dependency graph - --> $DIR/auxiliary/crate_a1.rs:1:1 + = note: `crate_a2::Foo` and `crate_a1::Foo` have similar names, but are actually distinct types +note: `crate_a2::Foo` is defined in crate `crate_a2` + --> $DIR/auxiliary/crate_a2.rs:1:1 | LL | pub struct Foo; - | ^^^^^^^^^^^^^^ this is the expected type - | - ::: $DIR/auxiliary/crate_a2.rs:1:1 + | ^^^^^^^^^^^^^^ +note: `crate_a1::Foo` is defined in crate `crate_a1` + --> $DIR/auxiliary/crate_a1.rs:1:1 | LL | pub struct Foo; - | -------------- this is the found type - = help: you can use `cargo tree` to explore your dependency tree + | ^^^^^^^^^^^^^^ note: function defined here --> $DIR/auxiliary/crate_a1.rs:10:8 | @@ -24,24 +24,15 @@ LL | pub fn try_foo(x: Foo){} | ^^^^^^^ error[E0308]: mismatched types - --> $DIR/type-mismatch-same-crate-name.rs:24:20 + --> $DIR/type-mismatch-same-crate-name.rs:25:20 | LL | a::try_bar(bar2); - | ---------- ^^^^ expected trait `main::a::Bar`, found a different trait `main::a::Bar` + | ---------- ^^^^ expected trait `crate_a1::Bar`, found trait `crate_a2::Bar` | | | arguments to this function are incorrect | -note: there are multiple different versions of crate `crate_a1` in the dependency graph - --> $DIR/auxiliary/crate_a1.rs:3:1 - | -LL | pub trait Bar {} - | ^^^^^^^^^^^^^ this is the expected trait - | - ::: $DIR/auxiliary/crate_a2.rs:3:1 - | -LL | pub trait Bar {} - | ------------- this is the found trait - = help: you can use `cargo tree` to explore your dependency tree + = note: expected struct `Box<(dyn crate_a1::Bar + 'static)>` + found struct `Box` note: function defined here --> $DIR/auxiliary/crate_a1.rs:11:8 | From 26b5f90dd7f716ffbba11ffc7b04415b5a72b342 Mon Sep 17 00:00:00 2001 From: EFanZh Date: Mon, 28 Sep 2026 16:41:56 +0800 Subject: [PATCH 08/16] Move `alloc::rc` into `alloc::rcs` --- library/alloc/src/lib.rs | 12 ++++++++++-- library/alloc/src/{ => rcs}/rc.rs | 0 src/etc/natvis/liballoc.natvis | 8 ++++---- src/tools/miri/tests/fail/memleak_rc.stderr | 4 ++-- tests/debuginfo/rc_arc.rs | 12 ++++++------ tests/debuginfo/strings-and-strs.rs | 6 +++--- .../methods/shadowed-intrinsic-method-deref.stderr | 2 +- 7 files changed, 26 insertions(+), 18 deletions(-) rename library/alloc/src/{ => rcs}/rc.rs (100%) diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index de6830a514656..60ada2ba866bb 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -230,12 +230,22 @@ // from other crates, but since this can only appear for lang items, it doesn't seem worth fixing. #![feature(intra_doc_pointers)] +#[cfg(not(no_rc))] +#[stable(feature = "rust1", since = "1.0.0")] +pub use rcs::rc; + // Module with internal macros used by other modules (needs to be included before other modules). #[macro_use] mod macros; mod raw_vec; +/// Implementations of reference-counted pointers. +#[cfg(not(no_rc))] +mod rcs { + pub mod rc; +} + // Heaps provided for low-level allocation strategies pub mod alloc; @@ -256,8 +266,6 @@ pub mod intrinsics; #[unstable(feature = "alloc_io", issue = "154046")] pub mod io; pub mod panicking; -#[cfg(not(no_rc))] -pub mod rc; pub mod slice; pub mod str; pub mod string; diff --git a/library/alloc/src/rc.rs b/library/alloc/src/rcs/rc.rs similarity index 100% rename from library/alloc/src/rc.rs rename to library/alloc/src/rcs/rc.rs diff --git a/src/etc/natvis/liballoc.natvis b/src/etc/natvis/liballoc.natvis index b56f6f5800eee..cfcca9014e70c 100644 --- a/src/etc/natvis/liballoc.natvis +++ b/src/etc/natvis/liballoc.natvis @@ -72,7 +72,7 @@ it is necessary for them. --> - + {ptr.pointer->value} @@ -87,7 +87,7 @@ - + {{ len={ptr.pointer.length} }} ptr.pointer.length @@ -102,7 +102,7 @@ - + {ptr.pointer->value} @@ -117,7 +117,7 @@ - + {{ len={ptr.pointer.length} }} ptr.pointer.length diff --git a/src/tools/miri/tests/fail/memleak_rc.stderr b/src/tools/miri/tests/fail/memleak_rc.stderr index a77cf55b0c317..26156050ae5d3 100644 --- a/src/tools/miri/tests/fail/memleak_rc.stderr +++ b/src/tools/miri/tests/fail/memleak_rc.stderr @@ -1,5 +1,5 @@ error: memory leaked: ALLOC (Rust heap, SIZE, ALIGN), allocated here: - --> RUSTLIB/alloc/src/rc.rs:LL:CC + --> RUSTLIB/alloc/src/rcs/rc.rs:LL:CC | LL | Self::from_inner(Box::into_non_null(Box::new(RcInner { | _________________________________________________^ @@ -11,7 +11,7 @@ LL | | }))) | = note: stack backtrace: 0: std::rc::Rc::>>::new - at RUSTLIB/alloc/src/rc.rs:LL:CC + at RUSTLIB/alloc/src/rcs/rc.rs:LL:CC 1: main at tests/fail/memleak_rc.rs:LL:CC diff --git a/tests/debuginfo/rc_arc.rs b/tests/debuginfo/rc_arc.rs index 4399dfe54e7a5..39b5dc0d84408 100644 --- a/tests/debuginfo/rc_arc.rs +++ b/tests/debuginfo/rc_arc.rs @@ -27,12 +27,12 @@ //@ cdb-command:g //@ cdb-command:dx rc,d -//@ cdb-check:rc,d : 111 [Type: alloc::rc::Rc] +//@ cdb-check:rc,d : 111 [Type: alloc::rcs::rc::Rc] //@ cdb-check: [Reference count] : 11 [Type: core::cell::Cell] //@ cdb-check: [Weak reference count] : 2 [Type: core::cell::Cell] //@ cdb-command:dx weak_rc,d -//@ cdb-check:weak_rc,d : 111 [Type: alloc::rc::Weak] +//@ cdb-check:weak_rc,d : 111 [Type: alloc::rcs::rc::Weak] //@ cdb-check: [Reference count] : 11 [Type: core::cell::Cell] //@ cdb-check: [Weak reference count] : 2 [Type: core::cell::Cell] @@ -47,17 +47,17 @@ //@ cdb-check: [Weak reference count] : 2 [Type: core::sync::atomic::Atomic] //@ cdb-command:dx dyn_rc,d -//@ cdb-check:dyn_rc,d [Type: alloc::rc::Rc,alloc::alloc::Global>] +//@ cdb-check:dyn_rc,d [Type: alloc::rcs::rc::Rc,alloc::alloc::Global>] //@ cdb-check: [Reference count] : 31 [Type: core::cell::Cell] //@ cdb-check: [Weak reference count] : 2 [Type: core::cell::Cell] //@ cdb-command:dx dyn_rc_weak,d -//@ cdb-check:dyn_rc_weak,d [Type: alloc::rc::Weak,alloc::alloc::Global>] +//@ cdb-check:dyn_rc_weak,d [Type: alloc::rcs::rc::Weak,alloc::alloc::Global>] //@ cdb-check: [Reference count] : 31 [Type: core::cell::Cell] //@ cdb-check: [Weak reference count] : 2 [Type: core::cell::Cell] //@ cdb-command:dx slice_rc,d -//@ cdb-check:slice_rc,d : { len=3 } [Type: alloc::rc::Rc,alloc::alloc::Global>] +//@ cdb-check:slice_rc,d : { len=3 } [Type: alloc::rcs::rc::Rc,alloc::alloc::Global>] //@ cdb-check: [Length] : 3 [Type: [...]] //@ cdb-check: [Reference count] : 41 [Type: core::cell::Cell] //@ cdb-check: [Weak reference count] : 2 [Type: core::cell::Cell] @@ -66,7 +66,7 @@ //@ cdb-check: [2] : 3 [Type: u32] //@ cdb-command:dx slice_rc_weak,d -//@ cdb-check:slice_rc_weak,d : { len=3 } [Type: alloc::rc::Weak,alloc::alloc::Global>] +//@ cdb-check:slice_rc_weak,d : { len=3 } [Type: alloc::rcs::rc::Weak,alloc::alloc::Global>] //@ cdb-check: [Length] : 3 [Type: [...]] //@ cdb-check: [Reference count] : 41 [Type: core::cell::Cell] //@ cdb-check: [Weak reference count] : 2 [Type: core::cell::Cell] diff --git a/tests/debuginfo/strings-and-strs.rs b/tests/debuginfo/strings-and-strs.rs index 81e3037f1e566..56e9f6a5b9773 100644 --- a/tests/debuginfo/strings-and-strs.rs +++ b/tests/debuginfo/strings-and-strs.rs @@ -20,13 +20,13 @@ //@ gdb-check:$4 = ("Hello", "World") //@ gdb-command:print str_in_rc -//@ gdb-check:$5 = alloc::rc::Rc<&str, alloc::alloc::Global> {ptr: core::ptr::non_null::NonNull> {pointer: 0x[...]}, phantom: core::marker::PhantomData>, alloc: alloc::alloc::Global} +//@ gdb-check:$5 = alloc::rcs::rc::Rc<&str, alloc::alloc::Global> {ptr: core::ptr::non_null::NonNull> {pointer: 0x[...]}, phantom: core::marker::PhantomData>, alloc: alloc::alloc::Global} //@ gdb-command:print box_str //@ gdb-check:$6 = alloc::boxed::Box [87, 111, 114, 108, 100] //@ gdb-command:print rc_str -//@ gdb-check:$7 = alloc::rc::Rc {ptr: core::ptr::non_null::NonNull> {pointer: alloc::rc::RcInner {strong: core::cell::Cell {value: core::cell::UnsafeCell {value: 1}}, weak: core::cell::Cell {value: core::cell::UnsafeCell {value: 1}}, value: 0x[...]}}, phantom: core::marker::PhantomData>, alloc: alloc::alloc::Global} +//@ gdb-check:$7 = alloc::rcs::rc::Rc {ptr: core::ptr::non_null::NonNull> {pointer: alloc::rcs::rc::RcInner {strong: core::cell::Cell {value: core::cell::UnsafeCell {value: 1}}, weak: core::cell::Cell {value: core::cell::UnsafeCell {value: 1}}, value: 0x[...]}}, phantom: core::marker::PhantomData>, alloc: alloc::alloc::Global} // === LLDB TESTS ================================================================================== //@ lldb-command:run @@ -56,7 +56,7 @@ // lldb-command:v rc_str // ignore-tidy-linelength -// lldb-check:(alloc::rc::Rc) rc_str = strong=1, weak=1 { value = "World" } +// lldb-check:(alloc::rcs::rc::Rc) rc_str = strong=1, weak=1 { value = "World" } #![allow(unused_variables)] diff --git a/tests/ui/methods/shadowed-intrinsic-method-deref.stderr b/tests/ui/methods/shadowed-intrinsic-method-deref.stderr index 4e0c5dfc55f09..d4dccd6dc5fdb 100644 --- a/tests/ui/methods/shadowed-intrinsic-method-deref.stderr +++ b/tests/ui/methods/shadowed-intrinsic-method-deref.stderr @@ -6,7 +6,7 @@ LL | let sb : &S = &s.borrow(); | help: the trait `Borrow` is not implemented for `Rc>` but trait `Borrow>` is implemented for it - --> $SRC_DIR/alloc/src/rc.rs:LL:COL + --> $SRC_DIR/alloc/src/rcs/rc.rs:LL:COL = help: for that trait implementation, expected `RefCell`, found `S` = note: there's an inherent method on `RefCell` of the same name, which can be auto-dereferenced from `&RefCell` help: to access the inherent method on `RefCell`, use the fully-qualified path From f74f6ea68cb07dc75af48fffc1efc41b80c411d1 Mon Sep 17 00:00:00 2001 From: EFanZh Date: Mon, 28 Sep 2026 16:41:59 +0800 Subject: [PATCH 09/16] Rename `library/alloc/src/sync.rs` to `library/alloc/src/rcs/arc.rs` temporarily to keep file history --- library/alloc/src/{sync.rs => rcs/arc.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename library/alloc/src/{sync.rs => rcs/arc.rs} (100%) diff --git a/library/alloc/src/sync.rs b/library/alloc/src/rcs/arc.rs similarity index 100% rename from library/alloc/src/sync.rs rename to library/alloc/src/rcs/arc.rs From cba32372c7b057d58d8d9f4d3fd17848d83d2b93 Mon Sep 17 00:00:00 2001 From: EFanZh Date: Mon, 28 Sep 2026 16:41:59 +0800 Subject: [PATCH 10/16] Re-export `alloc::rcs::arc::*` in `alloc::sync` --- library/alloc/src/lib.rs | 3 +++ library/alloc/src/rcs/arc.rs | 10 ---------- library/alloc/src/sync.rs | 14 ++++++++++++++ src/etc/natvis/liballoc.natvis | 8 ++++---- tests/codegen-llvm/debuginfo-cyclic-structure.rs | 2 +- tests/codegen-llvm/issues/issue-111603.rs | 8 ++++---- tests/debuginfo/rc_arc.rs | 12 ++++++------ tests/debuginfo/thread.rs | 2 +- 8 files changed, 33 insertions(+), 26 deletions(-) create mode 100644 library/alloc/src/sync.rs diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 60ada2ba866bb..b1aa1a9a4f525 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -244,6 +244,9 @@ mod raw_vec; #[cfg(not(no_rc))] mod rcs { pub mod rc; + + #[cfg(all(not(no_sync), target_has_atomic = "ptr"))] + pub(crate) mod arc; } // Heaps provided for low-level allocation strategies diff --git a/library/alloc/src/rcs/arc.rs b/library/alloc/src/rcs/arc.rs index e412cee62d8f6..e61ea5193eccb 100644 --- a/library/alloc/src/rcs/arc.rs +++ b/library/alloc/src/rcs/arc.rs @@ -1,13 +1,3 @@ -#![stable(feature = "rust1", since = "1.0.0")] - -//! Thread-safe reference-counting pointers. -//! -//! See the [`Arc`][Arc] documentation for more details. -//! -//! **Note**: This module is only available on platforms that support atomic -//! loads and stores of pointers. This may be detected at compile time using -//! `#[cfg(target_has_atomic = "ptr")]`. - use core::any::Any; use core::cell::CloneFromCell; #[cfg(not(no_global_oom_handling))] diff --git a/library/alloc/src/sync.rs b/library/alloc/src/sync.rs new file mode 100644 index 0000000000000..afb1848d21a24 --- /dev/null +++ b/library/alloc/src/sync.rs @@ -0,0 +1,14 @@ +#![stable(feature = "rust1", since = "1.0.0")] + +//! Thread-safe reference-counting pointers. +//! +//! See the [`Arc`][Arc] documentation for more details. +//! +//! **Note**: This module is only available on platforms that support atomic +//! loads and stores of pointers. This may be detected at compile time using +//! `#[cfg(target_has_atomic = "ptr")]`. + +#[unstable(feature = "unique_rc_arc", issue = "112566")] +pub use crate::rcs::arc::UniqueArc; +#[stable(feature = "rust1", since = "1.0.0")] +pub use crate::rcs::arc::{Arc, Weak}; diff --git a/src/etc/natvis/liballoc.natvis b/src/etc/natvis/liballoc.natvis index cfcca9014e70c..c8e143b49911b 100644 --- a/src/etc/natvis/liballoc.natvis +++ b/src/etc/natvis/liballoc.natvis @@ -131,7 +131,7 @@ - + {ptr.pointer->data} @@ -146,7 +146,7 @@ - + {{ len={ptr.pointer.length} }} ptr.pointer.length @@ -160,7 +160,7 @@ - + {ptr.pointer->data} @@ -175,7 +175,7 @@ - + {{ len={ptr.pointer.length} }} ptr.pointer.length diff --git a/tests/codegen-llvm/debuginfo-cyclic-structure.rs b/tests/codegen-llvm/debuginfo-cyclic-structure.rs index b8cc544774158..dc7193dd08ed2 100644 --- a/tests/codegen-llvm/debuginfo-cyclic-structure.rs +++ b/tests/codegen-llvm/debuginfo-cyclic-structure.rs @@ -3,7 +3,7 @@ // Check that debug information exists for structures containing loops (cyclic references). // Previously it may incorrectly prune member information during recursive type inference check. -// CHECK: !DICompositeType(tag: DW_TAG_structure_type, name: "Arc Arc<[u64]> { // CHECK-LABEL: @new_uninit #[no_mangle] pub fn new_uninit(x: u64) -> Arc<[u64; 1000]> { - // CHECK: call alloc::sync::arcinner_layout_for_value_layout - // CHECK-NOT: call alloc::sync::arcinner_layout_for_value_layout + // CHECK: call alloc::rcs::arc::arcinner_layout_for_value_layout + // CHECK-NOT: call alloc::rcs::arc::arcinner_layout_for_value_layout let mut arc = Arc::new_uninit(); unsafe { Arc::get_mut_unchecked(&mut arc) }.write([x; 1000]); unsafe { arc.assume_init() } @@ -30,8 +30,8 @@ pub fn new_uninit(x: u64) -> Arc<[u64; 1000]> { // CHECK-LABEL: @new_uninit_slice #[no_mangle] pub fn new_uninit_slice(x: u64) -> Arc<[u64]> { - // CHECK: call alloc::sync::arcinner_layout_for_value_layout - // CHECK-NOT: call alloc::sync::arcinner_layout_for_value_layout + // CHECK: call alloc::rcs::arc::arcinner_layout_for_value_layout + // CHECK-NOT: call alloc::rcs::arc::arcinner_layout_for_value_layout let mut arc = Arc::new_uninit_slice(1000); for elem in unsafe { Arc::get_mut_unchecked(&mut arc) } { elem.write(x); diff --git a/tests/debuginfo/rc_arc.rs b/tests/debuginfo/rc_arc.rs index 39b5dc0d84408..adeb96dba1b80 100644 --- a/tests/debuginfo/rc_arc.rs +++ b/tests/debuginfo/rc_arc.rs @@ -37,12 +37,12 @@ //@ cdb-check: [Weak reference count] : 2 [Type: core::cell::Cell] //@ cdb-command:dx arc,d -//@ cdb-check:arc,d : 222 [Type: alloc::sync::Arc] +//@ cdb-check:arc,d : 222 [Type: alloc::rcs::arc::Arc] //@ cdb-check: [Reference count] : 21 [Type: core::sync::atomic::Atomic] //@ cdb-check: [Weak reference count] : 2 [Type: core::sync::atomic::Atomic] //@ cdb-command:dx weak_arc,d -//@ cdb-check:weak_arc,d : 222 [Type: alloc::sync::Weak] +//@ cdb-check:weak_arc,d : 222 [Type: alloc::rcs::arc::Weak] //@ cdb-check: [Reference count] : 21 [Type: core::sync::atomic::Atomic] //@ cdb-check: [Weak reference count] : 2 [Type: core::sync::atomic::Atomic] @@ -75,17 +75,17 @@ //@ cdb-check: [2] : 3 [Type: u32] //@ cdb-command:dx dyn_arc,d -//@ cdb-check:dyn_arc,d [Type: alloc::sync::Arc,alloc::alloc::Global>] +//@ cdb-check:dyn_arc,d [Type: alloc::rcs::arc::Arc,alloc::alloc::Global>] //@ cdb-check: [Reference count] : 51 [Type: core::sync::atomic::Atomic] //@ cdb-check: [Weak reference count] : 2 [Type: core::sync::atomic::Atomic] //@ cdb-command:dx dyn_arc_weak,d -//@ cdb-check:dyn_arc_weak,d [Type: alloc::sync::Weak,alloc::alloc::Global>] +//@ cdb-check:dyn_arc_weak,d [Type: alloc::rcs::arc::Weak,alloc::alloc::Global>] //@ cdb-check: [Reference count] : 51 [Type: core::sync::atomic::Atomic] //@ cdb-check: [Weak reference count] : 2 [Type: core::sync::atomic::Atomic] //@ cdb-command:dx slice_arc,d -//@ cdb-check:slice_arc,d : { len=3 } [Type: alloc::sync::Arc,alloc::alloc::Global>] +//@ cdb-check:slice_arc,d : { len=3 } [Type: alloc::rcs::arc::Arc,alloc::alloc::Global>] //@ cdb-check: [Length] : 3 [Type: [...]] //@ cdb-check: [Reference count] : 61 [Type: core::sync::atomic::Atomic] //@ cdb-check: [Weak reference count] : 2 [Type: core::sync::atomic::Atomic] @@ -94,7 +94,7 @@ //@ cdb-check: [2] : 6 [Type: u32] //@ cdb-command:dx slice_arc_weak,d -//@ cdb-check:slice_arc_weak,d : { len=3 } [Type: alloc::sync::Weak,alloc::alloc::Global>] +//@ cdb-check:slice_arc_weak,d : { len=3 } [Type: alloc::rcs::arc::Weak,alloc::alloc::Global>] //@ cdb-check: [Length] : 3 [Type: [...]] //@ cdb-check: [Reference count] : 61 [Type: core::sync::atomic::Atomic] //@ cdb-check: [Weak reference count] : 2 [Type: core::sync::atomic::Atomic] diff --git a/tests/debuginfo/thread.rs b/tests/debuginfo/thread.rs index d78dad406ac6e..ee33f921cc033 100644 --- a/tests/debuginfo/thread.rs +++ b/tests/debuginfo/thread.rs @@ -14,7 +14,7 @@ // //@ cdb-command:dx t,d //@ cdb-check:t,d : [...] [Type: std::thread::thread::Thread *] -//@ cdb-check:[...] inner [...][Type: core::pin::Pin >] +//@ cdb-check:[...] inner [...][Type: core::pin::Pin >] use std::thread; From 2e58a77da46625e9233cfac7b5cc758c4db29a18 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 16 Sep 2026 20:34:55 +0200 Subject: [PATCH 11/16] Fix intra doc link resolution when a doc comment is composed of both inner and outer doc comment --- compiler/rustc_resolve/src/late.rs | 34 +++++++++--- compiler/rustc_resolve/src/rustdoc.rs | 53 +++++++++++-------- compiler/rustc_resolve/src/rustdoc/tests.rs | 3 ++ src/librustdoc/clean/types.rs | 12 +++-- src/librustdoc/clean/types/tests.rs | 1 + .../passes/collect_intra_doc_links.rs | 48 ++++++++++++----- .../passes/lint/redundant_explicit_links.rs | 9 ++-- .../intra-doc/mixed-inner-outer.rs | 22 ++++++++ 8 files changed, 132 insertions(+), 50 deletions(-) create mode 100644 tests/rustdoc-html/intra-doc/mixed-inner-outer.rs diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index e1787eadf5fb9..4050ec8eee5d5 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -12,6 +12,7 @@ use std::debug_assert_matches; use std::mem::{replace, swap, take}; use std::ops::{ControlFlow, Range}; +use rustc_ast::attr::AttributeExt; use rustc_ast::visit::{ AssocCtxt, BoundKind, FnCtxt, FnKind, Visitor, try_visit, visit_opt, walk_list, }; @@ -2860,10 +2861,22 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } fn resolve_item(&mut self, item: &'ast Item) { - let mod_inner_docs = - matches!(item.kind, ItemKind::Mod(..)) && rustdoc::inner_docs(&item.attrs); - if !mod_inner_docs && !matches!(item.kind, ItemKind::Impl(..) | ItemKind::Use(..)) { - self.resolve_doc_links(&item.attrs, MaybeExported::Ok(item.id)); + match item.kind { + ItemKind::Mod(..) => { + // We only handle outer doc comments for modules here. + let attrs = if let Some(pos) = item.attrs.iter().position(|a| { + a.doc_resolution_scope().is_some_and(|style| style == AttrStyle::Inner) + }) { + &item.attrs[..pos] + } else { + &item.attrs + }; + self.resolve_doc_links(attrs, MaybeExported::Ok(item.id)); + } + ItemKind::Impl(..) | ItemKind::Use(..) => {} + _ => { + self.resolve_doc_links(&item.attrs, MaybeExported::Ok(item.id)); + } } debug!("(resolving item) resolving {:?} ({:?})", item.kind.ident(), item.kind); @@ -2957,9 +2970,16 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { let orig_module = replace(&mut self.parent_scope.module, module); self.with_rib(ValueNS, RibKind::Module(module.expect_local()), |this| { this.with_rib(TypeNS, RibKind::Module(module.expect_local()), |this| { - if mod_inner_docs { - this.resolve_doc_links(&item.attrs, MaybeExported::Ok(item.id)); - } + // Outer doc comments were already handled above, now we handle + // inner doc comments. + let attrs = if let Some(pos) = item.attrs.iter().position(|a| { + a.doc_resolution_scope().is_some_and(|style| style == AttrStyle::Inner) + }) { + &item.attrs[pos..] + } else { + &[] + }; + this.resolve_doc_links(attrs, MaybeExported::Ok(item.id)); let old_macro_rules = this.parent_scope.macro_rules; visit::walk_item(this, item); // Maintain macro_rules scopes in the same way as during early resolution diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index ddf5600f76659..ac463297ad84d 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -9,9 +9,9 @@ use pulldown_cmark::{ }; use rustc_ast as ast; use rustc_ast::attr::AttributeExt; -use rustc_ast::join_path_syms; use rustc_ast::token::DocFragmentKind; use rustc_ast::util::comments::beautify_doc_string; +use rustc_ast::{AttrStyle, join_path_syms}; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::unord::UnordSet; use rustc_middle::ty::TyCtxt; @@ -48,6 +48,7 @@ pub struct DocFragment { /// Because we tamper with the spans context, this information cannot be correctly retrieved /// later on. So instead, we compute it and store it here. pub from_expansion: bool, + pub style: AttrStyle, } #[derive(Clone, Copy, Debug)] @@ -215,8 +216,15 @@ pub fn attrs_to_doc_fragments<'a, A: AttributeExt + Clone + 'a>( (value_span.with_ctxt(attr_span.ctxt()), value_span.from_expansion()) } }; - let fragment = - DocFragment { span, doc, kind: fragment_kind, item_id, indent: 0, from_expansion }; + let fragment = DocFragment { + span, + doc, + kind: fragment_kind, + item_id, + indent: 0, + from_expansion, + style: attr.doc_resolution_scope().unwrap(), + }; doc_fragments.push(fragment); } else if !doc_only { other_attrs.push(attr.clone()); @@ -231,6 +239,14 @@ pub fn attrs_to_doc_fragments<'a, A: AttributeExt + Clone + 'a>( (doc_fragments, other_attrs) } +/// Represents a doc comment, split in two parts, outer and inner attributes. It's needed for +/// the intra-doc link resolution context. +#[derive(Default)] +pub struct DocStrings { + pub outer: String, + pub inner: String, +} + /// Return the doc-comments on this item, grouped by the module they came from. /// The module can be different if this is a re-export with added documentation. /// @@ -238,11 +254,16 @@ pub fn attrs_to_doc_fragments<'a, A: AttributeExt + Clone + 'a>( /// early and late doc link resolution regardless of their position. pub fn prepare_to_doc_link_resolution( doc_fragments: &[DocFragment], -) -> FxIndexMap, String> { - let mut res = FxIndexMap::default(); +) -> FxIndexMap, DocStrings> { + let mut res: FxIndexMap, DocStrings> = FxIndexMap::default(); for fragment in doc_fragments { - let out_str = res.entry(fragment.item_id).or_default(); - add_doc_fragment(out_str, fragment); + let out_strs = res.entry(fragment.item_id).or_default(); + let out = if fragment.style == AttrStyle::Inner { + &mut out_strs.inner + } else { + &mut out_strs.outer + }; + add_doc_fragment(out, fragment); } res } @@ -351,19 +372,6 @@ pub fn strip_generics_from_path(path_str: &str) -> Result, MalformedGen } } -/// Returns whether the first doc-comment is an inner attribute. -/// -/// If there are no doc-comments, return true. -/// FIXME(#78591): Support both inner and outer attributes on the same item. -pub fn inner_docs(attrs: &[impl AttributeExt]) -> bool { - for attr in attrs { - if let Some(attr_style) = attr.doc_resolution_scope() { - return attr_style == ast::AttrStyle::Inner; - } - } - true -} - /// Has `#[rustc_doc_primitive]` or `#[doc(keyword)]` or `#[doc(attribute)]`. pub fn has_primitive_or_keyword_or_attribute_docs(attrs: &[impl AttributeExt]) -> bool { for attr in attrs { @@ -413,7 +421,10 @@ pub(crate) fn attrs_to_preprocessed_links(attrs: &[ast::Attribute]) -> Vec) -> bool { + use rustc_ast::attr::AttributeExt; + self.item_id .as_def_id() .map(|did| { - inner_docs( - #[allow(deprecated)] - tcx.get_all_attrs(did), - ) + #[allow(deprecated)] + tcx.get_all_attrs(did).iter().any(|attr| { + attr.doc_resolution_scope().is_some_and(|style| style == ast::AttrStyle::Inner) + }) }) .unwrap_or(false) } diff --git a/src/librustdoc/clean/types/tests.rs b/src/librustdoc/clean/types/tests.rs index a0fc623c20c48..29a8fd5e2678e 100644 --- a/src/librustdoc/clean/types/tests.rs +++ b/src/librustdoc/clean/types/tests.rs @@ -12,6 +12,7 @@ fn create_doc_fragment(s: &str) -> Vec { kind: DocFragmentKind::Sugared(CommentKind::Line), indent: 0, from_expansion: false, + style: rustc_ast::AttrStyle::Outer, }] } diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index d7815e64178a7..ac7cf2a42bd3b 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -21,8 +21,8 @@ use rustc_middle::ty; use rustc_middle::ty::{Ty, TyCtxt}; use rustc_resolve::rustdoc::pulldown_cmark::LinkType; use rustc_resolve::rustdoc::{ - MalformedGenerics, has_primitive_or_keyword_or_attribute_docs, prepare_to_doc_link_resolution, - source_span_for_markdown_range, strip_generics_from_path, + DocStrings, MalformedGenerics, has_primitive_or_keyword_or_attribute_docs, + prepare_to_doc_link_resolution, source_span_for_markdown_range, strip_generics_from_path, }; use rustc_span::def_id::ModId; use rustc_span::edit_distance::find_best_match_for_name; @@ -1091,14 +1091,7 @@ impl LinkCollector<'_, '_> { return; } - let mut try_insert_links = |item_id, doc: &str| { - if should_skip_link_resolution(item_id) { - return; - } - let module_id = match tcx.def_kind(item_id) { - DefKind::Mod if item.inner_docs(tcx) => ModId::new_unchecked(item_id), - _ => find_nearest_parent_module(tcx, item_id).unwrap(), - }; + let mut try_insert_links_inner = |item_id, module_id, doc: &str| { for md_link in preprocessed_markdown_links(&doc) { let link = self.resolve_link(&doc, item, item_id, module_id, &md_link); if let Some(link) = link { @@ -1112,16 +1105,45 @@ impl LinkCollector<'_, '_> { } }; + let mut try_insert_links = |item_id, docs: &DocStrings| { + if should_skip_link_resolution(item_id) { + return; + } + match tcx.def_kind(item_id) { + DefKind::Mod => { + if !docs.outer.is_empty() { + try_insert_links_inner( + item_id, + find_nearest_parent_module(tcx, item_id).unwrap(), + &docs.outer, + ); + } + if !docs.inner.is_empty() { + try_insert_links_inner(item_id, ModId::new_unchecked(item_id), &docs.inner); + } + } + _ => { + // It's the same handling for non-module items. + let module_id = find_nearest_parent_module(tcx, item_id).unwrap(); + try_insert_links_inner( + item_id, + module_id, + &format!("{}{}", docs.outer, docs.inner), + ); + } + } + }; + // We want to resolve in the lexical scope of the documentation. // In the presence of re-exports, this is not the same as the module of the item. // Rather than merging all documentation into one, resolve it one attribute at a time // so we know which module it came from. for (item_id, doc) in prepare_to_doc_link_resolution(&item.attrs.doc_strings) { - if !may_have_doc_links(&doc) { + if !may_have_doc_links(&doc.inner) && !may_have_doc_links(&doc.outer) { continue; } - debug!("combined_docs={doc}"); + debug!("combined_docs=outer: {} inner: {}", doc.outer, doc.inner); // NOTE: if there are links that start in one crate and end in another, this will not resolve them. // This is a degenerate case and it's not supported by rustdoc. let item_id = item_id.unwrap_or_else(|| item.item_id.expect_def_id()); @@ -1163,7 +1185,7 @@ impl LinkCollector<'_, '_> { } else { item.item_id.expect_def_id() }; - try_insert_links(item_id, note) + try_insert_links(item_id, &DocStrings { outer: note.to_owned(), inner: String::new() }); } } diff --git a/src/librustdoc/passes/lint/redundant_explicit_links.rs b/src/librustdoc/passes/lint/redundant_explicit_links.rs index 902054c917699..e02fbae3e0568 100644 --- a/src/librustdoc/passes/lint/redundant_explicit_links.rs +++ b/src/librustdoc/passes/lint/redundant_explicit_links.rs @@ -30,10 +30,11 @@ struct LinkData { pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId) { let hunks = prepare_to_doc_link_resolution(&item.attrs.doc_strings); for (item_id, doc) in hunks { - if let Some(item_id) = item_id.or(item.def_id()) - && !doc.is_empty() - { - check_redundant_explicit_link_for_did(cx, item, item_id, hir_id, &doc); + if let Some(item_id) = item_id.or(item.def_id()) { + if !doc.outer.is_empty() || !doc.inner.is_empty() { + let doc = format!("{}{}", doc.outer, doc.inner); + check_redundant_explicit_link_for_did(cx, item, item_id, hir_id, &doc); + } } } } diff --git a/tests/rustdoc-html/intra-doc/mixed-inner-outer.rs b/tests/rustdoc-html/intra-doc/mixed-inner-outer.rs new file mode 100644 index 0000000000000..5a0f92522662d --- /dev/null +++ b/tests/rustdoc-html/intra-doc/mixed-inner-outer.rs @@ -0,0 +1,22 @@ +// This test ensures that when a documentation is composed of both inner and outer +// doc comments, the intra-doc link resolution still works as expected. +// Regression test for . +// Regression test for . +// Regression test for . + +#![crate_name = "foo"] +#![deny(rustdoc::broken_intra_doc_links)] + +//@ has foo/demo/index.html +//@ has - '//a[@href="../struct.Foo.html"]' 'Foo' +//@ has - '//a[@href="struct.DemoStruct.html"]' 'DemoStruct' + +/// Outer doc-comment [`Foo`]. +pub mod demo { + //! + //! Inner doc-comment with link: [`DemoStruct`] + + pub struct DemoStruct; +} + +pub struct Foo; From ceee44bc9df314e1b522e6e479764b96ad5db82d Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Mon, 28 Sep 2026 16:01:46 +0200 Subject: [PATCH 12/16] Force the correct type variable to never for method resolution on an adjusted never type --- compiler/rustc_hir_typeck/src/method/probe.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs index 178585984cf27..aee246a61fb1e 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -524,8 +524,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { span, MethodCallOnDivergingInferenceVariable, ); - let root_ty = Ty::new_var(self.tcx, ty_id); - self.demand_eqtype(span, root_ty, self.tcx.types.never); + self.demand_eqtype(span, ty, self.tcx.types.never); } else { let guar = match *ty.kind() { _ if let Some(guar) = self.tainted_by_errors() => guar, From f3b65028f7a6f4cf250929317375d06437b445a2 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Mon, 28 Sep 2026 16:02:04 +0200 Subject: [PATCH 13/16] Run the `method-on-never` type on the next trait solver --- ....stderr => method-on-never.current.stderr} | 20 ++-- .../basic/method-on-never.next.stderr | 113 ++++++++++++++++++ tests/ui/never_type/basic/method-on-never.rs | 2 + 3 files changed, 125 insertions(+), 10 deletions(-) rename tests/ui/never_type/basic/{method-on-never.stderr => method-on-never.current.stderr} (93%) create mode 100644 tests/ui/never_type/basic/method-on-never.next.stderr diff --git a/tests/ui/never_type/basic/method-on-never.stderr b/tests/ui/never_type/basic/method-on-never.current.stderr similarity index 93% rename from tests/ui/never_type/basic/method-on-never.stderr rename to tests/ui/never_type/basic/method-on-never.current.stderr index f5903616be6f4..ec89b37bc403e 100644 --- a/tests/ui/never_type/basic/method-on-never.stderr +++ b/tests/ui/never_type/basic/method-on-never.current.stderr @@ -1,5 +1,5 @@ warning: method call on a diverging inference variable - --> $DIR/method-on-never.rs:42:7 + --> $DIR/method-on-never.rs:44:7 | LL | x.method(); | ^^^^^^ @@ -10,7 +10,7 @@ LL | x.method(); = note: `#[warn(method_call_on_diverging_infer_var)]` (part of `#[warn(future_incompatible)]`) on by default warning: method call on a diverging inference variable - --> $DIR/method-on-never.rs:46:17 + --> $DIR/method-on-never.rs:48:17 | LL | { loop {} }.method(); | ^^^^^^ @@ -20,7 +20,7 @@ LL | { loop {} }.method(); = note: for more information, see issue #156047 warning: method call on a diverging inference variable - --> $DIR/method-on-never.rs:51:16 + --> $DIR/method-on-never.rs:53:16 | LL | y => y.method(), | ^^^^^^ @@ -30,7 +30,7 @@ LL | y => y.method(), = note: for more information, see issue #156047 warning: method call on a diverging inference variable - --> $DIR/method-on-never.rs:57:27 + --> $DIR/method-on-never.rs:59:27 | LL | error => (&error).anyhow_kind().new(error), | ^^^^^^^^^^^ @@ -40,7 +40,7 @@ LL | error => (&error).anyhow_kind().new(error), = note: for more information, see issue #156047 warning: method call on a diverging inference variable - --> $DIR/method-on-never.rs:63:9 + --> $DIR/method-on-never.rs:65:9 | LL | res.method(); | ^^^^^^ @@ -53,7 +53,7 @@ warning: 5 warnings emitted Future incompatibility report: Future breakage diagnostic: warning: method call on a diverging inference variable - --> $DIR/method-on-never.rs:42:7 + --> $DIR/method-on-never.rs:44:7 | LL | x.method(); | ^^^^^^ @@ -65,7 +65,7 @@ LL | x.method(); Future breakage diagnostic: warning: method call on a diverging inference variable - --> $DIR/method-on-never.rs:46:17 + --> $DIR/method-on-never.rs:48:17 | LL | { loop {} }.method(); | ^^^^^^ @@ -77,7 +77,7 @@ LL | { loop {} }.method(); Future breakage diagnostic: warning: method call on a diverging inference variable - --> $DIR/method-on-never.rs:51:16 + --> $DIR/method-on-never.rs:53:16 | LL | y => y.method(), | ^^^^^^ @@ -89,7 +89,7 @@ LL | y => y.method(), Future breakage diagnostic: warning: method call on a diverging inference variable - --> $DIR/method-on-never.rs:57:27 + --> $DIR/method-on-never.rs:59:27 | LL | error => (&error).anyhow_kind().new(error), | ^^^^^^^^^^^ @@ -101,7 +101,7 @@ LL | error => (&error).anyhow_kind().new(error), Future breakage diagnostic: warning: method call on a diverging inference variable - --> $DIR/method-on-never.rs:63:9 + --> $DIR/method-on-never.rs:65:9 | LL | res.method(); | ^^^^^^ diff --git a/tests/ui/never_type/basic/method-on-never.next.stderr b/tests/ui/never_type/basic/method-on-never.next.stderr new file mode 100644 index 0000000000000..ec89b37bc403e --- /dev/null +++ b/tests/ui/never_type/basic/method-on-never.next.stderr @@ -0,0 +1,113 @@ +warning: method call on a diverging inference variable + --> $DIR/method-on-never.rs:44:7 + | +LL | x.method(); + | ^^^^^^ + | + = help: consider providing a type annotation + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #156047 + = note: `#[warn(method_call_on_diverging_infer_var)]` (part of `#[warn(future_incompatible)]`) on by default + +warning: method call on a diverging inference variable + --> $DIR/method-on-never.rs:48:17 + | +LL | { loop {} }.method(); + | ^^^^^^ + | + = help: consider providing a type annotation + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #156047 + +warning: method call on a diverging inference variable + --> $DIR/method-on-never.rs:53:16 + | +LL | y => y.method(), + | ^^^^^^ + | + = help: consider providing a type annotation + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #156047 + +warning: method call on a diverging inference variable + --> $DIR/method-on-never.rs:59:27 + | +LL | error => (&error).anyhow_kind().new(error), + | ^^^^^^^^^^^ + | + = help: consider providing a type annotation + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #156047 + +warning: method call on a diverging inference variable + --> $DIR/method-on-never.rs:65:9 + | +LL | res.method(); + | ^^^^^^ + | + = help: consider providing a type annotation + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #156047 + +warning: 5 warnings emitted + +Future incompatibility report: Future breakage diagnostic: +warning: method call on a diverging inference variable + --> $DIR/method-on-never.rs:44:7 + | +LL | x.method(); + | ^^^^^^ + | + = help: consider providing a type annotation + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #156047 + = note: `#[warn(method_call_on_diverging_infer_var)]` (part of `#[warn(future_incompatible)]`) on by default + +Future breakage diagnostic: +warning: method call on a diverging inference variable + --> $DIR/method-on-never.rs:48:17 + | +LL | { loop {} }.method(); + | ^^^^^^ + | + = help: consider providing a type annotation + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #156047 + = note: `#[warn(method_call_on_diverging_infer_var)]` (part of `#[warn(future_incompatible)]`) on by default + +Future breakage diagnostic: +warning: method call on a diverging inference variable + --> $DIR/method-on-never.rs:53:16 + | +LL | y => y.method(), + | ^^^^^^ + | + = help: consider providing a type annotation + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #156047 + = note: `#[warn(method_call_on_diverging_infer_var)]` (part of `#[warn(future_incompatible)]`) on by default + +Future breakage diagnostic: +warning: method call on a diverging inference variable + --> $DIR/method-on-never.rs:59:27 + | +LL | error => (&error).anyhow_kind().new(error), + | ^^^^^^^^^^^ + | + = help: consider providing a type annotation + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #156047 + = note: `#[warn(method_call_on_diverging_infer_var)]` (part of `#[warn(future_incompatible)]`) on by default + +Future breakage diagnostic: +warning: method call on a diverging inference variable + --> $DIR/method-on-never.rs:65:9 + | +LL | res.method(); + | ^^^^^^ + | + = help: consider providing a type annotation + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #156047 + = note: `#[warn(method_call_on_diverging_infer_var)]` (part of `#[warn(future_incompatible)]`) on by default + diff --git a/tests/ui/never_type/basic/method-on-never.rs b/tests/ui/never_type/basic/method-on-never.rs index 4194884ade9ea..ac928a0e9d071 100644 --- a/tests/ui/never_type/basic/method-on-never.rs +++ b/tests/ui/never_type/basic/method-on-never.rs @@ -1,3 +1,5 @@ +//@ revisions: current next +//@[next] compile-flags: -Znext-solver //@ check-pass // Regression test for https://github.com/rust-lang/rust/issues/143349 From b6d0fb14576c72e11f0754c6f457ab0341817a8b Mon Sep 17 00:00:00 2001 From: dianne Date: Fri, 11 Sep 2026 09:39:50 -0700 Subject: [PATCH 14/16] select: prefer impl candidates over where clauses --- .../rustc_trait_selection/src/solve/select.rs | 15 ++++++++-- ...tin-impl-over-where-clause-in-const-pat.rs | 26 +++++++++++++++++ ...ser-impl-over-where-clause-in-const-pat.rs | 29 +++++++++++++++++++ 3 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 tests/ui/trait-bounds/prefer-builtin-impl-over-where-clause-in-const-pat.rs create mode 100644 tests/ui/trait-bounds/prefer-user-impl-over-where-clause-in-const-pat.rs diff --git a/compiler/rustc_trait_selection/src/solve/select.rs b/compiler/rustc_trait_selection/src/solve/select.rs index 53b999e4e5244..f41808df8a5b2 100644 --- a/compiler/rustc_trait_selection/src/solve/select.rs +++ b/compiler/rustc_trait_selection/src/solve/select.rs @@ -2,7 +2,7 @@ use std::ops::ControlFlow; use rustc_infer::infer::InferCtxt; use rustc_infer::traits::solve::inspect::ProbeKind; -use rustc_infer::traits::solve::{CandidateSource, Certainty, Goal}; +use rustc_infer::traits::solve::{CandidateSource, Certainty, Goal, ParamEnvSource}; use rustc_infer::traits::{ BuiltinImplSource, ImplSource, ImplSourceUserDefinedData, Obligation, ObligationCause, PolyTraitObligation, Selection, SelectionError, SelectionResult, @@ -93,8 +93,8 @@ fn candidate_should_be_dropped_in_favor_of<'tcx>( victim: &inspect::InspectCandidate<'_, 'tcx>, other: &inspect::InspectCandidate<'_, 'tcx>, ) -> bool { - // Don't winnow until `Certainty::Yes` -- we don't need to winnow until - // codegen, and only on the good path. + // Don't winnow until `Certainty::Yes` -- we don't need to winnow until constant evaluation or + // codegen. if matches!(other.result().unwrap(), Certainty::Maybe(_)) { return false; } @@ -137,6 +137,15 @@ fn candidate_should_be_dropped_in_favor_of<'tcx>( victim.goal().infcx().tcx.specializes((other_def_id, victim_def_id)) } + // Prefer impl candidates over global where clause candidates. Unless `generic_const_args` + // is enabled, we currently don't use an empty environment when resolving and evaluating + // constants to lower them to patterns. If we don't drop where clause candidates here, we + // can fail to select impl candidates (#162331). + ( + CandidateSource::ParamEnv(ParamEnvSource::Global), + CandidateSource::Impl(_) | CandidateSource::BuiltinImpl(_), + ) => true, + _ => false, } } diff --git a/tests/ui/trait-bounds/prefer-builtin-impl-over-where-clause-in-const-pat.rs b/tests/ui/trait-bounds/prefer-builtin-impl-over-where-clause-in-const-pat.rs new file mode 100644 index 0000000000000..e774bef50c123 --- /dev/null +++ b/tests/ui/trait-bounds/prefer-builtin-impl-over-where-clause-in-const-pat.rs @@ -0,0 +1,26 @@ +//! Regression test adjacent to +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver +//@ check-pass + +#![feature(const_trait_impl)] +#![feature(const_clone)] + +// At the time of writing, `ZERO` is evaluated in an environment containing `g`'s `(u8,): Clone` +// clause. In the new solver, this wasn't dropped in favor of the built-in `(u8,): Clone` impl when +// resolving an instance for `<(u8,) as Clone>::clone`, which resulted in ambiguity. + +const ZERO: (u8,) = (0,).clone(); + +fn g() +where + (u8,): Clone, +{ + match (0,) { + ZERO => {} + _ => {} + } +} + +fn main() {} diff --git a/tests/ui/trait-bounds/prefer-user-impl-over-where-clause-in-const-pat.rs b/tests/ui/trait-bounds/prefer-user-impl-over-where-clause-in-const-pat.rs new file mode 100644 index 0000000000000..e2c27d4f90e11 --- /dev/null +++ b/tests/ui/trait-bounds/prefer-user-impl-over-where-clause-in-const-pat.rs @@ -0,0 +1,29 @@ +//! Regression test for +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver +//@ check-pass + +// At the time of writing, when resolving an instance for `::N`, the environment +// contains `f`'s `u8: Trait` clause. The old solver dropped the where clause candidate in favor of +// the `impl Trait for u8` candidate, but the new solver didn't, which resulted in ambiguity. + +pub trait Trait { + const N: usize; +} + +impl Trait for u8 { + const N: usize = 0; +} + +pub fn f() +where + u8: Trait, +{ + match 0 { + ::N => {} + _ => {} + } +} + +fn main() {} From 67cb4ce2794995b6d1561766251e03153230aef9 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 28 Sep 2026 09:47:35 -0600 Subject: [PATCH 15/16] Add .seek_read_buf_exact() to std::os::windows::fs::FileExt --- library/std/src/fs/tests.rs | 37 +++++++++++++++++++++ library/std/src/os/windows/fs.rs | 56 ++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 250109f277dab..db9a9f3f20b88 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -1075,6 +1075,43 @@ fn test_seek_read_buf() { check!(fs::remove_file(&filename)); } +#[test] +#[cfg(windows)] +fn test_seek_read_buf_exact() { + use crate::os::windows::fs::FileExt; + + let tmpdir = tmpdir(); + let filename = tmpdir.join("file_rt_io_file_test_seek_read_buf_exact.txt"); + { + let oo = OpenOptions::new().create_new(true).write(true).read(true).clone(); + let mut file = check!(oo.open(&filename)); + check!(file.write_all(b"0123456789")); + } + { + let mut file = check!(File::open(&filename)); + let mut buf: [MaybeUninit; 5] = [MaybeUninit::uninit(); 5]; + let mut buf = BorrowedBuf::from(buf.as_mut_slice()); + + // Exact read + check!(file.seek_read_buf_exact(buf.unfilled(), 2)); + assert_eq!(buf.filled(), b"23456"); + assert_eq!(check!(file.stream_position()), 7); + + // Already full + check!(file.seek_read_buf_exact(buf.unfilled(), 3)); + assert_eq!(check!(file.stream_position()), 7); + check!(file.seek_read_buf_exact(buf.unfilled(), 10)); // No call to seek_read() + assert_eq!(buf.filled(), b"23456"); + assert_eq!(check!(file.stream_position()), 7); + + // Non-empty exact read past eof fails + let err = file.seek_read_buf_exact(buf.clear().unfilled(), 6).unwrap_err(); + assert_eq!(err.kind(), ErrorKind::UnexpectedEof); + assert_eq!(check!(file.stream_position()), 10); + } + check!(fs::remove_file(&filename)); +} + #[test] fn file_test_read_buf() { let tmpdir = tmpdir(); diff --git a/library/std/src/os/windows/fs.rs b/library/std/src/os/windows/fs.rs index 3e6a934f318b2..bc3dae156ede4 100644 --- a/library/std/src/os/windows/fs.rs +++ b/library/std/src/os/windows/fs.rs @@ -152,6 +152,62 @@ pub trait FileExt { io::default_read_buf(|b| self.seek_read(b, offset), buf) } + /// Seeks to a given position and reads the exact number of bytes required to fill `buf`. + /// + /// This is equivalent to the [`seek_read_exact`](FileExt::seek_read_exact) method, except + /// that it is passed a [`BorrowedCursor`] rather than `&mut [u8]` to allow use with + /// uninitialized buffers. The new data will be appended to any existing contents of `buf`. + /// + /// Reading beyond the end of the file will always succeed without reading any bytes. + /// + /// # Examples + /// + #[cfg_attr(windows, doc = "```no_run")] + #[cfg_attr(not(windows), doc = "```ignore (needs windows)")] + /// #![feature(core_io_borrowed_buf)] + /// #![feature(seek_read_exact_seek_write_all)] + /// + /// use std::io; + /// use std::io::BorrowedBuf; + /// use std::fs::File; + /// use std::mem::MaybeUninit; + /// use std::os::windows::prelude::*; + /// + /// fn main() -> io::Result<()> { + /// let mut file = File::open("pi.txt")?; + /// + /// // Read some bytes starting from offset 2 + /// let mut buf: [MaybeUninit; 10] = [MaybeUninit::uninit(); 10]; + /// let mut buf = BorrowedBuf::from(buf.as_mut_slice()); + /// file.seek_read_buf_exact(buf.unfilled(), 2)?; + /// + /// assert!(buf.filled().starts_with(b"1")); + /// + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "seek_read_exact_seek_write_all", issue = "162868")] + fn seek_read_buf_exact( + &self, + mut buf: BorrowedCursor<'_, u8>, + mut offset: u64, + ) -> io::Result<()> { + while buf.capacity() > 0 { + let prev_written = buf.written(); + match self.seek_read_buf(buf.reborrow(), offset) { + Ok(()) => {} + Err(e) if e.is_interrupted() => {} + Err(e) => return Err(e), + } + let n = buf.written() - prev_written; + offset += n as u64; + if n == 0 { + return Err(io::Error::READ_EXACT_EOF); + } + } + Ok(()) + } + /// Seeks to a given position and writes a number of bytes. /// /// Returns the number of bytes written. From 5931727a13e08bdb8a7b60b65f37f283285a2d8c Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 28 Aug 2026 12:58:33 +0200 Subject: [PATCH 16/16] add `Div` and `Mul` for `Complex<{float}>` --- ...oot_tests-Disable-complex-type-tests.patch | 32 ++++++++++ library/core/src/num/complex.rs | 54 ++++++++++++++++- library/core/src/num/imp/libm.rs | 21 +++++++ library/coretests/tests/num/complex.rs | 60 +++++++++++++++++++ 4 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 compiler/rustc_codegen_cranelift/patches/0029-sysroot_tests-Disable-complex-type-tests.patch diff --git a/compiler/rustc_codegen_cranelift/patches/0029-sysroot_tests-Disable-complex-type-tests.patch b/compiler/rustc_codegen_cranelift/patches/0029-sysroot_tests-Disable-complex-type-tests.patch new file mode 100644 index 0000000000000..d27810a988492 --- /dev/null +++ b/compiler/rustc_codegen_cranelift/patches/0029-sysroot_tests-Disable-complex-type-tests.patch @@ -0,0 +1,32 @@ +From 20d296e9a8b837e586fcc8fc648d4ab9687a2a47 Mon Sep 17 00:00:00 2001 +From: Folkert de Vries +Date: Mon, 28 Sep 2026 19:14:22 +0200 +Subject: [PATCH] sysroot_tests: Disable complex type tests + +--- + coretests/tests/num/complex.rs | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/coretests/tests/num/complex.rs b/coretests/tests/num/complex.rs +index d73d04b1afc..289c0baec17 100644 +--- a/coretests/tests/num/complex.rs ++++ b/coretests/tests/num/complex.rs +@@ -75,6 +75,7 @@ fn complex_negation() { + } + + #[test] ++#[cfg_attr(target_os = "windows", ignore = "hits an ABI issue with cranelift on windows")] + fn complex_multiplication() { + #[cfg(target_has_reliable_f16)] + assert_eq!(Complex::new(1.0f16, 2.0) * Complex::new(3.0, 4.0), Complex::new(-5.0, 10.0)); +@@ -105,6 +106,7 @@ fn complex_multiplication() { + } + + #[test] ++#[cfg_attr(target_os = "windows", ignore = "hits an ABI issue with cranelift on windows")] + fn complex_div() { + #[cfg(target_has_reliable_f16)] + assert_eq!(Complex::new(2.0f16, 11.0) / Complex::new(2.0, 1.0), Complex::new(3.0, 4.0)); +-- +2.43.0 + diff --git a/library/core/src/num/complex.rs b/library/core/src/num/complex.rs index 66126c52fadad..c6a56285cdfb1 100644 --- a/library/core/src/num/complex.rs +++ b/library/core/src/num/complex.rs @@ -1,4 +1,5 @@ -use crate::ops::{Add, Neg, Sub}; +use crate::num::imp::libm::complex::*; +use crate::ops::{Add, Div, Mul, Neg, Sub}; /// A complex number. #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -91,3 +92,54 @@ impl> Sub for Complex { Complex::new(self.re - rhs, self.im) } } + +macro_rules! impl_complex_mul_div { + ($ty:ty, $mul:ident, $div:ident) => { + #[unstable(feature = "complex_numbers", issue = "154023")] + impl Mul for Complex<$ty> { + type Output = Self; + + #[inline] + fn mul(self, rhs: Self) -> Self::Output { + let Complex { re: a, im: b } = self; + let Complex { re: c, im: d } = rhs; + + let ac = a * c; + let bd = b * d; + let ad = a * d; + let bc = b * c; + + let z = Complex::new(ac - bd, ad + bc); + + // Only call the libcall when both components are NaN. + // + // The naive algorithm would return NaN + NaNi for an input like + // (1 + 0i) * (inf + infi). The libcall instead returns inf + infi. + // + // We duplicate the fast path here so that it can be inlined. We use a libcall + // for the NaN correction to reduce the size of `core`. + if z.re.is_nan() && z.im.is_nan() { + crate::hint::cold_path(); + $mul(a, b, c, d) + } else { + z + } + } + } + + #[unstable(feature = "complex_numbers", issue = "154023")] + impl Div for Complex<$ty> { + type Output = Self; + + #[inline] + fn div(self, rhs: Self) -> Self::Output { + $div(self.re, self.im, rhs.re, rhs.im) + } + } + }; +} + +impl_complex_mul_div!(f16, __rust_mulhc3, __rust_divhc3); +impl_complex_mul_div!(f32, __mulsc3, __divsc3); +impl_complex_mul_div!(f64, __muldc3, __divdc3); +impl_complex_mul_div!(f128, __rust_multc3, __rust_divtc3); diff --git a/library/core/src/num/imp/libm.rs b/library/core/src/num/imp/libm.rs index a8d6bdc0b5d7c..6ce02ad60a5df 100644 --- a/library/core/src/num/imp/libm.rs +++ b/library/core/src/num/imp/libm.rs @@ -76,6 +76,27 @@ unsafe extern "C" { pub(crate) safe fn truncf16(x: f16) -> f16; } +/// These symbols are always provided by compiler-builtins. +pub(crate) mod complex { + use crate::num::Complex; + + unsafe extern "C" { + pub(crate) safe fn __mulsc3(a: f32, b: f32, c: f32, d: f32) -> Complex; + pub(crate) safe fn __muldc3(a: f64, b: f64, c: f64, d: f64) -> Complex; + + pub(crate) safe fn __divsc3(a: f32, b: f32, c: f32, d: f32) -> Complex; + pub(crate) safe fn __divdc3(a: f64, b: f64, c: f64, d: f64) -> Complex; + } + + unsafe extern "Rust" { + pub(crate) safe fn __rust_mulhc3(a: f16, b: f16, c: f16, d: f16) -> Complex; + pub(crate) safe fn __rust_multc3(a: f128, b: f128, c: f128, d: f128) -> Complex; + + pub(crate) safe fn __rust_divhc3(a: f16, b: f16, c: f16, d: f16) -> Complex; + pub(crate) safe fn __rust_divtc3(a: f128, b: f128, c: f128, d: f128) -> Complex; + } +} + /// These symbols will be available when `std` is available, and on many no-std platforms. However, /// since this isn't a guarantee, we cannot rely on them for stable implementations. pub(crate) mod likely_available { diff --git a/library/coretests/tests/num/complex.rs b/library/coretests/tests/num/complex.rs index c22c5b9575b3d..d73d04b1afc98 100644 --- a/library/coretests/tests/num/complex.rs +++ b/library/coretests/tests/num/complex.rs @@ -73,3 +73,63 @@ fn complex_negation() { assert_eq!(-Complex::new(1.0, -2.0), Complex::new(-1.0, 2.0)); assert_eq!(-Complex::new(1.0, f32::INFINITY), Complex::new(-1.0, f32::NEG_INFINITY),); } + +#[test] +fn complex_multiplication() { + #[cfg(target_has_reliable_f16)] + assert_eq!(Complex::new(1.0f16, 2.0) * Complex::new(3.0, 4.0), Complex::new(-5.0, 10.0)); + assert_eq!(Complex::new(1.0f32, 2.0) * Complex::new(3.0, 4.0), Complex::new(-5.0, 10.0)); + assert_eq!(Complex::new(1.0f64, 2.0) * Complex::new(3.0, 4.0), Complex::new(-5.0, 10.0)); + #[cfg(target_has_reliable_f128)] + assert_eq!(Complex::new(1.0f128, 2.0) * Complex::new(3.0, 4.0), Complex::new(-5.0, 10.0)); + + // The naive algorithm would return NaN + NaNi for these inputs, but the libcall handles it. + #[cfg(target_has_reliable_f16)] + assert_eq!( + Complex::new(1.0, 0.0) * Complex::new(f16::INFINITY, f16::INFINITY), + Complex::new(f16::INFINITY, f16::INFINITY) + ); + assert_eq!( + Complex::new(1.0, 0.0) * Complex::new(f32::INFINITY, f32::INFINITY), + Complex::new(f32::INFINITY, f32::INFINITY) + ); + assert_eq!( + Complex::new(1.0, 0.0) * Complex::new(f64::INFINITY, f64::INFINITY), + Complex::new(f64::INFINITY, f64::INFINITY) + ); + #[cfg(target_has_reliable_f128)] + assert_eq!( + Complex::new(1.0, 0.0) * Complex::new(f128::INFINITY, f128::INFINITY), + Complex::new(f128::INFINITY, f128::INFINITY) + ); +} + +#[test] +fn complex_div() { + #[cfg(target_has_reliable_f16)] + assert_eq!(Complex::new(2.0f16, 11.0) / Complex::new(2.0, 1.0), Complex::new(3.0, 4.0)); + assert_eq!(Complex::new(2.0f32, 11.0) / Complex::new(2.0, 1.0), Complex::new(3.0, 4.0)); + assert_eq!(Complex::new(2.0f64, 11.0) / Complex::new(2.0, 1.0), Complex::new(3.0, 4.0)); + #[cfg(target_has_reliable_f128)] + assert_eq!(Complex::new(2.0f128, 11.0) / Complex::new(2.0, 1.0), Complex::new(3.0, 4.0)); + + // The naive algorithm would return NaN + NaNi for these inputs, but the libcall handles it. + #[cfg(target_has_reliable_f16)] + assert_eq!( + Complex::new(f16::INFINITY, 0.0) / Complex::new(1.0, 1.0), + Complex::new(f16::INFINITY, f16::NEG_INFINITY) + ); + assert_eq!( + Complex::new(f32::INFINITY, 0.0) / Complex::new(1.0, 1.0), + Complex::new(f32::INFINITY, f32::NEG_INFINITY) + ); + assert_eq!( + Complex::new(f64::INFINITY, 0.0) / Complex::new(1.0, 1.0), + Complex::new(f64::INFINITY, f64::NEG_INFINITY) + ); + #[cfg(target_has_reliable_f128)] + assert_eq!( + Complex::new(f128::INFINITY, 0.0) / Complex::new(1.0, 1.0), + Complex::new(f128::INFINITY, f128::NEG_INFINITY) + ); +}