Moderate firefox UAF 🔧 Commit mapped

Overview

Moderate
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionUse-after-free in the Graphics component
ComponentGraphics
Bug ClassUAF
Tracker2078904
Fix commit0f295923dfb7 (firefox) +6/-1
CISA KEVNot listed
CreditedMozilla
Disclosed2026-10-27

Background

`ExtractSubrect`
Returns a new SourceSurface holding a copy of a sub-rectangle of this surface’s pixels; it reads mImage and copies out of it.
`mChangeMutex`
A mutex guarding mutations/releases of the surface’s backing state. Skia snapshot pixels must stay valid for the duration of a read.
Snapshot stability
A SkImage snapshot can be invalidated/released by another thread (e.g. when the owning draw target changes or is destroyed); reading its pixels concurrently with release is a UAF.

Root Cause Analysis

ExtractSubrect checked !mImage and then proceeded to build an SkImageInfo and copy pixels out of mImage without holding mChangeMutex. Because the backing SkImage can be released/replaced on another thread, mImage could be non-null at the check yet freed before/while the copy reads its pixels — a classic time-of-check/time-of-use race that dereferences freed snapshot memory. The patch reorders and locks: it first validates the rectangle (aRect.IsEmpty() || !GetRect().Contains(aRect)), then acquires MutexAutoLock lock(mChangeMutex), and only then re-checks !mImage and performs the copy. The comment states the intent directly: “Shared snapshot pixels must remain stable until the copy completes.” Holding the lock across the null-check and the pixel copy prevents another thread from releasing mImage mid-read.

The advisory labels this a use-after-free in Graphics, which the diff supports: the missing lock permits a concurrent release of the backing image during the extract/copy. The bug is fundamentally a data race whose sink is a use-after-free of the Skia snapshot buffer.

Key insight
ExtractSubrect read and copied the shared SkImage without mChangeMutex, so another thread could release the snapshot between the null-check and the copy, freeing pixels being read. The fix takes the lock around the check-and-copy.

Attack Path

  1. Create Script/graphics activity produces a SourceSurfaceSkia snapshot (e.g. canvas/2D surface) whose mImage is shared.
  2. Race One thread calls ExtractSubrect while another thread releases/replaces mImage (surface change or destruction). (Thread interleaving inferred; the diff shows the missing lock.)
  3. TOCTOU ExtractSubrect passes the !mImage check, then the other thread frees the backing image.
  4. Use-after-free The copy reads pixels from the freed SkImage buffer.
  5. Primitive Read of freed heap memory (potential info-leak/corruption); controlled exploitation is inferred, not shown by the diff.

Impact Assessment

This runs in a graphics context (content or GPU/compositor process, inferred). The primitive is a concurrent use-after-free of a Skia snapshot buffer during ExtractSubrect, i.e. reading freed memory, which can leak or corrupt heap state. No privilege beyond driving the relevant surface/extract operation is needed; the exploit requires winning the release-vs-copy race (a timing precondition). Strength beyond a UAF read is inferred. The advisory rates it moderate.

Changed Functions

FunctionChangeNotes
if
gfx/2d/SourceSurfaceSkia.cpp
modified

Files Changed

  • gfx/2d/SourceSurfaceSkia.cpp

Audit Directions

  • Other `mImage` accesses
    Audit all reads of mImage / pixel accessors in SourceSurfaceSkia (and sibling SourceSurface backends) to confirm they hold mChangeMutex wherever the image can be released concurrently.
  • Snapshot lifetime across threads
    Review Moz2D surface-change / destruction paths that can release a SkImage while another thread reads it (CopyTo, GetDataSurface, draw-target snapshots).
  • Lock-ordering around copies
    Look for other “check then copy” sequences in gfx/2d that validate a pointer and then dereference it without a lock.
diff --git a/gfx/2d/SourceSurfaceSkia.cpp b/gfx/2d/SourceSurfaceSkia.cpp
index cb751bdc6f1..9bdfde84c64 100644
--- a/gfx/2d/SourceSurfaceSkia.cpp
+++ b/gfx/2d/SourceSurfaceSkia.cpp
@@ -160,7 +160,12 @@ bool SourceSurfaceSkia::InitFromImage(const sk_sp<SkImage>& aImage,
 
 already_AddRefed<SourceSurface> SourceSurfaceSkia::ExtractSubrect(
     const IntRect& aRect) {
-  if (!mImage || aRect.IsEmpty() || !GetRect().Contains(aRect)) {
+  if (aRect.IsEmpty() || !GetRect().Contains(aRect)) {
+    return nullptr;
+  }
+  // Shared snapshot pixels must remain stable until the copy completes.
+  MutexAutoLock lock(mChangeMutex);
+  if (!mImage) {
     return nullptr;
   }
   SkImageInfo info = MakeSkiaImageInfo(aRect.Size(), mFormat);
Loading diff…