Out-of-bounds read/write and invalid free with `externref`s and GC safepoints in Wasmtime
Description
Wasmtime is an open source runtime for WebAssembly & WASI. In Wasmtime from version 0.26.0 and before version 0.30.0 is affected by a memory unsoundness vulnerability. There was an invalid free and out-of-bounds read and write bug when running Wasm that uses externrefs in Wasmtime. To trigger this bug, Wasmtime needs to be running Wasm that uses externrefs, the host creates non-null externrefs, Wasmtime performs a garbage collection (GC), and there has to be a Wasm frame on the stack that is at a GC safepoint where there are no live references at this safepoint, and there is a safepoint with live references earlier in this frame's function. Under this scenario, Wasmtime would incorrectly use the GC stack map for the safepoint from earlier in the function instead of the empty safepoint. This would result in Wasmtime treating arbitrary stack slots as externrefs that needed to be rooted for GC. At the *next* GC, it would be determined that nothing was referencing these bogus externrefs (because nothing could ever reference them, because they are not really externrefs) and then Wasmtime would deallocate them and run <ExternRef as Drop>::drop on them. This results in a free of memory that is not necessarily on the heap (and shouldn't be freed at this moment even if it was), as well as potential out-of-bounds reads and writes. Even though support for externrefs (via the reference types proposal) is enabled by default, unless you are creating non-null externrefs in your host code or explicitly triggering GCs, you cannot be affected by this bug. We have reason to believe that the effective impact of this bug is relatively small because usage of externref is currently quite rare. This bug has been patched and users should upgrade to Wasmtime version 0.30.0. If you cannot upgrade Wasmtime at this time, you can avoid this bug by disabling the reference types proposal by passing false to wasmtime::Config::wasm_reference_types.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
wasmtimecrates.io | >= 0.26.0, < 0.30.0 | 0.30.0 |
wasmtimePyPI | >= 0.26.0, < 0.30.0 | 0.30.0 |
Affected products
1- Range: >= 0.26.0, <= 0.29.0
Patches
1398a73f0dd86Merge pull request from GHSA-4873-36h9-wv49
1 file changed · +52 −48
crates/wasmtime/src/module/registry.rs+52 −48 modified@@ -122,61 +122,65 @@ impl ModuleInfo for RegisteredModule { let info = self.module.func_info(index); // Do a binary search to find the stack map for the given offset. - // - // Because GC safepoints are technically only associated with a single - // PC, we should ideally only care about `Ok(index)` values returned - // from the binary search. However, safepoints are inserted right before - // calls, and there are two things that can disturb the PC/offset - // associated with the safepoint versus the PC we actually use to query - // for the stack map: - // - // 1. The `backtrace` crate gives us the PC in a frame that will be - // *returned to*, and where execution will continue from, rather than - // the PC of the call we are currently at. So we would need to - // disassemble one instruction backwards to query the actual PC for - // the stack map. - // - // TODO: One thing we *could* do to make this a little less error - // prone, would be to assert/check that the nearest GC safepoint - // found is within `max_encoded_size(any kind of call instruction)` - // our queried PC for the target architecture. - // - // 2. Cranelift's stack maps only handle the stack, not - // registers. However, some references that are arguments to a call - // may need to be in registers. In these cases, what Cranelift will - // do is: - // - // a. spill all the live references, - // b. insert a GC safepoint for those references, - // c. reload the references into registers, and finally - // d. make the call. - // - // Step (c) adds drift between the GC safepoint and the location of - // the call, which is where we actually walk the stack frame and - // collect its live references. - // - // Luckily, the spill stack slots for the live references are still - // up to date, so we can still find all the on-stack roots. - // Furthermore, we do not have a moving GC, so we don't need to worry - // whether the following code will reuse the references in registers - // (which would not have been updated to point to the moved objects) - // or reload from the stack slots (which would have been updated to - // point to the moved objects). - let index = match info .stack_maps .binary_search_by_key(&func_offset, |i| i.code_offset) { - // Exact hit. + // Found it. Ok(i) => i, - // `Err(0)` means that the associated stack map would have been the - // first element in the array if this pc had an associated stack - // map, but this pc does not have an associated stack map. This can - // only happen inside a Wasm frame if there are no live refs at this - // pc. + // No stack map associated with this PC. + // + // Because we know we are in Wasm code, and we must be at some kind + // of call/safepoint, then the Cranelift backend must have avoided + // emitting a stack map for this location because no refs were live. + #[cfg(not(feature = "old-x86-backend"))] + Err(_) => return None, + + // ### Old x86_64 backend specific code. + // + // Because GC safepoints are technically only associated with a + // single PC, we should ideally only care about `Ok(index)` values + // returned from the binary search. However, safepoints are inserted + // right before calls, and there are two things that can disturb the + // PC/offset associated with the safepoint versus the PC we actually + // use to query for the stack map: + // + // 1. The `backtrace` crate gives us the PC in a frame that will be + // *returned to*, and where execution will continue from, rather than + // the PC of the call we are currently at. So we would need to + // disassemble one instruction backwards to query the actual PC for + // the stack map. + // + // TODO: One thing we *could* do to make this a little less error + // prone, would be to assert/check that the nearest GC safepoint + // found is within `max_encoded_size(any kind of call instruction)` + // our queried PC for the target architecture. + // + // 2. Cranelift's stack maps only handle the stack, not + // registers. However, some references that are arguments to a call + // may need to be in registers. In these cases, what Cranelift will + // do is: + // + // a. spill all the live references, + // b. insert a GC safepoint for those references, + // c. reload the references into registers, and finally + // d. make the call. + // + // Step (c) adds drift between the GC safepoint and the location of + // the call, which is where we actually walk the stack frame and + // collect its live references. + // + // Luckily, the spill stack slots for the live references are still + // up to date, so we can still find all the on-stack roots. + // Furthermore, we do not have a moving GC, so we don't need to worry + // whether the following code will reuse the references in registers + // (which would not have been updated to point to the moved objects) + // or reload from the stack slots (which would have been updated to + // point to the moved objects). + #[cfg(feature = "old-x86-backend")] Err(0) => return None, - + #[cfg(feature = "old-x86-backend")] Err(i) => i - 1, };
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
12- github.com/advisories/GHSA-4873-36h9-wv49ghsaADVISORY
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/WAVBRYDDUIY2ZR3K3FO4BVYJKIMJ5TP7/mitrevendor-advisoryx_refsource_FEDORA
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/Z2Z33FTXFQ6EOINVEQIP4DFBG53G5XIY/mitrevendor-advisoryx_refsource_FEDORA
- nvd.nist.gov/vuln/detail/CVE-2021-39218ghsaADVISORY
- crates.io/crates/wasmtimeghsax_refsource_MISCWEB
- github.com/bytecodealliance/wasmtime-py/compare/0.29.0...0.30.0ghsaWEB
- github.com/bytecodealliance/wasmtime/commit/398a73f0dd862dbe703212ebae8e34036a18c11cghsax_refsource_MISCWEB
- github.com/bytecodealliance/wasmtime/security/advisories/GHSA-4873-36h9-wv49ghsax_refsource_CONFIRMWEB
- github.com/pypa/advisory-database/tree/main/vulns/wasmtime/PYSEC-2021-321.yamlghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/WAVBRYDDUIY2ZR3K3FO4BVYJKIMJ5TP7ghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/Z2Z33FTXFQ6EOINVEQIP4DFBG53G5XIYghsaWEB
- rustsec.org/advisories/RUSTSEC-2021-0110.htmlghsaWEB
News mentions
0No linked articles in our index yet.