elliekorn opened issue #14671:
Test Case
(module (tag $e (param i32)) (func (export "identity") (param i32) (result i32) (local.get 0)) )Nothing throws and no exception opcode is executed - merely defining a tag is
enough.Steps to Reproduce
Create a binary crate depending on
wasmtime(path or git dependency at the
commit below) with thegcandgc-drcfeatures:
toml [dependencies] wasmtime = { path = "../wasmtime/crates/wasmtime", features = ["gc", "gc-drc"] }
src/main.rs:
rust use wasmtime::*; const WAT: &str = r#" (module (tag $e (param i32)) (func (export "identity") (param i32) (result i32) (local.get 0)) ) "#; fn main() -> Result<()> { let mut config = Config::new(); config.wasm_exceptions(true); let engine = Engine::new(&config)?; let module = Module::new(&engine, WAT)?; let mut store: Store<()> = Store::new(&engine, ()); let instance = Linker::<()>::new(&engine).instantiate(&mut store, &module)?; let f = instance.get_typed_func::<i32, i32>(&mut store, "identity")?; assert_eq!(f.call(&mut store, 7)?, 7); Ok(()) }Build and run with a debug-assertions
std. Theptr::writealignment check
sits behind#[cfg(debug_assertions)]insidecore, so a plaincargo run
against the prebuilt releasestddoes not catch this;stdhas to be
rebuilt:
sh cargo +nightly run -Zbuild-std=std,panic_abort --target x86_64-unknown-linux-gnu
(Reproduced here with stable 1.98.1 andRUSTC_BOOTSTRAP=1, which is
equivalent for this purpose.)For comparison, remove the
(tag …)line, or add a second tag; both pass:
| Module | Debug-assertionsstd|
|---|---|
| no tags | ok |
| 1 tag | abort |
| 2 tags | ok |Expected Results
The module instantiates and
identityreturns 7. EveryVMFuncRefin the
VMContextis pointer-aligned regardless of how many tags the module defines.Actual Results
The process aborts during instantiation:
thread 'main' panicked at library/core/src/ptr/non_null.rs:1078:18: unsafe precondition(s) violated: ptr::write requires that the pointer argument is aligned and non-null This indicates a bug in the program. This Undefined Behavior check is optional, and cannot be relied on for safety. thread caused non-unwinding panic. aborting.The misalignment itself does not depend on the build mode. With the prebuilt
releasestdthe module appears to work, but the exported function's
VMFuncRef(fromFunc::to_raw) sits at a misaligned address:
| Module |VMFuncRefaddress |
|---|---|
| no tags |0x…300(0 mod 8) |
| 1 tag |0x…304(4 mod 8) |
| 2 tags |0x…308(0 mod 8) |
andVMOffsetsfor one defined tag, one escaped function, a start function and
one runtime data segment placesfunc_refsat 52,startup_func_refat 84 and
runtime_data_basesat 116 — all 4 mod 8 (48 / 80 / 112 with no tags).Versions and Environment
Wasmtime version or commit:
a473f42e459680d55ade5873e5ac95ac0f81966c
(51.0.0-dev)
Operating system: Linux
Architecture: x86_64Extra Info
Root cause.
VMTagDefinition(infor_each_vm_type!,
crates/environ/src/vmtypes.rs) is a plain#[repr(C)]struct with a single
VMSharedTypeIndexfield, so it is 4 bytes with 4-byte alignment.
TheVMContextlayout'sdynamicsection (infor_each_vmctx_type!,
crates/environ/src/vmctxtypes.rs) placestagsdirectly beforefunc_refs:align { 16 } array { #[aggregate] globals[num_defined_globals; DefinedGlobalIndex]: VMGlobalDefinition } array { #[aggregate] tags[num_defined_tags; DefinedTagIndex]: VMTagDefinition } array { #[aggregate] func_refs[num_escaped_funcs; FuncRefIndex]: VMFuncRef }The layout grammar never derives padding from a field's type - alignment is
always an explicitalign { ... }entry - and there is none betweentagsand
func_refs. Withglobalsstarting 16-aligned at 16 bytes per element,
func_refsbegins at16·k + 4·num_defined_tags, which is 4 mod 8 for an odd
tag count on 64-bit targets. Every later entry is a multiple of 8 bytes, so
startup_func_refandruntime_data_basesare misaligned too
(runtime_data_lengths, beingu32, is unaffected). 32-bit targets are
unaffected.VMComponentContext's layout, by contrast, has analign { ptr }
beforetrampoline_func_refs.
Instance::initialize_and_return_funcrefwrites theVMFuncRefwith
NonNull::writeatvmctx + func_refs().at(index); the index is valid and in
range, only the address is misaligned.
Why CI doesn't catch it. Theptr::writeprecondition is only compiled in
whencoreitself is built with debug assertions, which the prebuiltstd
shipped by rustup is not. Ordinarycargo testtherefore never runs it. The
existingcheck_vmtag_begins_alignedtest uses an empty module and only checks
wheretagsbegins.
Impact. On x86-64 and AArch64 the misaligned plain loads and stores are
tolerated by hardware, so release builds behave correctly; the practical effect
is aborts in debug-stdbuilds, plus Rust-level UB. Every C++ guest compiled
with native Wasm exception handling declares exactly one tag
(__cpp_exception), so such guests always land on the odd case.
Suggested fix. One alignment entry infor_each_vmctx_type!:array { #[aggregate] tags[num_defined_tags; DefinedTagIndex]: VMTagDefinition } align { ptr } array { #[aggregate] func_refs[num_escaped_funcs; FuncRefIndex]: VMFuncRef }
align { ptr }rather thanalign { 8 }so 32-bit targets get no unneeded
padding. The runtime and Cranelift both take their offsets from this one layout,
so the single entry fixes both and also re-alignsstartup_func_refand
runtime_data_bases.
Suggested regression test, built fromVMOffsetsFieldsso it covers both
pointer widths regardless of host or build mode:#[test] fn func_refs_aligned_after_odd_tag_count() { for ptr in [4u8, 8u8] { let offsets = VMOffsets::from(VMOffsetsFields { ptr, num_imported_functions: 0, num_imported_tables: 0, num_imported_memories: 0, num_imported_globals: 0, num_imported_tags: 0, num_defined_tables: 0, num_defined_memories: 0, num_owned_memories: 0, num_defined_globals: 0, num_defined_tags: 1, num_escaped_funcs: 1, num_runtime_data: 1, has_startup_func: true, }); let p = u32::from(ptr); assert_eq!(offsets.func_refs().begin() % p, 0); assert_eq!(offsets.startup_func_ref() % p, 0); assert_eq!(offsets.runtime_data_bases().begin() % p, 0); } }
elliekorn added the bug label to Issue #14671.
alexcrichton commented on issue #14671:
Thanks for the issue here, but this is a source of UB and thus a security issue for Wasmtime. We'll be publishing an advisory later today at https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-p79w-pv2r-29qp
alexcrichton closed issue #14671:
Test Case
(module (tag $e (param i32)) (func (export "identity") (param i32) (result i32) (local.get 0)) )Nothing throws and no exception opcode is executed - merely defining a tag is
enough.Steps to Reproduce
Create a binary crate depending on
wasmtime(path or git dependency at the
commit below) with thegcandgc-drcfeatures:
toml [dependencies] wasmtime = { path = "../wasmtime/crates/wasmtime", features = ["gc", "gc-drc"] }
src/main.rs:
rust use wasmtime::*; const WAT: &str = r#" (module (tag $e (param i32)) (func (export "identity") (param i32) (result i32) (local.get 0)) ) "#; fn main() -> Result<()> { let mut config = Config::new(); config.wasm_exceptions(true); let engine = Engine::new(&config)?; let module = Module::new(&engine, WAT)?; let mut store: Store<()> = Store::new(&engine, ()); let instance = Linker::<()>::new(&engine).instantiate(&mut store, &module)?; let f = instance.get_typed_func::<i32, i32>(&mut store, "identity")?; assert_eq!(f.call(&mut store, 7)?, 7); Ok(()) }Build and run with a debug-assertions
std. Theptr::writealignment check
sits behind#[cfg(debug_assertions)]insidecore, so a plaincargo run
against the prebuilt releasestddoes not catch this;stdhas to be
rebuilt:
sh cargo +nightly run -Zbuild-std=std,panic_abort --target x86_64-unknown-linux-gnu
(Reproduced here with stable 1.98.1 andRUSTC_BOOTSTRAP=1, which is
equivalent for this purpose.)For comparison, remove the
(tag …)line, or add a second tag; both pass:
| Module | Debug-assertionsstd|
|---|---|
| no tags | ok |
| 1 tag | abort |
| 2 tags | ok |Expected Results
The module instantiates and
identityreturns 7. EveryVMFuncRefin the
VMContextis pointer-aligned regardless of how many tags the module defines.Actual Results
The process aborts during instantiation:
thread 'main' panicked at library/core/src/ptr/non_null.rs:1078:18: unsafe precondition(s) violated: ptr::write requires that the pointer argument is aligned and non-null This indicates a bug in the program. This Undefined Behavior check is optional, and cannot be relied on for safety. thread caused non-unwinding panic. aborting.The misalignment itself does not depend on the build mode. With the prebuilt
releasestdthe module appears to work, but the exported function's
VMFuncRef(fromFunc::to_raw) sits at a misaligned address:
| Module |VMFuncRefaddress |
|---|---|
| no tags |0x…300(0 mod 8) |
| 1 tag |0x…304(4 mod 8) |
| 2 tags |0x…308(0 mod 8) |
andVMOffsetsfor one defined tag, one escaped function, a start function and
one runtime data segment placesfunc_refsat 52,startup_func_refat 84 and
runtime_data_basesat 116 — all 4 mod 8 (48 / 80 / 112 with no tags).Versions and Environment
Wasmtime version or commit:
a473f42e459680d55ade5873e5ac95ac0f81966c
(51.0.0-dev)
Operating system: Linux
Architecture: x86_64Extra Info
Root cause.
VMTagDefinition(infor_each_vm_type!,
crates/environ/src/vmtypes.rs) is a plain#[repr(C)]struct with a single
VMSharedTypeIndexfield, so it is 4 bytes with 4-byte alignment.
TheVMContextlayout'sdynamicsection (infor_each_vmctx_type!,
crates/environ/src/vmctxtypes.rs) placestagsdirectly beforefunc_refs:align { 16 } array { #[aggregate] globals[num_defined_globals; DefinedGlobalIndex]: VMGlobalDefinition } array { #[aggregate] tags[num_defined_tags; DefinedTagIndex]: VMTagDefinition } array { #[aggregate] func_refs[num_escaped_funcs; FuncRefIndex]: VMFuncRef }The layout grammar never derives padding from a field's type - alignment is
always an explicitalign { ... }entry - and there is none betweentagsand
func_refs. Withglobalsstarting 16-aligned at 16 bytes per element,
func_refsbegins at16·k + 4·num_defined_tags, which is 4 mod 8 for an odd
tag count on 64-bit targets. Every later entry is a multiple of 8 bytes, so
startup_func_refandruntime_data_basesare misaligned too
(runtime_data_lengths, beingu32, is unaffected). 32-bit targets are
unaffected.VMComponentContext's layout, by contrast, has analign { ptr }
beforetrampoline_func_refs.
Instance::initialize_and_return_funcrefwrites theVMFuncRefwith
NonNull::writeatvmctx + func_refs().at(index); the index is valid and in
range, only the address is misaligned.
Why CI doesn't catch it. Theptr::writeprecondition is only compiled in
whencoreitself is built with debug assertions, which the prebuiltstd
shipped by rustup is not. Ordinarycargo testtherefore never runs it. The
existingcheck_vmtag_begins_alignedtest uses an empty module and only checks
wheretagsbegins.
Impact. On x86-64 and AArch64 the misaligned plain loads and stores are
tolerated by hardware, so release builds behave correctly; the practical effect
is aborts in debug-stdbuilds, plus Rust-level UB. Every C++ guest compiled
with native Wasm exception handling declares exactly one tag
(__cpp_exception), so such guests always land on the odd case.
Suggested fix. One alignment entry infor_each_vmctx_type!:array { #[aggregate] tags[num_defined_tags; DefinedTagIndex]: VMTagDefinition } align { ptr } array { #[aggregate] func_refs[num_escaped_funcs; FuncRefIndex]: VMFuncRef }
align { ptr }rather thanalign { 8 }so 32-bit targets get no unneeded
padding. The runtime and Cranelift both take their offsets from this one layout,
so the single entry fixes both and also re-alignsstartup_func_refand
runtime_data_bases.
Suggested regression test, built fromVMOffsetsFieldsso it covers both
pointer widths regardless of host or build mode:#[test] fn func_refs_aligned_after_odd_tag_count() { for ptr in [4u8, 8u8] { let offsets = VMOffsets::from(VMOffsetsFields { ptr, num_imported_functions: 0, num_imported_tables: 0, num_imported_memories: 0, num_imported_globals: 0, num_imported_tags: 0, num_defined_tables: 0, num_defined_memories: 0, num_owned_memories: 0, num_defined_globals: 0, num_defined_tags: 1, num_escaped_funcs: 1, num_runtime_data: 1, has_startup_func: true, }); let p = u32::from(ptr); assert_eq!(offsets.func_refs().begin() % p, 0); assert_eq!(offsets.startup_func_ref() % p, 0); assert_eq!(offsets.runtime_data_bases().begin() % p, 0); } }
Last updated: Oct 31 2026 at 23:03 UTC