High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionUse-after-free in the Layout: Text and Fonts component
ComponentDOM
Bug ClassLogic Error
Tracker2074209
Fix commit2b4806238445 (firefox) +160/-107
CISA KEVNot listed
CreditedMozilla
Disclosed2026-10-27

Changed Functions

FunctionChangeNotes
if
gfx/thebes/FontPaletteCache.cpp
modified
gfxFontEntry
gfx/thebes/FontPaletteCache.h
modified
FontPaletteValueSet
gfx/thebes/FontPaletteCache.h
modified
PaletteCache
gfx/thebes/FontPaletteCache.h
modified
if
gfx/thebes/gfxFont.cpp
modified
if
gfx/thebes/gfxFontEntry.cpp
modified
FontPalette
gfx/thebes/gfxFontEntry.h
modified
PaletteCache
gfx/thebes/gfxFontEntry.h
modified
if
gfx/thebes/gfxFontEntry.h
modified

Files Changed

  • dom/canvas/CanvasRenderingContext2D.cpp
  • dom/canvas/CanvasRenderingContext2D.h
  • gfx/src/nsFontMetrics.cpp
  • gfx/thebes/FontPaletteCache.cpp
  • gfx/thebes/FontPaletteCache.h
  • gfx/thebes/gfxFont.cpp
  • gfx/thebes/gfxFont.h
  • gfx/thebes/gfxFontEntry.cpp
  • gfx/thebes/gfxFontEntry.h
  • gfx/thebes/gfxTextRun.cpp
  • gfx/thebes/gfxTextRun.h
  • layout/base/nsPresContext.cpp
  • layout/base/nsPresContext.h
  • layout/generic/TextOverflow.cpp
  • layout/generic/nsTextFrame.cpp
  • layout/mathml/nsMathMLChar.cpp
diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp
index 8cab5d392cd..bf131b51274 100644
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -4809,8 +4809,8 @@ struct MOZ_STACK_CLASS CanvasBidiProcessor final
     : public nsBidiPresUtils::BidiProcessor {
   using Style = CanvasRenderingContext2D::Style;
 
-  explicit CanvasBidiProcessor(mozilla::gfx::PaletteCache& aPaletteCache)
-      : mPaletteCache(aPaletteCache) {
+  explicit CanvasBidiProcessor(gfx::FontPaletteValueSet* aPaletteValueSet)
+      : mPaletteValueSet(aPaletteValueSet) {
     if (StaticPrefs::gfx_missing_fonts_notify()) {
       mMissingFonts = MakeUnique<gfxMissingFontRecorder>();
     }
@@ -5060,7 +5060,7 @@ struct MOZ_STACK_CLASS CanvasBidiProcessor final
     }
 
     gfxContext thebes(target, /* aPreserveTransform */ true);
-    gfxTextRun::DrawParams params(&thebes, mPaletteCache);
+    gfxTextRun::DrawParams params(&thebes, mPaletteValueSet);
 
     params.allowGDI = false;
 
@@ -5125,15 +5125,15 @@ struct MOZ_STACK_CLASS CanvasBidiProcessor final
   // Pointer to the draw target we should fill our text to
   CanvasRenderingContext2D* mCtx = nullptr;
 
+  // palette-values set from the presContext, if any.
+  gfx::FontPaletteValueSet* mPaletteValueSet = nullptr;
+
   // position of the left side of the string, alphabetic baseline
   gfx::Point mPt;
 
   // current font
   gfxFontGroup* mFontgrp = nullptr;
 
-  // palette cache for COLR font rendering
-  mozilla::gfx::PaletteCache& mPaletteCache;
-
   // spacing adjustments to be applied
   gfx::Float mLetterSpacing = 0.0f;
   gfx::Float mWordSpacing = 0.0f;
@@ -5256,7 +5256,9 @@ UniquePtr<TextMetrics> CanvasRenderingContext2D::DrawOrMeasureText(
     return nullptr;
   }
 
-  CanvasBidiProcessor processor(mPaletteCache);
+  auto* paletteValueSet =
+      presContext ? presContext->GetFontPaletteValueSet() : nullptr;
+  CanvasBidiProcessor processor(paletteValueSet);
 
   // If we don't have a ComputedStyle, we can't set up vertical-text flags
   // (for now, at least; perhaps we need new Canvas API to control this).
diff --git a/dom/canvas/CanvasRenderingContext2D.h b/dom/canvas/CanvasRenderingContext2D.h
index ae6b540f1b3..25a7b7921ca 100644
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -1322,8 +1322,6 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
 
   ColorStyleCacheEntry ParseColorSlow(const nsACString&);
 
-  mozilla::gfx::PaletteCache mPaletteCache;
-
   friend class CanvasGeneralPattern;
   friend class AdjustedTarget;
   friend class AdjustedTargetForShadow;
diff --git a/gfx/src/nsFontMetrics.cpp b/gfx/src/nsFontMetrics.cpp
index 3f329cfab6a..981df9465f4 100644
--- a/gfx/src/nsFontMetrics.cpp
+++ b/gfx/src/nsFontMetrics.cpp
@@ -402,8 +402,7 @@ void nsFontMetrics::DrawString(const char* aString, uint32_t aLength,
       pt.x += textRun->GetAdvanceWidth(range, &provider);
     }
   }
-  mozilla::gfx::PaletteCache paletteCache;
-  gfxTextRun::DrawParams params(aContext, paletteCache);
+  gfxTextRun::DrawParams params(aContext, /* aPaletteValueSet */ nullptr);
   params.provider = &provider;
   mozilla::image::imgDrawingParams dummy;
   textRun->Draw(range, pt, params, dummy);
@@ -429,8 +428,7 @@ void nsFontMetrics::DrawString(
       pt.x += textRun->GetAdvanceWidth(range, &provider);
     }
   }
