High firefox UAF 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionUse-after-free in the Graphics: Text component
ComponentGraphics
Bug ClassUAF
Tracker2054842
Fix commit55cf76d00b5a (firefox) +18/-3
CISA KEVNot listed
Creditedkiyong
Disclosed2026-08-18

Background

`gfxFontEntry`
Gecko’s per-face font object that caches shared resources like the unscaled font used to build scaled gfxFont instances.
`UnscaledFont`
A reference-counted (RefPtr) backend font object (UnscaledFontMac, UnscaledFontDWrite, UnscaledFontFreeType) shared across scaled instances of the same face.
`CreateFontInstance`
The per-backend method that lazily creates (or reuses) the cached mUnscaledFont and returns a scaled gfxFont.
`AutoReadLock` / `AutoWriteLock`
RAII wrappers over an RWLock (mLock) that serialize reader and writer access to shared member state.

Root Cause Analysis

Each backend’s CreateFontInstance read the cached member mUnscaledFont into a local RefPtr and, when empty, constructed a new UnscaledFont and stored it back into mUnscaledFont, all without holding any lock. Because font-instance creation can run concurrently on multiple threads (main and off-main-thread text/graphics work), this is an unsynchronized read/write race on a RefPtr-typed member: the invariant that a RefPtr’s refcount adjustment and pointer store are consistent under concurrency was violated. A racing store could tear the read or drop the last reference, so one thread could observe a pointer whose backing object was concurrently freed, producing a use-after-free.

The fix takes an AutoReadLock on mLock to snapshot mUnscaledFont into a local RefPtr, and takes an AutoWriteLock before constructing and assigning the new instance. This serializes the load-or-create sequence so the cached pointer is always read and written under mutual exclusion, closing the race.

Key insight
The single mistake was reading and lazily populating the shared mUnscaledFont cache without synchronization, allowing a concurrent write to free the object mid-use; the fix guards the snapshot with AutoReadLock and the create/store with AutoWriteLock on mLock.

Attack Path

  1. Trigger concurrent font instantiation Content loads styles/text that cause the same gfxFontEntry to be turned into scaled instances from more than one thread at nearly the same time.
  2. Race the cache read Two threads enter CreateFontInstance and both read the uncached mUnscaledFont member without a lock.
  3. Race the cache store Both proceed to construct a new UnscaledFont and assign it to mUnscaledFont, with the unsynchronized RefPtr assignment corrupting the refcount.
  4. Free while in use A dropped or torn reference frees an UnscaledFont while another thread still holds and dereferences it.
  5. Use-after-free Subsequent use of the freed backend font object corrupts memory in the graphics text path.

Impact Assessment

An attacker gains a use-after-free of a reference-counted graphics font object, a memory-corruption primitive that can lead to a crash or potentially exploitable state within the process handling font/text rendering. The precondition is that web-controlled content can drive concurrent creation of scaled instances from the same font entry so the unsynchronized cache read/write race is hit. No special privileges are required beyond loading attacker-influenced page content.

Changed Functions

FunctionChangeNotes
if
gfx/thebes/CoreTextFontList.cpp
modified
if
gfx/thebes/gfxDWriteFontList.cpp
modified
if
gfx/thebes/gfxFT2FontList.cpp
modified

Files Changed

  • gfx/thebes/CoreTextFontList.cpp
  • gfx/thebes/gfxDWriteFontList.cpp
  • gfx/thebes/gfxFT2FontList.cpp

Audit Directions

  • Unsynchronized lazy caches
    Look for other RefPtr/ThreadSafeWeakPtr members populated via check-then-create in font/graphics code that can run on multiple threads without holding mLock.
  • Partial lock coverage
    Verify both the read snapshot and the write-back of shared members are guarded; a lock only around the store still races with unlocked readers.
  • `gfxFontEntry` subclass parity
    Audit every backend CreateFontInstance and sibling methods for consistent AutoReadLock/AutoWriteLock usage, since the same pattern was duplicated across CoreText, DirectWrite, and FreeType.
