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
Tracker2078145
Fix commitf82c4df994e3 (firefox) +41/-33
CISA KEVNot listed
CreditedMassimo Palozzi
Disclosed2026-10-27

Background

`RemoteTextureMap`
A singleton (sInstance) in gfx/layers/RemoteTextureMap.cpp that coordinates remote texture transactions across content/compositor processes, guarded by mMonitor.
`RemoteTextureTxnScheduler`
A refcounted helper (NS_INLINE_DECL_THREADSAFE_REFCOUNTING) that schedules/waits on texture transactions for one (ProcessId, RemoteTextureTxnType) pair. Created by Create(), destroyed when its last strong ref drops.
`mTxnSchedulers`
A std::map keyed on std::pair<base::ProcessId, RemoteTextureTxnType>. Before the patch its value type was a raw RemoteTextureTxnScheduler*; after it is a ThreadSafeWeakPtr<RemoteTextureTxnScheduler>.
`ThreadSafeWeakPtr` / `SupportsThreadSafeWeakPtr`
Mozilla’s thread-safe weak-reference facility. A weak pointer yields a RefPtr only 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.

Key insight
A non-owning raw pointer was cached in a long-lived map but never invalidated when the pointee was freed, so later lookups resurrected (AddRef) and dereferenced a dangling pointer. Replacing it with a self-nulling ThreadSafeWeakPtr closes the window.

Attack Path

  1. Setup A content/compositor process registers a RemoteTextureTxnScheduler for a (pid, txnType) key, populating mTxnSchedulers with a raw pointer (inferred reachability via the remote-texture IPC surface driven by canvas/WebGL/video).
  2. 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.
  3. Reuse A later RegisterTxnScheduler or WaitForTxn for the same key finds the stale entry.
  4. Trigger do_AddRef(it->second) / it->second->WaitForTxn(...) operates on freed memory.
  5. 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

The affected code runs in the compositor/GPU process. An attacker who can drive remote-texture registration and process-teardown ordering (e.g. via canvas/WebGL/video pipelines across process shutdown) can cause an 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

FunctionChangeNotes
if
gfx/layers/RemoteTextureMap.cpp
modified
RemoteTextureTxnScheduler
gfx/layers/RemoteTextureMap.h
modified

Files Changed

  • gfx/layers/RemoteTextureMap.cpp
  • gfx/layers/RemoteTextureMap.h

Audit Directions

  • Raw pointers in long-lived containers
    Audit other std::map/nsTHashMap members in gfx/layers that store raw T* for refcounted objects (e.g. holder/owner maps) and verify an erase happens in every destructor path.
  • Shutdown ordering
    The 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 sites
    Verify all newly weak references are always promoted-and-null-checked rather than dereferenced directly, here and in related compositor maps.
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;
Loading diff…