High firefox UAF 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionInvalid pointer in the Graphics: WebRender component
ComponentGraphics
Bug ClassUAF
Tracker2079301
Fix commitcfb7a35115f0 (firefox) +10/-9
CISA KEVNot listed
CreditedNguyen Tuan Hung
Disclosed2026-10-27

Background

`BufferTextureData` / `ShmemHolder`
WebRender/compositor objects backing a shared-memory texture; the holder owns the shmem and must be released on the allocator’s own thread.
`AddUserData` / `UserDataKey`
a DrawTarget key/value store keyed by the address of a UserDataKey; the key must be a stable, shared address.
`NS_ProxyRelease`
the safe primitive that releases a refcounted object on a specified thread even if dispatch to that thread fails.

Root Cause Analysis

The patch makes three coupled changes to ShmemTextureData, and the diff leaves two candidate root causes for the reported invalid pointer. First, BufferTextureData::BorrowDrawTarget now calls OnBorrowDrawTarget(dt) — the registration that attaches the ShmemHolder keep-alive user data to the returned DrawTarget; if that registration was missing on this path before, a borrowed DrawTarget could use a ShmemHolder that was already freed (a lifetime use-after-free, and the likely primary cause). Second, DeleteShmemHolderUserData took the allocator thread as a raw auto* eventTarget and only re-dispatched the holder’s release off-thread; if Dispatch failed, the moved holder’s refcount drop released it on the wrong (current) thread — the new comment reads “Do not release the holder on the wrong thread if dispatch fails.”

The fix holds the target in an nsCOMPtr<nsIEventTarget> and always routes the release through NS_ProxyRelease, and also replaces the per-instance mShmemHolderKey with a stable static sShmemHolderKey (a UserDataKey must be a stable address). (Which candidate is the exploited primary — missing keep-alive registration vs wrong-thread release — is not decidable from the diff alone, and the reachability of the threading path depends on another caller of OnBorrowDrawTarget, so it is inferred.)

Key insight
The shmem holder’s lifetime and thread-ownership were unsafe — it was not reliably kept alive for a borrowed DrawTarget and could be released on the wrong thread; the fix registers the keep-alive, holds a ref to the target, and uses NS_ProxyRelease with a stable key.

Attack Path

  1. ENTRY Web content drives WebRender to allocate a shared-memory-backed texture (ShmemTextureData) and obtain draw targets.
  2. TRIGGER Cause the DrawTarget’s user data to be torn down (DeleteShmemHolderUserData) under thread conditions where the dispatch path is not taken or fails.
  3. MISSING CHECK The holder is released through a raw event-target pointer without a held reference and without a failure-safe proxy release, on a thread that may not own it.
  4. PRIMITIVE A ShmemHolder is freed/used off its owning thread, yielding an invalid pointer / use-after-free in the compositor. (Exact racing sequence inferred; the patch shows the unsafe release and its correction.)
  5. IMPACT Memory corruption in the GPU/compositor process reachable from content.

Impact Assessment

A use-after-free / invalid pointer in WebRender’s shmem texture teardown is reachable by web content and lands in the compositor process, a classic corruption primitive (exploit chain inferred). The threading nature means triggering it reliably likely needs a race, so difficulty is moderate. Advisory severity high; no ITW stated.

Changed Functions

FunctionChangeNotes
if
gfx/layers/BufferTexture.cpp
modified

Files Changed

  • gfx/layers/BufferTexture.cpp

Audit Directions

  • Cross-thread refcounted release
    Audit teardown callbacks that call ->Dispatch conditionally; prefer NS_ProxyRelease with a held nsCOMPtr target so a failed dispatch cannot release on the wrong thread.
  • UserDataKey identity
    Flag any AddUserData whose key is a non-static member; keys must be stable addresses.
diff --git a/gfx/layers/BufferTexture.cpp b/gfx/layers/BufferTexture.cpp
index afcfa307d33..0ff1518adc6 100644
--- a/gfx/layers/BufferTexture.cpp
+++ b/gfx/layers/BufferTexture.cpp
@@ -15,6 +15,7 @@
 #include "mozilla/layers/ISurfaceAllocator.h"
 #include "mozilla/layers/ImageDataSerializer.h"
 #include "mozilla/layers/TextureForwarder.h"
+#include "nsProxyRelease.h"
 
 #ifdef MOZ_WIDGET_GTK
 #  include "gfxPlatformGtk.h"
@@ -134,7 +135,6 @@ class ShmemTextureData : public BufferTextureData {
 
  protected:
   const RefPtr<ShmemHolder> mShmemHolder;
-  gfx::UserDataKey mShmemHolderKey = {0};
 };
 
 BufferTextureData* BufferTextureData::Create(
@@ -291,24 +291,24 @@ struct ShmemHolderUserData {
 
 void DeleteShmemHolderUserData(void* aClosure) {
   ShmemHolderUserData* data = reinterpret_cast<ShmemHolderUserData*>(aClosure);
-  auto* eventTarget = data->mShmemHolder->mAllocator->GetThread();
+  nsCOMPtr<nsIEventTarget> eventTarget =
+      data->mShmemHolder->mAllocator->GetThread();
 
-  if (!eventTarget->IsOnCurrentThread()) {
-    RefPtr<Runnable> runnable =
-        NS_NewRunnableFunction("DeleteShmemHolderUserData::Runnable",
-                               [wrapper = std::move(data->mShmemHolder)]() {});
-    eventTarget->Dispatch(runnable.forget());
-  }
+  // Do not release the holder on the wrong thread if dispatch fails.
+  NS_ProxyRelease("DeleteShmemHolderUserData", eventTarget,
+                  data->mShmemHolder.forget());
 
   delete data;
 }
 
+static gfx::UserDataKey sShmemHolderKey = {0};
+
 void ShmemTextureData::OnBorrowDrawTarget(gfx::DrawTarget* aDrawTarget) {
   if (!aDrawTarget) {
     return;
   }
   auto* data = new ShmemHolderUserData(mShmemHolder);
-  aDrawTarget->AddUserData(&mShmemHolderKey, data, DeleteShmemHolderUserData);
+  aDrawTarget->AddUserData(&sShmemHolderKey, data, DeleteShmemHolderUserData);
 }
 
 already_AddRefed<gfx::DrawTarget> BufferTextureData::BorrowDrawTarget() {
@@ -341,6 +341,7 @@ already_AddRefed<gfx::DrawTarget> BufferTextureData::BorrowDrawTarget() {
                     << (int)mMoz2DBackend;
   }
 
+  OnBorrowDrawTarget(dt);
   return dt.forget();
 }
 
Loading diff…