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(any(
29    target_os = "linux",
30    target_os = "freebsd",
31    target_os = "hurd",
32    target_os = "macos",
33    target_os = "netbsd",
34    target_os = "openbsd",
35    target_os = "solaris",
36    target_os = "illumos",
37))]
38mod imp {
39    use libc::{
40        MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK,
41        SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t,
42    };
43    #[cfg(not(all(target_os = "linux", target_env = "gnu")))]
44    use libc::{mmap as mmap64, mprotect, munmap};
45    #[cfg(all(target_os = "linux", target_env = "gnu"))]
46    use libc::{mmap64, mprotect, munmap};
47
48    use super::Handler;
49    use crate::cell::Cell;
50    use crate::ops::Range;
51    use crate::sync::OnceLock;
52    use crate::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
53    use crate::sys::pal::unix::os;
54    use crate::{io, mem, ptr, thread};
55
56    thread_local! {
62        static GUARD: Cell<(usize, usize)> = const { Cell::new((0, 0)) };
64    }
65
66    #[forbid(unsafe_op_in_unsafe_fn)]
91    unsafe extern "C" fn signal_handler(
92        signum: libc::c_int,
93        info: *mut libc::siginfo_t,
94        _data: *mut libc::c_void,
95    ) {
96        let (start, end) = GUARD.get();
97        let addr = unsafe { (*info).si_addr().addr() };
99
100        if start <= addr && addr < end {
103            thread::with_current_name(|name| {
104                let name = name.unwrap_or("<unknown>");
105                rtprintpanic!("\nthread '{name}' has overflowed its stack\n");
106            });
107
108            rtabort!("stack overflow");
109        } else {
110            let mut action: sigaction = unsafe { mem::zeroed() };
113            action.sa_sigaction = SIG_DFL;
114            unsafe { sigaction(signum, &action, ptr::null_mut()) };
116
117            }
119    }
120
121    static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0);
122    static MAIN_ALTSTACK: AtomicPtr<libc::c_void> = AtomicPtr::new(ptr::null_mut());
123    static NEED_ALTSTACK: AtomicBool = AtomicBool::new(false);
124
125    #[forbid(unsafe_op_in_unsafe_fn)]
128    pub unsafe fn init() {
129        PAGE_SIZE.store(os::page_size(), Ordering::Relaxed);
130
131        let guard = unsafe { install_main_guard().unwrap_or(0..0) };
133        GUARD.set((guard.start, guard.end));
134
135        let mut action: sigaction = unsafe { mem::zeroed() };
137        for &signal in &[SIGSEGV, SIGBUS] {
138            unsafe { sigaction(signal, ptr::null_mut(), &mut action) };
140            if action.sa_sigaction == SIG_DFL {
142                if !NEED_ALTSTACK.load(Ordering::Relaxed) {
143                    NEED_ALTSTACK.store(true, Ordering::Release);
145                    let handler = unsafe { make_handler(true) };
146                    MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed);
147                    mem::forget(handler);
148                }
149                action.sa_flags = SA_SIGINFO | SA_ONSTACK;
150                action.sa_sigaction = signal_handler as sighandler_t;
151                unsafe { sigaction(signal, &action, ptr::null_mut()) };
153            }
154        }
155    }
156
157    #[forbid(unsafe_op_in_unsafe_fn)]
160    pub unsafe fn cleanup() {
161        unsafe { drop_handler(MAIN_ALTSTACK.load(Ordering::Relaxed)) };
164    }
165
166    unsafe fn get_stack() -> libc::stack_t {
167        #[cfg(any(
171            target_os = "openbsd",
172            target_os = "netbsd",
173            target_os = "linux",
174            target_os = "dragonfly",
175        ))]
176        let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK;
177        #[cfg(not(any(
178            target_os = "openbsd",
179            target_os = "netbsd",
180            target_os = "linux",
181            target_os = "dragonfly",
182        )))]
183        let flags = MAP_PRIVATE | MAP_ANON;
184
185        let sigstack_size = sigstack_size();
186        let page_size = PAGE_SIZE.load(Ordering::Relaxed);
187
188        let stackp = mmap64(
189            ptr::null_mut(),
190            sigstack_size + page_size,
191            PROT_READ | PROT_WRITE,
192            flags,
193            -1,
194            0,
195        );
196        if stackp == MAP_FAILED {
197            panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error());
198        }
199        let guard_result = libc::mprotect(stackp, page_size, PROT_NONE);
200        if guard_result != 0 {
201            panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error());
202        }
203        let stackp = stackp.add(page_size);
204
205        libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size }
206    }
207
208    #[forbid(unsafe_op_in_unsafe_fn)]
211    pub unsafe fn make_handler(main_thread: bool) -> Handler {
212        if !NEED_ALTSTACK.load(Ordering::Acquire) {
213            return Handler::null();
214        }
215
216        if !main_thread {
217            let guard = unsafe { current_guard() }.unwrap_or(0..0);
219            GUARD.set((guard.start, guard.end));
220        }
221
222        let mut stack = unsafe { mem::zeroed() };
224        unsafe { sigaltstack(ptr::null(), &mut stack) };
226        if stack.ss_flags & SS_DISABLE != 0 {
228            unsafe {
230                stack = get_stack();
231                sigaltstack(&stack, ptr::null_mut());
232            }
233            Handler { data: stack.ss_sp as *mut libc::c_void }
234        } else {
235            Handler::null()
236        }
237    }
238
239    #[forbid(unsafe_op_in_unsafe_fn)]
245    pub unsafe fn drop_handler(data: *mut libc::c_void) {
246        if !data.is_null() {
247            let sigstack_size = sigstack_size();
248            let page_size = PAGE_SIZE.load(Ordering::Relaxed);
249            let disabling_stack = libc::stack_t {
250                ss_sp: ptr::null_mut(),
251                ss_flags: SS_DISABLE,
252                ss_size: sigstack_size,
257            };
258            unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) };
260            unsafe { munmap(data.sub(page_size), sigstack_size + page_size) };
263        }
264    }
265
266    #[cfg(any(target_os = "linux", target_os = "android"))]
268    fn sigstack_size() -> usize {
269        let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) };
270        libc::SIGSTKSZ.max(dynamic_sigstksz as _)
274    }
275
276    #[cfg(not(any(target_os = "linux", target_os = "android")))]
278    fn sigstack_size() -> usize {
279        libc::SIGSTKSZ
280    }
281
282    #[cfg(any(target_os = "solaris", target_os = "illumos"))]
283    unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
284        let mut current_stack: libc::stack_t = crate::mem::zeroed();
285        assert_eq!(libc::stack_getbounds(&mut current_stack), 0);
286        Some(current_stack.ss_sp)
287    }
288
289    #[cfg(target_os = "macos")]
290    unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
291        let th = libc::pthread_self();
292        let stackptr = libc::pthread_get_stackaddr_np(th);
293        Some(stackptr.map_addr(|addr| addr - libc::pthread_get_stacksize_np(th)))
294    }
295
296    #[cfg(target_os = "openbsd")]
297    unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
298        let mut current_stack: libc::stack_t = crate::mem::zeroed();
299        assert_eq!(libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack), 0);
300
301        let stack_ptr = current_stack.ss_sp;
302        let stackaddr = if libc::pthread_main_np() == 1 {
303            stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed)
305        } else {
306            stack_ptr.addr() - current_stack.ss_size
308        };
309        Some(stack_ptr.with_addr(stackaddr))
310    }
311
312    #[cfg(any(
313        target_os = "android",
314        target_os = "freebsd",
315        target_os = "netbsd",
316        target_os = "hurd",
317        target_os = "linux",
318        target_os = "l4re"
319    ))]
320    unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
321        let mut ret = None;
322        let mut attr: libc::pthread_attr_t = crate::mem::zeroed();
323        #[cfg(target_os = "freebsd")]
324        assert_eq!(libc::pthread_attr_init(&mut attr), 0);
325        #[cfg(target_os = "freebsd")]
326        let e = libc::pthread_attr_get_np(libc::pthread_self(), &mut attr);
327        #[cfg(not(target_os = "freebsd"))]
328        let e = libc::pthread_getattr_np(libc::pthread_self(), &mut attr);
329        if e == 0 {
330            let mut stackaddr = crate::ptr::null_mut();
331            let mut stacksize = 0;
332            assert_eq!(libc::pthread_attr_getstack(&attr, &mut stackaddr, &mut stacksize), 0);
333            ret = Some(stackaddr);
334        }
335        if e == 0 || cfg!(target_os = "freebsd") {
336            assert_eq!(libc::pthread_attr_destroy(&mut attr), 0);
337        }
338        ret
339    }
340
341    fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> {
342        let stackptr = unsafe { get_stack_start()? };
343        let stackaddr = stackptr.addr();
344
345        let remainder = stackaddr % page_size;
352        Some(if remainder == 0 {
353            stackptr
354        } else {
355            stackptr.with_addr(stackaddr + page_size - remainder)
356        })
357    }
358
359    #[forbid(unsafe_op_in_unsafe_fn)]
360    unsafe fn install_main_guard() -> Option<Range<usize>> {
361        let page_size = PAGE_SIZE.load(Ordering::Relaxed);
362
363        unsafe {
364            if cfg!(all(target_os = "linux", not(target_env = "musl"))) {
366                install_main_guard_linux(page_size)
367            } else if cfg!(all(target_os = "linux", target_env = "musl")) {
368                install_main_guard_linux_musl(page_size)
369            } else if cfg!(target_os = "freebsd") {
370                install_main_guard_freebsd(page_size)
371            } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) {
372                install_main_guard_bsds(page_size)
373            } else {
374                install_main_guard_default(page_size)
375            }
376        }
377    }
378
379    #[forbid(unsafe_op_in_unsafe_fn)]
380    unsafe fn install_main_guard_linux(page_size: usize) -> Option<Range<usize>> {
381        let stackptr = stack_start_aligned(page_size)?;
392        let stackaddr = stackptr.addr();
393        Some(stackaddr - page_size..stackaddr)
394    }
395
396    #[forbid(unsafe_op_in_unsafe_fn)]
397    unsafe fn install_main_guard_linux_musl(_page_size: usize) -> Option<Range<usize>> {
398        None
403    }
404
405    #[forbid(unsafe_op_in_unsafe_fn)]
406    unsafe fn install_main_guard_freebsd(page_size: usize) -> Option<Range<usize>> {
407        let stackptr = stack_start_aligned(page_size)?;
412        let guardaddr = stackptr.addr();
413        static PAGES: OnceLock<usize> = OnceLock::new();
418
419        let pages = PAGES.get_or_init(|| {
420            use crate::sys::weak::dlsym;
421            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);
422            let mut guard: usize = 0;
423            let mut size = mem::size_of_val(&guard);
424            let oid = c"security.bsd.stack_guard_page";
425            match sysctlbyname.get() {
426                Some(fcn) if unsafe {
427                    fcn(oid.as_ptr(),
428                        (&raw mut guard).cast(),
429                        &raw mut size,
430                        ptr::null_mut(),
431                        0) == 0
432                } => guard,
433                _ => 1,
434            }
435        });
436        Some(guardaddr..guardaddr + pages * page_size)
437    }
438
439    #[forbid(unsafe_op_in_unsafe_fn)]
440    unsafe fn install_main_guard_bsds(page_size: usize) -> Option<Range<usize>> {
441        let stackptr = stack_start_aligned(page_size)?;
449        let stackaddr = stackptr.addr();
450        Some(stackaddr - page_size..stackaddr)
451    }
452
453    #[forbid(unsafe_op_in_unsafe_fn)]
454    unsafe fn install_main_guard_default(page_size: usize) -> Option<Range<usize>> {
455        let stackptr = stack_start_aligned(page_size)?;
464        let result = unsafe {
465            mmap64(
466                stackptr,
467                page_size,
468                PROT_READ | PROT_WRITE,
469                MAP_PRIVATE | MAP_ANON | MAP_FIXED,
470                -1,
471                0,
472            )
473        };
474        if result != stackptr || result == MAP_FAILED {
475            panic!("failed to allocate a guard page: {}", io::Error::last_os_error());
476        }
477
478        let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) };
479        if result != 0 {
480            panic!("failed to protect the guard page: {}", io::Error::last_os_error());
481        }
482
483        let guardaddr = stackptr.addr();
484
485        Some(guardaddr..guardaddr + page_size)
486    }
487
488    #[cfg(any(
489        target_os = "macos",
490        target_os = "openbsd",
491        target_os = "solaris",
492        target_os = "illumos",
493    ))]
494    unsafe fn current_guard() -> Option<Range<usize>> {
496        let stackptr = get_stack_start()?;
497        let stackaddr = stackptr.addr();
498        Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr)
499    }
500
501    #[cfg(any(
502        target_os = "android",
503        target_os = "freebsd",
504        target_os = "hurd",
505        target_os = "linux",
506        target_os = "netbsd",
507        target_os = "l4re"
508    ))]
509    unsafe fn current_guard() -> Option<Range<usize>> {
511        let mut ret = None;
512        let mut attr: libc::pthread_attr_t = crate::mem::zeroed();
513        #[cfg(target_os = "freebsd")]
514        assert_eq!(libc::pthread_attr_init(&mut attr), 0);
515        #[cfg(target_os = "freebsd")]
516        let e = libc::pthread_attr_get_np(libc::pthread_self(), &mut attr);
517        #[cfg(not(target_os = "freebsd"))]
518        let e = libc::pthread_getattr_np(libc::pthread_self(), &mut attr);
519        if e == 0 {
520            let mut guardsize = 0;
521            assert_eq!(libc::pthread_attr_getguardsize(&attr, &mut guardsize), 0);
522            if guardsize == 0 {
523                if cfg!(all(target_os = "linux", target_env = "musl")) {
524                    guardsize = PAGE_SIZE.load(Ordering::Relaxed);
528                } else {
529                    panic!("there is no guard page");
530                }
531            }
532            let mut stackptr = crate::ptr::null_mut::<libc::c_void>();
533            let mut size = 0;
534            assert_eq!(libc::pthread_attr_getstack(&attr, &mut stackptr, &mut size), 0);
535
536            let stackaddr = stackptr.addr();
537            ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) {
538                Some(stackaddr - guardsize..stackaddr)
539            } else if cfg!(all(target_os = "linux", target_env = "musl")) {
540                Some(stackaddr - guardsize..stackaddr)
541            } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc")))
542            {
543                Some(stackaddr - guardsize..stackaddr + guardsize)
550            } else {
551                Some(stackaddr..stackaddr + guardsize)
552            };
553        }
554        if e == 0 || cfg!(target_os = "freebsd") {
555            assert_eq!(libc::pthread_attr_destroy(&mut attr), 0);
556        }
557        ret
558    }
559}
560
561#[cfg(not(any(
570    target_os = "linux",
571    target_os = "freebsd",
572    target_os = "hurd",
573    target_os = "macos",
574    target_os = "netbsd",
575    target_os = "openbsd",
576    target_os = "solaris",
577    target_os = "illumos",
578)))]
579mod imp {
580    pub unsafe fn init() {}
581
582    pub unsafe fn cleanup() {}
583
584    pub unsafe fn make_handler(_main_thread: bool) -> super::Handler {
585        super::Handler::null()
586    }
587
588    pub unsafe fn drop_handler(_data: *mut libc::c_void) {}
589}