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

@@ -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(())
}