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:
@@ -1,3 +1,4 @@
|
||||
use super::REALLOC_AND_FREE;
|
||||
use anyhow::Result;
|
||||
use wasmtime::component::*;
|
||||
use wasmtime::{Store, StoreContextMut, Trap};
|
||||
@@ -332,3 +333,293 @@ fn attempt_to_reenter_during_host() -> Result<()> {
|
||||
func.call(&mut store, ())?;
|
||||
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