A misplaced guardrail on a 256-lane highway that sends traffic off a cliff instead of to the shoulder
CVE-2026-115893 is a compiler miscompilation bug in Cranelift, the default code-generation backend for Wasmtime, affecting only the aarch64 (ARM64) architecture. The WebAssembly br_table instruction is a multi-way branch — think of a giant switch statement. On aarch64, Cranelift used a conditional branch (b.hs) with a ±1 MiB offset range to redirect out-of-bounds indices to a default target. When a br_table contained more than ~256,000 entries, the inline jump table exceeded that range, causing the offset to *silently wrap*, sending execution to an attacker-controlled location within the compiled code region. Because the attacker fully controls the Wasm module (and thus the table size and contents), they can craft a module that deterministically escapes the Wasm sandbox and executes arbitrary host-level code. All Wasmtime versions supporting aarch64 since the initial aarch64 backend release in 2020 are affected — that is every version prior to 36.0.19, 48.0.7, and 49.0.4. Wasmer, which also uses Cranelift, released 7.5.1 with the same fix.
The vendor severity of CRITICAL 9.9 is honest and largely accurate. Wasmtime's entire value proposition is *sandboxing untrusted code*, so a sandbox escape is an existential failure of the product's security model. The CVSS vector (AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H) correctly captures Scope: Changed — the attacker breaks out of the Wasm guest into the host OS. The one place the 9.9 overstates risk is the implicit assumption that all deployments are on aarch64. In reality, a significant but not universal share of production Wasmtime runs on ARM64 (AWS Graviton, Azure Cobalt, Ampere Altra on edge nodes). x86_64 deployments are entirely unaffected. This architecture gate is the only meaningful friction point; everything else — crafting a malicious br_table, submitting it to a multi-tenant runtime — is trivially within attacker capability.
4 steps from start to impact.
Attacker obtains Wasm submission capability
- Target Wasmtime engine runs on aarch64 architecture
- Attacker has ability to submit/deploy a Wasm module (tenant account, plugin developer, etc.)
- If the Wasmtime instance only executes trusted, internally-built Wasm modules, the attacker has no submission vector
- x86_64 deployments are entirely immune — this is aarch64-only
Craft malicious Wasm module with oversized br_table
br_table instruction with more than ~256,000 branch targets. This is well within the Wasm spec (which allows up to 2^32 targets). The module passes all standard Wasm validation checks. No obfuscation is needed — the oversized table is a perfectly legal Wasm construct. Tools like wasm-tools or walrus can trivially generate this.- Knowledge of the vulnerability (the advisory and fix commit fully document the trigger condition)
- Some Wasm platforms may impose module size limits that could prevent very large br_tables, but 256k entries × 4 bytes ≈ 1 MB of table data, which is well within typical upload limits
Cranelift miscompiles the br_table on aarch64
JTSequence macro-instruction emits a b.hs conditional branch for the default case with a ±1 MiB reach. The inline jump table of 32-bit offsets for >256k targets exceeds this range. The branch offset silently wraps, causing the default-case branch to land at an incorrect, attacker-influenceable location within the compiled code region. The attacker controls the offset by choosing the exact number of table entries.- Wasmtime uses Cranelift backend (this is the default)
- Target is aarch64
- If an alternative backend (e.g., Winch) were used instead of Cranelift, the bug would not trigger, but Cranelift is the default and recommended backend for production
Out-of-bounds branch achieves sandbox escape
br_table size, the attacker can control which host-level compiled code is reached. From this point, the attacker has escaped the Wasm sandbox and is executing code with the full privileges of the Wasmtime host process — which on multi-tenant platforms typically has access to other tenants' data, network resources, and host OS capabilities.- The Wasmtime process has meaningful privileges (network access, filesystem, other tenant data)
- If the Wasmtime process itself runs in a secondary sandbox (e.g., a minimal container with seccomp), the blast radius of the escape is reduced but not eliminated
cargo update -p wasmtime or update your pinned version. For Wasmer users, upgrade to 7.5.1+. Verify with cargo audit using RUSTSEC-2026-0379.br_table with >200,000 targets. This can be done with wasm-tools validate plus a custom pass or a simple binary scan of the br_table immediate count. This is a fragile mitigation — the exact threshold depends on compile-time table layout — but provides a detection layer.- WAF / network-level filtering — Wasmtime is a library, not a network service. There is no HTTP endpoint to filter. The malicious Wasm module is submitted through the application's own upload mechanism, which varies per platform.
- Wasm validation / spec compliance checks — The oversized br_table is *valid WebAssembly*. Standard Wasm validators (
wasm-tools validate, browser engines) will not flag it. The bug is in the compiler, not the Wasm module. - Switching to Winch compiler backend — While Winch would avoid the Cranelift-specific bug, Winch is not production-ready and generates significantly slower code. This is not a practical compensating control for production workloads.
- ASLR / DEP on the host — The attacker's controlled branch lands within the already-mapped executable code region of the Wasmtime process. ASLR does not randomize offsets *within* a compiled code section, and DEP is irrelevant because the target is already executable code.
The supporting signals.
| In-the-wild exploitation | Not observed. Not listed on CISA KEV. No reports of active exploitation from Fastly, Fermyon, or other production operators as of 2026-10-27. |
|---|---|
| Proof-of-concept availability | No public PoC found. No repositories on GitHub named after CVE-2026-115893 or GHSA-mghf-wjfp-p5xm. Not indexed on pocindex.io. However, the fix commit (e61df1b) and advisory fully document the trigger condition (~256k br_table entries on aarch64), making PoC construction straightforward for a competent researcher. |
| EPSS | 0.00432 (low exploitation probability in the next 30 days). This reflects the niche runtime and lack of public exploit code, not the severity of successful exploitation. |
| KEV status | Not listed as of 2026-10-27. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H — 9.9. Scope: Changed is the key metric: the attacker escapes the Wasm sandbox (the authorization scope) into the host OS (a different scope). PR:L reflects the need for a tenant/developer account to submit Wasm. |
| Affected versions | All Wasmtime versions with aarch64 Cranelift support (since initial aarch64 support ~2020, before v1.0) through 36.0.18, 37.0.0–48.0.6, and 49.0.0–49.0.3. Wasmer is also affected via shared Cranelift dependency — fixed in Wasmer 7.5.1 (Cranelift 0.136.4). |
| Fixed versions | Wasmtime 36.0.19, 48.0.7, 49.0.4, and 50.0.0+. Wasmer 7.5.1. Distro backports: NixOS tracking in #579126. RustSec advisory RUSTSEC-2026-0379 enables cargo audit detection. |
| Downstream consumers | Fastly Compute, Fermyon Cloud, Shopify Functions, wasmCloud, Envoy Wasm filters (if using Cranelift on ARM64), and any Rust crate depending on wasmtime or cranelift-codegen on aarch64. |
| Scanning / exposure data | Wasmtime is a library/runtime, not a network service — it does not appear on Shodan/Censys/GreyNoise scans. Exposure depends entirely on the hosting platform's architecture. AWS Graviton adoption (~30%+ of new EC2) means a material fraction of cloud Wasm workloads run on aarch64. |
| Disclosure timeline | Reported by ilyas-mallah. Advisory published 2026-10-23 via GHSA-mghf-wjfp-p5xm. Patches released same day. RustSec advisory RUSTSEC-2026-0379 published concurrently. |
| Reporter | ilyas-mallah (credited in GHSA advisory) |
Sources.
Why this verdict
- Architecture gate is real but not disqualifying: The bug is aarch64-only, which removes all x86_64, s390x, and riscv64 deployments from scope. However, ARM64 adoption in cloud (AWS Graviton, Azure Cobalt, GCP Axion) is 30%+ of new compute instances, and edge/serverless Wasm platforms (Fastly Compute) preferentially deploy on ARM64 for cost efficiency. This narrows the population but does not drop below the 10% threshold for high-value-role installs.
- Role multiplier: Wasmtime's canonical deployment role IS the security sandbox for untrusted code — analogous to a hypervisor. In multi-tenant serverless platforms (Fastly Compute serving NYT, Stripe, GitHub, Shopify; Fermyon Cloud), Wasmtime is *the* isolation boundary between tenants. A sandbox escape in this role yields tenant-to-host compromise with blast radius of *all co-located tenants' data and code*. This is fleet-scale / supply-chain-scale impact. Because the bug is IN the sandbox runtime itself, and ≥10% of production Wasmtime installs occupy this canonical high-value role on aarch64, the verdict floor is CRITICAL.
- Trivial trigger, no PoC needed: The exploit condition (br_table with >256k entries) is a valid Wasm construct that passes all validators. The fix commit documents the exact mechanism. Any competent attacker can weaponize this without a published PoC. The EPSS of 0.004 reflects lack of a *published* exploit, not difficulty of exploitation.
- No exploitation yet provides small downward pressure: No KEV listing, no observed in-the-wild use, no public PoC. This justifies a 0.7-point reduction from the vendor's 9.9 but does not break the CRITICAL floor given the canonical role analysis.
- Scope: Changed is correctly applied: The Wasm sandbox and the host OS are distinct authorization scopes. The attacker crosses that boundary, which is the textbook definition of S:C in CVSS 3.1. This is not a theoretical scope change — it's the entire attack narrative.
Why not higher?
The vendor's 9.9 would be justified if all Wasmtime deployments were on aarch64 and accepting untrusted modules. The aarch64 restriction means a meaningful fraction of the install base (x86_64 deployments) is immune. Additionally, the absence of observed exploitation or public PoC provides a narrow window where defenders can patch before weaponization.
Why not lower?
This is a sandbox escape in a product whose *sole purpose* is sandboxing. The affected architecture (ARM64) is the preferred platform for the highest-value deployment pattern (multi-tenant serverless edge). The exploit trigger is trivially craftable within the Wasm spec. The attacker does not need any special access beyond what the product is designed to give them (Wasm module submission). Downgrading below CRITICAL would require evidence that <1% of production Wasmtime runs on aarch64, which contradicts Graviton/ARM64 cloud adoption data.
Crowdsourced verification payload.
Run this script on each host running Wasmtime or Wasmer on aarch64. It checks both the CPU architecture and the installed wasmtime/cargo dependency version. Requires uname and either wasmtime --version or access to a Cargo.lock file. No elevated privileges needed. Example: bash check_cve_2026_115893.sh or bash check_cve_2026_115893.sh /path/to/Cargo.lock
#!/usr/bin/env bash
# CVE-2026-115893 checker — Wasmtime Cranelift aarch64 br_table sandbox escape
# Usage: bash check_cve_2026_115893.sh [path/to/Cargo.lock]
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -euo pipefail
ARCH=$(uname -m)
STATUS="UNKNOWN"
# Only aarch64 is affected
if [[ "$ARCH" != "aarch64" && "$ARCH" != "arm64" ]]; then
echo "PATCHED — architecture is $ARCH (only aarch64/arm64 affected)"
exit 0
fi
echo "Architecture: $ARCH (affected platform)"
check_version() {
local ver="$1"
local major minor patch
IFS='.' read -r major minor patch <<< "$ver"
patch=${patch%%[-+]*} # strip pre-release suffixes
# Fixed in 36.0.19, 48.0.7, 49.0.4, 50.0.0+
if (( major >= 50 )); then
echo "PATCHED"
elif (( major == 49 )); then
if (( minor == 0 && patch >= 4 )); then echo "PATCHED"; else echo "VULNERABLE"; fi
elif (( major == 48 )); then
if (( minor == 0 && patch >= 7 )); then echo "PATCHED"; else echo "VULNERABLE"; fi
elif (( major >= 37 && major <= 47 )); then
echo "VULNERABLE" # no patch for 37.x-47.x; must upgrade to 48.0.7+
elif (( major == 36 )); then
if (( minor == 0 && patch >= 19 )); then echo "PATCHED"; else echo "VULNERABLE"; fi
elif (( major <= 35 )); then
echo "VULNERABLE" # all pre-36 versions with aarch64 support
else
echo "UNKNOWN"
fi
}
# Method 1: wasmtime CLI
if command -v wasmtime &>/dev/null; then
VER=$(wasmtime --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
if [[ -n "$VER" ]]; then
STATUS=$(check_version "$VER")
echo "wasmtime CLI version: $VER → $STATUS"
fi
fi
# Method 2: Cargo.lock (if path provided or found in cwd)
CARGO_LOCK="${1:-Cargo.lock}"
if [[ -f "$CARGO_LOCK" ]]; then
# Extract wasmtime version from Cargo.lock
VER=$(grep -A1 'name = "wasmtime"' "$CARGO_LOCK" | grep 'version' | head -1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
if [[ -n "$VER" ]]; then
CARGO_STATUS=$(check_version "$VER")
echo "Cargo.lock wasmtime version: $VER → $CARGO_STATUS"
if [[ "$CARGO_STATUS" == "VULNERABLE" ]]; then
STATUS="VULNERABLE"
elif [[ "$STATUS" != "VULNERABLE" ]]; then
STATUS="$CARGO_STATUS"
fi
fi
fi
# Method 3: cargo audit (if available)
if command -v cargo-audit &>/dev/null && [[ -f "$CARGO_LOCK" ]]; then
if cargo audit --file "$CARGO_LOCK" 2>/dev/null | grep -q 'RUSTSEC-2026-0379'; then
echo "cargo audit: RUSTSEC-2026-0379 detected"
STATUS="VULNERABLE"
fi
fi
echo ""
echo "=== RESULT: $STATUS ==="
case "$STATUS" in
PATCHED) exit 0 ;;
VULNERABLE) exit 1 ;;
*) echo "Could not determine wasmtime version. Check manually."; exit 2 ;;
esac