std/sys/pal/unix/stack_overflow/handler_signal.rs
1use libc::{
2 MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, SA_ONSTACK,
3 SA_SIGINFO, SIG_DFL, SIGBUS, SIGSEGV, SS_DISABLE, sigaction, sigaltstack, sighandler_t,
4};
5#[cfg(not(all(target_os = "linux", target_env = "gnu")))]
6use libc::{mmap as mmap64, mprotect, munmap};
7#[cfg(all(target_os = "linux", target_env = "gnu"))]
8use libc::{mmap64, mprotect, munmap};
9
10use super::Handler;
11use super::thread_info::{delete_current_info, set_current_info, with_current_info};
12use crate::ops::Range;
13use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering};
14use crate::sys::pal::unix::conf;
15use crate::{io, mem, ptr};
16
17/// Signal handler for the SIGSEGV and SIGBUS handlers.
18///
19/// We've got guard pages (unmapped pages) at the end of every thread's
20/// stack, so if a thread ends up running into the guard page it'll trigger
21/// this handler. We want to detect these cases and print out a helpful error
22/// saying that the stack has overflowed. All other signals, however, should
23/// go back to what they were originally supposed to do.
24///
25/// This handler currently exists purely to print an informative message
26/// whenever a thread overflows its stack. We then abort to exit and
27/// indicate a crash, but to avoid a misleading SIGSEGV that might lead
28/// users to believe that unsafe code has accessed an invalid pointer; the
29/// SIGSEGV encountered when overflowing the stack is expected and
30/// well-defined.
31///
32/// If this is not a stack overflow, the handler un-registers itself and
33/// then returns (to allow the original signal to be delivered again).
34/// Returning from this kind of signal handler is technically not defined
35/// to work when reading the POSIX spec strictly, but in practice it turns
36/// out many large systems and all implementations allow returning from a
37/// signal handler to work. For a more detailed explanation see the
38/// comments on #26458.
39///
40/// # Safety
41/// Rust doesn't call this, it *gets called* by the kernel, which we expect
42/// to provide valid parameters. Apart from that, this function does not
43/// have any other preconditions.
44unsafe extern "C" fn signal_handler(
45 signum: libc::c_int,
46 info: *mut libc::siginfo_t,
47 _data: *mut libc::c_void,
48) {
49 // SAFETY: this pointer is provided by the system and will always point to a valid `siginfo_t`.
50 let fault_addr = unsafe { (*info).si_addr().addr() };
51
52 // `with_current_info` expects that the process aborts after it is
53 // called. If the signal was not caused by a memory access, this might
54 // not be true. We detect this by noticing that the `si_addr` field is
55 // zero if the signal is synthetic.
56 if fault_addr != 0 {
57 with_current_info(|thread_info| {
58 // If the faulting address is within the guard page, then we print a
59 // message saying so and abort.
60 if let Some(thread_info) = thread_info
61 && thread_info.guard_page_range.contains(&fault_addr)
62 {
63 // Hey you! Yes, you modifying the stack overflow message!
64 // Please make sure that all functions called here are
65 // actually async-signal-safe. If they're not, try retrieving
66 // the information beforehand and storing it in `ThreadInfo`.
67 // Thank you!
68 // - says Jonas after having had to watch his carefully
69 // written code get made unsound again.
70 let tid = thread_info.tid;
71 let name = thread_info.name.as_deref().unwrap_or("<unknown>");
72 rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n");
73 rtabort!("stack overflow");
74 }
75 })
76 }
77
78 // Unregister ourselves by reverting back to the default behavior.
79 // SAFETY: assuming all platforms define struct sigaction as "zero-initializable"
80 let mut action: sigaction = unsafe { mem::zeroed() };
81 action.sa_sigaction = SIG_DFL;
82 // SAFETY: pray this is a well-behaved POSIX implementation of fn sigaction
83 unsafe { sigaction(signum, &action, ptr::null_mut()) };
84
85 // See comment above for why this function returns.
86}
87
88static PAGE_SIZE: Atomic<usize> = AtomicUsize::new(0);
89// Store a pointer to the allocation for the main thread's altstack so that
90// tools like valgrind don't complain about a leaked unreachable allocation.
91//
92// If the main thread exits, the process will terminate so there's no use in
93// freeing resources. It also means that the altstack is still installed
94// while TLS destructors are run on the main thread (c.f. #111272).
95static MAIN_ALTSTACK: Atomic<*mut libc::c_void> = AtomicPtr::new(ptr::null_mut());
96static NEED_ALTSTACK: Atomic<bool> = AtomicBool::new(false);
97
98/// # Safety
99/// Must be called only once, on the main thread, during program startup.
100pub unsafe fn init() {
101 PAGE_SIZE.store(conf::page_size(), Ordering::Relaxed);
102
103 // SAFETY:
104 // This is only called on the main thread, and since it is still early
105 // in the programs lifetime there is (almost) certainly enough stack
106 // space left to install the guard page.
107 let mut guard_page_range = unsafe { install_main_guard() };
108
109 // Even for panic=immediate-abort, installing the guard pages is important for soundness.
110 // That said, we do not care about giving nice stackoverflow messages via our custom
111 // signal handler, just exit early and let the user enjoy the segfault.
112 if cfg!(panic = "immediate-abort") {
113 return;
114 }
115
116 // SAFETY: C structures are always zero-initializable.
117 let mut action: sigaction = unsafe { mem::zeroed() };
118 for &signal in &[SIGSEGV, SIGBUS] {
119 // SAFETY: just fetches the current signal handler into action
120 unsafe { sigaction(signal, ptr::null_mut(), &mut action) };
121 // We assume that overriding the signal handler is always safe,
122 // which might conflict with certain libraries that rely on a
123 // specific signal behaviour. To prevent problems, we only
124 // override the handler if it has not been set yet.
125 if action.sa_sigaction == SIG_DFL {
126 if !NEED_ALTSTACK.load(Ordering::Relaxed) {
127 // haven't set up our sigaltstack yet
128 NEED_ALTSTACK.store(true, Ordering::Release);
129 let handler = make_handler(true);
130 MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed);
131 mem::forget(handler);
132
133 if let Some(guard_page_range) = guard_page_range.take() {
134 set_current_info(guard_page_range);
135 }
136 }
137
138 action.sa_flags = SA_SIGINFO | SA_ONSTACK;
139 action.sa_sigaction = signal_handler
140 as unsafe extern "C" fn(i32, *mut libc::siginfo_t, *mut libc::c_void)
141 as sighandler_t;
142 // SAFETY:
143 // `&action` describes a valid `sigaction` and `signal_handler`
144 // is safe to use as a signal handler for `SIGSEGV` and `SIGBUS`.
145 unsafe { sigaction(signal, &action, ptr::null_mut()) };
146 }
147 }
148}
149
150fn get_stack() -> libc::stack_t {
151 // OpenBSD requires this flag for stack mapping
152 // otherwise the said mapping will fail as a no-op on most systems
153 // and has a different meaning on FreeBSD
154 #[cfg(any(
155 target_os = "openbsd",
156 target_os = "netbsd",
157 target_os = "linux",
158 target_os = "dragonfly",
159 ))]
160 let flags = MAP_PRIVATE | MAP_ANON | libc::MAP_STACK;
161 #[cfg(not(any(
162 target_os = "openbsd",
163 target_os = "netbsd",
164 target_os = "linux",
165 target_os = "dragonfly",
166 )))]
167 let flags = MAP_PRIVATE | MAP_ANON;
168
169 let sigstack_size = sigstack_size();
170 let page_size = PAGE_SIZE.load(Ordering::Relaxed);
171
172 // SAFETY: this does not unmap any existing pages.
173 let stackp = unsafe {
174 mmap64(ptr::null_mut(), sigstack_size + page_size, PROT_READ | PROT_WRITE, flags, -1, 0)
175 };
176 if stackp == MAP_FAILED {
177 panic!("failed to allocate an alternative stack: {}", io::Error::last_os_error());
178 }
179 // SAFETY: this only affects the memory we just allocated.
180 let guard_result = unsafe { libc::mprotect(stackp, page_size, PROT_NONE) };
181 if guard_result != 0 {
182 panic!("failed to set up alternative stack guard page: {}", io::Error::last_os_error());
183 }
184 // SAFETY:
185 // The region was allocated with a larger size than `page_size`, so this
186 // addition is within bounds.
187 let stackp = unsafe { stackp.add(page_size) };
188
189 libc::stack_t { ss_sp: stackp, ss_flags: 0, ss_size: sigstack_size }
190}
191
192pub fn make_handler(main_thread: bool) -> Handler {
193 if cfg!(panic = "immediate-abort") || !NEED_ALTSTACK.load(Ordering::Acquire) {
194 return Handler::null();
195 }
196
197 if !main_thread {
198 if let Some(guard_page_range) = current_guard() {
199 set_current_info(guard_page_range);
200 }
201 }
202
203 // Load the current alternate signal stack to see if we need to install
204 // our own.
205 //
206 // SAFETY: C structures are always zero-initializable.
207 let mut stack = unsafe { mem::zeroed() };
208 // SAFETY: `&mut stack` is valid for writing a `stack_t`.
209 unsafe { sigaltstack(ptr::null(), &mut stack) };
210
211 // Configure alternate signal stack, if one is not already set.
212 if stack.ss_flags & SS_DISABLE != 0 {
213 let stack = get_stack();
214 // SAFETY:
215 // `stack_t` is a freshly allocated stack that's not used anywhere
216 // else. It contains a guard page, so stack overflows in signal
217 // handlers will not cause undefined behaviour. We must make the
218 // fundamental runtime assumption that it is safe to install an
219 // alternate signal stack if there is none currently installed.
220 // This might conflict with foreign libraries that use the existence
221 // of an alternate signal stack as indication that certain runtime
222 // initialisation by the library has been performed (e.g. old
223 // versions of `std` assumed that certain thread-locals were already
224 // accessed and thus initialized in the thread if the stack overflow
225 // signal was successfully delivered). Such assumptions in other
226 // libraries are fundamentally flawed, so we pay no regard to them.
227 unsafe { sigaltstack(&stack, ptr::null_mut()) };
228 Handler { data: stack.ss_sp as *mut libc::c_void }
229 } else {
230 Handler::null()
231 }
232}
233
234/// # Safety
235/// Must only be called with a pointer returned by `make_handler`, and only
236/// once per `Handler`.
237pub unsafe fn drop_handler(data: *mut libc::c_void) {
238 if !data.is_null() {
239 let sigstack_size = sigstack_size();
240 let page_size = PAGE_SIZE.load(Ordering::Relaxed);
241 let disabling_stack = libc::stack_t {
242 ss_sp: ptr::null_mut(),
243 ss_flags: SS_DISABLE,
244 // Workaround for bug in macOS implementation of sigaltstack
245 // UNIX2003 which returns ENOMEM when disabling a stack while
246 // passing ss_size smaller than MINSIGSTKSZ. According to POSIX
247 // both ss_sp and ss_size should be ignored in this case.
248 ss_size: sigstack_size,
249 };
250 // SAFETY:
251 // We assume that disabling the alternate signal stack is always
252 // sound, even if the current alternate signal stack is not the one
253 // we installed in `make_handler`. Any stack overflows from this
254 // point on will abort the program when the kernel tries to write
255 // the signal information to the guard page.
256 //
257 // FIXME: detect if the stack has changed, and only uninstall if it hasn't.
258 unsafe { sigaltstack(&disabling_stack, ptr::null_mut()) };
259 // The stack returned by `get_stack` is part of a mapping that
260 // started one page earlier, so walk back a page and unmap from
261 // there.
262 //
263 // SAFETY:
264 // This allocation was created by us in `get_stack` and, as the
265 // alternate signal stack is now disabled, is no longer in use.
266 unsafe { munmap(data.sub(page_size), sigstack_size + page_size) };
267 }
268
269 delete_current_info();
270}
271
272/// Modern kernels on modern hardware can have dynamic signal stack sizes.
273#[cfg(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc")))]
274fn sigstack_size() -> usize {
275 // SAFETY: `getauxval` is always safe to call.
276 let dynamic_sigstksz = unsafe { libc::getauxval(libc::AT_MINSIGSTKSZ) };
277 // If getauxval couldn't find the entry, it returns 0,
278 // so take the higher of the "constant" and auxval.
279 // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ
280 libc::SIGSTKSZ.max(dynamic_sigstksz as _)
281}
282
283/// Not all OS support hardware where this is needed.
284#[cfg(not(all(any(target_os = "linux", target_os = "android"), not(target_env = "uclibc"))))]
285fn sigstack_size() -> usize {
286 libc::SIGSTKSZ
287}
288
289#[cfg(any(target_os = "solaris", target_os = "illumos"))]
290fn get_stack_start() -> Option<*mut libc::c_void> {
291 // SAFETY: C types are always zero-initializable.
292 let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() };
293 // SAFETY:
294 // The pointer is valid for writing a `stack_t`.
295 assert_eq!(unsafe { libc::stack_getbounds(&mut current_stack) }, 0);
296 Some(current_stack.ss_sp)
297}
298
299#[cfg(target_os = "macos")]
300fn get_stack_start() -> Option<*mut libc::c_void> {
301 // SAFETY: always safe to call.
302 let th = unsafe { libc::pthread_self() };
303 // SAFETY: `th` is a valid `pthread_t`.
304 unsafe {
305 let stackptr = libc::pthread_get_stackaddr_np(th);
306 let stacksize = libc::pthread_get_stacksize_np(th);
307 Some(stackptr.map_addr(|addr| addr - stacksize))
308 }
309}
310
311#[cfg(target_os = "openbsd")]
312fn get_stack_start() -> Option<*mut libc::c_void> {
313 // SAFETY: C types are always zero-initializable.
314 let mut current_stack: libc::stack_t = unsafe { crate::mem::zeroed() };
315 // SAFETY:
316 // * calling `pthread_self` is always valid and returns a valid `pthread_t`.
317 // * `&mut current_stack` is coerced to a pointer that is valid for writing
318 // a `stack_t`.
319 assert_eq!(unsafe { libc::pthread_stackseg_np(libc::pthread_self(), &mut current_stack) }, 0);
320
321 let stack_ptr = current_stack.ss_sp;
322 // SAFETY: this is always safe to call.
323 let stackaddr = if unsafe { libc::pthread_main_np() } == 1 {
324 // main thread
325 stack_ptr.addr() - current_stack.ss_size + PAGE_SIZE.load(Ordering::Relaxed)
326 } else {
327 // new thread
328 stack_ptr.addr() - current_stack.ss_size
329 };
330 Some(stack_ptr.with_addr(stackaddr))
331}
332
333#[cfg(any(
334 target_os = "android",
335 target_os = "freebsd",
336 target_os = "netbsd",
337 target_os = "hurd",
338 target_os = "linux",
339 target_os = "l4re"
340))]
341fn get_stack_start() -> Option<*mut libc::c_void> {
342 use crate::pin::pin;
343 use crate::sys::helpers::COpaque;
344
345 let mut ret = None;
346 let mut attr: COpaque<libc::pthread_attr_t> = COpaque::uninit();
347 if !cfg!(target_os = "freebsd") {
348 attr = COpaque::zeroed();
349 }
350 let attr = pin!(attr);
351 // FIXME(pin-ergonomics): remove the next line.
352 let attr = attr.into_ref();
353
354 // SAFETY:
355 // The attributes object has not been initialized yet and will not be
356 // moved until destroyed.
357 #[cfg(target_os = "freebsd")]
358 assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0);
359 // SAFETY:
360 // * calling `pthread_self` is always valid and returns a valid `pthread_t`
361 // * `attr` is an initialized attribute object that can be written to.
362 #[cfg(target_os = "freebsd")]
363 let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) };
364 // SAFETY:
365 // * calling `pthread_self` is always valid and returns a valid `pthread_t`
366 // * `attr` can be written to, and will be initialized by this call.
367 #[cfg(not(target_os = "freebsd"))]
368 let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) };
369 if e == 0 {
370 let mut stackaddr = crate::ptr::null_mut();
371 let mut stacksize = 0;
372 // SAFETY:
373 // `attr` is an initialized attribute object and both the pointers
374 // are valid for writing.
375 assert_eq!(
376 unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackaddr, &mut stacksize) },
377 0
378 );
379 ret = Some(stackaddr);
380 }
381 if e == 0 || cfg!(target_os = "freebsd") {
382 // SAFETY:
383 // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or
384 // by `pthread_attr_get_np`, and is not used after this point.
385 assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0);
386 }
387 ret
388}
389
390fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> {
391 let stackptr = get_stack_start()?;
392 let stackaddr = stackptr.addr();
393
394 // Ensure stackaddr is page aligned! A parent process might
395 // have reset RLIMIT_STACK to be non-page aligned. The
396 // pthread_attr_getstack() reports the usable stack area
397 // stackaddr < stackaddr + stacksize, so if stackaddr is not
398 // page-aligned, calculate the fix such that stackaddr <
399 // new_page_aligned_stackaddr < stackaddr + stacksize
400 let remainder = stackaddr % page_size;
401 Some(if remainder == 0 {
402 stackptr
403 } else {
404 stackptr.with_addr(stackaddr + page_size - remainder)
405 })
406}
407
408/// # Safety
409/// This function must only be called from the main thread, and there must
410/// be sufficient stack space remaining to place a stack guard.
411unsafe fn install_main_guard() -> Option<Range<usize>> {
412 let page_size = PAGE_SIZE.load(Ordering::Relaxed);
413
414 // this way someone on any unix-y OS can check that all these compile
415 if cfg!(all(target_os = "linux", not(target_env = "musl"))) {
416 install_main_guard_linux(page_size)
417 } else if cfg!(all(target_os = "linux", target_env = "musl")) {
418 install_main_guard_linux_musl(page_size)
419 } else if cfg!(target_os = "freebsd") {
420 #[cfg(not(target_os = "freebsd"))]
421 return None;
422 // The FreeBSD code cannot be checked on non-BSDs.
423 #[cfg(target_os = "freebsd")]
424 install_main_guard_freebsd(page_size)
425 } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) {
426 install_main_guard_bsds(page_size)
427 } else {
428 // SAFETY: guaranteed by caller.
429 unsafe { install_main_guard_default(page_size) }
430 }
431}
432
433fn install_main_guard_linux(page_size: usize) -> Option<Range<usize>> {
434 // See the corresponding conditional in init().
435 // Avoid stack_start_aligned, which makes slow syscalls to read /proc/self/maps
436 if cfg!(panic = "immediate-abort") {
437 return None;
438 }
439 // Linux doesn't allocate the whole stack right away, and
440 // the kernel has its own stack-guard mechanism to fault
441 // when growing too close to an existing mapping. If we map
442 // our own guard, then the kernel starts enforcing a rather
443 // large gap above that, rendering much of the possible
444 // stack space useless. See #43052.
445 //
446 // Instead, we'll just note where we expect rlimit to start
447 // faulting, so our handler can report "stack overflow", and
448 // trust that the kernel's own stack guard will work.
449 let stackptr = stack_start_aligned(page_size)?;
450 let stackaddr = stackptr.addr();
451 Some(stackaddr - page_size..stackaddr)
452}
453
454fn install_main_guard_linux_musl(_page_size: usize) -> Option<Range<usize>> {
455 // For the main thread, the musl's pthread_attr_getstack
456 // returns the current stack size, rather than maximum size
457 // it can eventually grow to. It cannot be used to determine
458 // the position of kernel's stack guard.
459 None
460}
461
462#[cfg(target_os = "freebsd")]
463fn install_main_guard_freebsd(page_size: usize) -> Option<Range<usize>> {
464 // See the corresponding conditional in install_main_guard_linux().
465 if cfg!(panic = "immediate-abort") {
466 return None;
467 }
468 // FreeBSD's stack autogrows, and optionally includes a guard page
469 // at the bottom. If we try to remap the bottom of the stack
470 // ourselves, FreeBSD's guard page moves upwards. So we'll just use
471 // the builtin guard page.
472 let stackptr = stack_start_aligned(page_size)?;
473 let guardaddr = stackptr.addr();
474 // Technically the number of guard pages is tunable and controlled
475 // by the security.bsd.stack_guard_page sysctl.
476 // By default it is 1, checking once is enough since it is
477 // a boot time config value.
478 // FIXME(joboet): this function is only called once, remove the caching.
479 static PAGES: crate::sync::OnceLock<usize> = crate::sync::OnceLock::new();
480
481 let pages = PAGES.get_or_init(|| {
482 let mut guard: usize = 0;
483 let mut size = size_of_val(&guard);
484 let oid = c"security.bsd.stack_guard_page";
485
486 let r = unsafe {
487 libc::sysctlbyname(
488 oid.as_ptr(),
489 (&raw mut guard).cast(),
490 &raw mut size,
491 ptr::null_mut(),
492 0,
493 )
494 };
495 if r == 0 { guard } else { 1 }
496 });
497 Some(guardaddr..guardaddr + pages * page_size)
498}
499
500fn install_main_guard_bsds(page_size: usize) -> Option<Range<usize>> {
501 // See the corresponding conditional in install_main_guard_linux().
502 if cfg!(panic = "immediate-abort") {
503 return None;
504 }
505 // OpenBSD stack already includes a guard page, and stack is
506 // immutable.
507 // NetBSD stack includes the guard page.
508 //
509 // We'll just note where we expect rlimit to start
510 // faulting, so our handler can report "stack overflow", and
511 // trust that the kernel's own stack guard will work.
512 let stackptr = stack_start_aligned(page_size)?;
513 let stackaddr = stackptr.addr();
514 Some(stackaddr - page_size..stackaddr)
515}
516
517/// # Safety
518/// This function must only be called from the main thread, and there must
519/// be sufficient stack space remaining to place a stack guard.
520unsafe fn install_main_guard_default(page_size: usize) -> Option<Range<usize>> {
521 // Reallocate the last page of the stack.
522 // This ensures SIGBUS will be raised on
523 // stack overflow.
524 // Systems which enforce strict PAX MPROTECT do not allow
525 // to mprotect() a mapping with less restrictive permissions
526 // than the initial mmap() used, so we mmap() here with
527 // read/write permissions and only then mprotect() it to
528 // no permissions at all. See issue #50313.
529 let stackptr = stack_start_aligned(page_size)?;
530 // SAFETY:
531 // The memory region from `stackptr..stackptr + page_size` belongs to
532 // the current thread's stack, and the caller has asserted that there
533 // is sufficient stack space, which means that this will not overwrite
534 // any existing allocations.
535 let result = unsafe {
536 mmap64(
537 stackptr,
538 page_size,
539 PROT_READ | PROT_WRITE,
540 MAP_PRIVATE | MAP_ANON | MAP_FIXED,
541 -1,
542 0,
543 )
544 };
545 if result != stackptr || result == MAP_FAILED {
546 panic!("failed to allocate a guard page: {}", io::Error::last_os_error());
547 }
548
549 // SAFETY:
550 // Since this function is only called on the main thread, the stack will
551 // not be reused until program exit, so the runtime will never observe
552 // that part of the stack has been made unusable in this way.
553 let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) };
554 if result != 0 {
555 panic!("failed to protect the guard page: {}", io::Error::last_os_error());
556 }
557
558 let guardaddr = stackptr.addr();
559
560 Some(guardaddr..guardaddr + page_size)
561}
562
563#[cfg(any(
564 target_os = "macos",
565 target_os = "openbsd",
566 target_os = "solaris",
567 target_os = "illumos",
568))]
569fn current_guard() -> Option<Range<usize>> {
570 let stackptr = get_stack_start()?;
571 let stackaddr = stackptr.addr();
572 Some(stackaddr - PAGE_SIZE.load(Ordering::Relaxed)..stackaddr)
573}
574
575#[cfg(any(
576 target_os = "android",
577 target_os = "freebsd",
578 target_os = "hurd",
579 target_os = "linux",
580 target_os = "netbsd",
581 target_os = "l4re"
582))]
583fn current_guard() -> Option<Range<usize>> {
584 use crate::pin::pin;
585 use crate::sys::helpers::COpaque;
586
587 let mut ret = None;
588
589 let mut attr: COpaque<libc::pthread_attr_t> = COpaque::uninit();
590 if !cfg!(target_os = "freebsd") {
591 attr = COpaque::zeroed();
592 }
593 let attr = pin!(attr);
594 // FIXME(pin-ergonomics): remove the next line.
595 let attr = attr.into_ref();
596
597 // SAFETY:
598 // The attributes object has not been initialized yet and will not be
599 // moved until destroyed.
600 #[cfg(target_os = "freebsd")]
601 assert_eq!(unsafe { libc::pthread_attr_init(attr.get()) }, 0);
602 // SAFETY:
603 // * calling `pthread_self` is always valid and returns a valid `pthread_t`
604 // * `attr` is an initialized attribute object that can be written to.
605 #[cfg(target_os = "freebsd")]
606 let e = unsafe { libc::pthread_attr_get_np(libc::pthread_self(), attr.get()) };
607 // SAFETY:
608 // * calling `pthread_self` is always valid and returns a valid `pthread_t`
609 // * `attr` can be written to, and will be initialized by this call.
610 #[cfg(not(target_os = "freebsd"))]
611 let e = unsafe { libc::pthread_getattr_np(libc::pthread_self(), attr.get()) };
612 if e == 0 {
613 let mut guardsize = 0;
614 // SAFETY:
615 // `attr` is an initialized attribute object and the pointer is valid
616 // for writing.
617 assert_eq!(unsafe { libc::pthread_attr_getguardsize(attr.get(), &mut guardsize) }, 0);
618 if guardsize == 0 {
619 if cfg!(all(target_os = "linux", target_env = "musl")) {
620 // musl versions before 1.1.19 always reported guard
621 // size obtained from pthread_attr_get_np as zero.
622 // Use page size as a fallback.
623 guardsize = PAGE_SIZE.load(Ordering::Relaxed);
624 } else {
625 panic!("there is no guard page");
626 }
627 }
628 let mut stackptr = crate::ptr::null_mut::<libc::c_void>();
629 let mut size = 0;
630 // SAFETY:
631 // `attr` is an initialized attribute object and both the pointers
632 // are valid for writing.
633 assert_eq!(unsafe { libc::pthread_attr_getstack(attr.get(), &mut stackptr, &mut size) }, 0);
634
635 let stackaddr = stackptr.addr();
636 ret = if cfg!(any(target_os = "freebsd", target_os = "netbsd", target_os = "hurd")) {
637 Some(stackaddr - guardsize..stackaddr)
638 } else if cfg!(all(target_os = "linux", target_env = "musl")) {
639 Some(stackaddr - guardsize..stackaddr)
640 } else if cfg!(all(target_os = "linux", any(target_env = "gnu", target_env = "uclibc"))) {
641 // glibc used to include the guard area within the stack, as noted in the BUGS
642 // section of `man pthread_attr_getguardsize`. This has been corrected starting
643 // with glibc 2.27, and in some distro backports, so the guard is now placed at the
644 // end (below) the stack. There's no easy way for us to know which we have at
645 // runtime, so we'll just match any fault in the range right above or below the
646 // stack base to call that fault a stack overflow.
647 Some(stackaddr - guardsize..stackaddr + guardsize)
648 } else {
649 Some(stackaddr..stackaddr + guardsize)
650 };
651 }
652 if e == 0 || cfg!(target_os = "freebsd") {
653 // SAFETY:
654 // `attr` was initialized either by `pthread_attr_init` (FreeBSD) or
655 // by `pthread_attr_get_np`, and is not used after this point.
656 assert_eq!(unsafe { libc::pthread_attr_destroy(attr.get()) }, 0);
657 }
658 ret
659}