Skip to content

Commit

Permalink
wasmtime: Ensure that Func::wrap'd return values are compatible wit…
Browse files Browse the repository at this point in the history
…h the current store
  • Loading branch information
fitzgen committed Jul 7, 2020
1 parent c2fc371 commit 392bbad
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 2 deletions.
116 changes: 114 additions & 2 deletions crates/wasmtime/src/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,18 @@ macro_rules! getters {
let weak_store = instance.store.weak();
let weak_store = WeakStore(&weak_store);

$( let $args = $args.into_abi_for_arg(weak_store); )*
$(
// Because this returned closure is not marked `unsafe`,
// we have to check that incoming values are compatible
// with our store.
if !$args.compatible_with_store(weak_store) {
return Err(Trap::new(
"attempt to pass cross-`Store` value to Wasm as function argument"
));
}

let $args = $args.into_abi_for_arg(weak_store);
)*

invoke_wasm_and_catch_traps(anyfunc.as_ref().vmctx, &instance.store, || {
ret = Some(fnptr(
Expand Down Expand Up @@ -827,6 +838,10 @@ pub unsafe trait WasmTy {
#[doc(hidden)]
type Abi: Copy;

// Is this value compatible with the given store?
#[doc(hidden)]
fn compatible_with_store<'a>(&self, store: WeakStore<'a>) -> bool;

// Convert this value into its ABI representation, when passing a value into
// Wasm as an argument.
#[doc(hidden)]
Expand Down Expand Up @@ -871,6 +886,10 @@ pub unsafe trait WasmRet {
#[doc(hidden)]
type Abi: Copy;

// Same as `WasmTy::compatible_with_store`.
#[doc(hidden)]
fn compatible_with_store<'a>(&self, store: WeakStore<'a>) -> bool;

// Similar to `WasmTy::into_abi_for_arg` but used when host code is
// returning a value into Wasm, rather than host code passing an argument to
// a Wasm call. Unlike `into_abi_for_arg`, implementors of this method can
Expand Down Expand Up @@ -904,6 +923,11 @@ pub unsafe trait WasmRet {
unsafe impl WasmTy for () {
type Abi = Self;

#[inline]
fn compatible_with_store<'a>(&self, _store: WeakStore<'a>) -> bool {
true
}

#[inline]
fn into_abi_for_arg<'a>(self, _store: WeakStore<'a>) -> Self::Abi {}

Expand All @@ -926,6 +950,11 @@ unsafe impl WasmTy for () {
unsafe impl WasmTy for i32 {
type Abi = Self;

#[inline]
fn compatible_with_store<'a>(&self, _store: WeakStore<'a>) -> bool {
true
}

#[inline]
fn into_abi_for_arg<'a>(self, _store: WeakStore<'a>) -> Self::Abi {
self
Expand Down Expand Up @@ -966,6 +995,11 @@ unsafe impl WasmTy for i32 {
unsafe impl WasmTy for u32 {
type Abi = <i32 as WasmTy>::Abi;

#[inline]
fn compatible_with_store<'a>(&self, _store: WeakStore<'a>) -> bool {
true
}

#[inline]
fn into_abi_for_arg<'a>(self, _store: WeakStore<'a>) -> Self::Abi {
self as i32
Expand Down Expand Up @@ -998,6 +1032,11 @@ unsafe impl WasmTy for u32 {
unsafe impl WasmTy for i64 {
type Abi = Self;

#[inline]
fn compatible_with_store<'a>(&self, _store: WeakStore<'a>) -> bool {
true
}

#[inline]
fn into_abi_for_arg<'a>(self, _store: WeakStore<'a>) -> Self::Abi {
self
Expand Down Expand Up @@ -1038,6 +1077,11 @@ unsafe impl WasmTy for i64 {
unsafe impl WasmTy for u64 {
type Abi = <i64 as WasmTy>::Abi;

#[inline]
fn compatible_with_store<'a>(&self, _store: WeakStore<'a>) -> bool {
true
}

#[inline]
fn into_abi_for_arg<'a>(self, _store: WeakStore<'a>) -> Self::Abi {
self as i64
Expand Down Expand Up @@ -1070,6 +1114,11 @@ unsafe impl WasmTy for u64 {
unsafe impl WasmTy for f32 {
type Abi = Self;

#[inline]
fn compatible_with_store<'a>(&self, _store: WeakStore<'a>) -> bool {
true
}

#[inline]
fn into_abi_for_arg<'a>(self, _store: WeakStore<'a>) -> Self::Abi {
self
Expand Down Expand Up @@ -1110,6 +1159,11 @@ unsafe impl WasmTy for f32 {
unsafe impl WasmTy for f64 {
type Abi = Self;

#[inline]
fn compatible_with_store<'a>(&self, _store: WeakStore<'a>) -> bool {
true
}

#[inline]
fn into_abi_for_arg<'a>(self, _store: WeakStore<'a>) -> Self::Abi {
self
Expand Down Expand Up @@ -1150,6 +1204,11 @@ unsafe impl WasmTy for f64 {
unsafe impl WasmTy for Option<ExternRef> {
type Abi = *mut u8;

#[inline]
fn compatible_with_store<'a>(&self, _store: WeakStore<'a>) -> bool {
true
}

#[inline]
fn into_abi_for_arg<'a>(self, store: WeakStore<'a>) -> Self::Abi {
if let Some(x) = self {
Expand Down Expand Up @@ -1205,6 +1264,16 @@ unsafe impl WasmTy for Option<ExternRef> {
unsafe impl WasmTy for Option<Func> {
type Abi = *mut wasmtime_runtime::VMCallerCheckedAnyfunc;

#[inline]
fn compatible_with_store<'a>(&self, store: WeakStore<'a>) -> bool {
if let Some(f) = self {
let store = Store::upgrade(store.0).unwrap();
Store::same(&store, f.store())
} else {
true
}
}

#[inline]
fn into_abi_for_arg<'a>(self, _store: WeakStore<'a>) -> Self::Abi {
if let Some(f) = self {
Expand Down Expand Up @@ -1251,6 +1320,11 @@ where
{
type Abi = <T as WasmTy>::Abi;

#[inline]
fn compatible_with_store<'a>(&self, store: WeakStore<'a>) -> bool {
<Self as WasmTy>::compatible_with_store(self, store)
}

#[inline]
unsafe fn into_abi_for_ret<'a>(self, store: WeakStore<'a>) -> Self::Abi {
<Self as WasmTy>::into_abi_for_arg(self, store)
Expand Down Expand Up @@ -1288,6 +1362,14 @@ where
{
type Abi = <T as WasmTy>::Abi;

#[inline]
fn compatible_with_store<'a>(&self, store: WeakStore<'a>) -> bool {
match self {
Ok(x) => <T as WasmTy>::compatible_with_store(x, store),
Err(_) => true,
}
}

#[inline]
unsafe fn into_abi_for_ret<'a>(self, store: WeakStore<'a>) -> Self::Abi {
match self {
Expand Down Expand Up @@ -1419,6 +1501,27 @@ impl Caller<'_> {
}
}

#[inline(never)]
#[cold]
unsafe fn raise_cross_store_trap() -> ! {
#[derive(Debug)]
struct CrossStoreError;

impl std::error::Error for CrossStoreError {}

impl fmt::Display for CrossStoreError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"host function attempted to return cross-`Store` \
value to Wasm",
)
}
}

raise_user_trap(Box::new(CrossStoreError));
}

macro_rules! impl_into_func {
($(
($($args:ident)*)
Expand Down Expand Up @@ -1482,8 +1585,17 @@ macro_rules! impl_into_func {
}))
};
match ret {
Ok(ret) => ret.into_abi_for_ret(weak_store),
Err(panic) => wasmtime_runtime::resume_panic(panic),
Ok(ret) => {
// Because the wrapped function is not `unsafe`, we
// can't assume it returned a value that is
// compatible with this store.
if !ret.compatible_with_store(weak_store) {
raise_cross_store_trap();
}

ret.into_abi_for_ret(weak_store)
}
}
}

Expand Down
65 changes: 65 additions & 0 deletions tests/all/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,3 +442,68 @@ fn func_write_nothing() -> anyhow::Result<()> {
.contains("function attempted to return an incompatible value"));
Ok(())
}

#[test]
// Note: Cranelift only supports refrerence types (used in the wasm in this
// test) on x64.
#[cfg(target_arch = "x86_64")]
fn return_cross_store_value() -> anyhow::Result<()> {
let wasm = wat::parse_str(
r#"
(import "" "" (func (result funcref)))
(func (export "run") (result funcref)
call 0
)
"#,
)?;
let mut config = Config::new();
config.wasm_reference_types(true);
let engine = Engine::new(&config);
let module = Module::new(&engine, &wasm)?;

let store1 = Store::new(&engine);
let store2 = Store::new(&engine);

let store2_func = Func::wrap(&store2, || {});
let return_cross_store_func = Func::wrap(&store1, move || Some(store2_func.clone()));

let instance = Instance::new(&store1, &module, &[return_cross_store_func.into()])?;

let run = instance.get_func("run").unwrap();
let result = run.call(&[]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("cross-`Store`"));

Ok(())
}

#[test]
// Note: Cranelift only supports refrerence types (used in the wasm in this
// test) on x64.
#[cfg(target_arch = "x86_64")]
fn pass_cross_store_arg() -> anyhow::Result<()> {
let mut config = Config::new();
config.wasm_reference_types(true);
let engine = Engine::new(&config);

let store1 = Store::new(&engine);
let store2 = Store::new(&engine);

let store1_func = Func::wrap(&store1, |_: Option<Func>| {});
let store2_func = Func::wrap(&store2, || {});

// Using regular `.call` fails with cross-Store arguments.
assert!(store1_func
.call(&[Val::FuncRef(Some(store2_func.clone()))])
.is_err());

// And using `.get` followed by a function call also fails with cross-Store
// arguments.
let f = store1_func.get1::<Option<Func>, ()>()?;
let result = f(Some(store2_func));
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("cross-`Store`"));

Ok(())
}

0 comments on commit 392bbad

Please sign in to comment.