
深刻度は高 (sec high)。CWE 416。Firefox 154 および ESR 140.14 で修正済み。Bugzilla 2057308。通常の Web ページからユーザー操作なしで到達可能。
Firefox はデコード済みイメージサーフェスを SurfaceCache に保持する。キャッシュは所有元イメージへの生の非所有ポインタを保持する。サーフェスが追い出されると、キャッシュはデコーダスレッド上でそのイメージに通知する。この通知は mIsBeingDestroyed アトミックフラグを読み取り、その後にのみ RefPtr<RasterImage> image = this を取得する。フラグの読み取りと参照の取得は別々の操作である。メインスレッドはその間に最後の参照を解放できる。消滅しつつあるオブジェクトが復活させられ、解放された後に読み取られる。
Decoder thread (holds the SurfaceCache lock) Main thread
OnSurfaceDiscarded reads mIsBeingDestroyed = false
Release drops the refcount to zero and commits delete
~RasterImage sets the flag then blocks on the cache lock
RefPtr image = this (AddRef from zero to one)
queues the runnable and releases the lock
the destructor finishes and frees the 248 byte object
the runnable runs and reads freed mProgressTracker ==> use after free
以前の修正で追加されたフラグは競合ウィンドウを狭めるだけであり、完全には塞げない。フラグが false と観測されても、参照カウントがまだゼロ以上であることは証明されない。なぜなら、フラグはデストラクタ内で、カウントがすでにゼロに達した後でのみ設定されるからである。
このバグを担う2つの命令は、コンパイル後の関数の中に明確に現れている。フラグチェックと this への参照カウントのインクリメントは分離されている。

影響を受けるバージョンの ASAN ビルドを使用する。prefs.js のプリファレンスを適用してサーフェスキャッシュを極めて小さく保ち、デコーダスレッドが常にサーフェスを追い出し続けるようにする。uaf-reduced.html を読み込んでループさせたままにする。これはクロススレッドのタイミングレースであるため、再現は断続的になる。
AddressSanitizer: heap-use-after-free
READ of size 8, 8 bytes into a freed 248-byte RasterImage
use : RasterImage::OnSurfaceDiscardedInternal (main thread)
freed : RasterImage::Release() via imgRequest teardown
alloc : ImageFactory::CreateRasterImage() on the ImageIO thread
コンテンツプロセスにおけるヒープの use-after-free。Web ページから到達可能であり、クリックや権限は不要である。テストしたビルドでは制御された実行は実証されなかった。オブジェクトは復活し、その後2回目の解放が行われる。
OnSurfaceDiscarded はもはやイメージを参照しない。別途参照カウントされる ProgressTracker への強い参照を保持し、トラッカーの弱い逆参照を通じてメインスレッド上でイメージを回復する。回復には、生存している場合にアトミック加算を使用する。廃止されたフラグは削除される。
Abdulaziz Alasaiqah · https://azoz.my/writeups.html