1 #![cfg_attr(test, allow(dead_code))] 2 3 use self::imp::{drop_handler, make_handler}; 4 5 pub use self::imp::cleanup; 6 pub use self::imp::init; 7 use dlibc; 8 9 pub struct Handler { 10 data: *mut dlibc::c_void, 11 } 12 13 impl Handler { 14 pub unsafe fn new() -> Handler { 15 make_handler() 16 } 17 18 fn null() -> Handler { 19 Handler { 20 data: crate::std::ptr::null_mut(), 21 } 22 } 23 } 24 25 impl Drop for Handler { 26 fn drop(&mut self) { 27 unsafe { 28 drop_handler(self.data); 29 } 30 } 31 } 32 33 #[cfg(any( 34 target_os = "linux", 35 target_os = "macos", 36 target_os = "dragonfly", 37 target_os = "freebsd", 38 target_os = "solaris", 39 target_os = "illumos", 40 target_os = "netbsd", 41 target_os = "openbsd", 42 target_os = "dragonos" 43 ))] 44 mod imp { 45 use super::Handler; 46 use crate::std::io; 47 use crate::std::mem; 48 use crate::std::ptr; 49 use crate::std::thread; 50 51 use dlibc::MAP_FAILED; 52 #[cfg(not(all(target_os = "linux", target_env = "gnu")))] 53 use dlibc::{mmap as mmap64, munmap}; 54 #[cfg(all(target_os = "linux", target_env = "gnu"))] 55 use dlibc::{mmap64, munmap}; 56 use dlibc::{sigaction, sighandler_t, SA_ONSTACK, SA_SIGINFO, SIGBUS, SIG_DFL}; 57 use dlibc::{sigaltstack, SIGSTKSZ, SS_DISABLE}; 58 use dlibc::{MAP_ANON, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SIGSEGV}; 59 60 use crate::std::sync::atomic::{AtomicBool, AtomicPtr, Ordering}; 61 use crate::std::sys::unix::os::page_size; 62 use crate::std::sys_common::thread_info; 63 use dlibc; 64 65 // Signal handler for the SIGSEGV and SIGBUS handlers. We've got guard pages 66 // (unmapped pages) at the end of every thread's stack, so if a thread ends 67 // up running into the guard page it'll trigger this handler. We want to 68 // detect these cases and print out a helpful error saying that the stack 69 // has overflowed. All other signals, however, should go back to what they 70 // were originally supposed to do. 71 // 72 // This handler currently exists purely to print an informative message 73 // whenever a thread overflows its stack. We then abort to exit and 74 // indicate a crash, but to avoid a misleading SIGSEGV that might lead 75 // users to believe that unsafe code has accessed an invalid pointer; the 76 // SIGSEGV encountered when overflowing the stack is expected and 77 // well-defined. 78 // 79 // If this is not a stack overflow, the handler un-registers itself and 80 // then returns (to allow the original signal to be delivered again). 81 // Returning from this kind of signal handler is technically not defined 82 // to work when reading the POSIX spec strictly, but in practice it turns 83 // out many large systems and all implementations allow returning from a 84 // signal handler to work. For a more detailed explanation see the 85 // comments on #26458. 86 unsafe extern "C" fn signal_handler( 87 signum: dlibc::c_int, 88 info: *mut dlibc::siginfo_t, 89 _data: *mut dlibc::c_void, 90 ) { 91 let guard = thread_info::stack_guard().unwrap_or(0..0); 92 let addr = (*info).si_addr() as usize; 93 94 // If the faulting address is within the guard page, then we print a 95 // message saying so and abort. 96 if guard.start <= addr && addr < guard.end { 97 rtprintpanic!( 98 "\nthread '{}' has overflowed its stack\n", 99 thread::current().name().unwrap_or("<unknown>") 100 ); 101 rtabort!("stack overflow"); 102 } else { 103 // Unregister ourselves by reverting back to the default behavior. 104 let mut action: sigaction = mem::zeroed(); 105 action.sa_sigaction = SIG_DFL; 106 sigaction(signum, &action, ptr::null_mut()); 107 108 // See comment above for why this function returns. 109 } 110 } 111 112 static MAIN_ALTSTACK: AtomicPtr<dlibc::c_void> = AtomicPtr::new(ptr::null_mut()); 113 static NEED_ALTSTACK: AtomicBool = AtomicBool::new(false); 114 115 pub unsafe fn init() { 116 let mut action: sigaction = mem::zeroed(); 117 for &signal in &[SIGSEGV, SIGBUS] { 118 sigaction(signal, ptr::null_mut(), &mut action); 119 // Configure our signal handler if one is not already set. 120 if action.sa_sigaction == SIG_DFL { 121 action.sa_flags = SA_SIGINFO | SA_ONSTACK; 122 action.sa_sigaction = signal_handler as sighandler_t; 123 sigaction(signal, &action, ptr::null_mut()); 124 NEED_ALTSTACK.store(true, Ordering::Relaxed); 125 } 126 } 127 128 let handler = make_handler(); 129 MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed); 130 mem::forget(handler); 131 } 132 133 pub unsafe fn cleanup() { 134 drop_handler(MAIN_ALTSTACK.load(Ordering::Relaxed)); 135 } 136 137 unsafe fn get_stackp() -> *mut dlibc::c_void { 138 // OpenBSD requires this flag for stack mapping 139 // otherwise the said mapping will fail as a no-op on most systems 140 // and has a different meaning on FreeBSD 141 #[cfg(any(target_os = "openbsd", target_os = "netbsd", target_os = "linux",))] 142 let flags = MAP_PRIVATE | MAP_ANON | dlibc::MAP_STACK; 143 #[cfg(not(any(target_os = "openbsd", target_os = "netbsd", target_os = "linux",)))] 144 let flags = MAP_PRIVATE | MAP_ANON; 145 let stackp = mmap64( 146 ptr::null_mut(), 147 SIGSTKSZ + page_size(), 148 PROT_READ | PROT_WRITE, 149 flags, 150 -1, 151 0, 152 ); 153 if stackp == MAP_FAILED { 154 panic!( 155 "failed to allocate an alternative stack: {}", 156 io::Error::last_os_error() 157 ); 158 } 159 let guard_result = dlibc::mprotect(stackp, page_size(), PROT_NONE); 160 if guard_result != 0 { 161 panic!( 162 "failed to set up alternative stack guard page: {}", 163 io::Error::last_os_error() 164 ); 165 } 166 stackp.add(page_size()) 167 } 168 169 unsafe fn get_stack() -> dlibc::stack_t { 170 dlibc::stack_t { 171 ss_sp: get_stackp(), 172 ss_flags: 0, 173 ss_size: SIGSTKSZ, 174 } 175 } 176 177 pub unsafe fn make_handler() -> Handler { 178 if !NEED_ALTSTACK.load(Ordering::Relaxed) { 179 return Handler::null(); 180 } 181 let mut stack = mem::zeroed(); 182 sigaltstack(ptr::null(), &mut stack); 183 // Configure alternate signal stack, if one is not already set. 184 if stack.ss_flags & SS_DISABLE != 0 { 185 stack = get_stack(); 186 sigaltstack(&stack, ptr::null_mut()); 187 Handler { 188 data: stack.ss_sp as *mut dlibc::c_void, 189 } 190 } else { 191 Handler::null() 192 } 193 } 194 195 pub unsafe fn drop_handler(data: *mut dlibc::c_void) { 196 if !data.is_null() { 197 let stack = dlibc::stack_t { 198 ss_sp: ptr::null_mut(), 199 ss_flags: SS_DISABLE, 200 // Workaround for bug in macOS implementation of sigaltstack 201 // UNIX2003 which returns ENOMEM when disabling a stack while 202 // passing ss_size smaller than MINSIGSTKSZ. According to POSIX 203 // both ss_sp and ss_size should be ignored in this case. 204 ss_size: SIGSTKSZ, 205 }; 206 sigaltstack(&stack, ptr::null_mut()); 207 // We know from `get_stackp` that the alternate stack we installed is part of a mapping 208 // that started one page earlier, so walk back a page and unmap from there. 209 munmap(data.sub(page_size()), SIGSTKSZ + page_size()); 210 } 211 } 212 } 213 214 #[cfg(not(any( 215 target_os = "linux", 216 target_os = "macos", 217 target_os = "dragonfly", 218 target_os = "freebsd", 219 target_os = "solaris", 220 target_os = "illumos", 221 target_os = "netbsd", 222 target_os = "openbsd", 223 target_os = "dragonos", 224 )))] 225 mod imp { 226 pub unsafe fn init() {} 227 228 pub unsafe fn cleanup() {} 229 230 pub unsafe fn make_handler() -> super::Handler { 231 super::Handler::null() 232 } 233 234 pub unsafe fn drop_handler(_data: *mut dlibc::c_void) {} 235 } 236