
このディレクトリには、mozilla::image::RasterImage::OnSurfaceDiscarded におけるスレッド間 use-after-free の、誠実な再現用スキャフォールドが含まれています。これは、合成画像データのみを用いて、ローカルにダウンロードした脆弱な Firefox ビルドに対して実行することを意図しています。稼働中のシステムや第三者システムには一切触れません。
対応する PoC リポジトリ: https://github.com/defineid/Revenant
解説記事: このポートフォリオ内の writeup-rasterimage-uaf.html。
これらは元の調査中に、脆弱なビルド(libxul.so、Firefox 140.9.0esr)を逆アセンブルすることで確認されたものです。コミット済みのスクリーンショット assets/writeup-uaf-disasm.png がその分析結果です。関数名は、その正確なビルド向けの公式 Mozilla Breakpad シンボルファイルに由来します。同梱の libxul.so はストリップ済みです。これらは、この PoC パッケージを組み立てた環境では再逆アセンブルされていません(検証状況を参照)。
| 事実 | 証拠 | 出典 |
|---|---|---|
脆弱な関数は RasterImage::OnSurfaceDiscarded(SurfaceKey const&) | FUNC 3eba200 16c 0 mozilla::image::RasterImage::OnSurfaceDiscarded(...) | Mozilla シンボルサーバー .sym |
this+0xC0 での早期バイト読み取り(解説記事: mIsBeingDestroyed) | 3eba20a: cmp BYTE PTR [rdi+0xc0],0x0(rdi = this) | objdump -d libxul.so |
this+0x40 の RasterImage 参照カウントに対する AddRef | 3eba25a: lock inc QWORD PTR [rbx+0x40](2 回発行)、rbx = this | objdump -d libxul.so |
0x20 バイトの runnable が割り当てられ、this をキャプチャする | 3eba264: mov edi,0x20 → call moz_xmalloc@plt、3eba280: mov QWORD PTR [rax+0x10],rbx | objdump -d libxul.so |
runnable がディスパッチされ、その後 this が Release される | 3eba298: call QWORD PTR [rcx+0x28](仮想ディスパッチ)、3eba29b: lock dec QWORD PTR [rbx+0x40] | objdump -d libxul.so |
参照カウントが 0 に達すると ~RasterImage が実行され、その後 free される | je 3eba345 → call 3ebbe90 <~RasterImage> → call free@plt | objdump + .sym |
ディスパッチされた runnable は OnSurfaceDiscarded::$_0 ラムダをキャプチャする | FUNC 5279d90 ... RunnableFunction<...OnSurfaceDiscarded(...)::$_0>::Run() | Mozilla シンボルサーバー .sym |
| フィールド | 値 |
|---|---|
| CVE | CVE-2026-74943 |
| コンポーネント | Core / Graphics: ImageLib |
| クラス | Use-After-Free (CWE-416) |
| 深刻度 | sec-high(Web から到達可能なメモリ安全性の問題) |
| 修正済みバージョン | Firefox 154 · ESR 140.14 |
| バグ | bugzilla.mozilla.org/show_bug.cgi?id=2057308(セキュリティ制限あり) |
SurfaceCacheImpl::Remove は、デコード済みサーフェスが破棄されたことを所有元のイメージに通知します。これは、サーフェスキャッシュのミューテックスを保持したまま、デコーダ / ImageIO スレッド上で、生の非所有 ImageKey ポインタを介して OnSurfaceDiscarded を呼び出すことで行われます。脆弱なビルドでは、このハンドラは次のように動作します:
this+0xC0 の 1 バイトのフラグを早期に読み取ります(cmp BYTE PTR [rdi+0xc0],0x0)。解説記事ではこれを mIsBeingDestroyed と特定しており、これは以前の緩和策です。this+0x40 の RasterImage 参照カウントをインクリメントすることで、this への強い参照を取得します(lock inc QWORD PTR [rbx+0x40])。これは単純な add-ref であり、非ゼロ時のみ加算するアトミックな add-ref-if-nonzero ではありません。moz_xmalloc(0x20) で this をキャプチャする RunnableFunction を割り当て(mov QWORD PTR [rax+0x10],rbx)、グローバルイベントターゲットにディスパッチし(call QWORD PTR [rcx+0x28])、その後ローカル参照を解放します(lock dec QWORD PTR [rbx+0x40])。フラグの読み取りとカウントのインクリメントは、ロックフリーの参照カウントに対する2 つの別個の操作であるため、メインスレッドが最後の参照をドロップする瞬間をまたぐ可能性があります。するとデコーダスレッドはゼロから AddRef を行い、デストラクタがすでに確定してしまったオブジェクトを復活させます。デストラクタが完了して RasterImage を free すると、キューに入れられた runnable が後でメインスレッド上で実行され、解放済みのストレージを読み取ります。
修正では、画像を直接参照することをやめます。OnSurfaceDiscarded は、別途参照カウントされる ProgressTracker への強い参照を保持し、トラッカーの弱い逆参照を通じてメインスレッド上で画像を復元し、アトミックな add-ref-if-live で昇格させます。mIsBeingDestroyed フラグは削除されます。
コンテンツプロセス内の通常の Web ページが、画像リクエストを所有するフレームを解放しながら、サーフェスキャッシュへの圧力(多数の破棄可能なデコード済みサーフェス)を発生させます。クリック、権限、拡張機能、設定変更は一切不要です。競合は、デコーダスレッドの破棄通知と、同じ画像のメインスレッドによる最終解放との間で発生します。
https://ftp.mozilla.org/pub/firefox/releases/140.9.0esr/ からダウンロードしてください。
ASAN ビルド(Nightly asan またはローカルの --enable-address-sanitizer ビルド)を使用すると、use-after-free が時折発生するサイレントクラッシュではなく、明確なレポートとして観測できるようになります。<canvas> を使って独自の画像を生成し、ネットワークからは何も読み込みません。脆弱なビルドで trigger.html を開きます。理想的には file:// パス、または自分で管理するローカル静的サーバーから開いてください:
/path/to/firefox --new-instance --profile ./poc-profile ./trigger.html
デフォルトでは、ドライバはデコード/解放の重なりを最大化するために、タイトなマイクロタスクループを実行します。URL に ?tick=N を付加すると、譲歩型の setTimeout(N) ループに切り替わり、同じくらい激しく回転しつつイベントループに息継ぎの余地を与えます。これにより、自動化ドライバが実行中にページをステップ実行してスクリーンショットを撮ることが可能になります。コミット済みの trigger-running.png はまさにその方法で撮影されたもので、ローカルの 140.11.0esr ビルドを Marionette 経由で ?tick=4 を付けて駆動しています。ハーネスがライブで動作している様子(サイクル数とティアダウン数が増加中)が写っています。このビルドはパッチ済みのためクラッシュは発生せず、画像はドライバが動作していることだけを証明しています。ヘッドレスではオフスクリーン画像が描画されないため、「images decoded」カウンタはそこで 0 のままです。ヘッド付きの脆弱なビルドでは画像がデコードされ、そこでレースが実行されます。
ページを実行させ続けてください。ページは多数の異なる大きな画像を繰り返しデコードし(サーフェスキャッシュを埋めて強制的にエビクションさせるため)、それらを所有する DOM サブツリーを破棄します(所有する画像の最終解放を駆動するため)。これにより、デコーダスレッド上の「サーフェス破棄」とメインスレッド上の「最終解放」の重なりが最大化されます。レースは確率的なものです。実行したままにしてください。
heap-use-after-free が発生し、その READ フレームが RasterImage::OnSurfaceDiscardedInternal / ディスパッチされたラナブル内にあり、freed フレームが RasterImage::Release のティアダウン内にあります。ASAN なしでは、断続的なコンテンツプロセスのクラッシュとして現れることがあります。ProgressTracker の弱い後方参照を経由し、破棄がすでに確定している場合は通知を破棄します。libxul.so(140.9.0esr)を逆アセンブルし、上記の最初の表にあるすべてのバイナリレベルの事実を、公式の Mozilla シンボルファイルとバイト単位で照合して確認しました。コミット済みの assets/writeup-uaf-disasm.png がその作業結果です。ライトアップからの 3 つの構造的主張(早期の this+0xC0 フラグ読み取り、this+0x40 での lock inc による AddRef、this をキャプチャしてディスパッチしその後 Release する moz_xmalloc(0x20) ラナブル)はすべて成立しています。これは、すでにパッチ済みの CVE に関するライトアップのために、合成された自己生成データを用いてローカルにダウンロードしたビルドを対象としています。稼働中または第三者のサービスを攻撃するものではなく、欠陥をトリガーする以上の悪用プリミティブを含みません。