1#![cfg_attr(test, allow(dead_code))]
2
3pub use self::imp::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() -> Handler {
12 make_handler(false)
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::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering};
73 use crate::sys::pal::unix::conf;
74 use crate::{io, mem, ptr};
75
76 #[forbid(unsafe_op_in_unsafe_fn)]
101 unsafe extern "C" fn signal_handler(
102 signum: libc::c_int,
103 info: *mut libc::siginfo_t,
104 _data: *mut libc::c_void,
105 ) {
106 let fault_addr = unsafe { (*info).si_addr().addr() };
108
109 if fault_addr != 0 {
114 with_current_info(|thread_info| {
115 if let Some(thread_info) = thread_info
118 && thread_info.guard_page_range.contains(&fault_addr)
119 {
120 let tid = thread_info.tid;
128 let name = thread_info.name.as_deref().unwrap_or("<unknown>");
129 rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n");
130 rtabort!("stack overflow");
131 }
132 })
133 }
134
135 let mut action: sigaction = unsafe { mem::zeroed() };
138 action.sa_sigaction = SIG_DFL;
139 unsafe { sigaction(signum, &action, ptr::null_mut()) };
141
142 }
144
145 static PAGE_SIZE: Atomic<usize> = AtomicUsize::new(0);
146 static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut());
153 static NEED_ALTSTACK: Atomic<bool> = AtomicBool::new(false);
154
155 #[forbid(unsafe_op_in_unsafe_fn)]
158 pub unsafe fn init() {
159 PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed);
160
161 let mut guard_page_range = unsafe { install_main_guard() };
162
163 if cfg!(panic = "immediate-abort") {
167 return;
168 }
169
170 let mut action: sigaction = unsafe { mem::zeroed() };
172 for &signal in &[SIGSEGV, SIGBUS] {
173 unsafe { sigaction(signal, ptr::null_mut(), &mut action) };
175 if action.sa_sigaction == SIG_DFL {
177 if !NEED_ALTSTACK.load(Ordering::Relaxed) {
178 NEED_ALTSTACK.store(true, Ordering::Release);
180 let handler = unsafe { make_handler(true) };
181 MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed);
182 mem::forget(handler);
183
184 if let Some(guard_page_range) = guard_page_range.take() {
185 set_current_info(guard_page_range);
186 }
187 }
188
189 action.sa_flags = SA_SIGINFO | SA_ONSTACK;
190 action.sa_sigaction = signal_handler
191 as unsafe extern "C" fn(i32, *mut libc::siginfo_t, *mut libc::c_void)
192 as sighandler_t;
193 unsafe { sigaction(signal, &action, ptr::null_mut()) };
195 }
196 }
197 }
198
199 unsafe fn get_stack() -> libc::stack_t {
200 #[cfg(any(
204 target_os = "openbsd",
205 target_os = "netbsd",
206 target_os = "linux",
207 target_os = "dragonfly",
208 ))]
209 let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK;
210 #[cfg(not(any(
211 target_os = "openbsd",
212 target_os = "netbsd",
213 target_os = "linux",
214 target_os = "dragonfly",
215 )))]
216 let flags = MAP_PRIVATE | MAP_ANON;
217
218 let sigstack_size = sigstack_size();
219 let page_size = PAGE_SIZE.load(Ordering::Relaxed);
220
221 let stackp = mmap64(
222 ptr::null_mut(),
223 sigstack_size + page_size,
224 PROT_READ | PROT_WRITE,
225 flags,
226 -1,
227 0,
228 );
229 if stackp == MAP_FAILED {
230 panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error());
231 }
232 let guard_result = libc::mprotect(stackp, page_size, PROT_NONE);
233 if guard_result != 0 {
234 panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error());
235 }
236 let stackp = stackp.add(page_size);
237
238 libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size }
239 }
240
241 #[forbid(unsafe_op_in_unsafe_fn)]
244 pub unsafe fn make_handler(main_thread: bool) -> Handler {
245 if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) {
246 return Handler::null();
247 }
248
249 if !main_thread {
250 if let Some(guard_page_range) = unsafe { current_guard() } {
251 set_current_info(guard_page_range);
252 }
253 }
254
255 let mut stack = unsafe { mem::zeroed() };
257 unsafe { sigaltstack(ptr::null(), &mut stack) };
259 if stack.ss_flags & SS_DISABLE != 0 {
261 unsafe {
263 stack = get_stack();
264 sigaltstack(&stack, ptr::null_mut());
265 }
266 Handler { data: stack.ss_sp as *mut libc::c_void }
267 } else {
268 Handler::null()
269 }
270 }
271
272 #[forbid(unsafe_op_in_unsafe_fn)]
278 pub unsafe fn drop_handler(data: *mut libc::c_void) {
279 if !data.is_null() {
280 let sigstack_size = sigstack_size();
281 let page_size = PAGE_SIZE.load(Ordering::Relaxed);
282 let disabling_stack = libc::stack_t {
283 ss_sp: ptr::null_mut(),
284 ss_flags: SS_DISABLE,
285 ss_size: sigstack_size,
290 };
291 unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) };
293 unsafe { munmap(data.sub(page_size), sigstack_size + page_size) };
296 }
297
298 delete_current_info();
299 }
300
301 #[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))]
303 fn sigstack_size() -> usize {
304 let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) };
305 libc::SIGSTKSZ.max(dynamic_sigstksz as _)
309 }
310
311 #[cfg(not(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc"))))]
313 fn sigstack_size() -> usize {
314 libc::SIGSTKSZ
315 }
316
317 #[cfg(any(target_os = "solaris", target_os = "illumos"))]
318 unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
319 let mut current_stack: libc::stack_t = crate::mem::zeroed();
320 assert_eq!(libc::stack_getbounds(&mut current_stack), 0);
321 Some(current_stack.ss_sp)
322 }
323
324 #[cfg(target_os = "macos")]
325 unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
326 let th = libc::pthread_self();
327 let stackptr = libc::pthread_get_stackaddr_np(th);
328 Some(stackptr.map_addr(|addr| addr - libc::pthread_get_stacksize_np(th)))
329 }
330
331 #[cfg(target_os = "openbsd")]
332 unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
333 let mut current_stack: libc::stack_t = crate::mem::zeroed();
334 assert_eq!(libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack), 0);
335
336 let stack_ptr = current_stack.ss_sp;
337 let stackaddr = if libc::pthread_main_np() == 1 {
338 stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed)
340 } else {
341 stack_ptr.addr() - current_stack.ss_size
343 };
344 Some(stack_ptr.with_addr(stackaddr))
345 }
346
347 #[cfg(any(
348 target_os = "android",
349 target_os = "freebsd",
350 target_os = "netbsd",
351 target_os = "hurd",
352 target_os = "linux",
353 target_os = "l4re"
354 ))]
355 unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
356 use crate::pin::pin;
357 use crate::sys::helpers::COpaque;
358
359 let mut ret = None;
360 let mut attr: COpaque<libc::pthread_attr_t> = COpaque::uninit();
361 if !cfg!(target_os = "freebsd") {
362 attr = COpaque::zeroed();
363 }
364 let attr = pin!(attr);
365 let attr = attr.into_ref();
367
368 #[cfg(target_os = "freebsd")]
369 assert_eq!(libc::pthread_attr_init(attr.get()), 0);
370 #[cfg(target_os = "freebsd")]
371 let e = libc::pthread_attr_get_np(libc::pthread_self(), attr.get());
372 #[cfg(not(target_os = "freebsd"))]
373 let e = libc::pthread_getattr_np(libc::pthread_self(), attr.get());
374 if e == 0 {
375 let mut stackaddr = crate::ptr::null_mut();
376 let mut stacksize = 0;
377 assert_eq!(libc::pthread_attr_getstack(attr.get(), &mut stackaddr, &mut stacksize), 0);
378 ret = Some(stackaddr);
379 }
380 if e == 0 || cfg!(target_os = "freebsd") {
381 assert_eq!(libc::pthread_attr_destroy(attr.get()), 0);
382 }
383 ret
384 }
385
386 fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> {
387 let stackptr = unsafe { get_stack_start()? };
388 let stackaddr = stackptr.addr();
389
390 let remainder = stackaddr % page_size;
397 Some(if remainder == 0 {
398 stackptr
399 } else {
400 stackptr.with_addr(stackaddr + page_size - remainder)
401 })
402 }
403
404 #[forbid(unsafe_op_in_unsafe_fn)]
405 unsafe fn install_main_guard() -> Option<Range<usize>> {
406 let page_size = PAGE_SIZE.load(Ordering::Relaxed);
407
408 unsafe {
409 if cfg!(all(target_os = "linux", not(target_env = "musl"))) {
411 install_main_guard_linux(page_size)
412 } else if cfg!(all(target_os = "linux", target_env = "musl")) {
413 install_main_guard_linux_musl(page_size)
414 } else if cfg!(target_os = "freebsd") {
415 #[cfg(not(target_os = "freebsd"))]
416 return None;
417 #[cfg(target_os = "freebsd")]
419 install_main_guard_freebsd(page_size)
420 } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) {
421 install_main_guard_bsds(page_size)
422 } else {
423 install_main_guard_default(page_size)
424 }
425 }
426 }
427
428 #[forbid(unsafe_op_in_unsafe_fn)]
429 unsafe fn install_main_guard_linux(page_size: usize) -> Option<Range<usize>> {
430 if cfg!(panic = "immediate-abort") {
433 return None;
434 }
435 let stackptr = stack_start_aligned(page_size)?;
446 let stackaddr = stackptr.addr();
447 Some(stackaddr - page_size..stackaddr)
448 }
449
450 #[forbid(unsafe_op_in_unsafe_fn)]
451 unsafe fn install_main_guard_linux_musl(_page_size: usize) -> Option<Range<usize>> {
452 None
457 }
458
459 #[forbid(unsafe_op_in_unsafe_fn)]
460 #[cfg(target_os = "freebsd")]
461 unsafe fn install_main_guard_freebsd(page_size: usize) -> Option<Range<usize>> {
462 if cfg!(panic = "immediate-abort") {
464 return None;
465 }
466 let stackptr = stack_start_aligned(page_size)?;
471 let guardaddr = stackptr.addr();
472 static PAGES: crate::sync::OnceLock<usize> = crate::sync::OnceLock::new();
477
478 let pages = PAGES.get_or_init(|| {
479 let mut guard: usize = 0;
480 let mut size = size_of_val(&guard);
481 let oid = c"security.bsd.stack_guard_page";
482
483 let r = unsafe {
484 libc::sysctlbyname(
485 oid.as_ptr(),
486 (&raw mut guard).cast(),
487 &raw mut size,
488 ptr::null_mut(),
489 0,
490 )
491 };
492 if r == 0 { guard } else { 1 }
493 });
494 Some(guardaddr..guardaddr + pages * page_size)
495 }
496
497 #[forbid(unsafe_op_in_unsafe_fn)]
498 unsafe fn install_main_guard_bsds(page_size: usize) -> Option<Range<usize>> {
499 if cfg!(panic = "immediate-abort") {
501 return None;
502 }
503 let stackptr = stack_start_aligned(page_size)?;
511 let stackaddr = stackptr.addr();
512 Some(stackaddr - page_size..stackaddr)
513 }
514
515 #[forbid(unsafe_op_in_unsafe_fn)]
516 unsafe fn install_main_guard_default(page_size: usize) -> Option<Range<usize>> {
517 let stackptr = stack_start_aligned(page_size)?;
526 let result = unsafe {
527 mmap64(
528 stackptr,
529 page_size,
530 PROT_READ | PROT_WRITE,
531 MAP_PRIVATE | MAP_ANON | MAP_FIXED,
532 -1,
533 0,
534 )
535 };
536 if result != stackptr || result == MAP_FAILED {
537 panic!("failed to allocate a guard page: {}", io::Error::last_os_error());
538 }
539
540 let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) };
541 if result != 0 {
542 panic!("failed to protect the guard page: {}", io::Error::last_os_error());
543 }
544
545 let guardaddr = stackptr.addr();
546
547 Some(guardaddr..guardaddr + page_size)
548 }
549
550 #[cfg(any(
551 target_os = "macos",
552 target_os = "openbsd",
553 target_os = "solaris",
554 target_os = "illumos",
555 ))]
556 unsafe fn current_guard() -> Option<Range<usize>> {
558 let stackptr = get_stack_start()?;
559 let stackaddr = stackptr.addr();
560 Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr)
561 }
562
563 #[cfg(any(
564 target_os = "android",
565 target_os = "freebsd",
566 target_os = "hurd",
567 target_os = "linux",
568 target_os = "netbsd",
569 target_os = "l4re"
570 ))]
571 unsafe fn current_guard() -> Option<Range<usize>> {
573 use crate::pin::pin;
574 use crate::sys::helpers::COpaque;
575
576 let mut ret = None;
577
578 let mut attr: COpaque<libc::pthread_attr_t> = COpaque::uninit();
579 if !cfg!(target_os = "freebsd") {
580 attr = COpaque::zeroed();
581 }
582 let attr = pin!(attr);
583 let attr = attr.into_ref();
585
586 #[cfg(target_os = "freebsd")]
587 assert_eq!(libc::pthread_attr_init(attr.get()), 0);
588 #[cfg(target_os = "freebsd")]
589 let e = libc::pthread_attr_get_np(libc::pthread_self(), attr.get());
590 #[cfg(not(target_os = "freebsd"))]
591 let e = libc::pthread_getattr_np(libc::pthread_self(), attr.get());
592 if e == 0 {
593 let mut guardsize = 0;
594 assert_eq!(libc::pthread_attr_getguardsize(attr.get(), &mut guardsize), 0);
595 if guardsize == 0 {
596 if cfg!(all(target_os = "linux", target_env = "musl")) {
597 guardsize = PAGE_SIZE.load(Ordering::Relaxed);
601 } else {
602 panic!("there is no guard page");
603 }
604 }
605 let mut stackptr = crate::ptr::null_mut::<libc::c_void>();
606 let mut size = 0;
607 assert_eq!(libc::pthread_attr_getstack(attr.get(), &mut stackptr, &mut size), 0);
608
609 let stackaddr = stackptr.addr();
610 ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) {
611 Some(stackaddr - guardsize..stackaddr)
612 } else if cfg!(all(target_os = "linux", target_env = "musl")) {
613 Some(stackaddr - guardsize..stackaddr)
614 } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")))
615 {
616 Some(stackaddr - guardsize..stackaddr + guardsize)
623 } else {
624 Some(stackaddr..stackaddr + guardsize)
625 };
626 }
627 if e == 0 || cfg!(target_os = "freebsd") {
628 assert_eq!(libc::pthread_attr_destroy(attr.get()), 0);
629 }
630 ret
631 }
632}
633
634#[cfg(any(
643 miri,
644 not(any(
645 target_os = "linux",
646 target_os = "freebsd",
647 target_os = "hurd",
648 target_os = "macos",
649 target_os = "netbsd",
650 target_os = "openbsd",
651 target_os = "solaris",
652 target_os = "illumos",
653 target_os = "cygwin",
654 ))
655))]
656mod imp {
657 pub unsafe fn init() {}
658
659 pub unsafe fn make_handler(_main_thread: bool) -> super::Handler {
660 super::Handler::null()
661 }
662
663 pub unsafe fn drop_handler(_data: *mut libc::c_void) {}
664}
665
666#[cfg(target_os = "cygwin")]
667mod imp {
668 mod c {
669 pub type PVECTORED_EXCEPTION_HANDLER =
670 Option<unsafe extern "system" fn(exceptioninfo: *mut EXCEPTION_POINTERS) -> i32>;
671 pub type NTSTATUS = i32;
672 pub type BOOL = i32;
673
674 unsafe extern "system" {
675 pub fn AddVectoredExceptionHandler(
676 first: u32,
677 handler: PVECTORED_EXCEPTION_HANDLER,
678 ) -> *mut core::ffi::c_void;
679 pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL;
680 }
681
682 pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _;
683 pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32;
684
685 #[repr(C)]
686 #[derive(Clone, Copy)]
687 pub struct EXCEPTION_POINTERS {
688 pub ExceptionRecord: *mut EXCEPTION_RECORD,
689 }
692 #[repr(C)]
693 #[derive(Clone, Copy)]
694 pub struct EXCEPTION_RECORD {
695 pub ExceptionCode: NTSTATUS,
696 pub ExceptionFlags: u32,
697 pub ExceptionRecord: *mut EXCEPTION_RECORD,
698 pub ExceptionAddress: *mut core::ffi::c_void,
699 pub NumberParameters: u32,
700 pub ExceptionInformation: [usize; 15],
701 }
702 }
703
704 fn reserve_stack() {
706 let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) };
707 debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling");
710 }
711
712 unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 {
713 unsafe {
715 let rec = &(*(*ExceptionInfo).ExceptionRecord);
716 let code = rec.ExceptionCode;
717
718 if code == c::EXCEPTION_STACK_OVERFLOW {
719 crate::thread::with_current_name(|name| {
720 let name = name.unwrap_or("<unknown>");
721 let tid = crate::thread::current_os_id();
722 rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n");
723 });
724 }
725 c::EXCEPTION_CONTINUE_SEARCH
726 }
727 }
728
729 pub unsafe fn init() {
730 unsafe {
732 let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler));
733 debug_assert!(!result.is_null(), "failed to install exception handler");
736 }
737 reserve_stack();
739 }
740
741 pub unsafe fn make_handler(main_thread: bool) -> super::Handler {
742 if !main_thread {
743 reserve_stack();
744 }
745 super::Handler::null()
746 }
747
748 pub unsafe fn drop_handler(_data: *mut libc::c_void) {}
749}