Wasmtime Sandbox Escape via Compiler Miscompilation (CVE-2026-115889)
CVE-2026-115889 is a Critical Wasmtime sandbox escape: Cranelift packs indices into 24-bit fields that a crafted module overflows. Fixed in 36.0.19 and 43.0.0.
TL;DR
- Wasmtime compiles WebAssembly with Cranelift, its own compiler backend. Inside the compiler's intermediate representation, each value record packed an instruction, block or value index into 24 bits of a 64-bit word, with a guard that only panics in debug builds.
- A Wasm function that pushes one of these instruction, block or value indices past 2^24 - 1, roughly 16.7 million, overflows the 24-bit fields in release builds. It can be a genuinely huge function or one crafted to inflate the count. The value then references the wrong instruction, block or value. The compiler generates machine code from this corrupted data. The result can be type confusion, which means a sandbox escape.
- The Bytecode Alliance rates the flaw Critical, CVSS v3.1 9.9, with the default Cranelift backend affected on any architecture. The main line has been fixed since Wasmtime 43.0.0 (March 2026); the 36.x maintenance line got the backport in 36.0.19, released October 19, 2026, the same day the advisory was published.
- Upgrade to 36.0.19 or later on the 36.x line. On the main line, 43.0.0 fixes only CVE-2026-115889; to clear all three flaws below, run 48.0.7 or later, or 49.0.4 or later on the 49 line. No exploitation in the wild is known as of October 24, 2026.
- Two companion sandbox escapes share the October window. CVE-2026-115893 lets a
br_tablewith more than about 256,000 targets jump to an attacker-chosen offset on aarch64; it is fixed in 36.0.19, 48.0.7 and 49.0.4. GHSA-m9f7-9w4p-gv38 lets a guest move any host directory into its sandbox through a symlink rename on macOS and FreeBSD; it is fixed in 36.0.18, 48.0.6 and 49.0.3.
Am I affected?
You are affected if all of these hold:
- You run Wasmtime, or a product embedding it, with the default Cranelift compiler backend.
- Your version is before 36.0.19 on the 36.x maintenance line, or between 37.0.0 and 42.x on the main line.
- Guest Wasm modules are not fully trusted: guests come from tenants, users or other parties whose code you confine rather than trust.
The advisory's impact statement is explicit: "A Wasmtime engine with default settings (using the Cranelift backend) on any architecture/platform could experience a sandbox escape from a specially-crafted Wasm module." Engines that only run first-party, trusted modules are not exposed to this bug.
The October 2026 advisory window carries two further sandbox escapes with different affected ranges, so the table lists each flaw separately:
| Flaw | Affected versions | Fixed version |
|---|---|---|
| CVE-2026-115889, any architecture | 36.x: before 36.0.19; main line: 37.0.0 – 42.x | 36.0.19 and 43.0.0 (main line, March 2026) |
| CVE-2026-115893, aarch64 only | 36.x: before 36.0.19; main line: 37.0.0 – 48.0.6 and 49.0.0 – 49.0.3 | 36.0.19, 48.0.7 and 49.0.4 (all released October 19, 2026) |
| GHSA-m9f7-9w4p-gv38, wasmtime-wasi on macOS and FreeBSD | 36.x: 36.0.17 and earlier; main line: 37.0.0 – 48.0.5 and 49.0.0 – 49.0.2 | 36.0.18, 48.0.6 and 49.0.3 (all released October 14, 2026) |
The aarch64 advisory states that every Wasmtime version supporting aarch64, back to the initial 2020 support before v1.0, is affected by CVE-2026-115893. The filesystem advisory needs a guest that has write access to a directory inside its sandbox and a host on macOS or FreeBSD; Linux and Windows hosts are not affected, because their renameat does not follow a symlink with a trailing slash.
Check what you run:
# CLI version
wasmtime --version
# Rust dependency tree
cargo tree -i wasmtime -e features | head
cargo tree --depth 1 | grep wasmtime
# Docker image check
docker run --rm <your-image> wasmtime --version
Wasmtime is usually embedded rather than run as a CLI: check the version in your platform's dependency tree, since serverless and plugin platforms commonly pin it.
How do I fix it?
Upgrade Wasmtime. To clear all three flaws at once, the minimum fixed versions are 36.0.19 on the 36.x line, 48.0.7 on the 48 line and 49.0.4 on the 49 line:
# Rust, 36.x maintenance line
cargo update -p wasmtime --precise 36.0.19
# Rust, 48 line
cargo update -p wasmtime --precise 48.0.7
# Rust, 49 line
cargo update -p wasmtime --precise 49.0.4
# CLI install script (installs the latest release)
curl https://wasmtime.dev/install.sh -sSf | bash
Version 43.0.0 fixes only CVE-2026-115889. Releases 43.0.0 through 48.0.6 and 49.0.0 through 49.0.3 still carry the aarch64 br_table escape. Releases 43.0.0 through 48.0.5 and 49.0.0 through 49.0.2 still carry the filesystem escape.
The advisory lists no workaround. Rejecting unusually large function bodies before compilation narrows the input that can reach the overflow, but it is not a fix. The advisory names a function crafted to use many SSA values as a separate shape from a very large body, so a byte-size cap is not a reliable boundary. Treat the engine upgrade as the remediation and any cap as a stopgap.
Scan your Rust repositories and container images with Ostorlab to flag every build that still pins a wasmtime release older than 36.0.19, 48.0.7 or 49.0.4.
What is the root cause?
Cranelift's intermediate representation tracks every SSA value in a ValueDataPacked record. To keep the record at 64 bits, the compiler packed four fields into one word: a 2-bit tag, a 14-bit type, and two 24-bit fields named x and y.
The y field holds the defining instruction's index, the block index, or the original value's index for an alias. These grow with the size of the function. For instruction results and block parameters, x holds only the output or parameter number, which stays small. For a union, a node the optimizer creates, both fields hold value indices. The layout, from the affected source:
// cranelift/codegen/src/ir/dfg.rs (before the fix)
// | tag:2 | type:14 | x:24 | y:24 |
//
// Inst 00 ty inst output inst index
// Param 01 ty blockparam num block index
// Alias 10 ty 0 value index
// Union 11 ty first value second value
The packing helper guarded the field width with only a debug assertion, a check that release builds compile away:
// cranelift/codegen/src/ir/dfg.rs (before the fix)
fn encode_narrow_field(x: u32, bits: u8) -> u32 {
let max = (1 << bits) - 1;
if x == 0xffff_ffff {
max
} else {
debug_assert!(
x < max,
"{x} does not fit into {bits} bits (must be less than {max} to \
allow for a 0xffffffff sentinel)"
);
x
}
}
With the assertion gone, encode_narrow_field returns the index unchanged. The packing code shifts it into place and ORs it into the word without masking, so bits above the 24th spill into the neighboring field. Reading the record back masks each field to 24 bits. An instruction index of 2^24 in y reads back as 0, a completely different instruction, and also corrupts x.
In one sentence: a compiler data structure stored entity indices in fields too narrow to hold them, and the only overflow check was one that release builds delete, so a function with enough values corrupted the compiler's own value-to-definition mapping before code generation began.
The consequence is miscompilation with attacker influence. A Value that claims the wrong defining instruction inherits that instruction's result type and semantics. The compiler then emits machine code where one operation's output is consumed as another type: type confusion in compiled code inside the guest, which breaks the sandbox Wasmtime exists to enforce.
How is it exploited?
An attacker supplies a Wasm module with one function that pushes Cranelift's instruction, block or value indices past 2^24 - 1. In a release build the indices wrap silently, and the miscompiled, type-confused code runs with the host's privileges. No public proof of concept exists, and no exploitation in the wild is known as of October 24, 2026. The path, from the advisory and the patch:
- The attacker can supply a Wasm module to the engine: a tenant deploying code, a user uploading a plugin, any guest author. This is why the CVSS v3.1 vector records privileges required as low.
- The module contains one function that pushes an instruction, block or value index past 2^24 minus 1, roughly 16.7 million. The advisory names two shapes: a very large Wasm function body, or one specially crafted to use many SSA values.
- During translation into Cranelift IR and optimization, instruction, block or value indices exceed the 24-bit fields. In a release build nothing panics; the packed records silently alias to other indices.
- The optimizer and the backend read the corrupted mapping. A value resolves to the wrong definition, with the wrong type, and the generated machine code performs operations the verifier never saw: for example, integer bits reinterpreted as a reference or as a differently typed value.
- Inside the guest, the attacker uses the type-confused code to read or write outside the Wasm linear memory bounds or to reach host machinery the sandbox should keep inaccessible: a sandbox escape, running with the host process's privileges.
The whole attack is one compiled module. No host API misuse, no engine misconfiguration, and no second vulnerability is required.
How does a crafted module progress from overflow to escape?
indices over 2^24 - 1"] --> B["Cranelift packs indices into
24-bit fields of one u64"] B --> C["Release build: debug-only check
is gone, overflow is silent"] C --> D["Value records alias to
wrong instruction or block"] D --> E["Backend emits code from
corrupted value typing"] E --> F["Type confusion inside the guest
breaks the Wasm sandbox"]
The failed check is at C: the only guard against the overflow does not exist in release builds.
What can an attacker do?
Escape the WebAssembly sandbox and execute code with the host process's privileges. Wasmtime's security model treats guest code as untrusted: a sandbox escape means the guest reaches host memory, host APIs and the process's capabilities directly. The CVSS v3.1 vector AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H scores confidentiality, integrity and availability high with a scope change, matching a guest compromising its host.
The conditions the attack needs:
- Wasmtime with the default Cranelift backend, any architecture, in a release build.
- An affected version: before 36.0.19 on the 36.x line, or 37.0.0 through 42.x.
- The attacker's code must actually be compiled by the engine, so engines that precompile only trusted modules or use a different backend are outside the demonstrated path.
What are the two companion sandbox escapes?
The same October advisory window carries two more Wasmtime sandbox escapes, published October 19 and October 14, 2026, both with fix versions that differ from CVE-2026-115889's.
CVE-2026-115893: how does a large br_table escape the sandbox on aarch64?
On the aarch64 backend, a Wasm module with more than roughly 256,000 targets in one br_table instruction compiles into a conditional branch with an incorrect target. The branch can jump to arbitrary code at an offset the module controls, which the advisory says leads to arbitrary sandbox escapes.
Every Wasmtime version with aarch64 support is affected, back to the initial 2020 support before v1.0. The fix shipped in 36.0.19, 48.0.7 and 49.0.4 on October 19, 2026. Main-line releases 43.0.0 through 48.0.6 fix CVE-2026-115889 but still carry this flaw.
GHSA-m9f7-9w4p-gv38: how does a symlink rename escape the filesystem sandbox?
In the wasmtime-wasi crate, a guest could move any host directory the process can reach into its own sandbox, then read and write it.
When renaming, wasmtime-wasi strips the trailing slash from the source path to resolve its parent directory. It then re-appends the slash before calling renameat. A guest creates a relative symlink pointing outside its sandbox, for example symlink("../outside", "up"), and renames "up/" into the sandbox. The host kernel follows the symlink and moves the outside directory in. The earlier GHSA-vqjp-4c8c-hfgg fix changed how paths are opened, not how they are renamed.
macOS and FreeBSD hosts are affected. Linux and Windows are not, because their renameat does not follow a symlink with a trailing slash. The guest needs write access to a directory inside its sandbox. The fix shipped in 36.0.18, 48.0.6 and 49.0.3 on October 14, 2026. There is no known workaround, and no CVE has been assigned.
Neither companion advisory reports exploitation in the wild.
Why does the fix work?
The fix removes the narrow fields: x and y become full u32 indices, so no function size can wrap an index into a different definition. On the main line, pull request #12613 (merged February 18, 2026, shipped in Wasmtime 43.0.0) replaced the packed u64 with:
// cranelift/codegen/src/ir/dfg.rs (after the fix)
-struct ValueDataPacked(u64);
+#[repr(Rust, packed)]
+struct ValueDataPacked {
+ x: u32,
+ y: u32,
+ flags_and_type: u16,
}
The layout comment changes from x:24 | y:24 to x:32 | y:32: both fields now hold their full u32 range, and the encode_narrow_field and decode_narrow_field helpers are deleted because there is nothing left to narrow. The record grows from 8 to 10 bytes. The PR still measured a 3 to 5 percent compile-time speedup on its benchmarks.
The 36.0.19 backport, pull request #14818, carries the same change to the maintenance line, alongside fixes for two other advisories in the same release: a wasi-http conflicting-content-length issue (GHSA-r8xh-cgf7-392j) and the aarch64 br_table jump target issue (CVE-2026-115893, GHSA-mghf-wjfp-p5xm).
The fix is complete for this class of overflow in this structure: the values no longer pass through any narrowing step, so no function size can corrupt the mapping. The advisory adds that Cranelift now uses a full u32 index and checks when that limit is exceeded, so even the u32 ceiling is guarded rather than silent.
How do I check if I was compromised?
No indicators of compromise have been published for this advisory. Sandbox escapes through miscompilation have no published signature or IOC, and no exploitation is known. If you run an affected engine with untrusted guests and want to audit:
- Review which modules were compiled and by whom, if your platform records module hashes or tenant upload history. A crafted module for this bug is one function with an unusually high SSA value count relative to its byte size.
- Treat any guest-side anomaly reports, out-of-bounds reads surfacing as host crashes or verifier-impossible behavior, as grounds for investigating the host process, not just the guest.
- If a compromise is suspected, treat the host process as fully compromised: rotate every secret it holds and rebuild from a known-good state, since escaped guest code runs with host privileges.
Timeline
- February 18, 2026: pull request #12613, "Cranelift: update ValueDataPacked to support full Value range", merges on the main line.
- March 20, 2026: Wasmtime 43.0.0 ships the full-width encoding.
- October 14, 2026: the Bytecode Alliance publishes GHSA-m9f7-9w4p-gv38, the wasmtime-wasi filesystem sandbox escape through trailing-slash symlink renames on macOS and FreeBSD, and releases 36.0.18, 48.0.6 and 49.0.3 with the fix.
- October 19, 2026: reporter jaydns is credited; the Bytecode Alliance publishes advisory GHSA-897f-g5gm-fmxm and companion advisory GHSA-mghf-wjfp-p5xm (CVE-2026-115893, the aarch64
br_tableescape), and releases 36.0.19 with both backports plus 48.0.7 and 49.0.4 with thebr_tablefix. - October 23, 2026: CVE-2026-115889 is published.
- October 24, 2026: this post. No exploitation in the wild is known for any of the three flaws; none is in the CISA KEV catalog.
- October 26, 2026: updated with the two companion advisories, GHSA-mghf-wjfp-p5xm (CVE-2026-115893) and GHSA-m9f7-9w4p-gv38.
FAQ
Why did the main line get the fix months before the advisory?
The February change to full u32 fields lifted a limit on function body size. It was a correctness improvement, not a published security fix. The security impact of the old encoding was reported later, and the October advisory records both facts: v43 and later are not susceptible, and 36.0.19 backports the change for the maintenance line.
Do I need 16.7 million instructions in one function to be at risk?
No. The threshold is on the indices packed into the value record: instruction, block and value indices, about 16.7 million of each. Value indices grow with the SSA value count, and the advisory says a function can be "specially crafted to use many SSA values". A byte-size cap is therefore not a reliable boundary. Upgrade instead.
Are debug builds affected?
No. In debug builds the debug_assert! fires and compilation aborts instead of corrupting the mapping. Production engines ship release builds, which is why the advisory concerns release behavior.
Is the Winch backend or precompiled module path affected?
The advisory scopes the flaw to the Cranelift backend, the default. Engines that run precompiled modules skip the compilation step where the corruption occurs, but they should still upgrade, since the advisory does not rule out other backend or precompile paths.
How widely deployed is Wasmtime?
The wasmtime crate records 41,373,089 all-time downloads and 11,809,615 downloads in the last 90 days on crates.io, and it is the engine behind serverless and plugin platforms that confine third-party code, the exact boundary an attacker crosses with this bug.
Does this affect wasmtime-based platforms that set module limits?
Yes, if the embedded engine is an affected release. Limits narrow the input but were not part of the fix, so upgrade the engine rather than rely on caps.
Does upgrading to 43.0.0 or later clear all three flaws?
No. On the main line, CVE-2026-115889 is fixed from 43.0.0. The filesystem escape is fixed first in 48.0.6 (49.0.3 on the 49 line), and the aarch64 br_table escape first in 48.0.7 (49.0.4 on the 49 line). On the 36.x maintenance line, 36.0.19 clears all three.
References
- Bytecode Alliance advisory GHSA-897f-g5gm-fmxm: https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-897f-g5gm-fmxm (checked October 24, 2026)
- Bytecode Alliance advisory GHSA-mghf-wjfp-p5xm, CVE-2026-115893, the aarch64
br_tablesandbox escape, Critical, CVSS v3.1 9.9, fixed 36.0.19, 48.0.7 and 49.0.4: https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-mghf-wjfp-p5xm (checked October 26, 2026) - Bytecode Alliance advisory GHSA-m9f7-9w4p-gv38, the wasmtime-wasi filesystem sandbox escape through trailing-slash symlink renames on macOS and FreeBSD, High, no CVE, fixed 36.0.18, 48.0.6 and 49.0.3: https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-m9f7-9w4p-gv38 (checked October 26, 2026)
- CVE-2026-115889 record (CVSS v3.1 9.9, CWE-197 and CWE-843, affected ranges): https://cveawg.mitre.org/api/cve/CVE-2026-115889 (checked October 24, 2026)
- Main-line fix, pull request #12613, merged February 18, 2026: https://github.com/bytecodealliance/wasmtime/pull/12613 (checked October 24, 2026)
- Bytecode Alliance advisory GHSA-r8xh-cgf7-392j, wasi-http conflicting content lengths, fixed in the same 36.0.19 backport: https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-r8xh-cgf7-392j (checked October 27, 2026)
- 36.x backport, pull request #14818: https://github.com/bytecodealliance/wasmtime/pull/14818 (checked October 24, 2026)
- Wasmtime 36.0.19 release (published October 19, 2026): https://github.com/bytecodealliance/wasmtime/releases/tag/v36.0.19 (checked October 24, 2026)
- Wasmtime 43.0.0 release (published March 20, 2026): https://github.com/bytecodealliance/wasmtime/releases/tag/v43.0.0 (checked October 24, 2026)
- Vulnerable dfg.rs at the 36.x line (ValueDataPacked encoding): https://github.com/bytecodealliance/wasmtime/blob/v36.0.18/cranelift/codegen/src/ir/dfg.rs (checked October 24, 2026)
- crates.io download counts for wasmtime (41,373,089 all-time, 11,809,615 in the last 90 days): https://crates.io/api/v1/crates/wasmtime (checked October 24, 2026)