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