critical

CVE

CVE-2026-115893, CVE-2026-115889, CVE-2026-115892

CWE

CWE-190, CWE-682, CWE-843, CWE-444

Affected Surface

  • Rust crates `wasmtime` and `cranelift-codegen` prior to `36.0.19` (`cranelift-codegen 0.123.19`), `48.0.7` (`0.135.7`), `49.0.4` (`0.136.4`), and `50.0.0` (`0.137.0`) on `aarch64` targets (`CVE-2026-115893` / `GHSA-mghf-wjfp-p5xm`)
  • Rust crates `wasmtime` and `cranelift-codegen` prior to `36.0.19` and `>= 37.0.0, < 43.0.0` on all architectures (`CVE-2026-115889` / `GHSA-897f-g5gm-fmxm`)
  • Rust crate `wasmtime` versions `>= 46.0.0, < 48.0.7` and `>= 49.0.0, < 49.0.4` using Component Model `StreamAny` / `FutureAny` (`CVE-2026-115892` / `GHSA-9pvw-hwfg-92h5`)
  • Rust crate `wasmtime-wasi-http` prior to `36.0.19`, `48.0.7`, `49.0.4`, and `50.0.0` (`GHSA-r8xh-cgf7-392j`, `GHSA-9prg-84vf-g5fc`, and `GHSA-jxcf-8qw5-5c33`)

TL;DR

On 23 and 24 October 2026, NVD, the GitHub Advisory Database, and RustSec published six advisories for Wasmtime (wasmtime, cranelift-codegen, and wasmtime-wasi-http), Bytecode Alliance’s WebAssembly runtime and compiler stack.

Two of the vulnerabilities are CVSS v3.1 9.9 Critical compiler miscompilations in Cranelift that allow an untrusted WebAssembly guest module to escape the Wasmtime sandbox under default engine settings:

  1. CVE-2026-115893 (GHSA-mghf-wjfp-p5xm, RUSTSEC-2026-0379, CVSS 9.9 Critical): On aarch64 hosts, Cranelift compiled each WebAssembly br_table instruction into a single monolithic machine-instruction sequence (MInst.JTSequence) that began with a conditional branch (b.hs default) jumping over the inline 32-bit jump-table entries to the default block. Because JTSequence forbids register-allocator spills and constant/branch veneers inside its body, and ARM64’s conditional branch (B.cond / LabelUse::Branch19) encodes a signed 19-bit word offset (+/- 1 MiB), a br_table with more than roughly 256,000 targets placed the default block beyond the 19-bit branch range, corrupting the conditional branch target so out-of-bounds indices jumped to an attacker-controlled offset in compiled code.
  2. CVE-2026-115889 (GHSA-897f-g5gm-fmxm, RUSTSEC-2026-0380, CVSS 9.9 Critical): On all CPU architectures in Wasmtime < 36.0.19 and 37.0.0 through 42.x, Cranelift’s data-flow graph packed SSA value definitions (ValueDataPacked(u64)) into two 24-bit fields (x:24 and y:24) and checked the 24-bit bound (16,777,215) using debug_assert!(). In release builds, encode_narrow_field() did not panic when an instruction, block, or SSA value index reached 2^24 - 1; instead, decode_narrow_field() translated 0xff_ffff into the sentinel 0xffff_ffff (u32::MAX) and higher indices truncated into adjacent bitfields, causing silent SSA value aliasing, type confusion, and sandbox escape.

The same patch train (36.0.19, 48.0.7, 49.0.4, and 50.0.0) also fixes a Component Model host panic when lowering type-erased StreamAny / FutureAny handles (CVE-2026-115892, GHSA-9pvw-hwfg-92h5) and three wasmtime-wasi-http flaws where a guest could emit duplicate Content-Length headers (GHSA-r8xh-cgf7-392j), mismatched spliced Content-Length values (GHSA-9prg-84vf-g5fc), or unfiltered HTTP trailers (GHSA-jxcf-8qw5-5c33).

Affected packages and projects

