CVE-2026-74973
Overview
Background
- `SharedRGBImage`
- a Gecko
layersImagesubclass that wraps a shared-memory RGB buffer backed by aTextureClientfor IPC compositing. - `mSourceSurface`
- a cached
RefPtr<gfx::SourceSurface>member ofSharedRGBImagelazily populated byGetAsSourceSurface. - `DataMutex`
- a Mozilla template (
mozilla/DataMutex.h) that bundles a value with a mutex so the value can only be accessed while its lock is held. - `NS_ReleaseOnMainThread`
- a helper that hands a refcounted pointer to the main thread for release, used when an object must be destroyed there.
Root Cause Analysis
The cached mSourceSurface field of SharedRGBImage was a plain RefPtr<gfx::SourceSurface> accessed without synchronization, while GetAsSourceSurface only asserted (NS_ASSERTION) rather than enforced main-thread execution, and the destructor read and forget()-ed the same field. Because a SharedRGBImage can be touched from more than one thread, two threads could concurrently read, write (mSourceSurface = surface), and release the pointer, violating the invariant that a non-atomic RefPtr must be mutated by only one thread at a time. This race allowed the surface’s refcount to be corrupted or the object to be freed while another thread still held or was about to store a reference, producing a use-after-free.
The fix converts mSourceSurface into a DataMutex<RefPtr<gfx::SourceSurface>>, so every read in GetAsSourceSurface (*guard), every write (*guard = surface), and the destructor’s forget() now occur under mSourceSurface.Lock(). Serializing all access to the cached surface eliminates the concurrent-mutation window and makes the caching path thread-safe.
RefPtr guarded only by a main-thread assertion instead of a real lock, leaving concurrent access unsynchronized; wrapping the field in a DataMutex forces every access under a mutex and closes the race.Attack Path
- Trigger concurrent surface access
Cause content that maps to a
SharedRGBImageto have itsGetAsSourceSurfaceinvoked from a thread other than the main thread while another thread also operates on the same image. - Race the cache write
Two threads simultaneously reach the caching logic, one reading
mSourceSurfacewhile the other assigns to it, corrupting the non-atomic refcount. - Force premature free
The refcount corruption or interleaving with the destructor’s
forget()drops the surface’s last reference while another thread still references it. - Reuse the freed surface
A thread dereferences the now-freed
gfx::SourceSurface, yielding a use-after-free. - Leverage the dangling object Reclaiming the freed allocation with attacker-influenced data can turn the dangling access into memory disclosure or corruption.
Impact Assessment
SharedRGBImage gains a use-after-free of a gfx::SourceSurface in the process running the compositing/graphics code, which may lead to memory corruption. Exploitation requires winning a timing race between threads accessing the same image, so it is non-deterministic. Rated moderate/medium with no assigned CVSS or CWE in the metadata.Changed Functions
| Function | Change | Notes |
|---|---|---|
mSourceSurfacegfx/layers/ipc/SharedRGBImage.cpp |
modified | |
mRecycleAllocatorgfx/layers/ipc/SharedRGBImage.cpp |
modified | |
ifgfx/layers/ipc/SharedRGBImage.cpp |
modified |
Files Changed
gfx/layers/ipc/SharedRGBImage.cppgfx/layers/ipc/SharedRGBImage.h
Audit Directions
- Non-atomic `RefPtr` cache fieldsFlag class members that lazily cache refcounted pointers and are read or written from more than one thread without a lock or atomic wrapper.
- Assertion-only thread guardsTreat
NS_ASSERTION(NS_IsMainThread())(or similar) as documentation, not enforcement; verify actual synchronization exists on any field the “asserted” thread invariant is meant to protect. - Destructor releases of shared stateAudit destructors that
forget()or release members which other threads may still touch, ensuring teardown takes the same lock as the accessors.
Patch
diff --git a/gfx/layers/ipc/SharedRGBImage.cpp b/gfx/layers/ipc/SharedRGBImage.cpp
index 135065743bb..d078d5a0877 100644
--- a/gfx/layers/ipc/SharedRGBImage.cpp
+++ b/gfx/layers/ipc/SharedRGBImage.cpp
@@ -51,20 +51,27 @@ class TextureClientForRawBufferAccessAllocationHelper
};
SharedRGBImage::SharedRGBImage(ImageClient* aCompositable)
- : Image(nullptr, ImageFormat::SHARED_RGB), mCompositable(aCompositable) {
+ : Image(nullptr, ImageFormat::SHARED_RGB),
+ mCompositable(aCompositable),
+ mSourceSurface("SharedRGBImage::mSourceSurface") {
MOZ_COUNT_CTOR(SharedRGBImage);
}
SharedRGBImage::SharedRGBImage(TextureClientRecycleAllocator* aRecycleAllocator)
: Image(nullptr, ImageFormat::SHARED_RGB),
- mRecycleAllocator(aRecycleAllocator) {
+ mRecycleAllocator(aRecycleAllocator),
+ mSourceSurface("SharedRGBImage::mSourceSurface") {
MOZ_COUNT_CTOR(SharedRGBImage);
}
SharedRGBImage::~SharedRGBImage() {
MOZ_COUNT_DTOR(SharedRGBImage);
- NS_ReleaseOnMainThread("SharedRGBImage::mSourceSurface",
- mSourceSurface.forget());
+ RefPtr<gfx::SourceSurface> surface;
+ {
+ auto guard = mSourceSurface.Lock();
+ surface = guard->forget();
+ }
+ NS_ReleaseOnMainThread("SharedRGBImage::mSourceSurface", surface.forget());
}
TextureClientRecycleAllocator* SharedRGBImage::RecycleAllocator() {
@@ -110,10 +117,10 @@ static void ReleaseTextureClient(void* aData) {
static gfx::UserDataKey sTextureClientKey;
already_AddRefed<gfx::SourceSurface> SharedRGBImage::GetAsSourceSurface() {
- NS_ASSERTION(NS_IsMainThread(), "Must be main thread");
+ auto guard = mSourceSurface.Lock();
- if (mSourceSurface) {
- RefPtr<gfx::SourceSurface> surface(mSourceSurface);
+ if (*guard) {
+ RefPtr<gfx::SourceSurface> surface(*guard);
return surface.forget();
}
@@ -148,7 +155,7 @@ already_AddRefed<gfx::SourceSurface> SharedRGBImage::GetAsSourceSurface() {
}
}
- mSourceSurface = surface;
+ *guard = surface;
return surface.forget();
}
diff --git a/gfx/layers/ipc/SharedRGBImage.h b/gfx/layers/ipc/SharedRGBImage.h
index 77a5cb471df..1151d4880fc 100644
--- a/gfx/layers/ipc/SharedRGBImage.h
+++ b/gfx/layers/ipc/SharedRGBImage.h
@@ -10,6 +10,7 @@
#include "ImageContainer.h" // for ISharedImage, Image, etc
#include "gfxTypes.h"
+#include "mozilla/DataMutex.h" // for DataMutex
#include "mozilla/RefPtr.h" // for RefPtr
#include "mozilla/gfx/Point.h" // for IntSize
#include "mozilla/gfx/Types.h" // for SurfaceFormat
@@ -49,7 +50,7 @@ class SharedRGBImage : public Image {
RefPtr<TextureClient> mTextureClient;
RefPtr<ImageClient> mCompositable;
RefPtr<TextureClientRecycleAllocator> mRecycleAllocator;
- RefPtr<gfx::SourceSurface> mSourceSurface;
+ DataMutex<RefPtr<gfx::SourceSurface>> mSourceSurface;
};
} // namespace layers