Skip to content

Commit

Permalink
Run Wasm code on a separate stack
Browse files Browse the repository at this point in the history
This uses the [corosensei](https://crates.io/crates/corosensei) crate to
run Wasm code on a separate stack from the main thread stack.

In trap handlers for stack overflows and memory out of bounds accesses,
we can now check whether we are executing on the Wasm stack and reset
execution back to the main thread stack when returning from the trap
handler.

When Wasm code needs to perform an operation which may modify internal
data structures (e.g. growing a memory) then execution must switch back
to the main thread stack using on_host_stack. This is necessary to avoid
leaving internal data structure in an inconsistent state when a stack
overflow happens.

In the future, this can also be used to suspend execution of a Wasm
module (#1127) by modeling it as an async function call.

Fixes #2757
Fixes #2562
  • Loading branch information
Amanieu authored and ptitSeb committed Oct 20, 2022
1 parent 47ac725 commit c50bed3
Show file tree
Hide file tree
Showing 14 changed files with 733 additions and 904 deletions.
280 changes: 139 additions & 141 deletions Cargo.lock

Large diffs are not rendered by default.

73 changes: 40 additions & 33 deletions lib/api/src/sys/externals/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use std::fmt;
use std::sync::Arc;
use wasmer_engine::{Export, ExportFunction, ExportFunctionMetadata};
use wasmer_vm::{
raise_user_trap, resume_panic, wasmer_call_trampoline, ImportInitializerFuncPtr,
on_host_stack, raise_user_trap, resume_panic, wasmer_call_trampoline, ImportInitializerFuncPtr,
VMCallerCheckedAnyfunc, VMDynamicFunctionContext, VMFuncRef, VMFunction, VMFunctionBody,
VMFunctionEnvironment, VMFunctionKind, VMTrampoline,
};
Expand Down Expand Up @@ -803,32 +803,34 @@ impl<T: VMDynamicFunction> VMDynamicFunctionCall<T> for VMDynamicFunctionContext
values_vec: *mut i128,
) {
use std::panic::{self, AssertUnwindSafe};
let result = panic::catch_unwind(AssertUnwindSafe(|| {
let func_ty = self.ctx.function_type();
let mut args = Vec::with_capacity(func_ty.params().len());
let store = self.ctx.store();
for (i, ty) in func_ty.params().iter().enumerate() {
args.push(Val::read_value_from(store, values_vec.add(i), *ty));
}
let returns = self.ctx.call(&args)?;

// We need to dynamically check that the returns
// match the expected types, as well as expected length.
let return_types = returns.iter().map(|ret| ret.ty()).collect::<Vec<_>>();
if return_types != func_ty.results() {
return Err(RuntimeError::new(format!(
"Dynamic function returned wrong signature. Expected {:?} but got {:?}",
func_ty.results(),
return_types
)));
}
for (i, ret) in returns.iter().enumerate() {
ret.write_value_to(values_vec.add(i));
}
Ok(())
})); // We get extern ref drops at the end of this block that we don't need.
// By preventing extern ref incs in the code above we can save the work of
// incrementing and decrementing. However the logic as-is is correct.
let result = on_host_stack(|| {
panic::catch_unwind(AssertUnwindSafe(|| {
let func_ty = self.ctx.function_type();
let mut args = Vec::with_capacity(func_ty.params().len());
let store = self.ctx.store();
for (i, ty) in func_ty.params().iter().enumerate() {
args.push(Val::read_value_from(store, values_vec.add(i), *ty));
}
let returns = self.ctx.call(&args)?;

// We need to dynamically check that the returns
// match the expected types, as well as expected length.
let return_types = returns.iter().map(|ret| ret.ty()).collect::<Vec<_>>();
if return_types != func_ty.results() {
return Err(RuntimeError::new(format!(
"Dynamic function returned wrong signature. Expected {:?} but got {:?}",
func_ty.results(),
return_types
)));
}
for (i, ret) in returns.iter().enumerate() {
ret.write_value_to(values_vec.add(i));
}
Ok(())
})) // We get extern ref drops at the end of this block that we don't need.
// By preventing extern ref incs in the code above we can save the work of
// incrementing and decrementing. However the logic as-is is correct.
});

match result {
Ok(Ok(())) => {}
Expand All @@ -846,6 +848,7 @@ mod inner {
use std::error::Error;
use std::marker::PhantomData;
use std::panic::{self, AssertUnwindSafe};
use wasmer_vm::on_host_stack;

#[cfg(feature = "experimental-reference-types-extern-ref")]
pub use wasmer_types::{ExternRef, VMExternRef};
Expand Down Expand Up @@ -1309,9 +1312,11 @@ mod inner {
Func: Fn( $( $x ),* ) -> RetsAsResult + 'static
{
let func: &Func = unsafe { &*(&() as *const () as *const Func) };
let result = panic::catch_unwind(AssertUnwindSafe(|| {
func( $( FromToNativeWasmType::from_native($x) ),* ).into_result()
}));
let result = on_host_stack(|| {
panic::catch_unwind(AssertUnwindSafe(|| {
func( $( FromToNativeWasmType::from_native($x) ),* ).into_result()
}))
});

match result {
Ok(Ok(result)) => return result.into_c_struct(),
Expand Down Expand Up @@ -1353,9 +1358,11 @@ mod inner {
{
let func: &Func = unsafe { &*(&() as *const () as *const Func) };

let result = panic::catch_unwind(AssertUnwindSafe(|| {
func(env, $( FromToNativeWasmType::from_native($x) ),* ).into_result()
}));
let result = on_host_stack(|| {
panic::catch_unwind(AssertUnwindSafe(|| {
func(env, $( FromToNativeWasmType::from_native($x) ),* ).into_result()
}))
});

match result {
Ok(Ok(result)) => return result.into_c_struct(),
Expand Down
10 changes: 2 additions & 8 deletions lib/api/src/sys/store.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use crate::sys::tunables::BaseTunables;
use loupe::MemoryUsage;
use std::any::Any;
use std::fmt;
use std::sync::{Arc, RwLock};
#[cfg(all(feature = "compiler", feature = "engine"))]
use wasmer_compiler::CompilerConfig;
use wasmer_engine::{is_wasm_pc, Engine, Tunables};
use wasmer_engine::{Engine, Tunables};
use wasmer_vm::{init_traps, TrapHandler, TrapHandlerFn};

/// The store represents all global state that can be manipulated by
Expand Down Expand Up @@ -48,7 +47,7 @@ impl Store {
{
// Make sure the signal handlers are installed.
// This is required for handling traps.
init_traps(is_wasm_pc);
init_traps();

Self {
engine: engine.cloned(),
Expand Down Expand Up @@ -82,11 +81,6 @@ impl PartialEq for Store {
}

unsafe impl TrapHandler for Store {
#[inline]
fn as_any(&self) -> &dyn Any {
self
}

fn custom_trap_handler(&self, call: &dyn Fn(&TrapHandlerFn) -> bool) -> bool {
if let Some(handler) = *&self.trap_handler.read().unwrap().as_ref() {
call(handler)
Expand Down
2 changes: 1 addition & 1 deletion lib/engine/src/artifact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ pub trait Artifact: Send + Sync + Upcastable + MemoryUsage {
/// See [`InstanceHandle::finish_instantiation`].
unsafe fn finish_instantiation(
&self,
trap_handler: &dyn TrapHandler,
trap_handler: &(dyn TrapHandler + 'static),
handle: &InstanceHandle,
) -> Result<(), InstantiationError> {
let data_initializers = self
Expand Down
8 changes: 0 additions & 8 deletions lib/engine/src/trap/frame_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,6 @@ pub struct GlobalFrameInfo {
ranges: BTreeMap<usize, ModuleInfoFrameInfo>,
}

/// Returns whether the `pc`, according to globally registered information,
/// is a wasm trap or not.
pub fn is_wasm_pc(pc: usize) -> bool {
let frame_info = FRAME_INFO.read().unwrap();
let module_info = frame_info.module_info(pc);
module_info.is_some()
}

/// An RAII structure used to unregister a module's frame information when the
/// module is destroyed.
#[derive(MemoryUsage)]
Expand Down
4 changes: 2 additions & 2 deletions lib/engine/src/trap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ mod error;
mod frame_info;
pub use error::RuntimeError;
pub use frame_info::{
is_wasm_pc, register as register_frame_info, FrameInfo, FunctionExtent,
GlobalFrameInfoRegistration, FRAME_INFO,
register as register_frame_info, FrameInfo, FunctionExtent, GlobalFrameInfoRegistration,
FRAME_INFO,
};
2 changes: 2 additions & 0 deletions lib/vm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ serde = { version = "1.0", features = ["derive", "rc"] }
rkyv = { version = "0.7.20", optional = true }
loupe = { version = "0.1", features = ["enable-indexmap"] }
enum-iterator = "0.7.0"
corosensei = { version = "0.1.1" }
scopeguard = "1.1.0"

[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3", features = ["winbase", "memoryapi", "errhandlingapi"] }
Expand Down
19 changes: 0 additions & 19 deletions lib/vm/build.rs

This file was deleted.

7 changes: 5 additions & 2 deletions lib/vm/src/instance/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,10 @@ impl Instance {
}

/// Invoke the WebAssembly start function of the instance, if one is present.
fn invoke_start_function(&self, trap_handler: &dyn TrapHandler) -> Result<(), Trap> {
fn invoke_start_function(
&self,
trap_handler: &(dyn TrapHandler + 'static),
) -> Result<(), Trap> {
let start_index = match self.module.start_function {
Some(idx) => idx,
None => return Ok(()),
Expand Down Expand Up @@ -1015,7 +1018,7 @@ impl InstanceHandle {
/// Only safe to call immediately after instantiation.
pub unsafe fn finish_instantiation(
&self,
trap_handler: &dyn TrapHandler,
trap_handler: &(dyn TrapHandler + 'static),
data_initializers: &[DataInitializer<'_>],
) -> Result<(), Trap> {
let instance = self.instance().as_ref();
Expand Down
92 changes: 52 additions & 40 deletions lib/vm/src/libcalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ use crate::probestack::PROBESTACK;
use crate::table::{RawTableElement, TableElement};
use crate::trap::{raise_lib_trap, Trap, TrapCode};
use crate::vmcontext::VMContext;
use crate::VMExternRef;
use crate::{on_host_stack, VMExternRef};
use enum_iterator::IntoEnumIterator;
use loupe::MemoryUsage;
#[cfg(feature = "enable-rkyv")]
Expand Down Expand Up @@ -155,13 +155,15 @@ pub unsafe extern "C" fn wasmer_vm_memory32_grow(
delta: u32,
memory_index: u32,
) -> u32 {
let instance = (&*vmctx).instance();
let memory_index = LocalMemoryIndex::from_u32(memory_index);
on_host_stack(|| {
let instance = (&*vmctx).instance();
let memory_index = LocalMemoryIndex::from_u32(memory_index);

instance
.memory_grow(memory_index, delta)
.map(|pages| pages.0)
.unwrap_or(u32::max_value())
instance
.memory_grow(memory_index, delta)
.map(|pages| pages.0)
.unwrap_or(u32::max_value())
})
}

/// Implementation of memory.grow for imported 32-bit memories.
Expand All @@ -175,13 +177,15 @@ pub unsafe extern "C" fn wasmer_vm_imported_memory32_grow(
delta: u32,
memory_index: u32,
) -> u32 {
let instance = (&*vmctx).instance();
let memory_index = MemoryIndex::from_u32(memory_index);
on_host_stack(|| {
let instance = (&*vmctx).instance();
let memory_index = MemoryIndex::from_u32(memory_index);

instance
.imported_memory_grow(memory_index, delta)
.map(|pages| pages.0)
.unwrap_or(u32::max_value())
instance
.imported_memory_grow(memory_index, delta)
.map(|pages| pages.0)
.unwrap_or(u32::max_value())
})
}

/// Implementation of memory.size for locally-defined 32-bit memories.
Expand Down Expand Up @@ -440,18 +444,20 @@ pub unsafe extern "C" fn wasmer_vm_table_grow(
delta: u32,
table_index: u32,
) -> u32 {
let instance = (&*vmctx).instance();
let table_index = LocalTableIndex::from_u32(table_index);
on_host_stack(|| {
let instance = (&*vmctx).instance();
let table_index = LocalTableIndex::from_u32(table_index);

let init_value = match instance.get_local_table(table_index).ty().ty {
Type::ExternRef => TableElement::ExternRef(init_value.extern_ref.into()),
Type::FuncRef => TableElement::FuncRef(init_value.func_ref),
_ => panic!("Unrecognized table type: does not contain references"),
};
let init_value = match instance.get_local_table(table_index).ty().ty {
Type::ExternRef => TableElement::ExternRef(init_value.extern_ref.into()),
Type::FuncRef => TableElement::FuncRef(init_value.func_ref),
_ => panic!("Unrecognized table type: does not contain references"),
};

instance
.table_grow(table_index, delta, init_value)
.unwrap_or(u32::max_value())
instance
.table_grow(table_index, delta, init_value)
.unwrap_or(u32::max_value())
})
}

/// Implementation of `table.grow` for imported tables.
Expand All @@ -466,17 +472,19 @@ pub unsafe extern "C" fn wasmer_vm_imported_table_grow(
delta: u32,
table_index: u32,
) -> u32 {
let instance = (&*vmctx).instance();
let table_index = TableIndex::from_u32(table_index);
let init_value = match instance.get_table(table_index).ty().ty {
Type::ExternRef => TableElement::ExternRef(init_value.extern_ref.into()),
Type::FuncRef => TableElement::FuncRef(init_value.func_ref),
_ => panic!("Unrecognized table type: does not contain references"),
};
on_host_stack(|| {
let instance = (&*vmctx).instance();
let table_index = TableIndex::from_u32(table_index);
let init_value = match instance.get_table(table_index).ty().ty {
Type::ExternRef => TableElement::ExternRef(init_value.extern_ref.into()),
Type::FuncRef => TableElement::FuncRef(init_value.func_ref),
_ => panic!("Unrecognized table type: does not contain references"),
};

instance
.imported_table_grow(table_index, delta, init_value)
.unwrap_or(u32::max_value())
instance
.imported_table_grow(table_index, delta, init_value)
.unwrap_or(u32::max_value())
})
}

/// Implementation of `func.ref`.
Expand Down Expand Up @@ -517,7 +525,7 @@ pub unsafe extern "C" fn wasmer_vm_externref_inc(externref: VMExternRef) {
/// and other serious memory bugs may occur.
#[no_mangle]
pub unsafe extern "C" fn wasmer_vm_externref_dec(mut externref: VMExternRef) {
externref.ref_drop()
on_host_stack(|| externref.ref_drop())
}

/// Implementation of `elem.drop`.
Expand All @@ -527,9 +535,11 @@ pub unsafe extern "C" fn wasmer_vm_externref_dec(mut externref: VMExternRef) {
/// `vmctx` must be dereferenceable.
#[no_mangle]
pub unsafe extern "C" fn wasmer_vm_elem_drop(vmctx: *mut VMContext, elem_index: u32) {
let elem_index = ElemIndex::from_u32(elem_index);
let instance = (&*vmctx).instance();
instance.elem_drop(elem_index);
on_host_stack(|| {
let elem_index = ElemIndex::from_u32(elem_index);
let instance = (&*vmctx).instance();
instance.elem_drop(elem_index);
})
}

/// Implementation of `memory.copy` for locally defined memories.
Expand Down Expand Up @@ -656,9 +666,11 @@ pub unsafe extern "C" fn wasmer_vm_memory32_init(
/// `vmctx` must be dereferenceable.
#[no_mangle]
pub unsafe extern "C" fn wasmer_vm_data_drop(vmctx: *mut VMContext, data_index: u32) {
let data_index = DataIndex::from_u32(data_index);
let instance = (&*vmctx).instance();
instance.data_drop(data_index)
on_host_stack(|| {
let data_index = DataIndex::from_u32(data_index);
let instance = (&*vmctx).instance();
instance.data_drop(data_index)
})
}

/// Implementation for raising a trap
Expand Down
Loading

0 comments on commit c50bed3

Please sign in to comment.