
Cadeia de exploit PoC para CVE-2026-2796: escape de sandbox WebAssembly do SpiderMonkey (confusão de tipo de assinatura -> R/W arbitrário -> RCE)
Cadeia de exploração (prova de conceito) para CVE-2026-2796, uma má-compilação JIT / confusão de tipos na otimização de importação WebAssembly do SpiderMonkey da Mozilla (componente "JavaScript: WebAssembly"). Um módulo wasm cuidadosamente elaborado obtém leitura/escrita arbitrária de todo o processo hospedeiro e execução arbitrária de código nativo, escapando da sandbox do WebAssembly.
e2acef67 ("Bug 2013165 - Fix import optimization")Quando um módulo wasm importa uma função JS, o SpiderMonkey aplica uma otimização em MaybeOptimizeFunctionCallBind (js/src/wasm/WasmInstance.cpp) que desencapsula imports da forma Function.prototype.call.bind(fn) e armazena diretamente como o callable do import. Ela não verificava se o valor vinculado é em si uma :
fn+ if (boundThis.toObject().is<JSFunction>() &&
+ boundThis.toObject().as<JSFunction>().isWasm()) {
+ return nullptr;
+ }
Com a verificação ausente, o import é tratado como uma função originalmente-wasm: o wrapper JS — e com ele a verificação de assinatura — é ignorado. Uma função wasm pode, portanto, ser invocada por meio de um tipo de import declarado que não corresponde ao seu tipo real. Os valores passam inalterados pelos registradores; apenas a interpretação deles muda (por exemplo, um i64 controlado pelo atacante é usado como um ponteiro GC (ref $t), e vice-versa).
| Arquivo | Estágio | Resultado |
|---|---|---|
poc-crash.js | Confusão de assinatura | i64.const 0xDEADBEEF desreferenciado como ponteiro funcref → SIGSEGV em 0xdeadbf2f |
poc-addrof.js | addrOf + fakeobj | Confusão em ambas as direções (i64 ↔ (ref $t)) → WasmArrayObject falso (numElements_ @+16, data_ @+24, elementos inline @+40) → leitura/escrita arbitrária em qualquer lugar do processo |
poc-recon.js | Reconhecimento de layout | ponteiro nativo de JSFunction @+0x20 → vazamento da base do binário; ponteiro typeDef de WasmFuncRef @+0x40 |
poc-forge.js | Fluxo de controle | funcref forjado passa na verificação de tipo call_ref; alvo de chamada carregado de [funcref+0x38] |
poc-rce.js | Execução de código | funcref forjado → system("touch /tmp/CVE-2026-2796-PWNED") via system() vazado (base do binário + entrada GOT @ base+0x11d47b0) |
A primitiva de confusão é obtida exatamente como no teste de regressão da própria Mozilla (js/src/jit-test/tests/wasm/regress/bug2013165.js): importe Function.prototype.call.bind(wasmExport) em um segundo módulo cuja declaração de import carrega uma assinatura diferente e, então, ref.func + call_ref.
# Código-fonte do Firefox @ 2fbc0748c460b38fc95407a3f14c41d12fb12026 (2026-01-14,
# nightly do Firefox 148 — anterior à correção). Qualquer revisão anterior à 148 funciona.
cd js/src
../../configure --enable-debug --enable-optimize --without-intl-api \
--enable-project=js # objdir, ex.: js/src/_obj
cd _obj && make -j8
# binário: dist/bin/js (informa "JavaScript-C149.0a1")
JS=/path/to/dist/bin/js
$JS poc/poc-crash.js # SIGSEGV em 0xdeadbf2f
$JS poc/poc-addrof.js # imprime [+] leitura arbitrária OK / escrita OK
$JS poc/poc-recon.js # despeja a memória de JSFunction / WasmFuncRef
$JS poc/poc-forge.js # trava com PC = canary plantado
rm -f /tmp/CVE-2026-2796-PWNED
$JS poc/poc-rce.js # cria /tmp/CVE-2026-2796-PWNED via system()
Em uma build corrigida (Firefox ≥ 148), poc-crash.js em vez disso lança TypeError: bad type — a verificação de assinatura é restaurada.
Os offsets são para macOS arm64 (shell js, esta revisão exata / flags de build exatas): WasmArrayObject { +16 numElements, +24 data_, +40 dados inline }, JSFunction nativo @ +0x20, WasmFuncRef { +0x40 typeDef, +0x38 alvo de chamada }. Eles são validados empiricamente em tempo de execução pelos autotestes dos PoCs; outras builds/arquiteturas exigem re-derivação (o PoC de reconhecimento automatiza a maior parte disso). Sem PAC em binários arm64 (não arm64e); regiões JIT não são graváveis no momento da chamada, então a cadeia sequestra um alvo de chamada existente em vez de injetar código.
Consulte docs/full-escape.md para a análise do estágio 2: escapar da sandbox do SO a partir da execução de código no renderer via CVE-2026-2768 (Bug 2014101, escrita OOB no IndexedDB do processo pai) — o "segundo bug" de um comprometimento completo do Firefox. A árvore vulnerável usada aqui também é anterior a essa correção.
Isto escapa da sandbox do motor wasm (confinamento de memória linear / GC) e produz execução de código nativo no processo atual. Em um ataque real ao navegador, isso aterrissa dentro do processo de conteúdo do Firefox; escapar da sandbox do SO (o "segundo bug": confusão de IPC ou exploit de kernel) é um problema separado e não faz parte deste PoC.
javascript.options.wasm=false bloqueia o vetor de disparo.Apenas para pesquisa de segurança, educação e testes defensivos. A vulnerabilidade está corrigida nas versões atuais do Firefox/Thunderbird. Não use contra sistemas que você não possui ou para os quais não tem autorização explícita para testar.