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:
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)) {
|
||||||
|
|||||||
@@ -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,)*))
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user