summaryrefslogtreecommitdiff
path: root/library/std/src/sys/unix/stack_overflow.rs
diff options
context:
space:
mode:
authormark <markm@cs.wisc.edu>2020-06-11 21:31:49 -0500
committermark <markm@cs.wisc.edu>2020-07-27 19:51:13 -0500
commit2c31b45ae878b821975c4ebd94cc1e49f6073fd0 (patch)
tree14f64e683e3f64dcbcfb8c2c7cb45ac7592e6e09 /library/std/src/sys/unix/stack_overflow.rs
parent9be8ffcb0206fc1558069a7b4766090df7877659 (diff)
downloadrust-2c31b45ae878b821975c4ebd94cc1e49f6073fd0.tar.gz
mv std libs to library/
Diffstat (limited to 'library/std/src/sys/unix/stack_overflow.rs')
-rw-r--r--library/std/src/sys/unix/stack_overflow.rs234
1 files changed, 234 insertions, 0 deletions
diff --git a/library/std/src/sys/unix/stack_overflow.rs b/library/std/src/sys/unix/stack_overflow.rs
new file mode 100644
index 00000000000..c74fc2b5903
--- /dev/null
+++ b/library/std/src/sys/unix/stack_overflow.rs
@@ -0,0 +1,234 @@
+#![cfg_attr(test, allow(dead_code))]
+
+use self::imp::{drop_handler, make_handler};
+
+pub use self::imp::cleanup;
+pub use self::imp::init;
+
+pub struct Handler {
+ _data: *mut libc::c_void,
+}
+
+impl Handler {
+ pub unsafe fn new() -> Handler {
+ make_handler()
+ }
+
+ fn null() -> Handler {
+ Handler { _data: crate::ptr::null_mut() }
+ }
+}
+
+impl Drop for Handler {
+ fn drop(&mut self) {
+ unsafe {
+ drop_handler(self);
+ }
+ }
+}
+
+#[cfg(any(
+ target_os = "linux",
+ target_os = "macos",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "solaris",
+ target_os = "illumos",
+ all(target_os = "netbsd", not(target_vendor = "rumprun")),
+ target_os = "openbsd"
+))]
+mod imp {
+ use super::Handler;
+ use crate::mem;
+ use crate::ptr;
+
+ use libc::MAP_FAILED;
+ use libc::{mmap, munmap};
+ use libc::{sigaction, sighandler_t, SA_ONSTACK, SA_SIGINFO, SIGBUS, SIG_DFL};
+ use libc::{sigaltstack, SIGSTKSZ, SS_DISABLE};
+ use libc::{MAP_ANON, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SIGSEGV};
+
+ use crate::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
+ use crate::sys::unix::os::page_size;
+ use crate::sys_common::thread_info;
+
+ #[cfg(any(target_os = "linux", target_os = "android"))]
+ unsafe fn siginfo_si_addr(info: *mut libc::siginfo_t) -> usize {
+ #[repr(C)]
+ struct siginfo_t {
+ a: [libc::c_int; 3], // si_signo, si_errno, si_code
+ si_addr: *mut libc::c_void,
+ }
+
+ (*(info as *const siginfo_t)).si_addr as usize
+ }
+
+ #[cfg(not(any(target_os = "linux", target_os = "android")))]
+ unsafe fn siginfo_si_addr(info: *mut libc::siginfo_t) -> usize {
+ (*info).si_addr as usize
+ }
+
+ // 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.
+ unsafe extern "C" fn signal_handler(
+ signum: libc::c_int,
+ info: *mut libc::siginfo_t,
+ _data: *mut libc::c_void,
+ ) {
+ use crate::sys_common::util::report_overflow;
+
+ let guard = thread_info::stack_guard().unwrap_or(0..0);
+ let addr = siginfo_si_addr(info);
+
+ // If the faulting address is within the guard page, then we print a
+ // message saying so and abort.
+ if guard.start <= addr && addr < guard.end {
+ report_overflow();
+ rtabort!("stack overflow");
+ } else {
+ // Unregister ourselves by reverting back to the default behavior.
+ let mut action: sigaction = mem::zeroed();
+ action.sa_sigaction = SIG_DFL;
+ sigaction(signum, &action, ptr::null_mut());
+
+ // See comment above for why this function returns.
+ }
+ }
+
+ static MAIN_ALTSTACK: AtomicPtr<libc::c_void> = AtomicPtr::new(ptr::null_mut());
+ static NEED_ALTSTACK: AtomicBool = AtomicBool::new(false);
+
+ pub unsafe fn init() {
+ let mut action: sigaction = mem::zeroed();
+ for &signal in &[SIGSEGV, SIGBUS] {
+ sigaction(signal, ptr::null_mut(), &mut action);
+ // Configure our signal handler if one is not already set.
+ if action.sa_sigaction == SIG_DFL {
+ action.sa_flags = SA_SIGINFO | SA_ONSTACK;
+ action.sa_sigaction = signal_handler as sighandler_t;
+ sigaction(signal, &action, ptr::null_mut());
+ NEED_ALTSTACK.store(true, Ordering::Relaxed);
+ }
+ }
+
+ let handler = make_handler();
+ MAIN_ALTSTACK.store(handler._data, Ordering::Relaxed);
+ mem::forget(handler);
+ }
+
+ pub unsafe fn cleanup() {
+ Handler { _data: MAIN_ALTSTACK.load(Ordering::Relaxed) };
+ }
+
+ unsafe fn get_stackp() -> *mut libc::c_void {
+ let stackp = mmap(
+ ptr::null_mut(),
+ SIGSTKSZ + page_size(),
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANON,
+ -1,
+ 0,
+ );
+ if stackp == MAP_FAILED {
+ panic!("failed to allocate an alternative stack");
+ }
+ let guard_result = libc::mprotect(stackp, page_size(), PROT_NONE);
+ if guard_result != 0 {
+ panic!("failed to set up alternative stack guard page");
+ }
+ stackp.add(page_size())
+ }
+
+ #[cfg(any(
+ target_os = "linux",
+ target_os = "macos",
+ target_os = "freebsd",
+ target_os = "netbsd",
+ target_os = "openbsd",
+ target_os = "solaris",
+ target_os = "illumos"
+ ))]
+ unsafe fn get_stack() -> libc::stack_t {
+ libc::stack_t { ss_sp: get_stackp(), ss_flags: 0, ss_size: SIGSTKSZ }
+ }
+
+ #[cfg(target_os = "dragonfly")]
+ unsafe fn get_stack() -> libc::stack_t {
+ libc::stack_t { ss_sp: get_stackp() as *mut i8, ss_flags: 0, ss_size: SIGSTKSZ }
+ }
+
+ pub unsafe fn make_handler() -> Handler {
+ if !NEED_ALTSTACK.load(Ordering::Relaxed) {
+ return Handler::null();
+ }
+ let mut stack = mem::zeroed();
+ sigaltstack(ptr::null(), &mut stack);
+ // Configure alternate signal stack, if one is not already set.
+ if stack.ss_flags & SS_DISABLE != 0 {
+ stack = get_stack();
+ sigaltstack(&stack, ptr::null_mut());
+ Handler { _data: stack.ss_sp as *mut libc::c_void }
+ } else {
+ Handler::null()
+ }
+ }
+
+ pub unsafe fn drop_handler(handler: &mut Handler) {
+ if !handler._data.is_null() {
+ let 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: SIGSTKSZ,
+ };
+ sigaltstack(&stack, ptr::null_mut());
+ // We know from `get_stackp` that the alternate stack we installed is part of a mapping
+ // that started one page earlier, so walk back a page and unmap from there.
+ munmap(handler._data.sub(page_size()), SIGSTKSZ + page_size());
+ }
+ }
+}
+
+#[cfg(not(any(
+ target_os = "linux",
+ target_os = "macos",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "solaris",
+ target_os = "illumos",
+ all(target_os = "netbsd", not(target_vendor = "rumprun")),
+ target_os = "openbsd"
+)))]
+mod imp {
+ pub unsafe fn init() {}
+
+ pub unsafe fn cleanup() {}
+
+ pub unsafe fn make_handler() -> super::Handler {
+ super::Handler::null()
+ }
+
+ pub unsafe fn drop_handler(_handler: &mut super::Handler) {}
+}