High firefox UAF 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionUse-after-free in the Graphics: ImageLib component
ComponentImageLib
Bug ClassUAF
Tracker2057308
Fix commit55dc28955415 (firefox) +10/-12
CISA KEVNot listed
CreditedAbdulaziz Alasaiqah
Disclosed2026-08-18

Background

`RasterImage`
Gecko’s ImageLib class representing a decoded raster (bitmap) image and owner of its decoders and cached surfaces.
`ProgressTracker`
A separately reference-counted helper object that tracks an image’s load/decode progress and holds a back-reference to its Image.
`OnSurfaceDiscarded`
A RasterImage callback invoked when the surface cache evicts one of the image’s decoded surfaces, which then dispatches work to the main thread.
Object resurrection
Taking a new RefPtr to an object whose refcount has already reached zero (i.e. one being destroyed), briefly reviving it and triggering a second destruction.

Root Cause Analysis

RasterImage::OnSurfaceDiscarded captured this into a strong RefPtr<RasterImage> image = this and enqueued a main-thread runnable that later called image->OnSurfaceDiscardedInternal(...). Because surface discard can occur while the RasterImage is being torn down (its destructor completes the mSourceBuffer and terminates outstanding decoders), taking a RefPtr to this at refcount zero resurrects a dying object, and dropping that RefPtr when the runnable finishes drives a second destruction, yielding a use-after-free/double-free. The prior guard, an Atomic<bool> mIsBeingDestroyed set at the top of ~RasterImage() with an early return in OnSurfaceDiscarded, was a fragile check-then-act mitigation with a race window between reading the flag and constructing the strong self-reference.

The fix removes both the flag and the strong self-capture; instead the runnable captures a RefPtr<ProgressTracker> and re-derives the image via progressTracker->GetImage(), returning early when that yields null. Since GetImage() returns null once the image is gone, liveness is checked without ever resurrecting the object. This preserves the invariant that no strong reference to a RasterImage is created after its refcount has dropped to zero.

Key insight
The core mistake was capturing a strong RefPtr to this in a deferred runnable from a path reachable during destruction, so the fix routes liveness through the independently owned ProgressTracker and only reconstructs the image pointer if ProgressTracker::GetImage() still returns it.

Attack Path

  1. Trigger surface caching Cause a RasterImage to decode and populate the surface cache, ideally with an animated image so both static and animated surfaces exist.
  2. Force teardown and eviction to race Drop the last reference to the RasterImage (e.g. navigate away or remove the element) while the surface cache concurrently discards one of its surfaces.
  3. Invoke the vulnerable callback during destruction OnSurfaceDiscarded runs as the object is being destroyed and captures this into a RefPtr, resurrecting the zero-refcount object.
  4. Double destruction When the dispatched runnable completes and its RefPtr is released, the RasterImage is destroyed a second time, freeing memory still referenced elsewhere.
  5. Exploit the freed object Reclaim the freed allocation to influence subsequent dereferences of the stale RasterImage.

Impact Assessment

An attacker who can drive image decoding and teardown timing gains a use-after-free of a RasterImage in the content/graphics context where ImageLib runs. Successful reclamation of the freed memory can corrupt heap state and potentially lead to controlled memory access; the precondition is triggering surface discard concurrently with the image’s final release, which web content can influence via navigation, DOM manipulation, and animated/large images that stress the surface cache.

Changed Functions

FunctionChangeNotes
if
image/RasterImage.cpp
modified

Files Changed

  • image/RasterImage.cpp
  • image/RasterImage.h

Audit Directions

  • Strong self-capture in deferred work
    Audit lambdas/runnables that capture this or RefPtr<Self> on paths reachable from destructors or decoder-teardown; prefer routing liveness through a separately owned tracker.
  • Flag-based destruction guards
    Treat Atomic<bool>/mIsBeingDestroyed-style check-then-act guards as suspect, since the gap between the read and taking a reference is a classic resurrection race.
  • Surface-cache callbacks
    Review other image/ callbacks such as surface-discard and decode-completion notifications for the same pattern of extending object lifetime during shutdown.
diff --git a/image/RasterImage.cpp b/image/RasterImage.cpp
index 25cd5d034db..318d72e061c 100644
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -76,8 +76,6 @@ RasterImage::RasterImage(nsIURI* aURI /* = nullptr */)
 
 //******************************************************************************
 RasterImage::~RasterImage() {
-  mIsBeingDestroyed = true;
-
   // Make sure our SourceBuffer is marked as complete. This will ensure that any
   // outstanding decoders terminate.
   if (!mSourceBuffer->IsComplete()) {
@@ -486,19 +484,21 @@ RasterImage::WillDrawOpaqueNow() {
 void RasterImage::OnSurfaceDiscarded(const SurfaceKey& aSurfaceKey) {
   MOZ_ASSERT(mProgressTracker);
 
-  if (mIsBeingDestroyed) {
-    return;
-  }
-
   bool animatedFramesDiscarded =
       aSurfaceKey.Playback() == PlaybackType::eAnimated;
 
   nsCOMPtr<nsIEventTarget> eventTarget = do_GetMainThread();
 
-  RefPtr<RasterImage> image = this;
-  nsCOMPtr<nsIRunnable> ev =
-      NS_NewRunnableFunction("RasterImage::OnSurfaceDiscarded", [=]() -> void {
-        image->OnSurfaceDiscardedInternal(animatedFramesDiscarded);
+  RefPtr<ProgressTracker> progressTracker = mProgressTracker;
+  nsCOMPtr<nsIRunnable> ev = NS_NewRunnableFunction(
+      "RasterImage::OnSurfaceDiscarded",
+      [progressTracker, animatedFramesDiscarded]() -> void {
+        RefPtr<Image> image = progressTracker->GetImage();
+        if (!image) {
+          return;
+        }
+        static_cast<RasterImage*>(image.get())
+            ->OnSurfaceDiscardedInternal(animatedFramesDiscarded);
       });
   eventTarget->Dispatch(ev.forget(), NS_DISPATCH_NORMAL);
 }
diff --git a/image/RasterImage.h b/image/RasterImage.h
index 7b7bba84196..30e90640dfb 100644
--- a/image/RasterImage.h
+++ b/image/RasterImage.h
@@ -380,8 +380,6 @@ class RasterImage final : public ImageResource,
   // This is currently only used for statistics
   int32_t mDecodeCount;
 
-  Atomic<bool> mIsBeingDestroyed{false};
-
 #ifdef DEBUG
   uint32_t mFramesNotified;
 #endif
Loading diff…