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() -> 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::os;
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());
147 static NEED_ALTSTACK: Atomic<bool> = AtomicBool::new(false);
148
149 #[forbid(unsafe_op_in_unsafe_fn)]
152 pub unsafe fn init() {
153 PAGE_SIZE.store(os::page_size(), Ordering::Relaxed);
154
155 let mut guard_page_range = unsafe { install_main_guard() };
156
157 if cfg!(panic = "immediate-abort") {
161 return;
162 }
163
164 let mut action: sigaction = unsafe { mem::zeroed() };
166 for &signal in &[SIGSEGV, SIGBUS] {
167 unsafe { sigaction(signal, ptr::null_mut(), &mut action) };
169 if action.sa_sigaction == SIG_DFL {
171 if !NEED_ALTSTACK.load(Ordering::Relaxed) {
172 NEED_ALTSTACK.store(true, Ordering::Release);
174 let handler = unsafe { make_handler(true) };
175 MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed);
176 mem::forget(handler);
177
178 if let Some(guard_page_range) = guard_page_range.take() {
179 set_current_info(guard_page_range);
180 }
181 }
182
183 action.sa_flags = SA_SIGINFO | SA_ONSTACK;
184 action.sa_sigaction = signal_handler
185 as unsafe extern "C" fn(i32, *mut libc::siginfo_t, *mut libc::c_void)
186 as sighandler_t;
187 unsafe { sigaction(signal, &action, ptr::null_mut()) };
189 }
190 }
191 }
192
193 #[forbid(unsafe_op_in_unsafe_fn)]
196 pub unsafe fn cleanup() {
197 if cfg!(panic = "immediate-abort") {
198 return;
199 }
200 unsafe { drop_handler(MAIN_ALTSTACK.load(Ordering::Relaxed)) };
203 }
204
205 unsafe fn get_stack() -> libc::stack_t {
206 #[cfg(any(
210 target_os = "openbsd",
211 target_os = "netbsd",
212 target_os = "linux",
213 target_os = "dragonfly",
214 ))]
215 let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK;
216 #[cfg(not(any(
217 target_os = "openbsd",
218 target_os = "netbsd",
219 target_os = "linux",
220 target_os = "dragonfly",
221 )))]
222 let flags = MAP_PRIVATE | MAP_ANON;
223
224 let sigstack_size = sigstack_size();
225 let page_size = PAGE_SIZE.load(Ordering::Relaxed);
226
227 let stackp = mmap64(
228 ptr::null_mut(),
229 sigstack_size + page_size,
230 PROT_READ | PROT_WRITE,
231 flags,
232 -1,
233 0,
234 );
235 if stackp == MAP_FAILED {
236 panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error());
237 }
238 let guard_result = libc::mprotect(stackp, page_size, PROT_NONE);
239 if guard_result != 0 {
240 panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error());
241 }
242 let stackp = stackp.add(page_size);
243
244 libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size }
245 }
246
247 #[forbid(unsafe_op_in_unsafe_fn)]
250 pub unsafe fn make_handler(main_thread: bool) -> Handler {
251 if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) {
252 return Handler::null();
253 }
254
255 if !main_thread {
256 if let Some(guard_page_range) = unsafe { current_guard() } {
257 set_current_info(guard_page_range);
258 }
259 }
260
261 let mut stack = unsafe { mem::zeroed() };
263 unsafe { sigaltstack(ptr::null(), &mut stack) };
265 if stack.ss_flags & SS_DISABLE != 0 {
267 unsafe {
269 stack = get_stack();
270 sigaltstack(&stack, ptr::null_mut());
271 }
272 Handler { data: stack.ss_sp as *mut libc::c_void }
273 } else {
274 Handler::null()
275 }
276 }
277
278 #[forbid(unsafe_op_in_unsafe_fn)]
284 pub unsafe fn drop_handler(data: *mut libc::c_void) {
285 if !data.is_null() {
286 let sigstack_size = sigstack_size();
287 let page_size = PAGE_SIZE.load(Ordering::Relaxed);
288 let disabling_stack = libc::stack_t {
289 ss_sp: ptr::null_mut(),
290 ss_flags: SS_DISABLE,
291 ss_size: sigstack_size,
296 };
297 unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) };
299 unsafe { munmap(data.sub(page_size), sigstack_size + page_size) };
302 }
303
304 delete_current_info();
305 }
306
307 #[cfg(any(target_os = "linux", target_os = "android"))]
309 fn sigstack_size() -> usize {
310 let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) };
311 libc::SIGSTKSZ.max(dynamic_sigstksz as _)
315 }
316
317 #[cfg(not(any(target_os = "linux", target_os = "android")))]
319 fn sigstack_size() -> usize {
320 libc::SIGSTKSZ
321 }
322
323 #[cfg(any(target_os = "solaris", target_os = "illumos"))]
324 unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
325 let mut current_stack: libc::stack_t = crate::mem::zeroed();
326 assert_eq!(libc::stack_getbounds(&mut current_stack), 0);
327 Some(current_stack.ss_sp)
328 }
329
330 #[cfg(target_os = "macos")]
331 unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
332 let th = libc::pthread_self();
333 let stackptr = libc::pthread_get_stackaddr_np(th);
334 Some(stackptr.map_addr(|addr| addr - libc::pthread_get_stacksize_np(th)))
335 }
336
337 #[cfg(target_os = "openbsd")]
338 unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
339 let mut current_stack: libc::stack_t = crate::mem::zeroed();
340 assert_eq!(libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack), 0);
341
342 let stack_ptr = current_stack.ss_sp;
343 let stackaddr = if libc::pthread_main_np() == 1 {
344 stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed)
346 } else {
347 stack_ptr.addr() - current_stack.ss_size
349 };
350 Some(stack_ptr.with_addr(stackaddr))
351 }
352
353 #[cfg(any(
354 target_os = "android",
355 target_os = "freebsd",
356 target_os = "netbsd",
357 target_os = "hurd",
358 target_os = "linux",
359 target_os = "l4re"
360 ))]
361 unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
362 let mut ret = None;
363 let mut attr: mem::MaybeUninit<libc::pthread_attr_t> = mem::MaybeUninit::uninit();
364 if !cfg!(target_os = "freebsd") {
365 attr = mem::MaybeUninit::zeroed();
366 }
367 #[cfg(target_os = "freebsd")]
368 assert_eq!(libc::pthread_attr_init(attr.as_mut_ptr()), 0);
369 #[cfg(target_os = "freebsd")]
370 let e = libc::pthread_attr_get_np(libc::pthread_self(), attr.as_mut_ptr());
371 #[cfg(not(target_os = "freebsd"))]
372 let e = libc::pthread_getattr_np(libc::pthread_self(), attr.as_mut_ptr());
373 if e == 0 {
374 let mut stackaddr = crate::ptr::null_mut();
375 let mut stacksize = 0;
376 assert_eq!(
377 libc::pthread_attr_getstack(attr.as_ptr(), &mut stackaddr, &mut stacksize),
378 0
379 );
380 ret = Some(stackaddr);
381 }
382 if e == 0 || cfg!(target_os = "freebsd") {
383 assert_eq!(libc::pthread_attr_destroy(attr.as_mut_ptr()), 0);
384 }
385 ret
386 }
387
388 fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> {
389 let stackptr = unsafe { get_stack_start()? };
390 let stackaddr = stackptr.addr();
391
392 let remainder = stackaddr % page_size;
399 Some(if remainder == 0 {
400 stackptr
401 } else {
402 stackptr.with_addr(stackaddr + page_size - remainder)
403 })
404 }
405
406 #[forbid(unsafe_op_in_unsafe_fn)]
407 unsafe fn install_main_guard() -> Option<Range<usize>> {
408 let page_size = PAGE_SIZE.load(Ordering::Relaxed);
409
410 unsafe {
411 if cfg!(all(target_os = "linux", not(target_env = "musl"))) {
413 install_main_guard_linux(page_size)
414 } else if cfg!(all(target_os = "linux", target_env = "musl")) {
415 install_main_guard_linux_musl(page_size)
416 } else if cfg!(target_os = "freebsd") {
417 #[cfg(not(target_os = "freebsd"))]
418 return None;
419 #[cfg(target_os = "freebsd")]
421 install_main_guard_freebsd(page_size)
422 } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) {
423 install_main_guard_bsds(page_size)
424 } else {
425 install_main_guard_default(page_size)
426 }
427 }
428 }
429
430 #[forbid(unsafe_op_in_unsafe_fn)]
431 unsafe fn install_main_guard_linux(page_size: usize) -> Option<Range<usize>> {
432 let stackptr = stack_start_aligned(page_size)?;
443 let stackaddr = stackptr.addr();
444 Some(stackaddr - page_size..stackaddr)
445 }
446
447 #[forbid(unsafe_op_in_unsafe_fn)]
448 unsafe fn install_main_guard_linux_musl(_page_size: usize) -> Option<Range<usize>> {
449 None
454 }
455
456 #[forbid(unsafe_op_in_unsafe_fn)]
457 #[cfg(target_os = "freebsd")]
458 unsafe fn install_main_guard_freebsd(page_size: usize) -> Option<Range<usize>> {
459 let stackptr = stack_start_aligned(page_size)?;
464 let guardaddr = stackptr.addr();
465 static PAGES: crate::sync::OnceLock<usize> = crate::sync::OnceLock::new();
470
471 let pages = PAGES.get_or_init(|| {
472 let mut guard: usize = 0;
473 let mut size = size_of_val(&guard);
474 let oid = c"security.bsd.stack_guard_page";
475
476 let r = unsafe {
477 libc::sysctlbyname(
478 oid.as_ptr(),
479 (&raw mut guard).cast(),
480 &raw mut size,
481 ptr::null_mut(),
482 0,
483 )
484 };
485 if r == 0 { guard } else { 1 }
486 });
487 Some(guardaddr..guardaddr + pages * page_size)
488 }
489
490 #[forbid(unsafe_op_in_unsafe_fn)]
491 unsafe fn install_main_guard_bsds(page_size: usize) -> Option<Range<usize>> {
492 let stackptr = stack_start_aligned(page_size)?;
500 let stackaddr = stackptr.addr();
501 Some(stackaddr - page_size..stackaddr)
502 }
503
504 #[forbid(unsafe_op_in_unsafe_fn)]
505 unsafe fn install_main_guard_default(page_size: usize) -> Option<Range<usize>> {
506 let stackptr = stack_start_aligned(page_size)?;
515 let result = unsafe {
516 mmap64(
517 stackptr,
518 page_size,
519 PROT_READ | PROT_WRITE,
520 MAP_PRIVATE | MAP_ANON | MAP_FIXED,
521 -1,
522 0,
523 )
524 };
525 if result != stackptr || result == MAP_FAILED {
526 panic!("failed to allocate a guard page: {}", io::Error::last_os_error());
527 }
528
529 let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) };
530 if result != 0 {
531 panic!("failed to protect the guard page: {}", io::Error::last_os_error());
532 }
533
534 let guardaddr = stackptr.addr();
535
536 Some(guardaddr..guardaddr + page_size)
537 }
538
539 #[cfg(any(
540 target_os = "macos",
541 target_os = "openbsd",
542 target_os = "solaris",
543 target_os = "illumos",
544 ))]
545 unsafe fn current_guard() -> Option<Range<usize>> {
547 let stackptr = get_stack_start()?;
548 let stackaddr = stackptr.addr();
549 Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr)
550 }
551
552 #[cfg(any(
553 target_os = "android",
554 target_os = "freebsd",
555 target_os = "hurd",
556 target_os = "linux",
557 target_os = "netbsd",
558 target_os = "l4re"
559 ))]
560 unsafe fn current_guard() -> Option<Range<usize>> {
562 let mut ret = None;
563
564 let mut attr: mem::MaybeUninit<libc::pthread_attr_t> = mem::MaybeUninit::uninit();
565 if !cfg!(target_os = "freebsd") {
566 attr = mem::MaybeUninit::zeroed();
567 }
568 #[cfg(target_os = "freebsd")]
569 assert_eq!(libc::pthread_attr_init(attr.as_mut_ptr()), 0);
570 #[cfg(target_os = "freebsd")]
571 let e = libc::pthread_attr_get_np(libc::pthread_self(), attr.as_mut_ptr());
572 #[cfg(not(target_os = "freebsd"))]
573 let e = libc::pthread_getattr_np(libc::pthread_self(), attr.as_mut_ptr());
574 if e == 0 {
575 let mut guardsize = 0;
576 assert_eq!(libc::pthread_attr_getguardsize(attr.as_ptr(), &mut guardsize), 0);
577 if guardsize == 0 {
578 if cfg!(all(target_os = "linux", target_env = "musl")) {
579 guardsize = PAGE_SIZE.load(Ordering::Relaxed);
583 } else {
584 panic!("there is no guard page");
585 }
586 }
587 let mut stackptr = crate::ptr::null_mut::<libc::c_void>();
588 let mut size = 0;
589 assert_eq!(libc::pthread_attr_getstack(attr.as_ptr(), &mut stackptr, &mut size), 0);
590
591 let stackaddr = stackptr.addr();
592 ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) {
593 Some(stackaddr - guardsize..stackaddr)
594 } else if cfg!(all(target_os = "linux", target_env = "musl")) {
595 Some(stackaddr - guardsize..stackaddr)
596 } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")))
597 {
598 Some(stackaddr - guardsize..stackaddr + guardsize)
605 } else {
606 Some(stackaddr..stackaddr + guardsize)
607 };
608 }
609 if e == 0 || cfg!(target_os = "freebsd") {
610 assert_eq!(libc::pthread_attr_destroy(attr.as_mut_ptr()), 0);
611 }
612 ret
613 }
614}
615
616#[cfg(any(
625 miri,
626 not(any(
627 target_os = "linux",
628 target_os = "freebsd",
629 target_os = "hurd",
630 target_os = "macos",
631 target_os = "netbsd",
632 target_os = "openbsd",
633 target_os = "solaris",
634 target_os = "illumos",
635 target_os = "cygwin",
636 ))
637))]
638mod imp {
639 pub unsafe fn init() {}
640
641 pub unsafe fn cleanup() {}
642
643 pub unsafe fn make_handler(_main_thread: bool) -> super::Handler {
644 super::Handler::null()
645 }
646
647 pub unsafe fn drop_handler(_data: *mut libc::c_void) {}
648}
649
650#[cfg(target_os = "cygwin")]
651mod imp {
652 mod c {
653 pub type PVECTORED_EXCEPTION_HANDLER =
654 Option<unsafe extern "system" fn(exceptioninfo: *mut EXCEPTION_POINTERS) -> i32>;
655 pub type NTSTATUS = i32;
656 pub type BOOL = i32;
657
658 unsafe extern "system" {
659 pub fn AddVectoredExceptionHandler(
660 first: u32,
661 handler: PVECTORED_EXCEPTION_HANDLER,
662 ) -> *mut core::ffi::c_void;
663 pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL;
664 }
665
666 pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _;
667 pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32;
668
669 #[repr(C)]
670 #[derive(Clone, Copy)]
671 pub struct EXCEPTION_POINTERS {
672 pub ExceptionRecord: *mut EXCEPTION_RECORD,
673 }
676 #[repr(C)]
677 #[derive(Clone, Copy)]
678 pub struct EXCEPTION_RECORD {
679 pub ExceptionCode: NTSTATUS,
680 pub ExceptionFlags: u32,
681 pub ExceptionRecord: *mut EXCEPTION_RECORD,
682 pub ExceptionAddress: *mut core::ffi::c_void,
683 pub NumberParameters: u32,
684 pub ExceptionInformation: [usize; 15],
685 }
686 }
687
688 fn reserve_stack() {
690 let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) };
691 debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling");
694 }
695
696 unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 {
697 unsafe {
699 let rec = &(*(*ExceptionInfo).ExceptionRecord);
700 let code = rec.ExceptionCode;
701
702 if code == c::EXCEPTION_STACK_OVERFLOW {
703 crate::thread::with_current_name(|name| {
704 let name = name.unwrap_or("<unknown>");
705 let tid = crate::thread::current_os_id();
706 rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n");
707 });
708 }
709 c::EXCEPTION_CONTINUE_SEARCH
710 }
711 }
712
713 pub unsafe fn init() {
714 unsafe {
716 let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler));
717 debug_assert!(!result.is_null(), "failed to install exception handler");
720 }
721 reserve_stack();
723 }
724
725 pub unsafe fn cleanup() {}
726
727 pub unsafe fn make_handler(main_thread: bool) -> super::Handler {
728 if !main_thread {
729 reserve_stack();
730 }
731 super::Handler::null()
732 }
733
734 pub unsafe fn drop_handler(_data: *mut libc::c_void) {}
735}