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