EcosystemCrateCVE / GHSA / RustSecSeverityAffected versionsFixed versionsComponent
crates.iowasmtime, cranelift-codegenCVE-2026-115893 (GHSA-mghf-wjfp-p5xm, RUSTSEC-2026-0379)CVSS 9.9 Criticalwasmtime < 36.0.19, >= 37.0.0, < 48.0.7, >= 49.0.0, < 49.0.4; cranelift-codegen < 0.123.19, 0.124.0..0.135.7, 0.136.0..0.136.4wasmtime 36.0.19, 48.0.7, 49.0.4, 50.0.0; cranelift-codegen 0.123.19, 0.135.7, 0.136.4, 0.137.0cranelift/codegen/src/isa/aarch64/inst/emit.rs and crates/cranelift/src/translate/code_translator.rs
crates.iowasmtime, cranelift-codegenCVE-2026-115889 (GHSA-897f-g5gm-fmxm, RUSTSEC-2026-0380)CVSS 9.9 Criticalwasmtime < 36.0.19, >= 37.0.0, < 43.0.0; cranelift-codegen < 0.123.19, 0.124.0..0.130.0wasmtime 36.0.19, >= 43.0.0; cranelift-codegen 0.123.19, >= 0.130.0cranelift/codegen/src/ir/dfg.rs (ValueDataPacked / encode_narrow_field)
crates.iowasmtimeCVE-2026-115892 (GHSA-9pvw-hwfg-92h5, RUSTSEC-2026-0378)CVSS 5.7 Medium>= 46.0.0, < 48.0.7, >= 49.0.0, < 49.0.448.0.7, 49.0.4, 50.0.0crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs
crates.iowasmtime-wasi-httpGHSA-r8xh-cgf7-392j, GHSA-9prg-84vf-g5fc, GHSA-jxcf-8qw5-5c33Medium / Low< 36.0.19, >= 37.0.0, < 48.0.7, >= 49.0.0, < 49.0.4 (9prg and jxcf affect wasip3 in 48.x/49.x)36.0.19, 48.0.7, 49.0.4, 50.0.0crates/wasi-http/src/field_map.rs, src/lib.rs, src/p3/body.rs

Technical analysis

1. CVE-2026-115893: aarch64 br_table 19-bit b.hs branch displacement overflow

On 64-bit ARM (aarch64), Cranelift lowered a WebAssembly br_table instruction into a single compound pseudo-instruction (Inst::JTSequence) in cranelift/codegen/src/isa/aarch64/inst/emit.rs. Keeping the sequence as one VCode instruction prevented the register allocator from inserting spills or reloads between the adr instruction and the inline jump table.

Before commit 222ff3a65d, JTSequence emitted a b.hs default conditional branch at the very top of the sequence, followed immediately by N 4-byte jump-table offsets (jt_entries) and then the target basic blocks:

// cranelift/codegen/src/isa/aarch64/inst/emit.rs (vulnerable)
// Branch to default when condition code from prior comparison indicates.
let br =
    enc_conditional_br(BranchTarget::Label(default), CondBrKind::Cond(Cond::Hs));

let default_br_offset = sink.cur_offset();
sink.use_label_at_offset(default_br_offset, default, LabelUse::Branch19);
sink.put4(br);

// Overwrite the index with a zero when the above branch misspeculates (Spectre mitigation).

That generated the following machine-code layout:

