未验证 提交 8b8c6ee7 编写于 作者: K kennytm 提交者: GitHub

Rollup merge of #47912 - cuviper:glibc-stack-guard, r=alexcrichton

Use a range to identify SIGSEGV in stack guards

Previously, the `guard::init()` and `guard::current()` functions were
returning a `usize` address representing the top of the stack guard,
respectively for the main thread and for spawned threads.  The `SIGSEGV`
handler on `unix` targets checked if a fault was within one page below that
address, if so reporting it as a stack overflow.

Now `unix` targets report a `Range<usize>` representing the guard memory,
so it can cover arbitrary guard sizes.  Non-`unix` targets which always
return `None` for guards now do so with `Option<!>`, so they don't pay any
overhead.

For `linux-gnu` in particular, the previous guard upper-bound was
`stackaddr + guardsize`, as the protected memory was *inside* the stack.
This was a glibc bug, and starting from 2.27 they are moving the guard
*past* the end of the stack.  However, there's no simple way for us to know
where the guard page actually lies, so now we declare it as the whole range
of `stackaddr ± guardsize`, and any fault therein will be called a stack
overflow.  This fixes #47863.
......@@ -111,10 +111,11 @@ fn drop(&mut self) {
#[cfg_attr(test, allow(dead_code))]
pub mod guard {
pub unsafe fn current() -> Option<usize> {
pub type Guard = !;
pub unsafe fn current() -> Option<Guard> {
None
}
pub unsafe fn init() -> Option<usize> {
pub unsafe fn init() -> Option<Guard> {
None
}
}
......
......@@ -88,6 +88,7 @@ pub fn into_id(self) -> usize {
}
pub mod guard {
pub unsafe fn current() -> Option<usize> { None }
pub unsafe fn init() -> Option<usize> { None }
pub type Guard = !;
pub unsafe fn current() -> Option<Guard> { None }
pub unsafe fn init() -> Option<Guard> { None }
}
......@@ -57,9 +57,6 @@ mod imp {
use sys_common::thread_info;
// This is initialized in init() and only read from after
static mut PAGE_SIZE: usize = 0;
#[cfg(any(target_os = "linux", target_os = "android"))]
unsafe fn siginfo_si_addr(info: *mut libc::siginfo_t) -> usize {
#[repr(C)]
......@@ -102,12 +99,12 @@ unsafe fn siginfo_si_addr(info: *mut libc::siginfo_t) -> usize {
_data: *mut libc::c_void) {
use sys_common::util::report_overflow;
let guard = thread_info::stack_guard().unwrap_or(0);
let guard = thread_info::stack_guard().unwrap_or(0..0);
let addr = siginfo_si_addr(info);
// If the faulting address is within the guard page, then we print a
// message saying so and abort.
if guard != 0 && guard - PAGE_SIZE <= addr && addr < guard {
if guard.start <= addr && addr < guard.end {
report_overflow();
rtabort!("stack overflow");
} else {
......@@ -123,8 +120,6 @@ unsafe fn siginfo_si_addr(info: *mut libc::siginfo_t) -> usize {
static mut MAIN_ALTSTACK: *mut libc::c_void = ptr::null_mut();
pub unsafe fn init() {
PAGE_SIZE = ::sys::os::page_size();
let mut action: sigaction = mem::zeroed();
action.sa_flags = SA_SIGINFO | SA_ONSTACK;
action.sa_sigaction = signal_handler as sighandler_t;
......
......@@ -205,8 +205,10 @@ fn drop(&mut self) {
not(target_os = "solaris")))]
#[cfg_attr(test, allow(dead_code))]
pub mod guard {
pub unsafe fn current() -> Option<usize> { None }
pub unsafe fn init() -> Option<usize> { None }
use ops::Range;
pub type Guard = Range<usize>;
pub unsafe fn current() -> Option<Guard> { None }
pub unsafe fn init() -> Option<Guard> { None }
}
......@@ -222,14 +224,43 @@ pub mod guard {
use libc;
use libc::mmap;
use libc::{PROT_NONE, MAP_PRIVATE, MAP_ANON, MAP_FAILED, MAP_FIXED};
use ops::Range;
use sys::os;
#[cfg(any(target_os = "macos",
target_os = "bitrig",
target_os = "openbsd",
target_os = "solaris"))]
// This is initialized in init() and only read from after
static mut PAGE_SIZE: usize = 0;
pub type Guard = Range<usize>;
#[cfg(target_os = "solaris")]
unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
let mut current_stack: libc::stack_t = ::mem::zeroed();
assert_eq!(libc::stack_getbounds(&mut current_stack), 0);
Some(current_stack.ss_sp)
}
#[cfg(target_os = "macos")]
unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
current().map(|s| s as *mut libc::c_void)
let stackaddr = libc::pthread_get_stackaddr_np(libc::pthread_self()) as usize -
libc::pthread_get_stacksize_np(libc::pthread_self());
Some(stackaddr as *mut libc::c_void)
}
#[cfg(any(target_os = "openbsd", target_os = "bitrig"))]
unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
let mut current_stack: libc::stack_t = ::mem::zeroed();
assert_eq!(libc::pthread_stackseg_np(libc::pthread_self(),
&mut current_stack), 0);
let extra = if cfg!(target_os = "bitrig") {3} else {1} * PAGE_SIZE;
let stackaddr = if libc::pthread_main_np() == 1 {
// main thread
current_stack.ss_sp as usize - current_stack.ss_size + extra
} else {
// new thread
current_stack.ss_sp as usize - current_stack.ss_size
};
Some(stackaddr as *mut libc::c_void)
}
#[cfg(any(target_os = "android", target_os = "freebsd",
......@@ -253,8 +284,9 @@ unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
ret
}
pub unsafe fn init() -> Option<usize> {
let psize = os::page_size();
pub unsafe fn init() -> Option<Guard> {
PAGE_SIZE = os::page_size();
let mut stackaddr = get_stack_start()?;
// Ensure stackaddr is page aligned! A parent process might
......@@ -263,9 +295,9 @@ pub unsafe fn init() -> Option<usize> {
// stackaddr < stackaddr + stacksize, so if stackaddr is not
// page-aligned, calculate the fix such that stackaddr <
// new_page_aligned_stackaddr < stackaddr + stacksize
let remainder = (stackaddr as usize) % psize;
let remainder = (stackaddr as usize) % PAGE_SIZE;
if remainder != 0 {
stackaddr = ((stackaddr as usize) + psize - remainder)
stackaddr = ((stackaddr as usize) + PAGE_SIZE - remainder)
as *mut libc::c_void;
}
......@@ -280,60 +312,42 @@ pub unsafe fn init() -> Option<usize> {
// 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.
Some(stackaddr as usize)
let stackaddr = stackaddr as usize;
Some(stackaddr - PAGE_SIZE..stackaddr)
} else {
// Reallocate the last page of the stack.
// This ensures SIGBUS will be raised on
// stack overflow.
let result = mmap(stackaddr, psize, PROT_NONE,
let result = mmap(stackaddr, PAGE_SIZE, PROT_NONE,
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
if result != stackaddr || result == MAP_FAILED {
panic!("failed to allocate a guard page");
}
let guardaddr = stackaddr as usize;
let offset = if cfg!(target_os = "freebsd") {
2
} else {
1
};
Some(stackaddr as usize + offset * psize)
Some(guardaddr..guardaddr + offset * PAGE_SIZE)
}
}
#[cfg(target_os = "solaris")]
pub unsafe fn current() -> Option<usize> {
let mut current_stack: libc::stack_t = ::mem::zeroed();
assert_eq!(libc::stack_getbounds(&mut current_stack), 0);
Some(current_stack.ss_sp as usize)
}
#[cfg(target_os = "macos")]
pub unsafe fn current() -> Option<usize> {
Some(libc::pthread_get_stackaddr_np(libc::pthread_self()) as usize -
libc::pthread_get_stacksize_np(libc::pthread_self()))
}
#[cfg(any(target_os = "openbsd", target_os = "bitrig"))]
pub unsafe fn current() -> Option<usize> {
let mut current_stack: libc::stack_t = ::mem::zeroed();
assert_eq!(libc::pthread_stackseg_np(libc::pthread_self(),
&mut current_stack), 0);
let extra = if cfg!(target_os = "bitrig") {3} else {1} * os::page_size();
Some(if libc::pthread_main_np() == 1 {
// main thread
current_stack.ss_sp as usize - current_stack.ss_size + extra
} else {
// new thread
current_stack.ss_sp as usize - current_stack.ss_size
})
#[cfg(any(target_os = "macos",
target_os = "bitrig",
target_os = "openbsd",
target_os = "solaris"))]
pub unsafe fn current() -> Option<Guard> {
let stackaddr = get_stack_start()? as usize;
Some(stackaddr - PAGE_SIZE..stackaddr)
}
#[cfg(any(target_os = "android", target_os = "freebsd",
target_os = "linux", target_os = "netbsd", target_os = "l4re"))]
pub unsafe fn current() -> Option<usize> {
pub unsafe fn current() -> Option<Guard> {
let mut ret = None;
let mut attr: libc::pthread_attr_t = ::mem::zeroed();
assert_eq!(libc::pthread_attr_init(&mut attr), 0);
......@@ -352,12 +366,23 @@ pub unsafe fn current() -> Option<usize> {
assert_eq!(libc::pthread_attr_getstack(&attr, &mut stackaddr,
&mut size), 0);
let stackaddr = stackaddr as usize;
ret = if cfg!(target_os = "freebsd") {
Some(stackaddr as usize - guardsize)
// FIXME does freebsd really fault *below* the guard addr?
let guardaddr = stackaddr - guardsize;
Some(guardaddr - PAGE_SIZE..guardaddr)
} else if cfg!(target_os = "netbsd") {
Some(stackaddr as usize)
Some(stackaddr - guardsize..stackaddr)
} else if cfg!(all(target_os = "linux", target_env = "gnu")) {
// glibc used to include the guard area within the stack, as noted in the BUGS
// section of `man pthread_attr_getguardsize`. This has been corrected starting
// with glibc 2.27, and in some distro backports, so the guard is now placed at the
// end (below) the stack. There's no easy way for us to know which we have at
// runtime, so we'll just match any fault in the range right above or below the
// stack base to call that fault a stack overflow.
Some(stackaddr - guardsize..stackaddr + guardsize)
} else {
Some(stackaddr as usize + guardsize)
Some(stackaddr..stackaddr + guardsize)
};
}
assert_eq!(libc::pthread_attr_destroy(&mut attr), 0);
......
......@@ -43,6 +43,7 @@ pub fn join(self) {
}
pub mod guard {
pub unsafe fn current() -> Option<usize> { None }
pub unsafe fn init() -> Option<usize> { None }
pub type Guard = !;
pub unsafe fn current() -> Option<Guard> { None }
pub unsafe fn init() -> Option<Guard> { None }
}
......@@ -93,6 +93,7 @@ pub fn into_handle(self) -> Handle { self.handle }
#[cfg_attr(test, allow(dead_code))]
pub mod guard {
pub unsafe fn current() -> Option<usize> { None }
pub unsafe fn init() -> Option<usize> { None }
pub type Guard = !;
pub unsafe fn current() -> Option<Guard> { None }
pub unsafe fn init() -> Option<Guard> { None }
}
......@@ -11,10 +11,11 @@
#![allow(dead_code)] // stack_guard isn't used right now on all platforms
use cell::RefCell;
use sys::thread::guard::Guard;
use thread::Thread;
struct ThreadInfo {
stack_guard: Option<usize>,
stack_guard: Option<Guard>,
thread: Thread,
}
......@@ -38,11 +39,11 @@ pub fn current_thread() -> Option<Thread> {
ThreadInfo::with(|info| info.thread.clone())
}
pub fn stack_guard() -> Option<usize> {
ThreadInfo::with(|info| info.stack_guard).and_then(|o| o)
pub fn stack_guard() -> Option<Guard> {
ThreadInfo::with(|info| info.stack_guard.clone()).and_then(|o| o)
}
pub fn set(stack_guard: Option<usize>, thread: Thread) {
pub fn set(stack_guard: Option<Guard>, thread: Thread) {
THREAD_INFO.with(|c| assert!(c.borrow().is_none()));
THREAD_INFO.with(move |c| *c.borrow_mut() = Some(ThreadInfo{
stack_guard,
......
......@@ -15,7 +15,7 @@
// ignore-wasm
// ignore-emscripten
// ignore-windows
// no-system-llvm
// min-system-llvm-version 5.0
// compile-flags: -C no-prepopulate-passes
#![crate_type = "lib"]
......
......@@ -15,7 +15,7 @@
// ignore-emscripten no processes
// ignore-musl FIXME #31506
// ignore-pretty
// no-system-llvm
// min-system-llvm-version 5.0
// compile-flags: -C lto
// no-prefer-dynamic
......
......@@ -14,7 +14,7 @@
// ignore-cloudabi no processes
// ignore-emscripten no processes
// ignore-musl FIXME #31506
// no-system-llvm
// min-system-llvm-version 5.0
use std::mem;
use std::process::Command;
......
......@@ -167,7 +167,7 @@ fn ignore_llvm(config: &Config, line: &str) -> bool {
.expect("Malformed llvm version directive");
// Ignore if using system LLVM and actual version
// is smaller the minimum required version
!(config.system_llvm && &actual_version[..] < min_version)
config.system_llvm && &actual_version[..] < min_version
} else {
false
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册