-  mozilla::gfx::PaletteCache paletteCache;
-  gfxTextRun::DrawParams params(aContext, paletteCache);
+  gfxTextRun::DrawParams params(aContext, /* aPaletteValueSet */ nullptr);
   params.provider = &provider;
   mozilla::image::imgDrawingParams dummy;
   textRun->Draw(range, pt, params, dummy);
diff --git a/gfx/thebes/FontPaletteCache.cpp b/gfx/thebes/FontPaletteCache.cpp
index 1686fbf5905..66cb45770a5 100644
--- a/gfx/thebes/FontPaletteCache.cpp
+++ b/gfx/thebes/FontPaletteCache.cpp
@@ -4,30 +4,38 @@
 
 #include "FontPaletteCache.h"
 
-#include "COLRFonts.h"
 #include "gfxFontEntry.h"
 
 using namespace mozilla;
 
-void gfx::PaletteCache::SetPaletteValueSet(
-    const gfx::FontPaletteValueSet* aSet) {
-  mPaletteValueSet = aSet;
-  Clear();
-}
+// Return a cached palette, if present; the caller is holding a read lock.
+already_AddRefed<gfx::FontPalette> gfx::PaletteCache::GetCachedPalette(
+    gfx::FontPaletteValueSet* aPaletteValueSet, nsAtom* aPaletteName) {
+  if (aPaletteValueSet != mPaletteValueSet) {
+    return nullptr;
+  }
 
-already_AddRefed<gfx::FontPalette> gfx::PaletteCache::GetPaletteFor(
-    gfxFontEntry* aFontEntry, nsAtom* aPaletteName) {
-  auto entry = Lookup(std::pair(aFontEntry, aPaletteName));
-  if (!entry) {
-    CacheData newData;
-    newData.mKey = std::pair(aFontEntry, aPaletteName);
+  auto entry = Lookup(aPaletteName);
+  if (entry) {
+    RefPtr result = entry.Data().mPalette;
+    return result.forget();
+  }
 
-    gfxFontEntry::AutoHBFace face = aFontEntry->GetHBFace();
-    newData.mPalette = new FontPalette(gfx::COLRFonts::CreateColorPalette(
-        face, mPaletteValueSet, aPaletteName, aFontEntry->FamilyName()));
+  return nullptr;
+}
 
-    entry.Set(std::move(newData));
+// Add a palette to the cache, checking that the palette value set matches.
+void gfx::PaletteCache::AddPalette(gfx::FontPaletteValueSet* aPaletteValueSet,
+                                   nsAtom* aPaletteName,
+                                   gfx::FontPalette* aPalette) {
+  if (aPaletteValueSet != mPaletteValueSet) {
+    // Discard any palettes that were created for a different value set.
+    Clear();
+    mPaletteValueSet = aPaletteValueSet;
   }
-  RefPtr result = entry.Data().mPalette;
-  return result.forget();
+
+  CacheData newData;
+  newData.mKey = aPaletteName;
+  newData.mPalette = aPalette;
+  Put(aPaletteName, std::move(newData));
 }
diff --git a/gfx/thebes/FontPaletteCache.h b/gfx/thebes/FontPaletteCache.h
index fd082514ed6..bcbbf161919 100644
--- a/gfx/thebes/FontPaletteCache.h
+++ b/gfx/thebes/FontPaletteCache.h
@@ -7,7 +7,6 @@
 
 #include <utility>
 
-#include "gfxFontEntry.h"
 #include "harfbuzz/hb.h"
 #include "mozilla/HashFunctions.h"
 #include "mozilla/MruCache.h"
@@ -16,6 +15,8 @@
 #include "nsAtom.h"
 #include "nsTArray.h"
 
+class gfxFontEntry;
+
 namespace mozilla::gfx {
 
 class FontPaletteValueSet;
@@ -39,33 +40,37 @@ class FontPalette {
 
 // MRU cache used for resolved color-font palettes, to avoid reconstructing
 // the palette for each glyph rendered with a given font.
-using CacheKey = std::pair<RefPtr<gfxFontEntry>, RefPtr<nsAtom>>;
+using CacheKey = RefPtr<nsAtom>;
 struct CacheData {
   CacheKey mKey;
   RefPtr<FontPalette> mPalette;
 };
 
+// PaletteCache is attached to a specific gfxFontEntry and holds palettes found
+// in that font, with lookup by palette name.
 class PaletteCache
-    : public mozilla::MruCache<CacheKey, CacheData, PaletteCache> {
+    : public mozilla::MruCache<CacheKey, CacheData, PaletteCache, 8> {
  public:
-  explicit PaletteCache(const FontPaletteValueSet* aPaletteValueSet = nullptr)
-      : mPaletteValueSet(aPaletteValueSet) {}
+  PaletteCache() = default;
 
-  void SetPaletteValueSet(const FontPaletteValueSet* aSet);
+  // Get the palette named aPaletteName, if present.
+  already_AddRefed<FontPalette> GetCachedPalette(
+      mozilla::gfx::FontPaletteValueSet* aPaletteValueSet,
+      nsAtom* aPaletteName);
 
-  already_AddRefed<FontPalette> GetPaletteFor(gfxFontEntry* aFontEntry,
-                                              nsAtom* aPaletteName);
+  // Store the given palette in the cache.
+  void AddPalette(mozilla::gfx::FontPaletteValueSet* aPaletteValueSet,
+                  nsAtom* aPaletteName, mozilla::gfx::FontPalette* aPalette);
 
-  static bool IsEmpty(const CacheData& aVal) { return !aVal.mKey.first; }
-  static mozilla::HashNumber Hash(const CacheKey& aKey) {
-    return mozilla::HashGeneric(aKey.first.get(), aKey.second.get());
-  }
+  static bool IsEmpty(const CacheData& aVal) { return !aVal.mKey; }
+  static mozilla::HashNumber Hash(const CacheKey& aKey) { return aKey->hash(); }
   static bool Match(const CacheKey& aKey, const CacheData& aVal) {
     return aVal.mKey == aKey;
   }
 
  protected:
-  const FontPaletteValueSet* mPaletteValueSet = nullptr;
+  // The palette value set for which cached entries were created.
+  RefPtr<FontPaletteValueSet> mPaletteValueSet;
 };
 
 }  // namespace mozilla::gfx
diff --git a/gfx/thebes/gfxFont.cpp b/gfx/thebes/gfxFont.cpp
index e5bf4a858a5..54991a24738 100644
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -1789,8 +1789,7 @@ bool gfxFont::HasFeatureSet(uint32_t aFeature, bool& aFeatureOn) {
 
 already_AddRefed<mozilla::gfx::ScaledFont> gfxFont::GetScaledFont(
     mozilla::gfx::DrawTarget* aDrawTarget) {
-  mozilla::gfx::PaletteCache dummy;
-  TextRunDrawParams params(dummy);
+  TextRunDrawParams params(nullptr);
   return GetScaledFont(params);
 }
 
@@ -2380,7 +2379,7 @@ void gfxFont::DrawEmphasisMarks(const gfxTextRun* aShapedText, gfx::Point* aPt,
                                 imgDrawingParams& aImgParams) {
   float& inlineCoord = aParams.isVertical ? aPt->y.value : aPt->x.value;
   gfxTextRun::Range markRange(aParams.mark);
-  gfxTextRun::DrawParams params(aParams.context, aParams.paletteCache);
+  gfxTextRun::DrawParams params(aParams.context, aParams.paletteValueSet);
 
   float clusterStart = -std::numeric_limits<float>::infinity();
   bool shouldDrawEmphasisMark = false;
@@ -2448,8 +2447,8 @@ void gfxFont::Draw(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd,
     fontParams.currentColor = aRunParams.context->GetDeviceColor(ctxColor)
                                   ? sRGBColor::FromABGR(ctxColor.ToABGR())
                                   : sRGBColor::OpaqueBlack();
-    fontParams.palette = aRunParams.paletteCache.GetPaletteFor(
-        GetFontEntry(), aRunParams.fontPalette);
+    fontParams.palette = GetFontEntry()->GetPalette(aRunParams.fontPalette,
+                                                    aRunParams.paletteValueSet);
   }
 
   if (textDrawer) {
diff --git a/gfx/thebes/gfxFont.h b/gfx/thebes/gfxFont.h
index b1539da49de..9b32f858780 100644
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -28,7 +28,6 @@
 #include "mozilla/TypedEnumBits.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/gfx/2D.h"
-#include "mozilla/gfx/FontPaletteCache.h"
 #include "mozilla/gfx/MatrixFwd.h"
 #include "mozilla/gfx/Point.h"
 #include "mozilla/intl/UnicodeScriptCodes.h"
@@ -2479,10 +2478,10 @@ class gfxFont {
 // are dependent on the specific font, so they are set per GlyphRun.
 
 struct MOZ_STACK_CLASS TextRunDrawParams {
-  explicit TextRunDrawParams(mozilla::gfx::PaletteCache& aPaletteCache)
-      : paletteCache(aPaletteCache) {}
+  explicit TextRunDrawParams(
+      mozilla::gfx::FontPaletteValueSet* aPaletteValueSet)
+      : paletteValueSet(aPaletteValueSet) {}
 
-  mozilla::gfx::PaletteCache& paletteCache;
   RefPtr<mozilla::gfx::DrawTarget> dt;
   gfxContext* context = nullptr;
   gfxFont::Spacing* spacing = nullptr;
@@ -2498,6 +2497,7 @@ struct MOZ_STACK_CLASS TextRunDrawParams {
   const mozilla::gfx::StrokeOptions* strokeOpts = nullptr;
   const mozilla::gfx::DrawOptions* drawOpts = nullptr;
   nsAtom* fontPalette = nullptr;
+  mozilla::gfx::FontPaletteValueSet* paletteValueSet = nullptr;
   // The source text for the range passed to gfxFont::Draw, as provided by
   // gfxTextRun::PropertyProvider::GetSourceText, but converted to UTF-8.
   // Only set if needsSourceText is true and the provider returned the text.
@@ -2537,10 +2537,10 @@ struct MOZ_STACK_CLASS FontDrawParams {
 
 struct MOZ_STACK_CLASS EmphasisMarkDrawParams {
   EmphasisMarkDrawParams(gfxContext* aContext,
-                         mozilla::gfx::PaletteCache& aPaletteCache)
-      : context(aContext), paletteCache(aPaletteCache) {}
+                         mozilla::gfx::FontPaletteValueSet* aPaletteValueSet)
+      : context(aContext), paletteValueSet(aPaletteValueSet) {}
   gfxContext* context;
-  mozilla::gfx::PaletteCache& paletteCache;
+  mozilla::gfx::FontPaletteValueSet* paletteValueSet;
   gfxFont::Spacing* spacing;
   gfxTextRun* mark;
   gfxFloat advance;
diff --git a/gfx/thebes/gfxFontEntry.cpp b/gfx/thebes/gfxFontEntry.cpp
index c3b7212671f..ec3f7b9145a 100644
--- a/gfx/thebes/gfxFontEntry.cpp
+++ b/gfx/thebes/gfxFontEntry.cpp
@@ -74,6 +74,8 @@ gfxFontEntry::~gfxFontEntry() {
   hb_blob_destroy(mCOLR.exchange(nullptr));
   hb_blob_destroy(mCPAL.exchange(nullptr));
 
+  delete mPaletteCache.exchange(nullptr);
+
   if (TrakTableInitialized()) {
     // Only if it was initialized, so that we don't try to call hb_blob_destroy
     // on the kTrakTableUninitialized flag value!
@@ -441,6 +443,46 @@ bool gfxFontEntry::TryGetColorGlyphs() {
   return mCOLR && mCPAL;
 }
 
+already_AddRefed<gfx::FontPalette> gfxFontEntry::GetPalette(
+    nsAtom* aPaletteName, gfx::FontPaletteValueSet* aPaletteValueSet) {
+  // Get the palette cache, creating it if necessary. Note that once the
+  // mPaletteCache has been assigned, it is never modified until font entry
+  // destruction, so there is no risk of the local `cache` pointer here
+  // becoming invalid during this function.
+  gfx::PaletteCache* cache = mPaletteCache;
+  if (!cache) {
+    cache = new gfx::PaletteCache();
+    if (!mPaletteCache.compareExchange(nullptr, cache)) {
+      delete cache;  // Lost a race: use the cache created by the winner.
+      cache = mPaletteCache;
+    }
+  }
+
+  // Reading and modifying the cache is guarded by our RWLock.
+  {
+    // First try to return an existing cached palette.
+    AutoReadLock lock(mLock);
+    RefPtr palette = cache->GetCachedPalette(aPaletteValueSet, aPaletteName);
+    if (palette) {
+      return palette.forget();
+    }
+  }
+
+  // Create a new resolved palette.
+  gfxFontEntry::AutoHBFace face = GetHBFace();
+  RefPtr palette = MakeRefPtr<FontPalette>(gfx::COLRFonts::CreateColorPalette(
+      face, aPaletteValueSet, aPaletteName, FamilyName()));
+
+  // Store it in the cache for future use. This may evict an existing cache
+  // entry, but any caller that retrieved the old entry was given its own
+  // strong reference to the palette it contained, which will remain valid
+  // as long as the caller holds its ref.
+  AutoWriteLock lock(mLock);
+  cache->AddPalette(aPaletteValueSet, aPaletteName, palette);
+
+  return palette.forget();
+}
+
 already_AddRefed<gfxCharacterMap> gfxFontEntry::GetCMAPFromFontInfo(
     FontInfoData* aFontInfoData, uint32_t& aUVSOffset) {
   if (!aFontInfoData || !aFontInfoData->mLoadCmaps) {
diff --git a/gfx/thebes/gfxFontEntry.h b/gfx/thebes/gfxFontEntry.h
index 74c9556c87b..b4bb5867fdf 100644
--- a/gfx/thebes/gfxFontEntry.h
+++ b/gfx/thebes/gfxFontEntry.h
@@ -64,6 +64,10 @@ namespace fontlist {
 struct Face;
 struct Family;
 }  // namespace fontlist
+namespace gfx {
+class FontPalette;
+class PaletteCache;
+}  // namespace gfx
 }  // namespace mozilla
 
 typedef struct gr_face gr_face;
@@ -366,6 +370,10 @@ class gfxFontEntry {
 
   bool TryGetColorGlyphs();
 
+  already_AddRefed<mozilla::gfx::FontPalette> GetPalette(
+      nsAtom* aPaletteName,
+      mozilla::gfx::FontPaletteValueSet* aPaletteValueSet);
+
   bool HasColorBitmapTable() {
     LazyFlag flag = mHasColorBitmapTable;
     if (flag == LazyFlag::Uninitialized) {
@@ -639,6 +647,8 @@ class gfxFontEntry {
   hb_blob_t* GetCOLR() const { return mCOLR; }
   hb_blob_t* GetCPAL() const { return mCPAL; }
 
+  mozilla::Atomic<mozilla::gfx::PaletteCache*> mPaletteCache;
+
   // bitvector of substitution space features per script, one each
   // for default and non-default features
   uint32_t mDefaultSubSpaceFeatures[(int(Script::NUM_SCRIPT_CODES) + 31) /
Loading diff…