
CVE-2026-84118の概念実証。SpiderMonkey GCのuse-after-freeにより、範囲外読み取り/書き込みと潜在的なコード実行が発生します。決定的なSEGVトリガーと、任意読み取りおよびASLR回避への昇格に関する調査を含みます。
これはMozillaがCriticalをHighと分類した事例の一つです。もしそうであれば、誰かがFirefoxに元のバグ発見者への報酬の更新を依頼したくなるかもしれません。
要約:制御されたポインタに対してOOB Rプリミティブを実行し、そのポインタから上下に移動できます。制御されたポインタを配列に向ければ、面白いことができます。
PoCはSEGV部分のみを含むため、残りは配列を指して任意のWASMにジャンプするという楽しい冒険です。
Firefox/SpiderMonkey 154.0.1向けの決定論的なリリースビルドSEGV概念実証。155で修正済み(AtomMarkingの再設計)。Mozillaの深刻度:HIGH。
/path/to/release/js poc.js
154.0.1で再現:通常の最適化シェルで3/3(SIGSEGV、終了コード139)、リリース+gczealシェルで5/5、毎回同一のフォールト:
SEGV on address 0x000000000014 (READ)
JS::shadow::Zone::gcState() <- zone == nullptr, +0x14 offset
154はゾーンごとのアトムマークビットマップと参照追跡を混同しています。弱い参照のみで保持されているSymbol — ここではBaseline CacheIR ICスタブ内の弱いValueフィールドとして捕捉 — が未回収ゾーンから参照されている場合、ゾーンスコープのGCでアトムゾーンが回収されると再マークされることはありません。その結果、スイープ/移動されたセルへの古いエッジが残ります。
リリースビルドでの決定的なキルショットは2つ目の静的バグです:MightBeForwarded<JS::Symbol>はfalseにハードコードされているため、IsForwardedはチェックなしでfalseを返し(gc/Marking-inl.h:92-97)、それに依存するすべてのポインタ修正パスはエッジを静かにスキップします。アトムゾーンがコンパクションされると、強いエッジは正しく更新されますが、この弱いCacheIRエッジは被害Symbolの古いアドレスに恒久的にぶら下がったままになります。(デバッグビルドではAssertion failure: !t->isForwarded(), gc/Marking-inl.h:94として捕捉されます。)
PoCは再利用可能なヒープへの古いポインタを残すだけではありません — バッキングページをmunmapさせ、次の逆参照で即座にフォールトさせます。
Zone::availableChunks/fullChunks)であるため、アトムゾーンのチャンクにはアトムゾーンのアリーナのみが含まれます。チャンクの運命はアトム/Symbolのライフタイムだけで制御できます。c.eval(b)によりトップレベル呼び出しICがSymbolを弱いスタブフィールドに捕捉します;b = undefinedで最後の強いルートを破棄;スプレーを破棄;2回のゾーンスコープGC(gc("zone")、schedulezone(c) + schedulezone("") + gc("zone") — 空文字列はアトムゾーンをスケジュール)で死んだスプレーをスイープし、被害をマグネットアリーナにコンパクションし、弱エッジの修正をスキップし、被害のアリーナを解放します。そのチャンクは完全に空になります。gcparam("minEmptyChunkCount", 0)によりすべての空チャンクが期限切れ可能になります;sleep(0.5)中にBackgroundDecommitTaskがexpireEmptyChunkPool -> FreeChunkPool -> を実行 — 被害チャンクの真のmunmapです。(アリーナレベルのデコミットはのみ — 読み取り可能なゼロ — チャンク全体の解放のみがmunmap経路であり、ステップ1のチャンク分離が重要な理由です。)これは単なるDoSではありません。実証された昇格(PoCとログはGC-NDAY/およびCHAIN/研究ディレクトリにあります):
segv9):munmap後、minorgc()セミスペースフリップによりnurseryが解放されたVAを要求;生の高密度ダブルスプレーが攻撃者のqwordを古いアリーナヘッダーのzoneフィールドに書き込みます。スイープはその後attacker_ptr + 0x14を逆参照 — 0x424242424256で決定論的に3/3。js::Zone*がこのリフィルを通じて植え付けられました;コレクタは偽造アリーナメタデータをフォールトなしで消費し(1ビット摂動の制御植え付けはクラッシュ)、ASLR回避 -> UAF植え付けの構成を実証します。UnmapPagesmadvise(MADV_DONTNEED)gc()がJITデータをスイープ:sweepJitDataOnMainThread -> ICEntry::traceWeak -> TraceWeakCacheIRStubが古いSymbolポインタを読み取り、その後SweepingTracer::onEdge(gc/Marking.cpp:3195)が古いアドレスに対してチャンク演算を実行してアリーナヘッダーのzoneフィールドをロードします。ページは消滅/ゼロ化されているためzone == nullptrとなり、zone->isGCSweeping()がnullptr + 0x14(shadow::Zone::gcState)を読み取ります -> SIGSEGV。フォールト時、rcxは古いSymbolポインタを保持し、rdxはmunmapされたチャンクベースを保持します。