Increase the size of the sigaltstack. (#1315)
* Increase the size of the sigaltstack. Rust's stack overflow handler installs a sigaltstack stack with size SIGSTKSZ, which is too small for some of the things we do in signal handlers, and as of this writing lacks a guard page. Install bigger sigaltstack stacks so that we have enough space, and have a guard page.
This commit is contained in:
@@ -50,6 +50,9 @@ impl Trap {
|
|||||||
wasmtime_runtime::Trap::Wasm { desc, backtrace } => {
|
wasmtime_runtime::Trap::Wasm { desc, backtrace } => {
|
||||||
Trap::new_with_trace(desc.to_string(), backtrace)
|
Trap::new_with_trace(desc.to_string(), backtrace)
|
||||||
}
|
}
|
||||||
|
wasmtime_runtime::Trap::OOM { backtrace } => {
|
||||||
|
Trap::new_with_trace("out of memory".to_string(), backtrace)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -252,16 +252,6 @@ pub fn init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn real_init() {
|
fn real_init() {
|
||||||
// This is a really weird and unfortunate function call. For all the gory
|
|
||||||
// details see #829, but the tl;dr; is that in a trap handler we have 2
|
|
||||||
// pages of stack space on Linux, and calling into libunwind which triggers
|
|
||||||
// the dynamic loader blows the stack.
|
|
||||||
//
|
|
||||||
// This is a dumb hack to work around this system-specific issue by
|
|
||||||
// capturing a backtrace once in the lifetime of a process to ensure that
|
|
||||||
// when we capture a backtrace in the trap handler all caches are primed,
|
|
||||||
// aka the dynamic loader has resolved all the relevant symbols.
|
|
||||||
drop(backtrace::Backtrace::new_unresolved());
|
|
||||||
unsafe {
|
unsafe {
|
||||||
platform_init();
|
platform_init();
|
||||||
}
|
}
|
||||||
@@ -337,6 +327,12 @@ pub enum Trap {
|
|||||||
/// Native stack backtrace at the time the trap occurred
|
/// Native stack backtrace at the time the trap occurred
|
||||||
backtrace: Backtrace,
|
backtrace: Backtrace,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// A trap indicating that the runtime was unable to allocate sufficient memory.
|
||||||
|
OOM {
|
||||||
|
/// Native stack backtrace at the time the OOM occurred
|
||||||
|
backtrace: Backtrace,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Trap {
|
impl fmt::Display for Trap {
|
||||||
@@ -344,6 +340,7 @@ impl fmt::Display for Trap {
|
|||||||
match self {
|
match self {
|
||||||
Trap::User(user) => user.fmt(f),
|
Trap::User(user) => user.fmt(f),
|
||||||
Trap::Wasm { desc, .. } => desc.fmt(f),
|
Trap::Wasm { desc, .. } => desc.fmt(f),
|
||||||
|
Trap::OOM { .. } => write!(f, "Out of memory"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -362,6 +359,14 @@ impl Trap {
|
|||||||
let backtrace = Backtrace::new();
|
let backtrace = Backtrace::new();
|
||||||
Trap::Wasm { desc, backtrace }
|
Trap::Wasm { desc, backtrace }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construct a new OOM trap with the given source location and trap code.
|
||||||
|
///
|
||||||
|
/// Internally saves a backtrace when constructed.
|
||||||
|
pub fn oom() -> Self {
|
||||||
|
let backtrace = Backtrace::new();
|
||||||
|
Trap::OOM { backtrace }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Catches any wasm traps that happen within the execution of `closure`,
|
/// Catches any wasm traps that happen within the execution of `closure`,
|
||||||
@@ -372,6 +377,10 @@ pub unsafe fn catch_traps<F>(vmctx: *mut VMContext, mut closure: F) -> Result<()
|
|||||||
where
|
where
|
||||||
F: FnMut(),
|
F: FnMut(),
|
||||||
{
|
{
|
||||||
|
// Ensure that we have our sigaltstack installed.
|
||||||
|
#[cfg(unix)]
|
||||||
|
setup_unix_sigaltstack()?;
|
||||||
|
|
||||||
return CallThreadState::new(vmctx).with(|cx| {
|
return CallThreadState::new(vmctx).with(|cx| {
|
||||||
RegisterSetjmp(
|
RegisterSetjmp(
|
||||||
cx.jmp_buf.as_ptr(),
|
cx.jmp_buf.as_ptr(),
|
||||||
@@ -593,3 +602,112 @@ mod tls {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A module for registering a custom alternate signal stack (sigaltstack).
|
||||||
|
///
|
||||||
|
/// Rust's libstd installs an alternate stack with size `SIGSTKSZ`, which is not
|
||||||
|
/// always large enough for our signal handling code. Override it by creating
|
||||||
|
/// and registering our own alternate stack that is large enough and has a guard
|
||||||
|
/// page.
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn setup_unix_sigaltstack() -> Result<(), Trap> {
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::ptr::null_mut;
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
/// Thread-local state is lazy-initialized on the first time it's used,
|
||||||
|
/// and dropped when the thread exits.
|
||||||
|
static TLS: RefCell<Tls> = RefCell::new(Tls::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The size of the sigaltstack (not including the guard, which will be
|
||||||
|
/// added). Make this large enough to run our signal handlers.
|
||||||
|
const MIN_STACK_SIZE: usize = 16 * 4096;
|
||||||
|
|
||||||
|
enum Tls {
|
||||||
|
None,
|
||||||
|
Allocated {
|
||||||
|
mmap_ptr: *mut libc::c_void,
|
||||||
|
mmap_size: usize,
|
||||||
|
},
|
||||||
|
BigEnough,
|
||||||
|
}
|
||||||
|
|
||||||
|
return TLS.with(|slot| unsafe {
|
||||||
|
let mut slot = slot.borrow_mut();
|
||||||
|
match *slot {
|
||||||
|
Tls::None => {}
|
||||||
|
// already checked
|
||||||
|
_ => return Ok(()),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see if the existing sigaltstack, if it exists, is big
|
||||||
|
// enough. If so we don't need to allocate our own.
|
||||||
|
let mut old_stack = mem::zeroed();
|
||||||
|
let r = libc::sigaltstack(ptr::null(), &mut old_stack);
|
||||||
|
assert_eq!(r, 0, "learning about sigaltstack failed");
|
||||||
|
if old_stack.ss_flags & libc::SS_DISABLE == 0 && old_stack.ss_size >= MIN_STACK_SIZE {
|
||||||
|
*slot = Tls::BigEnough;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... but failing that we need to allocate our own, so do all that
|
||||||
|
// here.
|
||||||
|
let page_size: usize = libc::sysconf(libc::_SC_PAGESIZE).try_into().unwrap();
|
||||||
|
let guard_size = page_size;
|
||||||
|
let alloc_size = guard_size + MIN_STACK_SIZE;
|
||||||
|
|
||||||
|
let ptr = libc::mmap(
|
||||||
|
null_mut(),
|
||||||
|
alloc_size,
|
||||||
|
libc::PROT_NONE,
|
||||||
|
libc::MAP_PRIVATE | libc::MAP_ANON,
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
if ptr == libc::MAP_FAILED {
|
||||||
|
return Err(Trap::oom());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the stack with readable/writable memory and then register it
|
||||||
|
// with `sigaltstack`.
|
||||||
|
let stack_ptr = (ptr as usize + guard_size) as *mut libc::c_void;
|
||||||
|
let r = libc::mprotect(
|
||||||
|
stack_ptr,
|
||||||
|
MIN_STACK_SIZE,
|
||||||
|
libc::PROT_READ | libc::PROT_WRITE,
|
||||||
|
);
|
||||||
|
assert_eq!(r, 0, "mprotect to configure memory for sigaltstack failed");
|
||||||
|
let new_stack = libc::stack_t {
|
||||||
|
ss_sp: stack_ptr,
|
||||||
|
ss_flags: 0,
|
||||||
|
ss_size: MIN_STACK_SIZE,
|
||||||
|
};
|
||||||
|
let r = libc::sigaltstack(&new_stack, ptr::null_mut());
|
||||||
|
assert_eq!(r, 0, "registering new sigaltstack failed");
|
||||||
|
|
||||||
|
*slot = Tls::Allocated {
|
||||||
|
mmap_ptr: ptr,
|
||||||
|
mmap_size: alloc_size,
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
impl Drop for Tls {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let (ptr, size) = match self {
|
||||||
|
Tls::Allocated {
|
||||||
|
mmap_ptr,
|
||||||
|
mmap_size,
|
||||||
|
} => (*mmap_ptr, *mmap_size),
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
// Deallocate the stack memory.
|
||||||
|
let r = libc::munmap(ptr, size);
|
||||||
|
debug_assert_eq!(r, 0, "munmap failed during thread shutdown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user