xref: /drstd/src/std/sys/unix/stack_overflow.rs (revision a1cd34728e2d4a5d4cf41974e4db28602cbb1b1c)
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