diff --git a/gfx/thebes/CoreTextFontList.cpp b/gfx/thebes/CoreTextFontList.cpp
index 196f90ef01d..459abdb76f3 100644
--- a/gfx/thebes/CoreTextFontList.cpp
+++ b/gfx/thebes/CoreTextFontList.cpp
@@ -374,12 +374,17 @@ nsresult CTFontEntry::ReadCMAP(FontInfoData* aFontInfoData) {
 }
 
 gfxFont* CTFontEntry::CreateFontInstance(const gfxFontStyle* aFontStyle) {
-  RefPtr<UnscaledFontMac> unscaledFont(mUnscaledFont);
+  RefPtr<UnscaledFontMac> unscaledFont;
+  {
+    AutoReadLock lock(mLock);
+    unscaledFont = RefPtr<UnscaledFontMac>(mUnscaledFont);
+  }
   if (!unscaledFont) {
     CGFontRef baseFont = GetFontRef();
     if (!baseFont) {
       return nullptr;
     }
+    AutoWriteLock lock(mLock);
     unscaledFont = new UnscaledFontMac(baseFont, mIsDataUserFont);
     mUnscaledFont = unscaledFont;
   }
diff --git a/gfx/thebes/gfxDWriteFontList.cpp b/gfx/thebes/gfxDWriteFontList.cpp
index e303b149635..6a3cbfd64ee 100644
--- a/gfx/thebes/gfxDWriteFontList.cpp
+++ b/gfx/thebes/gfxDWriteFontList.cpp
@@ -706,7 +706,11 @@ gfxFont* gfxDWriteFontEntry::CreateFontInstance(
       useBoldSim ? DWRITE_FONT_SIMULATIONS_BOLD : DWRITE_FONT_SIMULATIONS_NONE;
   ThreadSafeWeakPtr<UnscaledFontDWrite>& unscaledFontPtr =
       useBoldSim ? mUnscaledFontBold : mUnscaledFont;
-  RefPtr<UnscaledFontDWrite> unscaledFont(unscaledFontPtr);
+  RefPtr<UnscaledFontDWrite> unscaledFont;
+  {
+    AutoReadLock lock(mLock);
+    unscaledFont = RefPtr<UnscaledFontDWrite>((unscaledFontPtr);
+  }
   if (!unscaledFont) {
     RefPtr<IDWriteFontFace> fontFace;
     nsresult rv =
@@ -714,6 +718,7 @@ gfxFont* gfxDWriteFontEntry::CreateFontInstance(
     if (NS_FAILED(rv)) {
       return nullptr;
     }
+    AutoWriteLock lock(mLock);
     // Only pass in the underlying IDWriteFont if the unscaled font doesn't
     // reflect a data font. This signals whether or not we can safely query
     // a descriptor to represent the font for various transport use-cases.
diff --git a/gfx/thebes/gfxFT2FontList.cpp b/gfx/thebes/gfxFT2FontList.cpp
index ff0f22222be..bdbfa7f2cb5 100644
--- a/gfx/thebes/gfxFT2FontList.cpp
+++ b/gfx/thebes/gfxFT2FontList.cpp
@@ -226,8 +226,13 @@ gfxFont* FT2FontEntry::CreateFontInstance(const gfxFontStyle* aStyle) {
     loadFlags &= ~FT_LOAD_NO_AUTOHINT;
   }
 
-  RefPtr<UnscaledFontFreeType> unscaledFont(mUnscaledFont);
+  RefPtr<UnscaledFontFreeType> unscaledFont;
+  {
+    AutoReadLock lock(mLock);
+    unscaledFont = RefPtr<UnscaledFontFreeType>(mUnscaledFont);
+  }
   if (!unscaledFont) {
+    AutoWriteLock lock(mLock);
     RefPtr<SharedFTFace> origFace(mFTFace);
     unscaledFont = !mFilename.IsEmpty() && mFilename[0] == '/'
                        ? new UnscaledFontFreeType(mFilename.get(), mFTFontIndex,
Loading diff…