Skip to content

Latest commit

 

History

History

README.md

CVE-2026-2796 - WebAssembly JIT Type Confusion via call.bind

Found by: Claude (Anthropic) during automated Firefox source code review Affects: Firefox < 148, Thunderbird < 148 Severity: Critical (RCE from web content) Write-up: https://red.anthropic.com/2026/exploit/

Vulnerability

MaybeOptimizeFunctionCallBind() in WasmInstance.cpp unwraps Function.prototype.call.bind(wasmExport) without verifying that the inner function's Wasm type signature matches the import's declared type. This allows type confusion between Wasm function signatures at instantiation time.

When Module A declares an import with type (externref) -> (i64) and receives a call.bind-wrapped export from Module B with type (externref) -> (externref), the JIT strips the wrapper and links Module B's function body directly. Module A then interprets the externref return value as i64, leaking the raw object pointer.

Files

File Description
trigger.html Basic type confusion trigger (Stage 0 only)
exploit-chain.html Full exploitation chain (Stages 0-6)
setup-firefox.sh Download Firefox 147 on Linux
setup-firefox.ps1 Download Firefox 147 on Windows

Quick Start

# Linux
chmod +x setup-firefox.sh
./setup-firefox.sh

# Run the trigger
./firefox-147.0/run-exploit.sh trigger.html

# Run the full chain
./firefox-147.0/run-exploit.sh exploit-chain.html
# Windows
powershell -ExecutionPolicy Bypass -File setup-firefox.ps1

# Run
.\firefox-147.0\run-exploit.bat exploit-chain.html

Or serve over HTTP:

python3 -m http.server 8080
# Open http://localhost:8080/exploit-chain.html in Firefox 147

Exploitation Chain

Stage Primitive Technique
0 Type confusion trigger call.bind bypass - f(1337) returns 1337 instead of 0
1 addrof Module B: (externref)->externref, Module A declares (externref)->i64. Externref return reinterpreted as i64 leaks raw heap pointer
2 fakeobj Reverse: Module B: (i64)->i64, Module A declares (i64)->externref. i64 return reinterpreted as externref forges object reference
3 Arbitrary read Forge a fake Float64Array with controlled dataPointer, read through it
4 Arbitrary write Same forged TypedArray technique, write through fake[0] = value
5 Fake ArrayBuffer Forge ArrayBuffer with controlled backing store for byte-granular R/W
6 Code execution Leak Wasm JIT code address, overwrite entry point or write shellcode to RWX region

Root Cause (Patch Diff)

In js/src/wasm/WasmInstance.cpp, the fix adds a type signature check:

// Before (vulnerable):
if (IsCallBind(importFun)) {
  unwrapped = GetCallBindTarget(importFun);
  // Used unwrapped directly without type check
}

// After (patched, Firefox 148):
if (IsCallBind(importFun)) {
  unwrapped = GetCallBindTarget(importFun);
  if (!CheckWasmSignatureMatch(unwrapped, expectedType)) {
    // Reject - type mismatch
    return false;
  }
}

Expected Results

Firefox 147 (vulnerable):

  • Stage 0: result = 1337 (call.bind wrapper stripped)
  • Stage 1: addrof returns valid BigInt heap addresses
  • Stage 2: fakeobj(addrof(obj)) === obj round-trip works
  • Stages 3-6: Arbitrary R/W and code execution

Firefox 148+ (patched):

  • Stage 0: result = 0 (call.bind semantics preserved) or type trap
  • Stages 1-6: Type confusion does not trigger, primitives non-functional

Notes

  • The exploit requires Wasm GC support (struct types, externref) which is available in Firefox 147. The call.bind trigger itself works on any Firefox < 148 with basic Wasm support.
  • SpiderMonkey object layout offsets (Shape*, data pointer, etc.) are build-specific. The exploit-chain.html includes common offsets for release builds; debug builds may differ.
  • On aarch64, pointer tagging may affect address interpretation. The addrof/fakeobj primitives handle both x86-64 and aarch64 pointer formats.