
CVE-2026-15718 向け PoC エクスプロイトチェーン:SpiderMonkey wasm ベースラインコンパイラにおける array.fill の同期欠落 -> 無効なポインタ -> addrOf/fakeobj -> 任意のR/W -> RCE
CVE-2026-15718 の概念実証(PoC)エクスプロイトチェーン。これは Mozilla SpiderMonkey の WebAssembly baseline コンパイラ(「JavaScript: WebAssembly」コンポーネント)における sync() 欠落による誤コンパイルです。単一のゼロ回実行 array.fill が fakeobj プリミティブを生み出し、チェーンはホストプロセスの任意読み取り/書き込みと任意のネイティブコード実行(posix_spawnp("/bin/sh", ...))に昇格します。
4441102e62e8("Bug 2045443"、1行)BaseCompiler::emitArrayFill()(js/src/wasm/WasmBaselineCompile.cpp)は、最初に を呼び出さずに フィルループを発行します:
sync()array.fill if (elementType.isRefRepr()) {
freePtr(RegPtr(PreBarrierReg));
}
+ sync();
+
// Perform the fill loop using `numElements` as the loop variable ...
sync() は baseline コンパイラの値スタック(stk_)をレジスタからマシンスタックへフラッシュします。フィルループの各反復における不正確なポストバリア(emitPostBarrierEdgeImprecise)は内部で sync() を呼び出します — ただし、ループが実行された場合に限ります。ループの出口ラベル(done)は2つの経路の合流点です:
stk_ は同期済み — すべてのオペランドはマシンスタックスロットに存在します;numElements == 0): stk_ は未変更 — オペランドは依然としてレジスタのみに存在します。合流後のコンパイラの静的モデルは常に同期済みの経路を反映するため、array.fill の後に発行されたコードは、ゼロ回実行では決して書き込まれなかったスタックスロットからオペランドを読み取ります。このようにして読み戻された reftype オペランドは無効(陳腐化)ポインタ(CWE-763)です。
このデシンクは、フレームが同一の2つの wasm 関数間で双方向に発生します:
n = 1, ループ実行): ループ内の sync() がスロットに値を書き込みます — 実参照(addrOf)または制御された i64(fakeobj セットアップ);n = 0, ゼロ回実行): スロットは決して書き込まれません; 対になる関数が陳腐化したスロットを読み戻します — i64 として(リーク)、または (ref $t) として(fakeobj)。| File | Stage | Result |
|---|---|---|
poc-min.js (+ gen_trigger.py) | デシンクデモ | 制御経路は正しい値を返します; ゼロ回実行経路は、一度も書き込まれていないスロットから読み取ったガベージポインタを返します |
chain.js (+ gen_chain.py, chain.tpl.js, build.sh) | フルチェーン | addrOf + fakeobj → 偽の WasmArrayObject → 任意R/W → XULベースリーク → 実行時Mach-Oインポートウォーク → 偽造funcref → posix_spawnp("/bin/sh", ["-c", "touch /tmp/CVE-2026-15718-PWNED"]) |
chain.js 内のステージ詳細:
plantSpray/plantCmd/plantFun/plantExt + leak — wasm 配列、funcref、JS オブジェクト(externref)に対する addrOf。sprayV + fakeRd/fakeWr — fakeobj; 偽の WasmArrayObject(numElements_ @+16、data_ @+24、インライン要素 @+40)。偽のウィンドウは、GC がスキャン途中で移動できないようアウトライン(malloc 済み)配列に保持されます。0xfeedfacf)を探します → XUL ベース。LC_SEGMENT_64/LC_SYMTAB/LC_DYSYMTAB + 間接シンボルテーブル)、_posix_spawnp の __stubs エントリを特定します。ハードコードされたオフセットはありません; スタブは最初の呼び出しで遅延バインドを実行します。typeDef @+0x40 は実在の ref.func からコピー、呼び出しターゲット @+0x38 = スタブアドレス)、call_ref を通じて posix_spawnp(pid, "/bin/sh", 0, 0, [sh, -c, cmd], 0) で呼び出します。152.0.6 より前のツリーからビルドされた xpcshell(または js シェル / ブラウザ)でテスト済み(ここでの参照ビルドは Firefox 149.0a1、2026-01-14):
# minimal desync demo
xpcshell poc/poc-min.js
# full chain (regenerates chain.js from the template + generator)
(cd poc && sh build.sh)
rm -f /tmp/CVE-2026-15718-PWNED
xpcshell poc/chain.js # creates /tmp/CVE-2026-15718-PWNED
ls -l /tmp/CVE-2026-15718-PWNED
ハーネスは javascript.options.wasm_optimizingjit=false を設定して、長時間の Mach-O スキャン中に baseline ティアを固定します(このバグは baseline 専用です; ブラウザではトリガーはティアアップしきい値を十分下回って実行されるため、ブラウザではこの設定は不要です)。tools/xul_slots.py は、XUL の名前付きスタブ/GOT スロットを一覧表示するオフライン検証ツールです(ランタイムウォーカーのクロスチェックに使用)。
オフセットは macOS arm64、この正確なツリー/ビルド向けです(WasmArrayObject { +16 numElements, +24 data_, +40 inline }、JSFunction ネイティブ @ +0x20、WasmFuncRef { +0x38/+0x48 呼び出しターゲット, +0x40 typeDef })。これらはすべてチェーンのセルフテストによって実行時に再導出されます; 構造レイアウトのみが仮定されます。PAC の懸念はありません(XUL は arm64、非 arm64e バイナリのため); 遅延バインドスタブは最初の呼び出しでターゲットを解決するため、dyld 共有キャッシュのオフセットは不要です。
この PoC はサンドボックス化されていないシェルプロセスで実行されます。実際の Firefox 攻撃では、チェーンはコンテンツプロセス内に到達し、macOS サンドボックスは posix_spawnp を拒否します — 完全な侵害には別個の OS サンドボックスエスケープバグが必要です(例: CVE-2026-2796-and-CVE-2026-2768-escape-the-wasm-box のステージ2分析を参照)。
javascript.options.wasm=false はトリガーベクターをブロックする。セキュリティ研究、教育、および防御的テストのみを目的としています。この脆弱性は現在の Firefox/Thunderbird リリースで修正済みです。所有していないシステム、または明示的なテスト許可を得ていないシステムに対して使用しないでください。