cmp   w0, #N                      ; compare table index against N
b.hs  default                     ; 19-bit signed PC-relative branch (LabelUse::Branch19)
csel  x11, xzr, x0, hs
csdb
adr   x10, jt
ldrsw x11, [x10, x11, uxtw #2]
add   x10, x10, x11
br    x10
jt:
  .word target_0 - jt             ; 4 bytes per br_table target
  .word target_1 - jt
  ...

In the A64 instruction set, B.cond (LabelUse::Branch19) has a 19-bit signed immediate field counting 4-byte instructions, giving a maximum forward reach of 2^18 - 1 = 262,143 instructions (1,048,572 bytes). Because the inline jump table jt sits directly between b.hs default and the default block label, a single br_table with more than ~256,000 targets pushes default past +1 MiB. Moreover, because JTSequence is emitted as an indivisible block, Cranelift’s MachBuffer island emitter cannot insert a branch veneer inside the table. When the label offset wraps the 19-bit field during relocation patching, b.hs jumps into the middle of the jump table or compiled guest code at an attacker-chosen offset.

The fix in 222ff3a65d removes the conditional branch from JTSequence altogether and enforces an explicit compiler limit of MAX_BR_TABLE_TARGETS = 65520 (matching V8 and SpiderMonkey):

--- a/cranelift/codegen/src/isa/aarch64/inst.isle
+++ b/cranelift/codegen/src/isa/aarch64/inst.isle
@@ -5194,14 +5194,18 @@
 ;; Helper for emitting `MInst.JTSequence` instructions.
 ;; Emit the compound instruction that does:
 ;;
-;; b.hs default
-;; csel rB, xzr, rIndex, hs
+;; mov rB, #N            ; N = number of non-default targets
+;; csel rB, rIndex, rB, lo ; clamp out-of-bounds indices to N
 ;; csdb
 ;; adr rA, jt
 ;; ldrsw rB, [rA, rB, uxtw #2]
 ;; add rA, rA, rB
 ;; br rA
-;; [jt entries]
+;; jt: [N target entries] [default entry]
+;;
+;; The flags must be set by a comparison of `rIndex` against `N`.
+;; There is deliberately no conditional branch to the default target:
+;; its range would be limited, and the inline jump table may be large.

Instead of branching around the table on an out-of-bounds index, Cranelift now appends the default target as the final entry in jt (at index N) and uses csel rB, rIndex, rB, lo to clamp any index >= N to N. Both wasmtime-cranelift and winch-codegen also reject any Operator::BrTable with targets.len() > 65520 before reachability analysis.

2. CVE-2026-115889: silent 24-bit overflow in ValueDataPacked SSA value packing

To keep Cranelift’s IR compact, cranelift/codegen/src/ir/dfg.rs packed each SSA ValueData entry (Inst, Param, Alias, Union) into a single 64-bit integer (struct ValueDataPacked(u64)) with a 2-bit tag, a 14-bit Type, and two 24-bit operands x and y:

// cranelift/codegen/src/ir/dfg.rs in Wasmtime < 36.0.19 and 37.0.0..42.x
///        | tag:2 |  type:14        |    x:24       | y:24          |
struct ValueDataPacked(u64);

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
    }
}

fn decode_narrow_field(x: u32, bits: u8) -> u32 {
    if x == (1 << bits) - 1 { 0xffff_ffff } else { x }
}

Look at what happens in a release build (cfg(not(debug_assertions))) when a large or crafted WebAssembly function allocates 2^24 - 1 = 16,777,215 (0x00ff_ffff) or more instructions, blocks, or SSA values:

  1. debug_assert!(x < max) is compiled out completely.
  2. When x == 0x00ff_ffff (2^24 - 1), encode_narrow_field(x, 24) returns 0x00ff_ffff, which decode_narrow_field() unpacks as 0xffff_ffff (the reserved_value sentinel).
  3. When x >= 0x0100_0000 (2^24), encode_narrow_field() returns x unchanged without masking it to 24 bits. Inside ValueDataPacked::make(), (x as u64) << 24 shifts bit 24 and higher directly into the type:14 field (or from y into x), corrupting both the SSA value’s IR Type and its referenced instruction or alias index.

Because optimization passes (such as e-graph / GVN alias resolution and bounds-check elimination) trust the Type and SSA value indices stored in ValueDataPacked, corrupting those fields lets a crafted Wasm function induce type confusion in Cranelift IR and escape the sandbox.

