Skip to content

Commit 21e5551

Browse files
authored
Unrolled build for rust-lang#127845
Rollup merge of rust-lang#127845 - workingjubilee:actually-break-up-big-ass-stack-overflow-fn, r=joboet unix: break `stack_overflow::install_main_guard` into smaller fn This was one big deeply-indented function for no reason. This made it hard to reason about the boundaries of its safety. Or just, y'know, read. Simplify it by splitting it into platform-specific functions, but which are still asked to keep compiling (a desirable property, since all of these OS use a similar API). This is mostly a whitespace change, so I suggest reviewing it only after setting Files changed -> (the options gear) -> [x] Hide whitespace as that will make it easier to see how the code was actually broken up instead of raw line diffs.
2 parents e35364a + d47cb26 commit 21e5551

File tree

1 file changed

+119
-90
lines changed

1 file changed

+119
-90
lines changed

library/std/src/sys/pal/unix/stack_overflow.rs

+119-90
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ mod imp {
4444
use crate::ops::Range;
4545
use crate::ptr;
4646
use crate::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
47+
use crate::sync::OnceLock;
4748
use crate::sys::pal::unix::os;
4849
use crate::thread;
4950

@@ -306,9 +307,8 @@ mod imp {
306307
ret
307308
}
308309

309-
unsafe fn get_stack_start_aligned() -> Option<*mut libc::c_void> {
310-
let page_size = PAGE_SIZE.load(Ordering::Relaxed);
311-
let stackptr = get_stack_start()?;
310+
fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> {
311+
let stackptr = unsafe { get_stack_start()? };
312312
let stackaddr = stackptr.addr();
313313

314314
// Ensure stackaddr is page aligned! A parent process might
@@ -325,104 +325,133 @@ mod imp {
325325
})
326326
}
327327

328+
#[forbid(unsafe_op_in_unsafe_fn)]
328329
unsafe fn install_main_guard() -> Option<Range<usize>> {
329330
let page_size = PAGE_SIZE.load(Ordering::Relaxed);
330-
if cfg!(all(target_os = "linux", not(target_env = "musl"))) {
331-
// Linux doesn't allocate the whole stack right away, and
332-
// the kernel has its own stack-guard mechanism to fault
333-
// when growing too close to an existing mapping. If we map
334-
// our own guard, then the kernel starts enforcing a rather
335-
// large gap above that, rendering much of the possible
336-
// stack space useless. See #43052.
337-
//
338-
// Instead, we'll just note where we expect rlimit to start
339-
// faulting, so our handler can report "stack overflow", and
340-
// trust that the kernel's own stack guard will work.
341-
let stackptr = get_stack_start_aligned()?;
342-
let stackaddr = stackptr.addr();
343-
Some(stackaddr - page_size..stackaddr)
344-
} else if cfg!(all(target_os = "linux", target_env = "musl")) {
345-
// For the main thread, the musl's pthread_attr_getstack
346-
// returns the current stack size, rather than maximum size
347-
// it can eventually grow to. It cannot be used to determine
348-
// the position of kernel's stack guard.
349-
None
350-
} else if cfg!(target_os = "freebsd") {
351-
// FreeBSD's stack autogrows, and optionally includes a guard page
352-
// at the bottom. If we try to remap the bottom of the stack
353-
// ourselves, FreeBSD's guard page moves upwards. So we'll just use
354-
// the builtin guard page.
355-
let stackptr = get_stack_start_aligned()?;
356-
let guardaddr = stackptr.addr();
357-
// Technically the number of guard pages is tunable and controlled
358-
// by the security.bsd.stack_guard_page sysctl.
359-
// By default it is 1, checking once is enough since it is
360-
// a boot time config value.
361-
static PAGES: crate::sync::OnceLock<usize> = crate::sync::OnceLock::new();
362-
363-
let pages = PAGES.get_or_init(|| {
364-
use crate::sys::weak::dlsym;
365-
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);
366-
let mut guard: usize = 0;
367-
let mut size = crate::mem::size_of_val(&guard);
368-
let oid = crate::ffi::CStr::from_bytes_with_nul(
369-
b"security.bsd.stack_guard_page\0",
370-
)
371-
.unwrap();
372-
match sysctlbyname.get() {
373-
Some(fcn) => {
374-
if fcn(oid.as_ptr(), core::ptr::addr_of_mut!(guard) as *mut _, core::ptr::addr_of_mut!(size) as *mut _, crate::ptr::null_mut(), 0) == 0 {
375-
guard
376-
} else {
377-
1
378-
}
379-
},
380-
_ => 1,
381-
}
382-
});
383-
Some(guardaddr..guardaddr + pages * page_size)
384-
} else if cfg!(any(target_os = "openbsd", target_os = "netbsd")) {
385-
// OpenBSD stack already includes a guard page, and stack is
386-
// immutable.
387-
// NetBSD stack includes the guard page.
388-
//
389-
// We'll just note where we expect rlimit to start
390-
// faulting, so our handler can report "stack overflow", and
391-
// trust that the kernel's own stack guard will work.
392-
let stackptr = get_stack_start_aligned()?;
393-
let stackaddr = stackptr.addr();
394-
Some(stackaddr - page_size..stackaddr)
395-
} else {
396-
// Reallocate the last page of the stack.
397-
// This ensures SIGBUS will be raised on
398-
// stack overflow.
399-
// Systems which enforce strict PAX MPROTECT do not allow
400-
// to mprotect() a mapping with less restrictive permissions
401-
// than the initial mmap() used, so we mmap() here with
402-
// read/write permissions and only then mprotect() it to
403-
// no permissions at all. See issue #50313.
404-
let stackptr = get_stack_start_aligned()?;
405-
let result = mmap64(
331+
332+
unsafe {
333+
// this way someone on any unix-y OS can check that all these compile
334+
if cfg!(all(target_os = "linux", not(target_env = "musl"))) {
335+
install_main_guard_linux(page_size)
336+
} else if cfg!(all(target_os = "linux", target_env = "musl")) {
337+
install_main_guard_linux_musl(page_size)
338+
} else if cfg!(target_os = "freebsd") {
339+
install_main_guard_freebsd(page_size)
340+
} else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) {
341+
install_main_guard_bsds(page_size)
342+
} else {
343+
install_main_guard_default(page_size)
344+
}
345+
}
346+
}
347+
348+
#[forbid(unsafe_op_in_unsafe_fn)]
349+
unsafe fn install_main_guard_linux(page_size: usize) -> Option<Range<usize>> {
350+
// Linux doesn't allocate the whole stack right away, and
351+
// the kernel has its own stack-guard mechanism to fault
352+
// when growing too close to an existing mapping. If we map
353+
// our own guard, then the kernel starts enforcing a rather
354+
// large gap above that, rendering much of the possible
355+
// stack space useless. See #43052.
356+
//
357+
// Instead, we'll just note where we expect rlimit to start
358+
// faulting, so our handler can report "stack overflow", and
359+
// trust that the kernel's own stack guard will work.
360+
let stackptr = stack_start_aligned(page_size)?;
361+
let stackaddr = stackptr.addr();
362+
Some(stackaddr - page_size..stackaddr)
363+
}
364+
365+
#[forbid(unsafe_op_in_unsafe_fn)]
366+
unsafe fn install_main_guard_linux_musl(_page_size: usize) -> Option<Range<usize>> {
367+
// For the main thread, the musl's pthread_attr_getstack
368+
// returns the current stack size, rather than maximum size
369+
// it can eventually grow to. It cannot be used to determine
370+
// the position of kernel's stack guard.
371+
None
372+
}
373+
374+
#[forbid(unsafe_op_in_unsafe_fn)]
375+
unsafe fn install_main_guard_freebsd(page_size: usize) -> Option<Range<usize>> {
376+
// FreeBSD's stack autogrows, and optionally includes a guard page
377+
// at the bottom. If we try to remap the bottom of the stack
378+
// ourselves, FreeBSD's guard page moves upwards. So we'll just use
379+
// the builtin guard page.
380+
let stackptr = stack_start_aligned(page_size)?;
381+
let guardaddr = stackptr.addr();
382+
// Technically the number of guard pages is tunable and controlled
383+
// by the security.bsd.stack_guard_page sysctl.
384+
// By default it is 1, checking once is enough since it is
385+
// a boot time config value.
386+
static PAGES: OnceLock<usize> = OnceLock::new();
387+
388+
let pages = PAGES.get_or_init(|| {
389+
use crate::sys::weak::dlsym;
390+
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);
391+
let mut guard: usize = 0;
392+
let mut size = mem::size_of_val(&guard);
393+
let oid = c"security.bsd.stack_guard_page";
394+
match sysctlbyname.get() {
395+
Some(fcn) if unsafe {
396+
fcn(oid.as_ptr(),
397+
ptr::addr_of_mut!(guard).cast(),
398+
ptr::addr_of_mut!(size),
399+
ptr::null_mut(),
400+
0) == 0
401+
} => guard,
402+
_ => 1,
403+
}
404+
});
405+
Some(guardaddr..guardaddr + pages * page_size)
406+
}
407+
408+
#[forbid(unsafe_op_in_unsafe_fn)]
409+
unsafe fn install_main_guard_bsds(page_size: usize) -> Option<Range<usize>> {
410+
// OpenBSD stack already includes a guard page, and stack is
411+
// immutable.
412+
// NetBSD stack includes the guard page.
413+
//
414+
// We'll just note where we expect rlimit to start
415+
// faulting, so our handler can report "stack overflow", and
416+
// trust that the kernel's own stack guard will work.
417+
let stackptr = stack_start_aligned(page_size)?;
418+
let stackaddr = stackptr.addr();
419+
Some(stackaddr - page_size..stackaddr)
420+
}
421+
422+
#[forbid(unsafe_op_in_unsafe_fn)]
423+
unsafe fn install_main_guard_default(page_size: usize) -> Option<Range<usize>> {
424+
// Reallocate the last page of the stack.
425+
// This ensures SIGBUS will be raised on
426+
// stack overflow.
427+
// Systems which enforce strict PAX MPROTECT do not allow
428+
// to mprotect() a mapping with less restrictive permissions
429+
// than the initial mmap() used, so we mmap() here with
430+
// read/write permissions and only then mprotect() it to
431+
// no permissions at all. See issue #50313.
432+
let stackptr = stack_start_aligned(page_size)?;
433+
let result = unsafe {
434+
mmap64(
406435
stackptr,
407436
page_size,
408437
PROT_READ | PROT_WRITE,
409438
MAP_PRIVATE | MAP_ANON | MAP_FIXED,
410439
-1,
411440
0,
412-
);
413-
if result != stackptr || result == MAP_FAILED {
414-
panic!("failed to allocate a guard page: {}", io::Error::last_os_error());
415-
}
441+
)
442+
};
443+
if result != stackptr || result == MAP_FAILED {
444+
panic!("failed to allocate a guard page: {}", io::Error::last_os_error());
445+
}
416446

417-
let result = mprotect(stackptr, page_size, PROT_NONE);
418-
if result != 0 {
419-
panic!("failed to protect the guard page: {}", io::Error::last_os_error());
420-
}
447+
let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) };
448+
if result != 0 {
449+
panic!("failed to protect the guard page: {}", io::Error::last_os_error());
450+
}
421451

422-
let guardaddr = stackptr.addr();
452+
let guardaddr = stackptr.addr();
423453

424-
Some(guardaddr..guardaddr + page_size)
425-
}
454+
Some(guardaddr..guardaddr + page_size)
426455
}
427456

428457
#[cfg(any(target_os = "macos", target_os = "openbsd", target_os = "solaris"))]

0 commit comments

Comments
 (0)