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