Commit ba8f7d70ca (landed in Wasmtime 43.0.0 via PR #12613 and backported to 36.0.19 in PR #14818) deletes encode_narrow_field() and decode_narrow_field() and replaces the 64-bit bitfield with a 10-byte packed struct that stores full 32-bit x and y indices:

#[repr(Rust, packed)]
struct ValueDataPacked {
    x: u32,
    y: u32,
    flags_and_type: u16,
}

3. CVE-2026-115892 and wasmtime-wasi-http header framing fixes

  • CVE-2026-115892 (GHSA-9pvw-hwfg-92h5): When a host embedder created a Component Model StreamReader<T> or FutureReader<T>, passed it into a guest, received it back as a type-erased StreamAny or FutureAny (or Val::Stream / Val::Future), and then lowered it into another guest function expecting a different payload type, FutureAny::lower_to_index and StreamAny::lower_to_index missed the payload-equivalence check for host-originated handles (TransmitOrigin::Host) and triggered a host panic!. Commit 48dacc7624 centralizes the check inside typecheck_lowered_transmit() in futures_and_streams.rs so lowering returns a clean Err ("future or stream payload types differ").
  • GHSA-r8xh-cgf7-392j and GHSA-9prg-84vf-g5fc (wasmtime-wasi-http): FieldMap::append() and set() previously allowed a WASI guest to attach multiple conflicting Content-Length headers (Content-Length: 5 and Content-Length: 6) while get_content_length() only inspected the first one (headers.get(CONTENT_LENGTH)), enabling HTTP request smuggling through downstream proxies. In addition, on wasip3, splicing a body into a new response did not enforce self.sent == self.limit before reporting is_end_stream(). FieldMap and get_content_length() now reject any message with more than one Content-Length header value.

Detection and remediation

1. Audit Cargo.lock for vulnerable wasmtime and cranelift-codegen versions

Run the following commands across your Rust workspaces:

# Check resolved versions of wasmtime, cranelift-codegen, and wasmtime-wasi-http
cargo tree -i wasmtime -i cranelift-codegen -i wasmtime-wasi-http 2>/dev/null || true

rg -n -A 3 '^name = "(wasmtime|cranelift-codegen|wasmtime-wasi-http)"' Cargo.lock 2>/dev/null

# If cargo-audit is installed, scan against the updated RustSec database (RUSTSEC-2026-0378..0380)
cargo audit 2>/dev/null || true

2. Upgrade to patched releases and clear precompiled .cwasm caches

  1. Upgrade to a patched release line:
    • On the 49.x line: upgrade to wasmtime >= 49.0.4 (cranelift-codegen >= 0.136.4).
    • On the 48.x line: upgrade to wasmtime >= 48.0.7 (cranelift-codegen >= 0.135.7).
    • On the 36.x LTS line: upgrade to wasmtime >= 36.0.19 (cranelift-codegen >= 0.123.19).
    • If you are on an unmaintained intermediate release (37.x through 47.x), upgrade to 48.0.7, 49.0.4, or 50.0.0.
  2. Invalidate serialized / AOT-compiled .cwasm artifacts compiled from untrusted modules: Because CVE-2026-115893 and CVE-2026-115889 are code-generation bugs in Cranelift, any precompiled module (Engine::precompile_module / Module::serialize) produced by an unpatched compiler from an untrusted .wasm input already contains the miscompiled machine code. Recompile all cached artifacts after upgrading Wasmtime.
  3. If you implement a custom WasiHttpHooks::is_forbidden_header on wasip3 (GHSA-jxcf-8qw5-5c33): Also implement WasiHttpHooks::forbidden_header_filter on 48.0.7 / 49.0.4 (or migrate to 50.0.0, where forbidden_header_filter replaces is_forbidden_header) so custom forbidden headers are stripped from forwarded HTTP body trailers.

Timeline

  • 2026-02-23: Cranelift merges PR #12613 (ba8f7d70ca), replacing 24-bit ValueDataPacked(u64) bit-packing with full 32-bit x and y fields for Wasmtime 43.0.0.
  • 2026-10-19: Bytecode Alliance publishes wasmtime 36.0.19, 48.0.7, and 49.0.4 (followed by 50.0.0 on 20 October) with commits f517bd6e89, 222ff3a65d, and 994549891c.
  • 2026-10-23 (22:02 to 22:16 UTC): GitHub and NVD publish CVE-2026-115889 (GHSA-897f-g5gm-fmxm, CVSS 9.9 Critical), CVE-2026-115892 (GHSA-9pvw-hwfg-92h5), and CVE-2026-115893 (GHSA-mghf-wjfp-p5xm, CVSS 9.9 Critical).
  • 2026-10-24 (12:00 UTC): RustSec publishes RUSTSEC-2026-0378, RUSTSEC-2026-0379, and RUSTSEC-2026-0380.

From research to remediation

Check whether this pattern exists in your codebase

Turn this research into a remediation workflow. Scan dependencies and package manifests for similar supply-chain risk, then prioritize fixes with reachability context.

References