1#![cfg_attr(test, allow(dead_code))]
2
3pub use self::imp::{cleanup, init};
4use self::imp::{drop_handler, make_handler};
5
6pub struct Handler {
7 data: *mut libc::c_void,
8}
9
10impl Handler {
11 pub unsafe fn new(thread_name: Option<Box<str>>) -> Handler {
12 make_handler(false, thread_name)
13 }
14
15 fn null() -> Handler {
16 Handler { data: crate::ptr::null_mut() }
17 }
18}
19
20impl Drop for Handler {
21 fn drop(&mut self) {
22 unsafe {
23 drop_handler(self.data);
24 }
25 }
26}
27
28#[cfg(all(
29 not(miri),
30 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 target_os = "illumos",
39 ),
40))]
41mod thread_info;
42
43#[cfg(all(
47 not(miri),
48 any(
49 target_os = "linux",
50 target_os = "freebsd",
51 target_os = "hurd",
52 target_os = "macos",
53 target_os = "netbsd",
54 target_os = "openbsd",
55 target_os = "solaris",
56 target_os = "illumos",
57 )
58))]
59mod imp {
60 use libc::{
61 MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK,
62 SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t,
63 };
64 #[cfg(not(all(target_os = "linux", target_env = "gnu")))]
65 use libc::{mmap as mmap64, mprotect, munmap};
66 #[cfg(all(target_os = "linux", target_env = "gnu"))]
67 use libc::{mmap64, mprotect, munmap};
68
69 use super::Handler;
70 use super::thread_info::{delete_current_info, set_current_info, with_current_info};
71 use crate::ops::Range;
72 use crate::sync::OnceLock;
73 use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering};
74 use crate::sys::pal::unix::os;
75 use crate::{io, mem, panic, ptr};
76
77 #[forbid(unsafe_op_in_unsafe_fn)]
102 unsafe extern "C" fn signal_handler(
103 signum: libc::c_int,
104 info: *mut libc::siginfo_t,
105 _data: *mut libc::c_void,
106 ) {
107 let fault_addr = unsafe { (*info).si_addr().addr() };
109
110 if fault_addr != 0 {
115 with_current_info(|thread_info| {
116 if let Some(thread_info) = thread_info
119 && thread_info.guard_page_range.contains(&fault_addr)
120 {
121 let name = thread_info.thread_name.as_deref().unwrap_or("<unknown>");
122 let tid = crate::thread::current_os_id();
123 rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n");
124 rtabort!("stack overflow");
125 }
126 })
127 }
128
129 let mut action: sigaction = unsafe { mem::zeroed() };
132 action.sa_sigaction = SIG_DFL;
133 unsafe { sigaction(signum, &action, ptr::null_mut()) };
135
136 }
138
139 static PAGE_SIZE: Atomic<usize> = AtomicUsize::new(0);
140 static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut());
141 static NEED_ALTSTACK: Atomic<bool> = AtomicBool::new(false);
142
143 #[forbid(unsafe_op_in_unsafe_fn)]
146 pub unsafe fn init() {
147 PAGE_SIZE.store(os::page_size(), Ordering::Relaxed);
148
149 let mut guard_page_range = unsafe { install_main_guard() };
150
151 let mut action: sigaction = unsafe { mem::zeroed() };
153 for &signal in &[SIGSEGV, SIGBUS] {
154 unsafe { sigaction(signal, ptr::null_mut(), &mut action) };
156 if action.sa_sigaction == SIG_DFL {
158 if !NEED_ALTSTACK.load(Ordering::Relaxed) {
159 NEED_ALTSTACK.store(true, Ordering::Release);
161 let handler = unsafe { make_handler(true, None) };
162 MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed);
163 mem::forget(handler);
164
165 if let Some(guard_page_range) = guard_page_range.take() {
166 set_current_info(guard_page_range, Some(Box::from("main")));
167 }
168 }
169
170 action.sa_flags = SA_SIGINFO | SA_ONSTACK;
171 action.sa_sigaction = signal_handler as sighandler_t;
172 unsafe { sigaction(signal, &action, ptr::null_mut()) };
174 }
175 }
176 }
177
178 #[forbid(unsafe_op_in_unsafe_fn)]
181 pub unsafe fn cleanup() {
182 unsafe { drop_handler(MAIN_ALTSTACK.load(Ordering::Relaxed)) };
185 }
186
187 unsafe fn get_stack() -> libc::stack_t {
188 #[cfg(any(
192 target_os = "openbsd",
193 target_os = "netbsd",
194 target_os = "linux",
195 target_os = "dragonfly",
196 ))]
197 let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK;
198 #[cfg(not(any(
199 target_os = "openbsd",
200 target_os = "netbsd",
201 target_os = "linux",
202 target_os = "dragonfly",
203 )))]
204 let flags = MAP_PRIVATE | MAP_ANON;
205
206 let sigstack_size = sigstack_size();
207 let page_size = PAGE_SIZE.load(Ordering::Relaxed);
208
209 let stackp = mmap64(
210 ptr::null_mut(),
211 sigstack_size + page_size,
212 PROT_READ | PROT_WRITE,
213 flags,
214 -1,
215 0,
216 );
217 if stackp == MAP_FAILED {
218 panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error());
219 }
220 let guard_result = libc::mprotect(stackp, page_size, PROT_NONE);
221 if guard_result != 0 {
222 panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error());
223 }
224 let stackp = stackp.add(page_size);
225
226 libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size }
227 }
228
229 #[forbid(unsafe_op_in_unsafe_fn)]
232 pub unsafe fn make_handler(main_thread: bool, thread_name: Option<Box<str>>) -> Handler {
233 if !NEED_ALTSTACK.load(Ordering::Acquire) {
234 return Handler::null();
235 }
236
237 if !main_thread {
238 if let Some(guard_page_range) = unsafe { current_guard() } {
239 set_current_info(guard_page_range, thread_name);
240 }
241 }
242
243 let mut stack = unsafe { mem::zeroed() };
245 unsafe { sigaltstack(ptr::null(), &mut stack) };
247 if stack.ss_flags & SS_DISABLE != 0 {
249 unsafe {
251 stack = get_stack();
252 sigaltstack(&stack, ptr::null_mut());
253 }
254 Handler { data: stack.ss_sp as *mut libc::c_void }
255 } else {
256 Handler::null()
257 }
258 }
259
260 #[forbid(unsafe_op_in_unsafe_fn)]
266 pub unsafe fn drop_handler(data: *mut libc::c_void) {
267 if !data.is_null() {
268 let sigstack_size = sigstack_size();
269 let page_size = PAGE_SIZE.load(Ordering::Relaxed);
270 let disabling_stack = libc::stack_t {
271 ss_sp: ptr::null_mut(),
272 ss_flags: SS_DISABLE,
273 ss_size: sigstack_size,
278 };
279 unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) };
281 unsafe { munmap(data.sub(page_size), sigstack_size + page_size) };
284 }
285
286 delete_current_info();
287 }
288
289 #[cfg(any(target_os = "linux", target_os = "android"))]
291 fn sigstack_size() -> usize {
292 let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) };
293 libc::SIGSTKSZ.max(dynamic_sigstksz as _)
297 }
298
299 #[cfg(not(any(target_os = "linux", target_os = "android")))]
301 fn sigstack_size() -> usize {
302 libc::SIGSTKSZ
303 }
304
305 #[cfg(any(target_os = "solaris", target_os = "illumos"))]
306 unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
307 let mut current_stack: libc::stack_t = crate::mem::zeroed();
308 assert_eq!(libc::stack_getbounds(&mut current_stack), 0);
309 Some(current_stack.ss_sp)
310 }
311
312 #[cfg(target_os = "macos")]
313 unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
314 let th = libc::pthread_self();
315 let stackptr = libc::pthread_get_stackaddr_np(th);
316 Some(stackptr.map_addr(|addr| addr - libc::pthread_get_stacksize_np(th)))
317 }
318
319 #[cfg(target_os = "openbsd")]
320 unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
321 let mut current_stack: libc::stack_t = crate::mem::zeroed();
322 assert_eq!(libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack), 0);
323
324 let stack_ptr = current_stack.ss_sp;
325 let stackaddr = if libc::pthread_main_np() == 1 {
326 stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed)
328 } else {
329 stack_ptr.addr() - current_stack.ss_size
331 };
332 Some(stack_ptr.with_addr(stackaddr))
333 }
334
335 #[cfg(any(
336 target_os = "android",
337 target_os = "freebsd",
338 target_os = "netbsd",
339 target_os = "hurd",
340 target_os = "linux",
341 target_os = "l4re"
342 ))]
343 unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
344 let mut ret = None;
345 let mut attr: mem::MaybeUninit<libc::pthread_attr_t> = mem::MaybeUninit::uninit();
346 if !cfg!(target_os = "freebsd") {
347 attr = mem::MaybeUninit::zeroed();
348 }
349 #[cfg(target_os = "freebsd")]
350 assert_eq!(libc::pthread_attr_init(attr.as_mut_ptr()), 0);
351 #[cfg(target_os = "freebsd")]
352 let e = libc::pthread_attr_get_np(libc::pthread_self(), attr.as_mut_ptr());
353 #[cfg(not(target_os = "freebsd"))]
354 let e = libc::pthread_getattr_np(libc::pthread_self(), attr.as_mut_ptr());
355 if e == 0 {
356 let mut stackaddr = crate::ptr::null_mut();
357 let mut stacksize = 0;
358 assert_eq!(
359 libc::pthread_attr_getstack(attr.as_ptr(), &mut stackaddr, &mut stacksize),
360 0
361 );
362 ret = Some(stackaddr);
363 }
364 if e == 0 || cfg!(target_os = "freebsd") {
365 assert_eq!(libc::pthread_attr_destroy(attr.as_mut_ptr()), 0);
366 }
367 ret
368 }
369
370 fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> {
371 let stackptr = unsafe { get_stack_start()? };
372 let stackaddr = stackptr.addr();
373
374 let remainder = stackaddr % page_size;
381 Some(if remainder == 0 {
382 stackptr
383 } else {
384 stackptr.with_addr(stackaddr + page_size - remainder)
385 })
386 }
387
388 #[forbid(unsafe_op_in_unsafe_fn)]
389 unsafe fn install_main_guard() -> Option<Range<usize>> {
390 let page_size = PAGE_SIZE.load(Ordering::Relaxed);
391
392 unsafe {
393 if cfg!(all(target_os = "linux", not(target_env = "musl"))) {
395 install_main_guard_linux(page_size)
396 } else if cfg!(all(target_os = "linux", target_env = "musl")) {
397 install_main_guard_linux_musl(page_size)
398 } else if cfg!(target_os = "freebsd") {
399 install_main_guard_freebsd(page_size)
400 } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) {
401 install_main_guard_bsds(page_size)
402 } else {
403 install_main_guard_default(page_size)
404 }
405 }
406 }
407
408 #[forbid(unsafe_op_in_unsafe_fn)]
409 unsafe fn install_main_guard_linux(page_size: usize) -> Option<Range<usize>> {
410 let stackptr = stack_start_aligned(page_size)?;
421 let stackaddr = stackptr.addr();
422 Some(stackaddr - page_size..stackaddr)
423 }
424
425 #[forbid(unsafe_op_in_unsafe_fn)]
426 unsafe fn install_main_guard_linux_musl(_page_size: usize) -> Option<Range<usize>> {
427 None
432 }
433
434 #[forbid(unsafe_op_in_unsafe_fn)]
435 unsafe fn install_main_guard_freebsd(page_size: usize) -> Option<Range<usize>> {
436 let stackptr = stack_start_aligned(page_size)?;
441 let guardaddr = stackptr.addr();
442 static PAGES: OnceLock<usize> = OnceLock::new();
447
448 let pages = PAGES.get_or_init(|| {
449 use crate::sys::weak::dlsym;
450 dlsym!(
451 fn sysctlbyname(
452 name: *const libc::c_char,
453 oldp: *mut libc::c_void,
454 oldlenp: *mut libc::size_t,
455 newp: *const libc::c_void,
456 newlen: libc::size_t,
457 ) -> libc::c_int;
458 );
459 let mut guard: usize = 0;
460 let mut size = size_of_val(&guard);
461 let oid = c"security.bsd.stack_guard_page";
462 match sysctlbyname.get() {
463 Some(fcn)
464 if unsafe {
465 fcn(
466 oid.as_ptr(),
467 (&raw mut guard).cast(),
468 &raw mut size,
469 ptr::null_mut(),
470 0,
471 ) == 0
472 } =>
473 {
474 guard
475 }
476 _ => 1,
477 }
478 });
479 Some(guardaddr..guardaddr + pages * page_size)
480 }
481
482 #[forbid(unsafe_op_in_unsafe_fn)]
483 unsafe fn install_main_guard_bsds(page_size: usize) -> Option<Range<usize>> {
484 let stackptr = stack_start_aligned(page_size)?;
492 let stackaddr = stackptr.addr();
493 Some(stackaddr - page_size..stackaddr)
494 }
495
496 #[forbid(unsafe_op_in_unsafe_fn)]
497 unsafe fn install_main_guard_default(page_size: usize) -> Option<Range<usize>> {
498 let stackptr = stack_start_aligned(page_size)?;
507 let result = unsafe {
508 mmap64(
509 stackptr,
510 page_size,
511 PROT_READ | PROT_WRITE,
512 MAP_PRIVATE | MAP_ANON | MAP_FIXED,
513 -1,
514 0,
515 )
516 };
517 if result != stackptr || result == MAP_FAILED {
518 panic!("failed to allocate a guard page: {}", io::Error::last_os_error());
519 }
520
521 let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) };
522 if result != 0 {
523 panic!("failed to protect the guard page: {}", io::Error::last_os_error());
524 }
525
526 let guardaddr = stackptr.addr();
527
528 Some(guardaddr..guardaddr + page_size)
529 }
530
531 #[cfg(any(
532 target_os = "macos",
533 target_os = "openbsd",
534 target_os = "solaris",
535 target_os = "illumos",
536 ))]
537 unsafe fn current_guard() -> Option<Range<usize>> {
539 let stackptr = get_stack_start()?;
540 let stackaddr = stackptr.addr();
541 Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr)
542 }
543
544 #[cfg(any(
545 target_os = "android",
546 target_os = "freebsd",
547 target_os = "hurd",
548 target_os = "linux",
549 target_os = "netbsd",
550 target_os = "l4re"
551 ))]
552 unsafe fn current_guard() -> Option<Range<usize>> {
554 let mut ret = None;
555
556 let mut attr: mem::MaybeUninit<libc::pthread_attr_t> = mem::MaybeUninit::uninit();
557 if !cfg!(target_os = "freebsd") {
558 attr = mem::MaybeUninit::zeroed();
559 }
560 #[cfg(target_os = "freebsd")]
561 assert_eq!(libc::pthread_attr_init(attr.as_mut_ptr()), 0);
562 #[cfg(target_os = "freebsd")]
563 let e = libc::pthread_attr_get_np(libc::pthread_self(), attr.as_mut_ptr());
564 #[cfg(not(target_os = "freebsd"))]
565 let e = libc::pthread_getattr_np(libc::pthread_self(), attr.as_mut_ptr());
566 if e == 0 {
567 let mut guardsize = 0;
568 assert_eq!(libc::pthread_attr_getguardsize(attr.as_ptr(), &mut guardsize), 0);
569 if guardsize == 0 {
570 if cfg!(all(target_os = "linux", target_env = "musl")) {
571 guardsize = PAGE_SIZE.load(Ordering::Relaxed);
575 } else {
576 panic!("there is no guard page");
577 }
578 }
579 let mut stackptr = crate::ptr::null_mut::<libc::c_void>();
580 let mut size = 0;
581 assert_eq!(libc::pthread_attr_getstack(attr.as_ptr(), &mut stackptr, &mut size), 0);
582
583 let stackaddr = stackptr.addr();
584 ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) {
585 Some(stackaddr - guardsize..stackaddr)
586 } else if cfg!(all(target_os = "linux", target_env = "musl")) {
587 Some(stackaddr - guardsize..stackaddr)
588 } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")))
589 {
590 Some(stackaddr - guardsize..stackaddr + guardsize)
597 } else {
598 Some(stackaddr..stackaddr + guardsize)
599 };
600 }
601 if e == 0 || cfg!(target_os = "freebsd") {
602 assert_eq!(libc::pthread_attr_destroy(attr.as_mut_ptr()), 0);
603 }
604 ret
605 }
606}
607
608#[cfg(any(
617 miri,
618 not(any(
619 target_os = "linux",
620 target_os = "freebsd",
621 target_os = "hurd",
622 target_os = "macos",
623 target_os = "netbsd",
624 target_os = "openbsd",
625 target_os = "solaris",
626 target_os = "illumos",
627 target_os = "cygwin",
628 ))
629))]
630mod imp {
631 pub unsafe fn init() {}
632
633 pub unsafe fn cleanup() {}
634
635 pub unsafe fn make_handler(
636 _main_thread: bool,
637 _thread_name: Option<Box<str>>,
638 ) -> super::Handler {
639 super::Handler::null()
640 }
641
642 pub unsafe fn drop_handler(_data: *mut libc::c_void) {}
643}
644
645#[cfg(target_os = "cygwin")]
646mod imp {
647 mod c {
648 pub type PVECTORED_EXCEPTION_HANDLER =
649 Option<unsafe extern "system" fn(exceptioninfo: *mut EXCEPTION_POINTERS) -> i32>;
650 pub type NTSTATUS = i32;
651 pub type BOOL = i32;
652
653 unsafe extern "system" {
654 pub fn AddVectoredExceptionHandler(
655 first: u32,
656 handler: PVECTORED_EXCEPTION_HANDLER,
657 ) -> *mut core::ffi::c_void;
658 pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL;
659 }
660
661 pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _;
662 pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32;
663
664 #[repr(C)]
665 #[derive(Clone, Copy)]
666 pub struct EXCEPTION_POINTERS {
667 pub ExceptionRecord: *mut EXCEPTION_RECORD,
668 }
671 #[repr(C)]
672 #[derive(Clone, Copy)]
673 pub struct EXCEPTION_RECORD {
674 pub ExceptionCode: NTSTATUS,
675 pub ExceptionFlags: u32,
676 pub ExceptionRecord: *mut EXCEPTION_RECORD,
677 pub ExceptionAddress: *mut core::ffi::c_void,
678 pub NumberParameters: u32,
679 pub ExceptionInformation: [usize; 15],
680 }
681 }
682
683 fn reserve_stack() {
685 let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) };
686 debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling");
689 }
690
691 unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 {
692 unsafe {
694 let rec = &(*(*ExceptionInfo).ExceptionRecord);
695 let code = rec.ExceptionCode;
696
697 if code == c::EXCEPTION_STACK_OVERFLOW {
698 crate::thread::with_current_name(|name| {
699 let name = name.unwrap_or("<unknown>");
700 let tid = crate::thread::current_os_id();
701 rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n");
702 });
703 }
704 c::EXCEPTION_CONTINUE_SEARCH
705 }
706 }
707
708 pub unsafe fn init() {
709 unsafe {
711 let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler));
712 debug_assert!(!result.is_null(), "failed to install exception handler");
715 }
716 reserve_stack();
718 }
719
720 pub unsafe fn cleanup() {}
721
722 pub unsafe fn make_handler(
723 main_thread: bool,
724 _thread_name: Option<Box<str>>,
725 ) -> super::Handler {
726 if !main_thread {
727 reserve_stack();
728 }
729 super::Handler::null()
730 }
731
732 pub unsafe fn drop_handler(_data: *mut libc::c_void) {}
733}