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::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 name = thread_info.thread_name.as_deref().unwrap_or("<unknown>");
121                    let tid = crate::thread::current_os_id();
122                    rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n");
123                    rtabort!("stack overflow");
124                }
125            })
126        }
127
128        let mut action: sigaction = unsafe { mem::zeroed() };
131        action.sa_sigaction = SIG_DFL;
132        unsafe { sigaction(signum, &action, ptr::null_mut()) };
134
135        }
137
138    static PAGE_SIZE: Atomic<usize> = AtomicUsize::new(0);
139    static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut());
140    static NEED_ALTSTACK: Atomic<bool> = AtomicBool::new(false);
141
142    #[forbid(unsafe_op_in_unsafe_fn)]
145    pub unsafe fn init() {
146        PAGE_SIZE.store(os::page_size(), Ordering::Relaxed);
147
148        let mut guard_page_range = unsafe { install_main_guard() };
149
150        if cfg!(panic = "immediate-abort") {
154            return;
155        }
156
157        let mut action: sigaction = unsafe { mem::zeroed() };
159        for &signal in &[SIGSEGV, SIGBUS] {
160            unsafe { sigaction(signal, ptr::null_mut(), &mut action) };
162            if action.sa_sigaction == SIG_DFL {
164                if !NEED_ALTSTACK.load(Ordering::Relaxed) {
165                    NEED_ALTSTACK.store(true, Ordering::Release);
167                    let handler = unsafe { make_handler(true, None) };
168                    MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed);
169                    mem::forget(handler);
170
171                    if let Some(guard_page_range) = guard_page_range.take() {
172                        set_current_info(guard_page_range, Some(Box::from("main")));
173                    }
174                }
175
176                action.sa_flags = SA_SIGINFO | SA_ONSTACK;
177                action.sa_sigaction = signal_handler as sighandler_t;
178                unsafe { sigaction(signal, &action, ptr::null_mut()) };
180            }
181        }
182    }
183
184    #[forbid(unsafe_op_in_unsafe_fn)]
187    pub unsafe fn cleanup() {
188        if cfg!(panic = "immediate-abort") {
189            return;
190        }
191        unsafe { drop_handler(MAIN_ALTSTACK.load(Ordering::Relaxed)) };
194    }
195
196    unsafe fn get_stack() -> libc::stack_t {
197        #[cfg(any(
201            target_os = "openbsd",
202            target_os = "netbsd",
203            target_os = "linux",
204            target_os = "dragonfly",
205        ))]
206        let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK;
207        #[cfg(not(any(
208            target_os = "openbsd",
209            target_os = "netbsd",
210            target_os = "linux",
211            target_os = "dragonfly",
212        )))]
213        let flags = MAP_PRIVATE | MAP_ANON;
214
215        let sigstack_size = sigstack_size();
216        let page_size = PAGE_SIZE.load(Ordering::Relaxed);
217
218        let stackp = mmap64(
219            ptr::null_mut(),
220            sigstack_size + page_size,
221            PROT_READ | PROT_WRITE,
222            flags,
223            -1,
224            0,
225        );
226        if stackp == MAP_FAILED {
227            panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error());
228        }
229        let guard_result = libc::mprotect(stackp, page_size, PROT_NONE);
230        if guard_result != 0 {
231            panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error());
232        }
233        let stackp = stackp.add(page_size);
234
235        libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size }
236    }
237
238    #[forbid(unsafe_op_in_unsafe_fn)]
241    pub unsafe fn make_handler(main_thread: bool, thread_name: Option<Box<str>>) -> Handler {
242        if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) {
243            return Handler::null();
244        }
245
246        if !main_thread {
247            if let Some(guard_page_range) = unsafe { current_guard() } {
248                set_current_info(guard_page_range, thread_name);
249            }
250        }
251
252        let mut stack = unsafe { mem::zeroed() };
254        unsafe { sigaltstack(ptr::null(), &mut stack) };
256        if stack.ss_flags & SS_DISABLE != 0 {
258            unsafe {
260                stack = get_stack();
261                sigaltstack(&stack, ptr::null_mut());
262            }
263            Handler { data: stack.ss_sp as *mut libc::c_void }
264        } else {
265            Handler::null()
266        }
267    }
268
269    #[forbid(unsafe_op_in_unsafe_fn)]
275    pub unsafe fn drop_handler(data: *mut libc::c_void) {
276        if !data.is_null() {
277            let sigstack_size = sigstack_size();
278            let page_size = PAGE_SIZE.load(Ordering::Relaxed);
279            let disabling_stack = libc::stack_t {
280                ss_sp: ptr::null_mut(),
281                ss_flags: SS_DISABLE,
282                ss_size: sigstack_size,
287            };
288            unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) };
290            unsafe { munmap(data.sub(page_size), sigstack_size + page_size) };
293        }
294
295        delete_current_info();
296    }
297
298    #[cfg(any(target_os = "linux", target_os = "android"))]
300    fn sigstack_size() -> usize {
301        let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) };
302        libc::SIGSTKSZ.max(dynamic_sigstksz as _)
306    }
307
308    #[cfg(not(any(target_os = "linux", target_os = "android")))]
310    fn sigstack_size() -> usize {
311        libc::SIGSTKSZ
312    }
313
314    #[cfg(any(target_os = "solaris", target_os = "illumos"))]
315    unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
316        let mut current_stack: libc::stack_t = crate::mem::zeroed();
317        assert_eq!(libc::stack_getbounds(&mut current_stack), 0);
318        Some(current_stack.ss_sp)
319    }
320
321    #[cfg(target_os = "macos")]
322    unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
323        let th = libc::pthread_self();
324        let stackptr = libc::pthread_get_stackaddr_np(th);
325        Some(stackptr.map_addr(|addr| addr - libc::pthread_get_stacksize_np(th)))
326    }
327
328    #[cfg(target_os = "openbsd")]
329    unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
330        let mut current_stack: libc::stack_t = crate::mem::zeroed();
331        assert_eq!(libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack), 0);
332
333        let stack_ptr = current_stack.ss_sp;
334        let stackaddr = if libc::pthread_main_np() == 1 {
335            stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed)
337        } else {
338            stack_ptr.addr() - current_stack.ss_size
340        };
341        Some(stack_ptr.with_addr(stackaddr))
342    }
343
344    #[cfg(any(
345        target_os = "android",
346        target_os = "freebsd",
347        target_os = "netbsd",
348        target_os = "hurd",
349        target_os = "linux",
350        target_os = "l4re"
351    ))]
352    unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
353        let mut ret = None;
354        let mut attr: mem::MaybeUninit<libc::pthread_attr_t> = mem::MaybeUninit::uninit();
355        if !cfg!(target_os = "freebsd") {
356            attr = mem::MaybeUninit::zeroed();
357        }
358        #[cfg(target_os = "freebsd")]
359        assert_eq!(libc::pthread_attr_init(attr.as_mut_ptr()), 0);
360        #[cfg(target_os = "freebsd")]
361        let e = libc::pthread_attr_get_np(libc::pthread_self(), attr.as_mut_ptr());
362        #[cfg(not(target_os = "freebsd"))]
363        let e = libc::pthread_getattr_np(libc::pthread_self(), attr.as_mut_ptr());
364        if e == 0 {
365            let mut stackaddr = crate::ptr::null_mut();
366            let mut stacksize = 0;
367            assert_eq!(
368                libc::pthread_attr_getstack(attr.as_ptr(), &mut stackaddr, &mut stacksize),
369                0
370            );
371            ret = Some(stackaddr);
372        }
373        if e == 0 || cfg!(target_os = "freebsd") {
374            assert_eq!(libc::pthread_attr_destroy(attr.as_mut_ptr()), 0);
375        }
376        ret
377    }
378
379    fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> {
380        let stackptr = unsafe { get_stack_start()? };
381        let stackaddr = stackptr.addr();
382
383        let remainder = stackaddr % page_size;
390        Some(if remainder == 0 {
391            stackptr
392        } else {
393            stackptr.with_addr(stackaddr + page_size - remainder)
394        })
395    }
396
397    #[forbid(unsafe_op_in_unsafe_fn)]
398    unsafe fn install_main_guard() -> Option<Range<usize>> {
399        let page_size = PAGE_SIZE.load(Ordering::Relaxed);
400
401        unsafe {
402            if cfg!(all(target_os = "linux", not(target_env = "musl"))) {
404                install_main_guard_linux(page_size)
405            } else if cfg!(all(target_os = "linux", target_env = "musl")) {
406                install_main_guard_linux_musl(page_size)
407            } else if cfg!(target_os = "freebsd") {
408                #[cfg(not(target_os = "freebsd"))]
409                return None;
410                #[cfg(target_os = "freebsd")]
412                install_main_guard_freebsd(page_size)
413            } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) {
414                install_main_guard_bsds(page_size)
415            } else {
416                install_main_guard_default(page_size)
417            }
418        }
419    }
420
421    #[forbid(unsafe_op_in_unsafe_fn)]
422    unsafe fn install_main_guard_linux(page_size: usize) -> Option<Range<usize>> {
423        let stackptr = stack_start_aligned(page_size)?;
434        let stackaddr = stackptr.addr();
435        Some(stackaddr - page_size..stackaddr)
436    }
437
438    #[forbid(unsafe_op_in_unsafe_fn)]
439    unsafe fn install_main_guard_linux_musl(_page_size: usize) -> Option<Range<usize>> {
440        None
445    }
446
447    #[forbid(unsafe_op_in_unsafe_fn)]
448    #[cfg(target_os = "freebsd")]
449    unsafe fn install_main_guard_freebsd(page_size: usize) -> Option<Range<usize>> {
450        let stackptr = stack_start_aligned(page_size)?;
455        let guardaddr = stackptr.addr();
456        static PAGES: crate::sync::OnceLock<usize> = crate::sync::OnceLock::new();
461
462        let pages = PAGES.get_or_init(|| {
463            let mut guard: usize = 0;
464            let mut size = size_of_val(&guard);
465            let oid = c"security.bsd.stack_guard_page";
466
467            let r = unsafe {
468                libc::sysctlbyname(
469                    oid.as_ptr(),
470                    (&raw mut guard).cast(),
471                    &raw mut size,
472                    ptr::null_mut(),
473                    0,
474                )
475            };
476            if r == 0 { guard } else { 1 }
477        });
478        Some(guardaddr..guardaddr + pages * page_size)
479    }
480
481    #[forbid(unsafe_op_in_unsafe_fn)]
482    unsafe fn install_main_guard_bsds(page_size: usize) -> Option<Range<usize>> {
483        let stackptr = stack_start_aligned(page_size)?;
491        let stackaddr = stackptr.addr();
492        Some(stackaddr - page_size..stackaddr)
493    }
494
495    #[forbid(unsafe_op_in_unsafe_fn)]
496    unsafe fn install_main_guard_default(page_size: usize) -> Option<Range<usize>> {
497        let stackptr = stack_start_aligned(page_size)?;
506        let result = unsafe {
507            mmap64(
508                stackptr,
509                page_size,
510                PROT_READ | PROT_WRITE,
511                MAP_PRIVATE | MAP_ANON | MAP_FIXED,
512                -1,
513                0,
514            )
515        };
516        if result != stackptr || result == MAP_FAILED {
517            panic!("failed to allocate a guard page: {}", io::Error::last_os_error());
518        }
519
520        let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) };
521        if result != 0 {
522            panic!("failed to protect the guard page: {}", io::Error::last_os_error());
523        }
524
525        let guardaddr = stackptr.addr();
526
527        Some(guardaddr..guardaddr + page_size)
528    }
529
530    #[cfg(any(
531        target_os = "macos",
532        target_os = "openbsd",
533        target_os = "solaris",
534        target_os = "illumos",
535    ))]
536    unsafe fn current_guard() -> Option<Range<usize>> {
538        let stackptr = get_stack_start()?;
539        let stackaddr = stackptr.addr();
540        Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr)
541    }
542
543    #[cfg(any(
544        target_os = "android",
545        target_os = "freebsd",
546        target_os = "hurd",
547        target_os = "linux",
548        target_os = "netbsd",
549        target_os = "l4re"
550    ))]
551    unsafe fn current_guard() -> Option<Range<usize>> {
553        let mut ret = None;
554
555        let mut attr: mem::MaybeUninit<libc::pthread_attr_t> = mem::MaybeUninit::uninit();
556        if !cfg!(target_os = "freebsd") {
557            attr = mem::MaybeUninit::zeroed();
558        }
559        #[cfg(target_os = "freebsd")]
560        assert_eq!(libc::pthread_attr_init(attr.as_mut_ptr()), 0);
561        #[cfg(target_os = "freebsd")]
562        let e = libc::pthread_attr_get_np(libc::pthread_self(), attr.as_mut_ptr());
563        #[cfg(not(target_os = "freebsd"))]
564        let e = libc::pthread_getattr_np(libc::pthread_self(), attr.as_mut_ptr());
565        if e == 0 {
566            let mut guardsize = 0;
567            assert_eq!(libc::pthread_attr_getguardsize(attr.as_ptr(), &mut guardsize), 0);
568            if guardsize == 0 {
569                if cfg!(all(target_os = "linux", target_env = "musl")) {
570                    guardsize = PAGE_SIZE.load(Ordering::Relaxed);
574                } else {
575                    panic!("there is no guard page");
576                }
577            }
578            let mut stackptr = crate::ptr::null_mut::<libc::c_void>();
579            let mut size = 0;
580            assert_eq!(libc::pthread_attr_getstack(attr.as_ptr(), &mut stackptr, &mut size), 0);
581
582            let stackaddr = stackptr.addr();
583            ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) {
584                Some(stackaddr - guardsize..stackaddr)
585            } else if cfg!(all(target_os = "linux", target_env = "musl")) {
586                Some(stackaddr - guardsize..stackaddr)
587            } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")))
588            {
589                Some(stackaddr - guardsize..stackaddr + guardsize)
596            } else {
597                Some(stackaddr..stackaddr + guardsize)
598            };
599        }
600        if e == 0 || cfg!(target_os = "freebsd") {
601            assert_eq!(libc::pthread_attr_destroy(attr.as_mut_ptr()), 0);
602        }
603        ret
604    }
605}
606
607#[cfg(any(
616    miri,
617    not(any(
618        target_os = "linux",
619        target_os = "freebsd",
620        target_os = "hurd",
621        target_os = "macos",
622        target_os = "netbsd",
623        target_os = "openbsd",
624        target_os = "solaris",
625        target_os = "illumos",
626        target_os = "cygwin",
627    ))
628))]
629mod imp {
630    pub unsafe fn init() {}
631
632    pub unsafe fn cleanup() {}
633
634    pub unsafe fn make_handler(
635        _main_thread: bool,
636        _thread_name: Option<Box<str>>,
637    ) -> super::Handler {
638        super::Handler::null()
639    }
640
641    pub unsafe fn drop_handler(_data: *mut libc::c_void) {}
642}
643
644#[cfg(target_os = "cygwin")]
645mod imp {
646    mod c {
647        pub type PVECTORED_EXCEPTION_HANDLER =
648            Option<unsafe extern "system" fn(exceptioninfo: *mut EXCEPTION_POINTERS) -> i32>;
649        pub type NTSTATUS = i32;
650        pub type BOOL = i32;
651
652        unsafe extern "system" {
653            pub fn AddVectoredExceptionHandler(
654                first: u32,
655                handler: PVECTORED_EXCEPTION_HANDLER,
656            ) -> *mut core::ffi::c_void;
657            pub fn SetThreadStackGuarantee(stacksizeinbytes: *mut u32) -> BOOL;
658        }
659
660        pub const EXCEPTION_STACK_OVERFLOW: NTSTATUS = 0xC00000FD_u32 as _;
661        pub const EXCEPTION_CONTINUE_SEARCH: i32 = 1i32;
662
663        #[repr(C)]
664        #[derive(Clone, Copy)]
665        pub struct EXCEPTION_POINTERS {
666            pub ExceptionRecord: *mut EXCEPTION_RECORD,
667            }
670        #[repr(C)]
671        #[derive(Clone, Copy)]
672        pub struct EXCEPTION_RECORD {
673            pub ExceptionCode: NTSTATUS,
674            pub ExceptionFlags: u32,
675            pub ExceptionRecord: *mut EXCEPTION_RECORD,
676            pub ExceptionAddress: *mut core::ffi::c_void,
677            pub NumberParameters: u32,
678            pub ExceptionInformation: [usize; 15],
679        }
680    }
681
682    fn reserve_stack() {
684        let result = unsafe { c::SetThreadStackGuarantee(&mut 0x5000) };
685        debug_assert_ne!(result, 0, "failed to reserve stack space for exception handling");
688    }
689
690    unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POINTERS) -> i32 {
691        unsafe {
693            let rec = &(*(*ExceptionInfo).ExceptionRecord);
694            let code = rec.ExceptionCode;
695
696            if code == c::EXCEPTION_STACK_OVERFLOW {
697                crate::thread::with_current_name(|name| {
698                    let name = name.unwrap_or("<unknown>");
699                    let tid = crate::thread::current_os_id();
700                    rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n");
701                });
702            }
703            c::EXCEPTION_CONTINUE_SEARCH
704        }
705    }
706
707    pub unsafe fn init() {
708        unsafe {
710            let result = c::AddVectoredExceptionHandler(0, Some(vectored_handler));
711            debug_assert!(!result.is_null(), "failed to install exception handler");
714        }
715        reserve_stack();
717    }
718
719    pub unsafe fn cleanup() {}
720
721    pub unsafe fn make_handler(
722        main_thread: bool,
723        _thread_name: Option<Box<str>>,
724    ) -> super::Handler {
725        if !main_thread {
726            reserve_stack();
727        }
728        super::Handler::null()
729    }
730
731    pub unsafe fn drop_handler(_data: *mut libc::c_void) {}
732}