CVE-2026-117980
Overview
Background
- `RemoteTextureMap`
- A singleton (
sInstance) ingfx/layers/RemoteTextureMap.cppthat coordinates remote texture transactions across content/compositor processes, guarded bymMonitor. - `RemoteTextureTxnScheduler`
- A refcounted helper (
NS_INLINE_DECL_THREADSAFE_REFCOUNTING) that schedules/waits on texture transactions for one(ProcessId, RemoteTextureTxnType)pair. Created byCreate(), destroyed when its last strong ref drops. - `mTxnSchedulers`
- A
std::mapkeyed onstd::pair<base::ProcessId, RemoteTextureTxnType>. Before the patch its value type was a rawRemoteTextureTxnScheduler*; after it is aThreadSafeWeakPtr<RemoteTextureTxnScheduler>. - `ThreadSafeWeakPtr` / `SupportsThreadSafeWeakPtr`
- Mozilla’s thread-safe weak-reference facility. A weak pointer yields a
RefPtronly if the object is still alive, and nulls out once it is destroyed.
Root Cause Analysis
The map mTxnSchedulers stored a raw, non-owning RemoteTextureTxnScheduler*, but the scheduler’s lifetime is governed by thread-safe refcounting held elsewhere (the owning IPC protocol). When the scheduler’s last strong reference dropped, its destructor ran and the object was freed, yet the raw pointer remained in the map as a dangling entry — nothing erased it on destruction. RegisterTxnScheduler then did return do_AddRef(it->second) on that stale pointer, calling AddRef on freed memory, and WaitForTxn dereferenced it->second->WaitForTxn(...) on a dead object. Both are use-after-free.
The patch converts the stored pointer to a ThreadSafeWeakPtr, makes RemoteTextureTxnScheduler inherit SupportsThreadSafeWeakPtr, and promotes the weak pointer to a RefPtr at the point of use (if (RefPtr<...> scheduler = {it->second})). If the object is already dead the weak promotion yields null: RegisterTxnScheduler then erases the stale entry and creates a fresh scheduler, and WaitForTxn falls through to the “scheduler shut down” path instead of touching freed memory. The destructor was also moved to public because SupportsThreadSafeWeakPtr requires it.
AddRef) and dereferenced a dangling pointer. Replacing it with a self-nulling ThreadSafeWeakPtr closes the window.Attack Path
- Setup
A content/compositor process registers a
RemoteTextureTxnSchedulerfor a(pid, txnType)key, populatingmTxnSchedulerswith a raw pointer (inferred reachability via the remote-texture IPC surface driven by canvas/WebGL/video). - Free The owning protocol tears down, dropping the scheduler’s last strong ref; the object is freed but its raw entry stays in the map.
- Reuse
A later
RegisterTxnSchedulerorWaitForTxnfor the same key finds the stale entry. - Trigger
do_AddRef(it->second)/it->second->WaitForTxn(...)operates on freed memory. - Primitive Use-after-free (AddRef/vtable deref) of a heap object whose storage may be reclaimed or groomed; corruption of refcount/vtable memory (exploitation strength inferred).
Impact Assessment
AddRef on, or virtual call into, freed memory. The diff substantiates a genuine UAF; the exploitability (controlled reuse of the freed allocation to reach RCE) is not shown by the diff and is inferred. The advisory rates it moderate.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifgfx/layers/RemoteTextureMap.cpp |
modified | |
RemoteTextureTxnSchedulergfx/layers/RemoteTextureMap.h |
modified |
Files Changed
gfx/layers/RemoteTextureMap.cppgfx/layers/RemoteTextureMap.h
Audit Directions
- Raw pointers in long-lived containersAudit other
std::map/nsTHashMapmembers ingfx/layersthat store rawT*for refcounted objects (e.g. holder/owner maps) and verify an erase happens in every destructor path. - Shutdown orderingThe comment notes toplevel protocols tear down in arbitrary order; review any code that assumes a scheduler/owner outlives its map entry across process shutdown.
- Weak-pointer promotion sitesVerify all newly weak references are always promoted-and-null-checked rather than dereferenced directly, here and in related compositor maps.
Patch
diff --git a/gfx/layers/RemoteTextureMap.cpp b/gfx/layers/RemoteTextureMap.cpp
index b28a7855837..ee1bfbf8892 100644
--- a/gfx/layers/RemoteTextureMap.cpp
+++ b/gfx/layers/RemoteTextureMap.cpp
@@ -813,12 +813,16 @@ RemoteTextureMap::RegisterTxnScheduler(base::ProcessId aForPid,
const auto key = std::pair(aForPid, aType);
auto it = mTxnSchedulers.find(key);
if (it != mTxnSchedulers.end()) {
- return do_AddRef(it->second);
+ if (RefPtr<RemoteTextureTxnScheduler> scheduler = {it->second}) {
+ return scheduler.forget();
+ }
+ mTxnSchedulers.erase(it);
}
RefPtr<RemoteTextureTxnScheduler> scheduler(
new RemoteTextureTxnScheduler(aForPid, aType));
- mTxnSchedulers.emplace(key, scheduler.get());
+ mTxnSchedulers.emplace(
+ key, ThreadSafeWeakPtr<RemoteTextureTxnScheduler>(scheduler));
return scheduler.forget();
}
@@ -1168,34 +1172,37 @@ bool RemoteTextureMap::WaitForTxn(const RemoteTextureOwnerId aOwnerId,
RemoteTextureTxnType aTxnType,
RemoteTextureTxnId aTxnId) {
MonitorAutoLock lock(mMonitor);
- if (auto* owner = GetTextureOwner(lock, aOwnerId, aForPid)) {
- if (owner->mDeferUnregister) {
- MOZ_ASSERT_UNREACHABLE(
- "Texture owner must wait for txn before unregistering.");
- return false;
- }
- if (owner->mWaitForTxn) {
- MOZ_ASSERT_UNREACHABLE("Texture owner already waiting for txn.");
- return false;
- }
- const auto key = std::pair(aForPid, aTxnType);
- auto it = mTxnSchedulers.find(key);
- if (it == mTxnSchedulers.end()) {
- // During shutdown, different toplevel protocols may go away in
- // disadvantageous orders, causing us to sometimes be processing
- // waits even though the source of transactions upon which the
- // wait depends shut down. This is generally harmless to ignore,
- // as it means no further transactions will be generated of that
- // type and all such transactions have been processed before it
- // unregistered.
- NS_WARNING("Could not find scheduler for txn type.");
- return false;
- }
- if (it->second->WaitForTxn(lock, aOwnerId, aTxnId)) {
- owner->mWaitForTxn = true;
+ auto* owner = GetTextureOwner(lock, aOwnerId, aForPid);
+ if (!owner) {
+ return false;
+ }
+ if (owner->mDeferUnregister) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Texture owner must wait for txn before unregistering.");
+ return false;
+ }
+ if (owner->mWaitForTxn) {
+ MOZ_ASSERT_UNREACHABLE("Texture owner already waiting for txn.");
+ return false;
+ }
+ const auto key = std::pair(aForPid, aTxnType);
+ auto it = mTxnSchedulers.find(key);
+ if (it != mTxnSchedulers.end()) {
+ if (RefPtr<RemoteTextureTxnScheduler> scheduler = {it->second}) {
+ if (scheduler->WaitForTxn(lock, aOwnerId, aTxnId)) {
+ owner->mWaitForTxn = true;
+ }
+ return true;
}
- return true;
}
+ // During shutdown, different toplevel protocols may go away in
+ // disadvantageous orders, causing us to sometimes be processing
+ // waits even though the source of transactions upon which the
+ // wait depends shut down. This is generally harmless to ignore,
+ // as it means no further transactions will be generated of that
+ // type and all such transactions have been processed before it
+ // unregistered.
+ NS_WARNING("Could not find scheduler for txn type.");
return false;
}
diff --git a/gfx/layers/RemoteTextureMap.h b/gfx/layers/RemoteTextureMap.h
index 1c186180049..e222eea066f 100644
--- a/gfx/layers/RemoteTextureMap.h
+++ b/gfx/layers/RemoteTextureMap.h
@@ -16,6 +16,7 @@
#include "mozilla/Monitor.h"
#include "mozilla/StaticPtr.h"
+#include "mozilla/ThreadSafeWeakPtr.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/gfx/Point.h" // for IntSize
#include "mozilla/gfx/Types.h" // for SurfaceFormat
@@ -164,13 +165,14 @@ class RemoteTextureRecycleBin final {
* a given top-level protocol, without having to directly refer to the original
* top-level protocol.
*/
-class RemoteTextureTxnScheduler final {
+class RemoteTextureTxnScheduler final
+ : public SupportsThreadSafeWeakPtr<RemoteTextureTxnScheduler> {
public:
- NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteTextureTxnScheduler)
-
static already_AddRefed<RemoteTextureTxnScheduler> Create(
mozilla::ipc::IProtocol* aProtocol);
+ ~RemoteTextureTxnScheduler();
+
void NotifyTxn(RemoteTextureTxnId aTxnId);
private:
@@ -178,7 +180,6 @@ class RemoteTextureTxnScheduler final {
RemoteTextureTxnScheduler(base::ProcessId aForPid, RemoteTextureTxnType aType)
: mForPid(aForPid), mType(aType) {}
- ~RemoteTextureTxnScheduler();
bool WaitForTxn(const MonitorAutoLock& aProofOfLock,
RemoteTextureOwnerId aOwnerId, RemoteTextureTxnId aTxnId);
@@ -493,7 +494,7 @@ class RemoteTextureMap {
mRemoteTextureHostWrapperHolders;
std::map<std::pair<base::ProcessId, RemoteTextureTxnType>,
- RemoteTextureTxnScheduler*>
+ ThreadSafeWeakPtr<RemoteTextureTxnScheduler>>
mTxnSchedulers;
static StaticAutoPtr<RemoteTextureMap> sInstance;