CVE-2026-117983
Overview
Background
- `ExtractSubrect`
- Returns a new
SourceSurfaceholding a copy of a sub-rectangle of this surface’s pixels; it readsmImageand 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
SkImagesnapshot 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.
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
- Create
Script/graphics activity produces a
SourceSurfaceSkiasnapshot (e.g. canvas/2D surface) whosemImageis shared. - Race
One thread calls
ExtractSubrectwhile another thread releases/replacesmImage(surface change or destruction). (Thread interleaving inferred; the diff shows the missing lock.) - TOCTOU
ExtractSubrectpasses the!mImagecheck, then the other thread frees the backing image. - Use-after-free
The copy reads pixels from the freed
SkImagebuffer. - Primitive Read of freed heap memory (potential info-leak/corruption); controlled exploitation is inferred, not shown by the diff.
Impact Assessment
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
| Function | Change | Notes |
|---|---|---|
ifgfx/2d/SourceSurfaceSkia.cpp |
modified |
Files Changed
gfx/2d/SourceSurfaceSkia.cpp
Audit Directions
- Other `mImage` accessesAudit all reads of
mImage/ pixel accessors inSourceSurfaceSkia(and siblingSourceSurfacebackends) to confirm they holdmChangeMutexwherever the image can be released concurrently. - Snapshot lifetime across threadsReview Moz2D surface-change / destruction paths that can release a
SkImagewhile another thread reads it (CopyTo,GetDataSurface, draw-target snapshots). - Lock-ordering around copiesLook for other “check then copy” sequences in
gfx/2dthat validate a pointer and then dereference it without a lock.
Patch
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);