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 #[allow(dead_code)] 116 pub unsafe fn init() { 117 let mut action: sigaction = mem::zeroed(); 118 for &signal in &[SIGSEGV, SIGBUS] { 119 sigaction(signal, ptr::null_mut(), &mut action); 120 // Configure our signal handler if one is not already set. 121 if action.sa_sigaction == SIG_DFL { 122 action.sa_flags = SA_SIGINFO | SA_ONSTACK; 123 action.sa_sigaction = signal_handler as sighandler_t; 124 sigaction(signal, &action, ptr::null_mut()); 125 NEED_ALTSTACK.store(true, Ordering::Relaxed); 126 } 127 } 128 129 let handler = make_handler(); 130 MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed); 131 mem::forget(handler); 132 } 133 134 pub unsafe fn cleanup() { 135 drop_handler(MAIN_ALTSTACK.load(Ordering::Relaxed)); 136 } 137 138 unsafe fn get_stackp() -> *mut dlibc::c_void { 139 // OpenBSD requires this flag for stack mapping 140 // otherwise the said mapping will fail as a no-op on most systems 141 // and has a different meaning on FreeBSD 142 #[cfg(any(target_os = "openbsd", target_os = "netbsd", target_os = "linux",))] 143 let flags = MAP_PRIVATE | MAP_ANON | dlibc::MAP_STACK; 144 #[cfg(not(any(target_os = "openbsd", target_os = "netbsd", target_os = "linux",)))] 145 let flags = MAP_PRIVATE | MAP_ANON; 146 let stackp = mmap64( 147 ptr::null_mut(), 148 SIGSTKSZ + page_size(), 149 PROT_READ | PROT_WRITE, 150 flags, 151 -1, 152 0, 153 ); 154 if stackp == MAP_FAILED { 155 panic!( 156 "failed to allocate an alternative stack: {}", 157 io::Error::last_os_error() 158 ); 159 } 160 let guard_result = dlibc::mprotect(stackp, page_size(), PROT_NONE); 161 if guard_result != 0 { 162 panic!( 163 "failed to set up alternative stack guard page: {}", 164 io::Error::last_os_error() 165 ); 166 } 167 stackp.add(page_size()) 168 } 169 170 unsafe fn get_stack() -> dlibc::stack_t { 171 dlibc::stack_t { 172 ss_sp: get_stackp(), 173 ss_flags: 0, 174 ss_size: SIGSTKSZ, 175 } 176 } 177 178 pub unsafe fn make_handler() -> Handler { 179 if !NEED_ALTSTACK.load(Ordering::Relaxed) { 180 return Handler::null(); 181 } 182 let mut stack = mem::zeroed(); 183 sigaltstack(ptr::null(), &mut stack); 184 // Configure alternate signal stack, if one is not already set. 185 if stack.ss_flags & SS_DISABLE != 0 { 186 stack = get_stack(); 187 sigaltstack(&stack, ptr::null_mut()); 188 Handler { 189 data: stack.ss_sp as *mut dlibc::c_void, 190 } 191 } else { 192 Handler::null() 193 } 194 } 195 196 pub unsafe fn drop_handler(data: *mut dlibc::c_void) { 197 if !data.is_null() { 198 let stack = dlibc::stack_t { 199 ss_sp: ptr::null_mut(), 200 ss_flags: SS_DISABLE, 201 // Workaround for bug in macOS implementation of sigaltstack 202 // UNIX2003 which returns ENOMEM when disabling a stack while 203 // passing ss_size smaller than MINSIGSTKSZ. According to POSIX 204 // both ss_sp and ss_size should be ignored in this case. 205 ss_size: SIGSTKSZ, 206 }; 207 sigaltstack(&stack, ptr::null_mut()); 208 // We know from `get_stackp` that the alternate stack we installed is part of a mapping 209 // that started one page earlier, so walk back a page and unmap from there. 210 munmap(data.sub(page_size()), SIGSTKSZ + page_size()); 211 } 212 } 213 } 214 215 #[cfg(not(any( 216 target_os = "linux", 217 target_os = "macos", 218 target_os = "dragonfly", 219 target_os = "freebsd", 220 target_os = "solaris", 221 target_os = "illumos", 222 target_os = "netbsd", 223 target_os = "openbsd", 224 target_os = "dragonos", 225 )))] 226 mod imp { 227 pub unsafe fn init() {} 228 229 pub unsafe fn cleanup() {} 230 231 pub unsafe fn make_handler() -> super::Handler { 232 super::Handler::null() 233 } 234 235 pub unsafe fn drop_handler(_data: *mut dlibc::c_void) {} 236 } 237