1#![cfg_attr(test, allow(dead_code))]
2
3use self::imp::{drop_handler, make_handler};
4
5pub use self::imp::cleanup;
6pub use self::imp::init;
7
8pub struct Handler {
9 data: *mut libc::c_void,
10}
11
12impl 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
22impl 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))]
41mod 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)))]
213mod 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