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(main_thread:false)
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 = "freebsd",
33 target_os = "hurd",
34 target_os = "macos",
35 target_os = "netbsd",
36 target_os = "openbsd",
37 target_os = "solaris"
38))]
39mod imp {
40 use super::Handler;
41 use crate::cell::Cell;
42 use crate::io;
43 use crate::mem;
44 use crate::ops::Range;
45 use crate::ptr;
46 use crate::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
47 use crate::sys::pal::unix::os;
48 use crate::thread;
49
50 #[cfg(not(all(target_os = "linux", target_env = "gnu")))]
51 use libc::{mmap as mmap64, mprotect, munmap};
52 #[cfg(all(target_os = "linux", target_env = "gnu"))]
53 use libc::{mmap64, mprotect, munmap};
54 use libc::{sigaction, sighandler_t, SA_ONSTACK, SA_SIGINFO, SIGBUS, SIGSEGV, SIG_DFL};
55 use libc::{sigaltstack, SS_DISABLE};
56 use libc::{MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE};
57
58 // We use a TLS variable to store the address of the guard page. While TLS
59 // variables are not guaranteed to be signal-safe, this works out in practice
60 // since we make sure to write to the variable before the signal stack is
61 // installed, thereby ensuring that the variable is always allocated when
62 // the signal handler is called.
63 thread_local! {
64 // FIXME: use `Range` once that implements `Copy`.
65 static GUARD: Cell<(usize, usize)> = const { Cell::new((0, 0)) };
66 }
67
68 // Signal handler for the SIGSEGV and SIGBUS handlers. We've got guard pages
69 // (unmapped pages) at the end of every thread's stack, so if a thread ends
70 // up running into the guard page it'll trigger this handler. We want to
71 // detect these cases and print out a helpful error saying that the stack
72 // has overflowed. All other signals, however, should go back to what they
73 // were originally supposed to do.
74 //
75 // This handler currently exists purely to print an informative message
76 // whenever a thread overflows its stack. We then abort to exit and
77 // indicate a crash, but to avoid a misleading SIGSEGV that might lead
78 // users to believe that unsafe code has accessed an invalid pointer; the
79 // SIGSEGV encountered when overflowing the stack is expected and
80 // well-defined.
81 //
82 // If this is not a stack overflow, the handler un-registers itself and
83 // then returns (to allow the original signal to be delivered again).
84 // Returning from this kind of signal handler is technically not defined
85 // to work when reading the POSIX spec strictly, but in practice it turns
86 // out many large systems and all implementations allow returning from a
87 // signal handler to work. For a more detailed explanation see the
88 // comments on #26458.
89 unsafe extern "C" fn signal_handler(
90 signum: libc::c_int,
91 info: *mut libc::siginfo_t,
92 _data: *mut libc::c_void,
93 ) {
94 let (start, end) = GUARD.get();
95 let addr = (*info).si_addr() as usize;
96
97 // If the faulting address is within the guard page, then we print a
98 // message saying so and abort.
99 if start <= addr && addr < end {
100 rtprintpanic!(
101 "\nthread '{}' has overflowed its stack\n",
102 thread::current().name().unwrap_or("<unknown>")
103 );
104 rtabort!("stack overflow");
105 } else {
106 // Unregister ourselves by reverting back to the default behavior.
107 let mut action: sigaction = mem::zeroed();
108 action.sa_sigaction = SIG_DFL;
109 sigaction(signum, &action, ptr::null_mut());
110
111 // See comment above for why this function returns.
112 }
113 }
114
115 static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0);
116 static MAIN_ALTSTACK: AtomicPtr<libc::c_void> = AtomicPtr::new(ptr::null_mut());
117 static NEED_ALTSTACK: AtomicBool = AtomicBool::new(false);
118
119 pub unsafe fn init() {
120 PAGE_SIZE.store(os::page_size(), Ordering::Relaxed);
121
122 // Always write to GUARD to ensure the TLS variable is allocated.
123 let guard = install_main_guard().unwrap_or(0..0);
124 GUARD.set((guard.start, guard.end));
125
126 let mut action: sigaction = mem::zeroed();
127 for &signal in &[SIGSEGV, SIGBUS] {
128 sigaction(signal, ptr::null_mut(), &mut action);
129 // Configure our signal handler if one is not already set.
130 if action.sa_sigaction == SIG_DFL {
131 action.sa_flags = SA_SIGINFO | SA_ONSTACK;
132 action.sa_sigaction = signal_handler as sighandler_t;
133 sigaction(signal, &action, ptr::null_mut());
134 NEED_ALTSTACK.store(true, Ordering::Relaxed);
135 }
136 }
137
138 let handler = make_handler(true);
139 MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed);
140 mem::forget(handler);
141 }
142
143 pub unsafe fn cleanup() {
144 drop_handler(MAIN_ALTSTACK.load(Ordering::Relaxed));
145 }
146
147 unsafe fn get_stack() -> libc::stack_t {
148 // OpenBSD requires this flag for stack mapping
149 // otherwise the said mapping will fail as a no-op on most systems
150 // and has a different meaning on FreeBSD
151 #[cfg(any(
152 target_os = "openbsd",
153 target_os = "netbsd",
154 target_os = "linux",
155 target_os = "dragonfly",
156 ))]
157 let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK;
158 #[cfg(not(any(
159 target_os = "openbsd",
160 target_os = "netbsd",
161 target_os = "linux",
162 target_os = "dragonfly",
163 )))]
164 let flags = MAP_PRIVATE | MAP_ANON;
165
166 let sigstack_size = sigstack_size();
167 let page_size = PAGE_SIZE.load(Ordering::Relaxed);
168
169 let stackp = mmap64(
170 ptr::null_mut(),
171 sigstack_size + page_size,
172 PROT_READ | PROT_WRITE,
173 flags,
174 -1,
175 0,
176 );
177 if stackp == MAP_FAILED {
178 panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error());
179 }
180 let guard_result = libc::mprotect(stackp, page_size, PROT_NONE);
181 if guard_result != 0 {
182 panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error());
183 }
184 let stackp = stackp.add(page_size);
185
186 libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size }
187 }
188
189 pub unsafe fn make_handler(main_thread: bool) -> Handler {
190 if !NEED_ALTSTACK.load(Ordering::Relaxed) {
191 return Handler::null();
192 }
193
194 if !main_thread {
195 // Always write to GUARD to ensure the TLS variable is allocated.
196 let guard = current_guard().unwrap_or(0..0);
197 GUARD.set((guard.start, guard.end));
198 }
199
200 let mut stack = mem::zeroed();
201 sigaltstack(ptr::null(), &mut stack);
202 // Configure alternate signal stack, if one is not already set.
203 if stack.ss_flags & SS_DISABLE != 0 {
204 stack = get_stack();
205 sigaltstack(&stack, ptr::null_mut());
206 Handler { data: stack.ss_sp as *mut libc::c_void }
207 } else {
208 Handler::null()
209 }
210 }
211
212 pub unsafe fn drop_handler(data: *mut libc::c_void) {
213 if !data.is_null() {
214 let sigstack_size = sigstack_size();
215 let page_size = PAGE_SIZE.load(Ordering::Relaxed);
216 let stack = libc::stack_t {
217 ss_sp: ptr::null_mut(),
218 ss_flags: SS_DISABLE,
219 // Workaround for bug in macOS implementation of sigaltstack
220 // UNIX2003 which returns ENOMEM when disabling a stack while
221 // passing ss_size smaller than MINSIGSTKSZ. According to POSIX
222 // both ss_sp and ss_size should be ignored in this case.
223 ss_size: sigstack_size,
224 };
225 sigaltstack(&stack, ptr::null_mut());
226 // We know from `get_stackp` that the alternate stack we installed is part of a mapping
227 // that started one page earlier, so walk back a page and unmap from there.
228 munmap(data.sub(page_size), sigstack_size + page_size);
229 }
230 }
231
232 /// Modern kernels on modern hardware can have dynamic signal stack sizes.
233 #[cfg(any(target_os = "linux", target_os = "android"))]
234 fn sigstack_size() -> usize {
235 // FIXME: reuse const from libc when available?
236 const AT_MINSIGSTKSZ: crate::ffi::c_ulong = 51;
237 let dynamic_sigstksz = unsafe { libc::getauxval(AT_MINSIGSTKSZ) };
238 // If getauxval couldn't find the entry, it returns 0,
239 // so take the higher of the "constant" and auxval.
240 // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ
241 libc::SIGSTKSZ.max(dynamic_sigstksz as _)
242 }
243
244 /// Not all OS support hardware where this is needed.
245 #[cfg(not(any(target_os = "linux", target_os = "android")))]
246 fn sigstack_size() -> usize {
247 libc::SIGSTKSZ
248 }
249
250 #[cfg(target_os = "solaris")]
251 unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
252 let mut current_stack: libc::stack_t = crate::mem::zeroed();
253 assert_eq!(libc::stack_getbounds(&mut current_stack), 0);
254 Some(current_stack.ss_sp)
255 }
256
257 #[cfg(target_os = "macos")]
258 unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
259 let th = libc::pthread_self();
260 let stackptr = libc::pthread_get_stackaddr_np(th);
261 Some(stackptr.map_addr(|addr| addr - libc::pthread_get_stacksize_np(th)))
262 }
263
264 #[cfg(target_os = "openbsd")]
265 unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
266 let mut current_stack: libc::stack_t = crate::mem::zeroed();
267 assert_eq!(libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack), 0);
268
269 let stack_ptr = current_stack.ss_sp;
270 let stackaddr = if libc::pthread_main_np() == 1 {
271 // main thread
272 stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed)
273 } else {
274 // new thread
275 stack_ptr.addr() - current_stack.ss_size
276 };
277 Some(stack_ptr.with_addr(stackaddr))
278 }
279
280 #[cfg(any(
281 target_os = "android",
282 target_os = "freebsd",
283 target_os = "netbsd",
284 target_os = "hurd",
285 target_os = "linux",
286 target_os = "l4re"
287 ))]
288 unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
289 let mut ret = None;
290 let mut attr: libc::pthread_attr_t = crate::mem::zeroed();
291 #[cfg(target_os = "freebsd")]
292 assert_eq!(libc::pthread_attr_init(&mut attr), 0);
293 #[cfg(target_os = "freebsd")]
294 let e = libc::pthread_attr_get_np(libc::pthread_self(), &mut attr);
295 #[cfg(not(target_os = "freebsd"))]
296 let e = libc::pthread_getattr_np(libc::pthread_self(), &mut attr);
297 if e == 0 {
298 let mut stackaddr = crate::ptr::null_mut();
299 let mut stacksize = 0;
300 assert_eq!(libc::pthread_attr_getstack(&attr, &mut stackaddr, &mut stacksize), 0);
301 ret = Some(stackaddr);
302 }
303 if e == 0 || cfg!(target_os = "freebsd") {
304 assert_eq!(libc::pthread_attr_destroy(&mut attr), 0);
305 }
306 ret
307 }
308
309 unsafe fn get_stack_start_aligned() -> Option<*mut libc::c_void> {
310 let page_size = PAGE_SIZE.load(Ordering::Relaxed);
311 let stackptr = get_stack_start()?;
312 let stackaddr = stackptr.addr();
313
314 // Ensure stackaddr is page aligned! A parent process might
315 // have reset RLIMIT_STACK to be non-page aligned. The
316 // pthread_attr_getstack() reports the usable stack area
317 // stackaddr < stackaddr + stacksize, so if stackaddr is not
318 // page-aligned, calculate the fix such that stackaddr <
319 // new_page_aligned_stackaddr < stackaddr + stacksize
320 let remainder = stackaddr % page_size;
321 Some(if remainder == 0 {
322 stackptr
323 } else {
324 stackptr.with_addr(stackaddr + page_size - remainder)
325 })
326 }
327
328 unsafe fn install_main_guard() -> Option<Range<usize>> {
329 let page_size = PAGE_SIZE.load(Ordering::Relaxed);
330 if cfg!(all(target_os = "linux", not(target_env = "musl"))) {
331 // Linux doesn't allocate the whole stack right away, and
332 // the kernel has its own stack-guard mechanism to fault
333 // when growing too close to an existing mapping. If we map
334 // our own guard, then the kernel starts enforcing a rather
335 // large gap above that, rendering much of the possible
336 // stack space useless. See #43052.
337 //
338 // Instead, we'll just note where we expect rlimit to start
339 // faulting, so our handler can report "stack overflow", and
340 // trust that the kernel's own stack guard will work.
341 let stackptr = get_stack_start_aligned()?;
342 let stackaddr = stackptr.addr();
343 Some(stackaddr - page_size..stackaddr)
344 } else if cfg!(all(target_os = "linux", target_env = "musl")) {
345 // For the main thread, the musl's pthread_attr_getstack
346 // returns the current stack size, rather than maximum size
347 // it can eventually grow to. It cannot be used to determine
348 // the position of kernel's stack guard.
349 None
350 } else if cfg!(target_os = "freebsd") {
351 // FreeBSD's stack autogrows, and optionally includes a guard page
352 // at the bottom. If we try to remap the bottom of the stack
353 // ourselves, FreeBSD's guard page moves upwards. So we'll just use
354 // the builtin guard page.
355 let stackptr = get_stack_start_aligned()?;
356 let guardaddr = stackptr.addr();
357 // Technically the number of guard pages is tunable and controlled
358 // by the security.bsd.stack_guard_page sysctl.
359 // By default it is 1, checking once is enough since it is
360 // a boot time config value.
361 static PAGES: crate::sync::OnceLock<usize> = crate::sync::OnceLock::new();
362
363 let pages = PAGES.get_or_init(|| {
364 use crate::sys::weak::dlsym;
365 dlsym!(fn sysctlbyname(*const libc::c_char, *mut libc::c_void, *mut libc::size_t, *const libc::c_void, libc::size_t) -> libc::c_int);
366 let mut guard: usize = 0;
367 let mut size = crate::mem::size_of_val(&guard);
368 let oid = crate::ffi::CStr::from_bytes_with_nul(
369 b"security.bsd.stack_guard_page\0",
370 )
371 .unwrap();
372 match sysctlbyname.get() {
373 Some(fcn) => {
374 if fcn(oid.as_ptr(), core::ptr::addr_of_mut!(guard) as *mut _, core::ptr::addr_of_mut!(size) as *mut _, crate::ptr::null_mut(), 0) == 0 {
375 guard
376 } else {
377 1
378 }
379 },
380 _ => 1,
381 }
382 });
383 Some(guardaddr..guardaddr + pages * page_size)
384 } else if cfg!(any(target_os = "openbsd", target_os = "netbsd")) {
385 // OpenBSD stack already includes a guard page, and stack is
386 // immutable.
387 // NetBSD stack includes the guard page.
388 //
389 // We'll just note where we expect rlimit to start
390 // faulting, so our handler can report "stack overflow", and
391 // trust that the kernel's own stack guard will work.
392 let stackptr = get_stack_start_aligned()?;
393 let stackaddr = stackptr.addr();
394 Some(stackaddr - page_size..stackaddr)
395 } else {
396 // Reallocate the last page of the stack.
397 // This ensures SIGBUS will be raised on
398 // stack overflow.
399 // Systems which enforce strict PAX MPROTECT do not allow
400 // to mprotect() a mapping with less restrictive permissions
401 // than the initial mmap() used, so we mmap() here with
402 // read/write permissions and only then mprotect() it to
403 // no permissions at all. See issue #50313.
404 let stackptr = get_stack_start_aligned()?;
405 let result = mmap64(
406 stackptr,
407 page_size,
408 PROT_READ | PROT_WRITE,
409 MAP_PRIVATE | MAP_ANON | MAP_FIXED,
410 -1,
411 0,
412 );
413 if result != stackptr || result == MAP_FAILED {
414 panic!("failed to allocate a guard page: {}", io::Error::last_os_error());
415 }
416
417 let result = mprotect(stackptr, page_size, PROT_NONE);
418 if result != 0 {
419 panic!("failed to protect the guard page: {}", io::Error::last_os_error());
420 }
421
422 let guardaddr = stackptr.addr();
423
424 Some(guardaddr..guardaddr + page_size)
425 }
426 }
427
428 #[cfg(any(target_os = "macos", target_os = "openbsd", target_os = "solaris"))]
429 unsafe fn current_guard() -> Option<Range<usize>> {
430 let stackptr = get_stack_start()?;
431 let stackaddr = stackptr.addr();
432 Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr)
433 }
434
435 #[cfg(any(
436 target_os = "android",
437 target_os = "freebsd",
438 target_os = "hurd",
439 target_os = "linux",
440 target_os = "netbsd",
441 target_os = "l4re"
442 ))]
443 unsafe fn current_guard() -> Option<Range<usize>> {
444 let mut ret = None;
445 let mut attr: libc::pthread_attr_t = crate::mem::zeroed();
446 #[cfg(target_os = "freebsd")]
447 assert_eq!(libc::pthread_attr_init(&mut attr), 0);
448 #[cfg(target_os = "freebsd")]
449 let e = libc::pthread_attr_get_np(libc::pthread_self(), &mut attr);
450 #[cfg(not(target_os = "freebsd"))]
451 let e = libc::pthread_getattr_np(libc::pthread_self(), &mut attr);
452 if e == 0 {
453 let mut guardsize = 0;
454 assert_eq!(libc::pthread_attr_getguardsize(&attr, &mut guardsize), 0);
455 if guardsize == 0 {
456 if cfg!(all(target_os = "linux", target_env = "musl")) {
457 // musl versions before 1.1.19 always reported guard
458 // size obtained from pthread_attr_get_np as zero.
459 // Use page size as a fallback.
460 guardsize = PAGE_SIZE.load(Ordering::Relaxed);
461 } else {
462 panic!("there is no guard page");
463 }
464 }
465 let mut stackptr = crate::ptr::null_mut::<libc::c_void>();
466 let mut size = 0;
467 assert_eq!(libc::pthread_attr_getstack(&attr, &mut stackptr, &mut size), 0);
468
469 let stackaddr = stackptr.addr();
470 ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) {
471 Some(stackaddr - guardsize..stackaddr)
472 } else if cfg!(all(target_os = "linux", target_env = "musl")) {
473 Some(stackaddr - guardsize..stackaddr)
474 } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")))
475 {
476 // glibc used to include the guard area within the stack, as noted in the BUGS
477 // section of `man pthread_attr_getguardsize`. This has been corrected starting
478 // with glibc 2.27, and in some distro backports, so the guard is now placed at the
479 // end (below) the stack. There's no easy way for us to know which we have at
480 // runtime, so we'll just match any fault in the range right above or below the
481 // stack base to call that fault a stack overflow.
482 Some(stackaddr - guardsize..stackaddr + guardsize)
483 } else {
484 Some(stackaddr..stackaddr + guardsize)
485 };
486 }
487 if e == 0 || cfg!(target_os = "freebsd") {
488 assert_eq!(libc::pthread_attr_destroy(&mut attr), 0);
489 }
490 ret
491 }
492}
493
494#[cfg(not(any(
495 target_os = "linux",
496 target_os = "freebsd",
497 target_os = "hurd",
498 target_os = "macos",
499 target_os = "netbsd",
500 target_os = "openbsd",
501 target_os = "solaris"
502)))]
503mod imp {
504 pub unsafe fn init() {}
505
506 pub unsafe fn cleanup() {}
507
508 pub unsafe fn make_handler(_main_thread: bool) -> super::Handler {
509 super::Handler::null()
510 }
511
512 pub unsafe fn drop_handler(_data: *mut libc::c_void) {}
513}
514