Validate alignment in the canonical ABI (#4238)

This commit updates the lifting and lowering done by Wasmtime to
validate that alignment is all correct. Previously alignment was ignored
because I wasn't sure how this would all work out.

To be extra safe I haven't actually modified any loads/stores and
they're all still unaligned. If this becomes a performance issue we can
investigate aligned loads and stores but otherwise I believe the
requisite locations have been guarded with traps and I've also added
debug asserts to catch possible future mistakes.
This commit is contained in:
Alex Crichton
2022-06-07 13:34:34 -05:00
committed by GitHub
parent 8ca3af0e37
commit 0b4448a423
6 changed files with 493 additions and 77 deletions

View File

@@ -230,12 +230,15 @@ where
fn validate_inbounds<T: ComponentType>(memory: &[u8], ptr: &ValRaw) -> Result<usize> { fn validate_inbounds<T: ComponentType>(memory: &[u8], ptr: &ValRaw) -> Result<usize> {
// FIXME: needs memory64 support // FIXME: needs memory64 support
let ptr = usize::try_from(ptr.get_u32())?; let ptr = usize::try_from(ptr.get_u32())?;
if ptr % usize::try_from(T::align())? != 0 {
bail!("pointer not aligned");
}
let end = match ptr.checked_add(T::size()) { let end = match ptr.checked_add(T::size()) {
Some(n) => n, Some(n) => n,
None => bail!("return pointer size overflow"), None => bail!("pointer size overflow"),
}; };
if end > memory.len() { if end > memory.len() {
bail!("return pointer out of bounds") bail!("pointer out of bounds")
} }
Ok(ptr) Ok(ptr)
} }

View File

@@ -83,7 +83,7 @@ impl Options {
// Invoke the wasm malloc function using its raw and statically known // Invoke the wasm malloc function using its raw and statically known
// signature. // signature.
let result = unsafe { let result = unsafe {
usize::try_from(crate::TypedFunc::<(u32, u32, u32, u32), u32>::call_raw( crate::TypedFunc::<(u32, u32, u32, u32), u32>::call_raw(
store, store,
realloc, realloc,
( (
@@ -92,9 +92,14 @@ impl Options {
old_align, old_align,
u32::try_from(new_size)?, u32::try_from(new_size)?,
), ),
)?)? )?
}; };
if result % old_align != 0 {
bail!("realloc return: result not aligned");
}
let result = usize::try_from(result)?;
let memory = self.memory_mut(store.0); let memory = self.memory_mut(store.0);
let result_slice = match memory.get_mut(result..).and_then(|s| s.get_mut(..new_size)) { let result_slice = match memory.get_mut(result..).and_then(|s| s.get_mut(..new_size)) {

View File

@@ -236,7 +236,11 @@ where
fn lift_heap_result(store: &StoreOpaque, options: &Options, dst: &ValRaw) -> Result<Return> { fn lift_heap_result(store: &StoreOpaque, options: &Options, dst: &ValRaw) -> Result<Return> {
assert!(Return::flatten_count() > MAX_STACK_RESULTS); assert!(Return::flatten_count() > MAX_STACK_RESULTS);
// FIXME: needs to read an i64 for memory64 // FIXME: needs to read an i64 for memory64
let ptr = usize::try_from(dst.get_u32()).unwrap(); let ptr = usize::try_from(dst.get_u32())?;
if ptr % usize::try_from(Return::align())? != 0 {
bail!("return pointer not aligned");
}
let memory = Memory::new(store, options); let memory = Memory::new(store, options);
let bytes = memory let bytes = memory
.as_slice() .as_slice()
@@ -704,6 +708,7 @@ macro_rules! integers {
} }
fn store<T>(&self, memory: &mut MemoryMut<'_, T>, offset: usize) -> Result<()> { fn store<T>(&self, memory: &mut MemoryMut<'_, T>, offset: usize) -> Result<()> {
debug_assert!(offset % Self::size() == 0);
*memory.get(offset) = self.to_le_bytes(); *memory.get(offset) = self.to_le_bytes();
Ok(()) Ok(())
} }
@@ -717,6 +722,7 @@ macro_rules! integers {
#[inline] #[inline]
fn load(_mem: &Memory<'_>, bytes: &[u8]) -> Result<Self> { fn load(_mem: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
debug_assert!((bytes.as_ptr() as usize) % Self::size() == 0);
Ok($primitive::from_le_bytes(bytes.try_into().unwrap())) Ok($primitive::from_le_bytes(bytes.try_into().unwrap()))
} }
} }
@@ -780,6 +786,7 @@ macro_rules! floats {
} }
fn store<T>(&self, memory: &mut MemoryMut<'_, T>, offset: usize) -> Result<()> { fn store<T>(&self, memory: &mut MemoryMut<'_, T>, offset: usize) -> Result<()> {
debug_assert!(offset % Self::size() == 0);
let ptr = memory.get(offset); let ptr = memory.get(offset);
*ptr = canonicalize(*self).to_bits().to_le_bytes(); *ptr = canonicalize(*self).to_bits().to_le_bytes();
Ok(()) Ok(())
@@ -794,6 +801,7 @@ macro_rules! floats {
#[inline] #[inline]
fn load(_mem: &Memory<'_>, bytes: &[u8]) -> Result<Self> { fn load(_mem: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
debug_assert!((bytes.as_ptr() as usize) % Self::size() == 0);
Ok(canonicalize($float::from_le_bytes(bytes.try_into().unwrap()))) Ok(canonicalize($float::from_le_bytes(bytes.try_into().unwrap())))
} }
} }
@@ -838,6 +846,7 @@ unsafe impl Lower for bool {
} }
fn store<T>(&self, memory: &mut MemoryMut<'_, T>, offset: usize) -> Result<()> { fn store<T>(&self, memory: &mut MemoryMut<'_, T>, offset: usize) -> Result<()> {
debug_assert!(offset % Self::size() == 0);
memory.get::<1>(offset)[0] = *self as u8; memory.get::<1>(offset)[0] = *self as u8;
Ok(()) Ok(())
} }
@@ -894,6 +903,7 @@ unsafe impl Lower for char {
} }
fn store<T>(&self, memory: &mut MemoryMut<'_, T>, offset: usize) -> Result<()> { fn store<T>(&self, memory: &mut MemoryMut<'_, T>, offset: usize) -> Result<()> {
debug_assert!(offset % Self::size() == 0);
*memory.get::<4>(offset) = u32::from(*self).to_le_bytes(); *memory.get::<4>(offset) = u32::from(*self).to_le_bytes();
Ok(()) Ok(())
} }
@@ -907,6 +917,7 @@ unsafe impl Lift for char {
#[inline] #[inline]
fn load(_memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> { fn load(_memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
debug_assert!((bytes.as_ptr() as usize) % Self::size() == 0);
let bits = u32::from_le_bytes(bytes.try_into().unwrap()); let bits = u32::from_le_bytes(bytes.try_into().unwrap());
Ok(char::try_from(bits)?) Ok(char::try_from(bits)?)
} }
@@ -948,6 +959,7 @@ unsafe impl Lower for str {
} }
fn store<T>(&self, mem: &mut MemoryMut<'_, T>, offset: usize) -> Result<()> { fn store<T>(&self, mem: &mut MemoryMut<'_, T>, offset: usize) -> Result<()> {
debug_assert!(offset % (Self::align() as usize) == 0);
let (ptr, len) = lower_string(mem, self)?; let (ptr, len) = lower_string(mem, self)?;
// FIXME: needs memory64 handling // FIXME: needs memory64 handling
*mem.get(offset + 0) = (ptr as i32).to_le_bytes(); *mem.get(offset + 0) = (ptr as i32).to_le_bytes();
@@ -1109,6 +1121,7 @@ unsafe impl Lift for WasmStr {
} }
fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> { fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
debug_assert!((bytes.as_ptr() as usize) % (Self::align() as usize) == 0);
// FIXME: needs memory64 treatment // FIXME: needs memory64 treatment
let ptr = u32::from_le_bytes(bytes[..4].try_into().unwrap()); let ptr = u32::from_le_bytes(bytes[..4].try_into().unwrap());
let len = u32::from_le_bytes(bytes[4..].try_into().unwrap()); let len = u32::from_le_bytes(bytes[4..].try_into().unwrap());
@@ -1160,6 +1173,7 @@ where
} }
fn store<U>(&self, mem: &mut MemoryMut<'_, U>, offset: usize) -> Result<()> { fn store<U>(&self, mem: &mut MemoryMut<'_, U>, offset: usize) -> Result<()> {
debug_assert!(offset % (Self::align() as usize) == 0);
let (ptr, len) = lower_list(mem, self)?; let (ptr, len) = lower_list(mem, self)?;
*mem.get(offset + 0) = (ptr as i32).to_le_bytes(); *mem.get(offset + 0) = (ptr as i32).to_le_bytes();
*mem.get(offset + 4) = (len as i32).to_le_bytes(); *mem.get(offset + 4) = (len as i32).to_le_bytes();
@@ -1225,6 +1239,9 @@ impl<T: Lift> WasmList<T> {
Some(n) if n <= memory.as_slice().len() => {} Some(n) if n <= memory.as_slice().len() => {}
_ => bail!("list pointer/length out of bounds of memory"), _ => bail!("list pointer/length out of bounds of memory"),
} }
if ptr % usize::try_from(T::align())? != 0 {
bail!("list pointer is not aligned")
}
Ok(WasmList { Ok(WasmList {
ptr, ptr,
len, len,
@@ -1324,6 +1341,7 @@ unsafe impl<T: Lift> Lift for WasmList<T> {
} }
fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> { fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
debug_assert!((bytes.as_ptr() as usize) % (Self::align() as usize) == 0);
// FIXME: needs memory64 treatment // FIXME: needs memory64 treatment
let ptr = u32::from_le_bytes(bytes[..4].try_into().unwrap()); let ptr = u32::from_le_bytes(bytes[..4].try_into().unwrap());
let len = u32::from_le_bytes(bytes[4..].try_into().unwrap()); let len = u32::from_le_bytes(bytes[4..].try_into().unwrap());
@@ -1432,6 +1450,7 @@ where
} }
fn store<U>(&self, mem: &mut MemoryMut<'_, U>, offset: usize) -> Result<()> { fn store<U>(&self, mem: &mut MemoryMut<'_, U>, offset: usize) -> Result<()> {
debug_assert!(offset % (Self::align() as usize) == 0);
match self { match self {
None => { None => {
mem.get::<1>(offset)[0] = 0; mem.get::<1>(offset)[0] = 0;
@@ -1458,6 +1477,7 @@ where
} }
fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> { fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
debug_assert!((bytes.as_ptr() as usize) % (Self::align() as usize) == 0);
let discrim = bytes[0]; let discrim = bytes[0];
let payload = &bytes[align_to(1, T::align())..]; let payload = &bytes[align_to(1, T::align())..];
match discrim { match discrim {
@@ -1555,6 +1575,7 @@ where
} }
fn store<U>(&self, mem: &mut MemoryMut<'_, U>, offset: usize) -> Result<()> { fn store<U>(&self, mem: &mut MemoryMut<'_, U>, offset: usize) -> Result<()> {
debug_assert!(offset % (Self::align() as usize) == 0);
match self { match self {
Ok(e) => { Ok(e) => {
mem.get::<1>(offset)[0] = 0; mem.get::<1>(offset)[0] = 0;
@@ -1602,7 +1623,8 @@ where
} }
fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> { fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
let align = <Result<T, E> as ComponentType>::align(); debug_assert!((bytes.as_ptr() as usize) % (Self::align() as usize) == 0);
let align = Self::align();
let discrim = bytes[0]; let discrim = bytes[0];
let payload = &bytes[align_to(1, align)..]; let payload = &bytes[align_to(1, align)..];
match discrim { match discrim {
@@ -1674,9 +1696,8 @@ macro_rules! impl_component_ty_for_tuples {
} }
fn store<U>(&self, memory: &mut MemoryMut<'_, U>, mut offset: usize) -> Result<()> { fn store<U>(&self, memory: &mut MemoryMut<'_, U>, mut offset: usize) -> Result<()> {
debug_assert!(offset % (Self::align() as usize) == 0);
let ($($t,)*) = self; let ($($t,)*) = self;
// TODO: this requires that `offset` is aligned which we may not
// want to do
$($t.store(memory, next_field::<$t>(&mut offset))?;)* $($t.store(memory, next_field::<$t>(&mut offset))?;)*
Ok(()) Ok(())
} }
@@ -1691,6 +1712,7 @@ macro_rules! impl_component_ty_for_tuples {
} }
fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> { fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
debug_assert!((bytes.as_ptr() as usize) % (Self::align() as usize) == 0);
let mut offset = 0; let mut offset = 0;
$(let $t = $t::load(memory, &bytes[next_field::<$t>(&mut offset)..][..$t::size()])?;)* $(let $t = $t::load(memory, &bytes[next_field::<$t>(&mut offset)..][..$t::size()])?;)*
Ok(($($t,)*)) Ok(($($t,)*))

View File

@@ -5,6 +5,67 @@ use wasmtime::{Config, Engine};
mod func; mod func;
mod import; mod import;
// A simple bump allocator which can be used with modules
const REALLOC_AND_FREE: &str = r#"
(global $last (mut i32) (i32.const 8))
(func $realloc (export "canonical_abi_realloc")
(param $old_ptr i32)
(param $old_size i32)
(param $align i32)
(param $new_size i32)
(result i32)
;; Test if the old pointer is non-null
local.get $old_ptr
if
;; If the old size is bigger than the new size then
;; this is a shrink and transparently allow it
local.get $old_size
local.get $new_size
i32.gt_u
if
local.get $old_ptr
return
end
;; ... otherwise this is unimplemented
unreachable
end
;; align up `$last`
(global.set $last
(i32.and
(i32.add
(global.get $last)
(i32.add
(local.get $align)
(i32.const -1)))
(i32.xor
(i32.add
(local.get $align)
(i32.const -1))
(i32.const -1))))
;; save the current value of `$last` as the return value
global.get $last
;; ensure anything necessary is set to valid data by spraying a bit
;; pattern that is invalid
global.get $last
i32.const 0xde
local.get $new_size
memory.fill
;; bump our pointer
(global.set $last
(i32.add
(global.get $last)
(local.get $new_size)))
)
(func (export "canonical_abi_free") (param i32 i32 i32))
"#;
fn engine() -> Engine { fn engine() -> Engine {
let mut config = Config::new(); let mut config = Config::new();
config.wasm_component_model(true); config.wasm_component_model(true);

View File

@@ -1,3 +1,4 @@
use super::REALLOC_AND_FREE;
use anyhow::Result; use anyhow::Result;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
@@ -7,67 +8,6 @@ use wasmtime::{Store, Trap, TrapCode};
const CANON_32BIT_NAN: u32 = 0b01111111110000000000000000000000; const CANON_32BIT_NAN: u32 = 0b01111111110000000000000000000000;
const CANON_64BIT_NAN: u64 = 0b0111111111111000000000000000000000000000000000000000000000000000; const CANON_64BIT_NAN: u64 = 0b0111111111111000000000000000000000000000000000000000000000000000;
// A simple bump allocator which can be used with modules below
const REALLOC_AND_FREE: &str = r#"
(global $last (mut i32) (i32.const 8))
(func $realloc (export "canonical_abi_realloc")
(param $old_ptr i32)
(param $old_size i32)
(param $align i32)
(param $new_size i32)
(result i32)
;; Test if the old pointer is non-null
local.get $old_ptr
if
;; If the old size is bigger than the new size then
;; this is a shrink and transparently allow it
local.get $old_size
local.get $new_size
i32.gt_u
if
local.get $old_ptr
return
end
;; ... otherwise this is unimplemented
unreachable
end
;; align up `$last`
(global.set $last
(i32.and
(i32.add
(global.get $last)
(i32.add
(local.get $align)
(i32.const -1)))
(i32.xor
(i32.add
(local.get $align)
(i32.const -1))
(i32.const -1))))
;; save the current value of `$last` as the return value
global.get $last
;; ensure anything necessary is set to valid data by spraying a bit
;; pattern that is invalid
global.get $last
i32.const 0xde
local.get $new_size
memory.fill
;; bump our pointer
(global.set $last
(i32.add
(global.get $last)
(local.get $new_size)))
)
(func (export "canonical_abi_free") (param i32 i32 i32))
"#;
#[test] #[test]
fn thunks() -> Result<()> { fn thunks() -> Result<()> {
let component = r#" let component = r#"
@@ -676,7 +616,7 @@ fn tuple_result() -> Result<()> {
) )
(func (export "invalid") (result i32) (func (export "invalid") (result i32)
i32.const -1 i32.const -8
) )
(func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32) (func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32)
@@ -1001,7 +941,7 @@ fn many_parameters() -> Result<()> {
#[test] #[test]
fn some_traps() -> Result<()> { fn some_traps() -> Result<()> {
let middle_of_memory = i32::MAX / 2; let middle_of_memory = (i32::MAX / 2) & (!0xff);
let component = format!( let component = format!(
r#"(component r#"(component
(module $m (module $m
@@ -1067,7 +1007,7 @@ fn some_traps() -> Result<()> {
(func (export "take-list") (param i32 i32)) (func (export "take-list") (param i32 i32))
(func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32) (func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32)
i32.const 65535) i32.const 65532)
(func (export "canonical_abi_free") (param i32 i32 i32) (func (export "canonical_abi_free") (param i32 i32 i32)
unreachable) unreachable)
) )
@@ -1194,11 +1134,10 @@ fn some_traps() -> Result<()> {
.call(&mut store, (&[],))?; .call(&mut store, (&[],))?;
instance instance
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-end-oob")? .get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-end-oob")?
.call(&mut store, (&[1],))?; .call(&mut store, (&[1, 2, 3, 4],))?;
assert_oob(&err);
let err = instance let err = instance
.get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-end-oob")? .get_typed_func::<(&[u8],), (), _>(&mut store, "take-list-end-oob")?
.call(&mut store, (&[1, 2],)) .call(&mut store, (&[1, 2, 3, 4, 5],))
.unwrap_err(); .unwrap_err();
assert_oob(&err); assert_oob(&err);
instance instance
@@ -1206,10 +1145,10 @@ fn some_traps() -> Result<()> {
.call(&mut store, ("",))?; .call(&mut store, ("",))?;
instance instance
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-end-oob")? .get_typed_func::<(&str,), (), _>(&mut store, "take-string-end-oob")?
.call(&mut store, ("x",))?; .call(&mut store, ("abcd",))?;
let err = instance let err = instance
.get_typed_func::<(&str,), (), _>(&mut store, "take-string-end-oob")? .get_typed_func::<(&str,), (), _>(&mut store, "take-string-end-oob")?
.call(&mut store, ("xy",)) .call(&mut store, ("abcde",))
.unwrap_err(); .unwrap_err();
assert_oob(&err); assert_oob(&err);
let err = instance let err = instance
@@ -1858,3 +1797,98 @@ impl<'a> SliceExt<'a> for &'a [u8] {
a.try_into().unwrap() a.try_into().unwrap()
} }
} }
#[test]
fn invalid_alignment() -> Result<()> {
let component = format!(
r#"(component
(module $m
(memory (export "memory") 1)
(func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32)
i32.const 1)
(func (export "canonical_abi_free") (param i32 i32 i32)
unreachable)
(func (export "take-i32") (param i32))
(func (export "ret-1") (result i32) i32.const 1)
(func (export "ret-unaligned-list") (result i32)
(i32.store offset=0 (i32.const 8) (i32.const 1))
(i32.store offset=4 (i32.const 8) (i32.const 1))
i32.const 8)
)
(instance $i (instantiate (module $m)))
(func (export "many-params")
(canon.lift
(func
(param string) (param string) (param string) (param string)
(param string) (param string) (param string) (param string)
(param string) (param string) (param string) (param string)
)
(into $i)
(func $i "take-i32")
)
)
(func (export "string-ret")
(canon.lift (func (result string)) (into $i) (func $i "ret-1"))
)
(func (export "list-u32-ret")
(canon.lift (func (result (list u32))) (into $i) (func $i "ret-unaligned-list"))
)
)"#
);
let engine = super::engine();
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
let err = instance
.get_typed_func::<(
&str,
&str,
&str,
&str,
&str,
&str,
&str,
&str,
&str,
&str,
&str,
&str,
), (), _>(&mut store, "many-params")?
.call(&mut store, ("", "", "", "", "", "", "", "", "", "", "", ""))
.unwrap_err();
assert!(
err.to_string()
.contains("realloc return: result not aligned"),
"{}",
err
);
let err = instance
.get_typed_func::<(), WasmStr, _>(&mut store, "string-ret")?
.call(&mut store, ())
.err()
.unwrap();
assert!(
err.to_string().contains("return pointer not aligned"),
"{}",
err
);
let err = instance
.get_typed_func::<(), WasmList<u32>, _>(&mut store, "list-u32-ret")?
.call(&mut store, ())
.err()
.unwrap();
assert!(
err.to_string().contains("list pointer is not aligned"),
"{}",
err
);
Ok(())
}

View File

@@ -1,3 +1,4 @@
use super::REALLOC_AND_FREE;
use anyhow::Result; use anyhow::Result;
use wasmtime::component::*; use wasmtime::component::*;
use wasmtime::{Store, StoreContextMut, Trap}; use wasmtime::{Store, StoreContextMut, Trap};
@@ -332,3 +333,293 @@ fn attempt_to_reenter_during_host() -> Result<()> {
func.call(&mut store, ())?; func.call(&mut store, ())?;
Ok(()) Ok(())
} }
#[test]
fn stack_and_heap_args_and_rets() -> Result<()> {
let component = format!(
r#"
(component
(type $many_params (tuple
string string string string
string string string string
string))
(import "f1" (func $f1 (param u32) (result u32)))
(import "f2" (func $f2 (param $many_params) (result u32)))
(import "f3" (func $f3 (param u32) (result string)))
(import "f4" (func $f4 (param $many_params) (result string)))
(module $libc
{REALLOC_AND_FREE}
(memory (export "memory") 1)
)
(instance $libc (instantiate (module $libc)))
(func $f1_lower (canon.lower (into $libc) (func $f1)))
(func $f2_lower (canon.lower (into $libc) (func $f2)))
(func $f3_lower (canon.lower (into $libc) (func $f3)))
(func $f4_lower (canon.lower (into $libc) (func $f4)))
(module $m
(import "host" "f1" (func $f1 (param i32) (result i32)))
(import "host" "f2" (func $f2 (param i32) (result i32)))
(import "host" "f3" (func $f3 (param i32 i32)))
(import "host" "f4" (func $f4 (param i32 i32)))
(import "libc" "memory" (memory 1))
(func $run (export "run")
block
i32.const 1
call $f1
i32.const 2
i32.eq
br_if 0
unreachable
end
block
call $allocate_empty_strings
call $f2
i32.const 3
i32.eq
br_if 0
unreachable
end
block
i32.const 8
i32.const 16000
call $f3
(call $validate_string_ret (i32.const 16000))
end
block
call $allocate_empty_strings
i32.const 20000
call $f4
(call $validate_string_ret (i32.const 20000))
end
)
(func $allocate_empty_strings (result i32)
(local $ret i32)
(local $offset i32)
(local $cnt i32)
(local.set $ret (i32.const 8000))
(local.set $cnt (i32.const 9))
loop
(call $setup_str (i32.add (local.get $ret) (local.get $offset)))
(local.set $offset (i32.add (local.get $offset) (i32.const 8)))
(local.tee $cnt (i32.add (local.get $cnt) (i32.const -1)))
br_if 0
end
local.get $ret
)
(func $setup_str (param $addr i32)
(i32.store offset=0 (local.get $addr) (i32.const 1000))
(i32.store offset=4 (local.get $addr) (i32.const 3))
)
(func $validate_string_ret (param $addr i32)
(local $base i32)
(local $len i32)
(local.set $base (i32.load (local.get $addr)))
(local.set $len (i32.load offset=4 (local.get $addr)))
block
local.get $len
i32.const 3
i32.eq
br_if 0
unreachable
end
(i32.load8_u offset=0 (local.get $base))
i32.const 120 ;; 'x'
i32.ne
if unreachable end
(i32.load8_u offset=1 (local.get $base))
i32.const 121 ;; 'y'
i32.ne
if unreachable end
(i32.load8_u offset=2 (local.get $base))
i32.const 122 ;; 'z'
i32.ne
if unreachable end
)
(data (i32.const 1000) "abc")
)
(instance $m (instantiate (module $m)
(with "libc" (instance $libc))
(with "host" (instance
(export "f1" (func $f1_lower))
(export "f2" (func $f2_lower))
(export "f3" (func $f3_lower))
(export "f4" (func $f4_lower))
))
))
(func (export "run")
(canon.lift (func) (func $m "run"))
)
)
"#
);
let engine = super::engine();
let mut linker = Linker::new(&engine);
linker.root().func_wrap("f1", |x: u32| -> Result<u32> {
assert_eq!(x, 1);
Ok(2)
})?;
linker.root().func_wrap(
"f2",
|cx: StoreContextMut<'_, ()>,
arg: (
WasmStr,
WasmStr,
WasmStr,
WasmStr,
WasmStr,
WasmStr,
WasmStr,
WasmStr,
WasmStr,
)|
-> Result<u32> {
assert_eq!(arg.0.to_str(&cx).unwrap(), "abc");
Ok(3)
},
)?;
linker
.root()
.func_wrap("f3", |arg: u32| -> Result<String> {
assert_eq!(arg, 8);
Ok("xyz".to_string())
})?;
linker.root().func_wrap(
"f4",
|cx: StoreContextMut<'_, ()>,
arg: (
WasmStr,
WasmStr,
WasmStr,
WasmStr,
WasmStr,
WasmStr,
WasmStr,
WasmStr,
WasmStr,
)|
-> Result<String> {
assert_eq!(arg.0.to_str(&cx).unwrap(), "abc");
Ok("xyz".to_string())
},
)?;
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
let instance = linker.instantiate(&mut store, &component)?;
instance
.get_typed_func::<(), (), _>(&mut store, "run")?
.call(&mut store, ())?;
Ok(())
}
#[test]
fn bad_import_alignment() -> Result<()> {
let component = format!(
r#"
(component
(import "unaligned-retptr" (func $unaligned_retptr (result string)))
(type $many_arg (tuple
string string string string
string string string string
string
))
(import "unaligned-argptr" (func $unaligned_argptr (param $many_arg)))
(module $libc_panic
(memory (export "memory") 1)
(func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32)
unreachable)
(func (export "canonical_abi_free") (param i32 i32 i32)
unreachable)
)
(instance $libc_panic (instantiate (module $libc_panic)))
(func $unaligned_retptr_lower
(canon.lower (into $libc_panic) (func $unaligned_retptr))
)
(func $unaligned_argptr_lower
(canon.lower (into $libc_panic) (func $unaligned_argptr))
)
(module $m
(import "host" "unaligned-retptr" (func $unaligned_retptr (param i32)))
(import "host" "unaligned-argptr" (func $unaligned_argptr (param i32)))
(func (export "unaligned-retptr")
(call $unaligned_retptr (i32.const 1)))
(func (export "unaligned-argptr")
(call $unaligned_argptr (i32.const 1)))
)
(instance $m (instantiate (module $m)
(with "host" (instance
(export "unaligned-retptr" (func $unaligned_retptr_lower))
(export "unaligned-argptr" (func $unaligned_argptr_lower))
))
))
(func (export "unaligned-retptr")
(canon.lift (func) (func $m "unaligned-retptr"))
)
(func (export "unaligned-argptr")
(canon.lift (func) (func $m "unaligned-argptr"))
)
)
"#
);
let engine = super::engine();
let mut linker = Linker::new(&engine);
linker
.root()
.func_wrap("unaligned-retptr", || -> Result<String> {
Ok(String::new())
})?;
linker.root().func_wrap(
"unaligned-argptr",
|_: (
WasmStr,
WasmStr,
WasmStr,
WasmStr,
WasmStr,
WasmStr,
WasmStr,
WasmStr,
WasmStr,
)|
-> Result<()> { unreachable!() },
)?;
let component = Component::new(&engine, component)?;
let mut store = Store::new(&engine, ());
let instance = linker.instantiate(&mut store, &component)?;
let trap = instance
.get_typed_func::<(), (), _>(&mut store, "unaligned-retptr")?
.call(&mut store, ())
.unwrap_err()
.downcast::<Trap>()?;
assert!(trap.to_string().contains("pointer not aligned"), "{}", trap);
let trap = instance
.get_typed_func::<(), (), _>(&mut store, "unaligned-argptr")?
.call(&mut store, ())
.unwrap_err()
.downcast::<Trap>()?;
assert!(trap.to_string().contains("pointer not aligned"), "{}", trap);
Ok(())
}