From 17c70a9aacd54fefa76d6e349e6d050a9445c9a7 Mon Sep 17 00:00:00 2001 From: Jubilee Young Date: Tue, 16 Jul 2024 22:50:25 -0700 Subject: [PATCH 1/4] unix: split stack_overflow::install_main_guard by os --- .../std/src/sys/pal/unix/stack_overflow.rs | 205 ++++++++++-------- 1 file changed, 115 insertions(+), 90 deletions(-) diff --git a/library/std/src/sys/pal/unix/stack_overflow.rs b/library/std/src/sys/pal/unix/stack_overflow.rs index 2e5bd85327a19..431fb9652a74e 100644 --- a/library/std/src/sys/pal/unix/stack_overflow.rs +++ b/library/std/src/sys/pal/unix/stack_overflow.rs @@ -325,104 +325,129 @@ mod imp { }) } + #[forbid(unsafe_op_in_unsafe_fn)] unsafe fn install_main_guard() -> Option> { let page_size = PAGE_SIZE.load(Ordering::Relaxed); - if cfg!(all(target_os = "linux", not(target_env = "musl"))) { - // Linux doesn't allocate the whole stack right away, and - // the kernel has its own stack-guard mechanism to fault - // when growing too close to an existing mapping. If we map - // our own guard, then the kernel starts enforcing a rather - // large gap above that, rendering much of the possible - // stack space useless. See #43052. - // - // Instead, we'll just note where we expect rlimit to start - // faulting, so our handler can report "stack overflow", and - // trust that the kernel's own stack guard will work. - let stackptr = get_stack_start_aligned()?; - let stackaddr = stackptr.addr(); - Some(stackaddr - page_size..stackaddr) - } else if cfg!(all(target_os = "linux", target_env = "musl")) { - // For the main thread, the musl's pthread_attr_getstack - // returns the current stack size, rather than maximum size - // it can eventually grow to. It cannot be used to determine - // the position of kernel's stack guard. - None - } else if cfg!(target_os = "freebsd") { - // FreeBSD's stack autogrows, and optionally includes a guard page - // at the bottom. If we try to remap the bottom of the stack - // ourselves, FreeBSD's guard page moves upwards. So we'll just use - // the builtin guard page. - let stackptr = get_stack_start_aligned()?; - let guardaddr = stackptr.addr(); - // Technically the number of guard pages is tunable and controlled - // by the security.bsd.stack_guard_page sysctl. - // By default it is 1, checking once is enough since it is - // a boot time config value. - static PAGES: crate::sync::OnceLock = crate::sync::OnceLock::new(); - - let pages = PAGES.get_or_init(|| { - use crate::sys::weak::dlsym; - 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); - let mut guard: usize = 0; - let mut size = crate::mem::size_of_val(&guard); - let oid = crate::ffi::CStr::from_bytes_with_nul( - b"security.bsd.stack_guard_page\0", - ) - .unwrap(); - match sysctlbyname.get() { - Some(fcn) => { - 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 { - guard - } else { - 1 - } - }, - _ => 1, - } - }); - Some(guardaddr..guardaddr + pages * page_size) - } else if cfg!(any(target_os = "openbsd", target_os = "netbsd")) { - // OpenBSD stack already includes a guard page, and stack is - // immutable. - // NetBSD stack includes the guard page. - // - // We'll just note where we expect rlimit to start - // faulting, so our handler can report "stack overflow", and - // trust that the kernel's own stack guard will work. - let stackptr = get_stack_start_aligned()?; - let stackaddr = stackptr.addr(); - Some(stackaddr - page_size..stackaddr) - } else { - // Reallocate the last page of the stack. - // This ensures SIGBUS will be raised on - // stack overflow. - // Systems which enforce strict PAX MPROTECT do not allow - // to mprotect() a mapping with less restrictive permissions - // than the initial mmap() used, so we mmap() here with - // read/write permissions and only then mprotect() it to - // no permissions at all. See issue #50313. - let stackptr = get_stack_start_aligned()?; - let result = mmap64( - stackptr, - page_size, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON | MAP_FIXED, - -1, - 0, - ); - if result != stackptr || result == MAP_FAILED { - panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); + + unsafe { + // this way someone on any unix-y OS can check that all these compile + if cfg!(all(target_os = "linux", not(target_env = "musl"))) { + install_main_guard_linux(page_size) + } else if cfg!(all(target_os = "linux", target_env = "musl")) { + install_main_guard_linux_musl(page_size) + } else if cfg!(target_os = "freebsd") { + install_main_guard_freebsd(page_size) + } else if cfg!(any(target_os = "netbsd", target_os = "openbsd")) { + install_main_guard_bsds(page_size) + } else { + install_main_guard_default(page_size) } + } + } + + unsafe fn install_main_guard_linux(page_size: usize) -> Option> { + // Linux doesn't allocate the whole stack right away, and + // the kernel has its own stack-guard mechanism to fault + // when growing too close to an existing mapping. If we map + // our own guard, then the kernel starts enforcing a rather + // large gap above that, rendering much of the possible + // stack space useless. See #43052. + // + // Instead, we'll just note where we expect rlimit to start + // faulting, so our handler can report "stack overflow", and + // trust that the kernel's own stack guard will work. + let stackptr = get_stack_start_aligned()?; + let stackaddr = stackptr.addr(); + Some(stackaddr - page_size..stackaddr) + } - let result = mprotect(stackptr, page_size, PROT_NONE); - if result != 0 { - panic!("failed to protect the guard page: {}", io::Error::last_os_error()); + unsafe fn install_main_guard_linux_musl(_page_size: usize) -> Option> { + // For the main thread, the musl's pthread_attr_getstack + // returns the current stack size, rather than maximum size + // it can eventually grow to. It cannot be used to determine + // the position of kernel's stack guard. + None + } + + unsafe fn install_main_guard_freebsd(page_size: usize) -> Option> { + // FreeBSD's stack autogrows, and optionally includes a guard page + // at the bottom. If we try to remap the bottom of the stack + // ourselves, FreeBSD's guard page moves upwards. So we'll just use + // the builtin guard page. + let stackptr = get_stack_start_aligned()?; + let guardaddr = stackptr.addr(); + // Technically the number of guard pages is tunable and controlled + // by the security.bsd.stack_guard_page sysctl. + // By default it is 1, checking once is enough since it is + // a boot time config value. + static PAGES: crate::sync::OnceLock = crate::sync::OnceLock::new(); + + let pages = PAGES.get_or_init(|| { + use crate::sys::weak::dlsym; + 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); + let mut guard: usize = 0; + let mut size = crate::mem::size_of_val(&guard); + let oid = crate::ffi::CStr::from_bytes_with_nul( + b"security.bsd.stack_guard_page\0", + ) + .unwrap(); + match sysctlbyname.get() { + Some(fcn) => { + 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 { + guard + } else { + 1 + } + }, + _ => 1, } + }); + Some(guardaddr..guardaddr + pages * page_size) + } - let guardaddr = stackptr.addr(); + unsafe fn install_main_guard_bsds(page_size: usize) -> Option> { + // OpenBSD stack already includes a guard page, and stack is + // immutable. + // NetBSD stack includes the guard page. + // + // We'll just note where we expect rlimit to start + // faulting, so our handler can report "stack overflow", and + // trust that the kernel's own stack guard will work. + let stackptr = get_stack_start_aligned()?; + let stackaddr = stackptr.addr(); + Some(stackaddr - page_size..stackaddr) + } - Some(guardaddr..guardaddr + page_size) + unsafe fn install_main_guard_default(page_size: usize) -> Option> { + // Reallocate the last page of the stack. + // This ensures SIGBUS will be raised on + // stack overflow. + // Systems which enforce strict PAX MPROTECT do not allow + // to mprotect() a mapping with less restrictive permissions + // than the initial mmap() used, so we mmap() here with + // read/write permissions and only then mprotect() it to + // no permissions at all. See issue #50313. + let stackptr = get_stack_start_aligned()?; + let result = mmap64( + stackptr, + page_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, + -1, + 0, + ); + if result != stackptr || result == MAP_FAILED { + panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); + } + + let result = mprotect(stackptr, page_size, PROT_NONE); + if result != 0 { + panic!("failed to protect the guard page: {}", io::Error::last_os_error()); } + + let guardaddr = stackptr.addr(); + + Some(guardaddr..guardaddr + page_size) } #[cfg(any(target_os = "macos", target_os = "openbsd", target_os = "solaris"))] From e285c95cee3b5aaa01d731df8a7096e028460eb9 Mon Sep 17 00:00:00 2001 From: Jubilee Young Date: Tue, 16 Jul 2024 23:37:18 -0700 Subject: [PATCH 2/4] unix: stack_start_aligned is a safe fn This function is purely informative, answering where a stack starts. This is a safe operation, even if an answer requires unsafe code, and even if the result is some unsafe code decides to trust the answer. It also doesn't need to fetch the PAGE_SIZE when its caller just did so! Let's complicate its signature and in doing so simplify its operation. This allows sprinkling around #[forbid(unsafe_op_in_unsafe_fn)] --- library/std/src/sys/pal/unix/stack_overflow.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/library/std/src/sys/pal/unix/stack_overflow.rs b/library/std/src/sys/pal/unix/stack_overflow.rs index 431fb9652a74e..5916be54fe846 100644 --- a/library/std/src/sys/pal/unix/stack_overflow.rs +++ b/library/std/src/sys/pal/unix/stack_overflow.rs @@ -306,9 +306,8 @@ mod imp { ret } - unsafe fn get_stack_start_aligned() -> Option<*mut libc::c_void> { - let page_size = PAGE_SIZE.load(Ordering::Relaxed); - let stackptr = get_stack_start()?; + fn stack_start_aligned(page_size: usize) -> Option<*mut libc::c_void> { + let stackptr = unsafe { get_stack_start()? }; let stackaddr = stackptr.addr(); // Ensure stackaddr is page aligned! A parent process might @@ -345,6 +344,7 @@ mod imp { } } + #[forbid(unsafe_op_in_unsafe_fn)] unsafe fn install_main_guard_linux(page_size: usize) -> Option> { // Linux doesn't allocate the whole stack right away, and // the kernel has its own stack-guard mechanism to fault @@ -356,11 +356,12 @@ mod imp { // Instead, we'll just note where we expect rlimit to start // faulting, so our handler can report "stack overflow", and // trust that the kernel's own stack guard will work. - let stackptr = get_stack_start_aligned()?; + let stackptr = stack_start_aligned(page_size)?; let stackaddr = stackptr.addr(); Some(stackaddr - page_size..stackaddr) } + #[forbid(unsafe_op_in_unsafe_fn)] unsafe fn install_main_guard_linux_musl(_page_size: usize) -> Option> { // For the main thread, the musl's pthread_attr_getstack // returns the current stack size, rather than maximum size @@ -374,7 +375,7 @@ mod imp { // at the bottom. If we try to remap the bottom of the stack // ourselves, FreeBSD's guard page moves upwards. So we'll just use // the builtin guard page. - let stackptr = get_stack_start_aligned()?; + let stackptr = stack_start_aligned(page_size)?; let guardaddr = stackptr.addr(); // Technically the number of guard pages is tunable and controlled // by the security.bsd.stack_guard_page sysctl. @@ -405,6 +406,7 @@ mod imp { Some(guardaddr..guardaddr + pages * page_size) } + #[forbid(unsafe_op_in_unsafe_fn)] unsafe fn install_main_guard_bsds(page_size: usize) -> Option> { // OpenBSD stack already includes a guard page, and stack is // immutable. @@ -413,7 +415,7 @@ mod imp { // We'll just note where we expect rlimit to start // faulting, so our handler can report "stack overflow", and // trust that the kernel's own stack guard will work. - let stackptr = get_stack_start_aligned()?; + let stackptr = stack_start_aligned(page_size)?; let stackaddr = stackptr.addr(); Some(stackaddr - page_size..stackaddr) } @@ -427,7 +429,7 @@ mod imp { // than the initial mmap() used, so we mmap() here with // read/write permissions and only then mprotect() it to // no permissions at all. See issue #50313. - let stackptr = get_stack_start_aligned()?; + let stackptr = stack_start_aligned(page_size)?; let result = mmap64( stackptr, page_size, From 6ed563d49144f38b1066716833baf505646b274c Mon Sep 17 00:00:00 2001 From: Jubilee Young Date: Wed, 17 Jul 2024 00:06:00 -0700 Subject: [PATCH 3/4] unix: clean up install_main_guard_freebsd This just was a mess. --- .../std/src/sys/pal/unix/stack_overflow.rs | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/library/std/src/sys/pal/unix/stack_overflow.rs b/library/std/src/sys/pal/unix/stack_overflow.rs index 5916be54fe846..0e15049be9a1e 100644 --- a/library/std/src/sys/pal/unix/stack_overflow.rs +++ b/library/std/src/sys/pal/unix/stack_overflow.rs @@ -44,6 +44,7 @@ mod imp { use crate::ops::Range; use crate::ptr; use crate::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering}; + use crate::sync::OnceLock; use crate::sys::pal::unix::os; use crate::thread; @@ -370,6 +371,7 @@ mod imp { None } + #[forbid(unsafe_op_in_unsafe_fn)] unsafe fn install_main_guard_freebsd(page_size: usize) -> Option> { // FreeBSD's stack autogrows, and optionally includes a guard page // at the bottom. If we try to remap the bottom of the stack @@ -381,25 +383,22 @@ mod imp { // by the security.bsd.stack_guard_page sysctl. // By default it is 1, checking once is enough since it is // a boot time config value. - static PAGES: crate::sync::OnceLock = crate::sync::OnceLock::new(); + static PAGES: OnceLock = OnceLock::new(); let pages = PAGES.get_or_init(|| { use crate::sys::weak::dlsym; 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); let mut guard: usize = 0; - let mut size = crate::mem::size_of_val(&guard); - let oid = crate::ffi::CStr::from_bytes_with_nul( - b"security.bsd.stack_guard_page\0", - ) - .unwrap(); + let mut size = mem::size_of_val(&guard); + let oid = c"security.bsd.stack_guard_page"; match sysctlbyname.get() { - Some(fcn) => { - 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 { - guard - } else { - 1 - } - }, + Some(fcn) if unsafe { + fcn(oid.as_ptr(), + ptr::addr_of_mut!(guard).cast(), + ptr::addr_of_mut!(size), + ptr::null_mut(), + 0) == 0 + } => guard, _ => 1, } }); From d47cb26ddd6574de6c81caf9fdaadef1a108628a Mon Sep 17 00:00:00 2001 From: Jubilee Young Date: Wed, 17 Jul 2024 00:08:05 -0700 Subject: [PATCH 4/4] unix: unsafe-wrap install_main_guard_default --- .../std/src/sys/pal/unix/stack_overflow.rs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/library/std/src/sys/pal/unix/stack_overflow.rs b/library/std/src/sys/pal/unix/stack_overflow.rs index 0e15049be9a1e..0db08c1a926f5 100644 --- a/library/std/src/sys/pal/unix/stack_overflow.rs +++ b/library/std/src/sys/pal/unix/stack_overflow.rs @@ -419,6 +419,7 @@ mod imp { Some(stackaddr - page_size..stackaddr) } + #[forbid(unsafe_op_in_unsafe_fn)] unsafe fn install_main_guard_default(page_size: usize) -> Option> { // Reallocate the last page of the stack. // This ensures SIGBUS will be raised on @@ -429,19 +430,21 @@ mod imp { // read/write permissions and only then mprotect() it to // no permissions at all. See issue #50313. let stackptr = stack_start_aligned(page_size)?; - let result = mmap64( - stackptr, - page_size, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON | MAP_FIXED, - -1, - 0, - ); + let result = unsafe { + mmap64( + stackptr, + page_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, + -1, + 0, + ) + }; if result != stackptr || result == MAP_FAILED { panic!("failed to allocate a guard page: {}", io::Error::last_os_error()); } - let result = mprotect(stackptr, page_size, PROT_NONE); + let result = unsafe { mprotect(stackptr, page_size, PROT_NONE) }; if result != 0 { panic!("failed to protect the guard page: {}", io::Error::last_os_error()); }