High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionUse-after-free in the Graphics: Canvas2D component
ComponentDOM
Bug ClassLogic Error
Tracker2072429
Fix commit0dcb137827c7 (firefox) +49/-26
CISA KEVNot listed
CreditedMozilla
Disclosed2026-09-29

Changed Functions

FunctionChangeNotes
explicitLang
dom/canvas/CanvasRenderingContext2D.cpp
modified
if
dom/canvas/CanvasRenderingContext2D.cpp
modified
for
dom/canvas/CanvasRenderingContext2D.cpp
modified
mActive
layout/svg/SVGObserverUtils.cpp
modified
if
layout/svg/SVGObserverUtils.cpp
modified

Files Changed

  • dom/canvas/CanvasRenderingContext2D.cpp
  • dom/canvas/CanvasRenderingContext2D.h
  • layout/svg/SVGObserverUtils.cpp
  • layout/svg/SVGObserverUtils.h
diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp
index 25f10fb25d4..a20003c9110 100644
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -1036,12 +1036,9 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CanvasRenderingContext2D)
       /*
        * XXXjwatt: I don't think this is doing anything useful.  All we do under
        * this function is clear a raw C-style (i.e. not strong) pointer.  That's
-       * clearly not helping in breaking any cycles.  The fact that we MOZ_CRASH
-       * in OnRenderingChange if that pointer is null indicates that this isn't
-       * even doing anything useful in terms of preventing further invalidation
-       * from any observed filters.
+       * clearly not helping in breaking any cycles.
        */
-      autoSVGFiltersObserver->Detach();
+      autoSVGFiltersObserver->SetIsActive(false);
     }
     ImplCycleCollectionUnlink(state.autoSVGFiltersObserver);
   }
@@ -1131,12 +1128,21 @@ CanvasRenderingContext2D::ContextState::ContextState(const ContextState& aOther)
       lineJoin(aOther.lineJoin),
       filterString(aOther.filterString),
       filterChain(aOther.filterChain),
-      autoSVGFiltersObserver(aOther.autoSVGFiltersObserver),
       filter(aOther.filter),
       filterAdditionalImages(aOther.filterAdditionalImages.Clone()),
       filterSourceGraphicTainted(aOther.filterSourceGraphicTainted),
       imageSmoothingEnabled(aOther.imageSmoothingEnabled),
-      explicitLang(aOther.explicitLang) {}
+      explicitLang(aOther.explicitLang) {
+  if (aOther.autoSVGFiltersObserver) {
+    autoSVGFiltersObserver = aOther.autoSVGFiltersObserver->Clone();
+  }
+}
+
+CanvasRenderingContext2D::ContextState::~ContextState() {
+  if (autoSVGFiltersObserver.get()) {
+    autoSVGFiltersObserver->SetIsActive(false);
+  }
+}
 
 void CanvasRenderingContext2D::ContextState::SetColorStyle(Style aWhichStyle,
                                                            nscolor aColor) {
@@ -1234,12 +1240,6 @@ CanvasRenderingContext2D::~CanvasRenderingContext2D() {
   RemoveShutdownObserver();
   ResetBitmap();
 
-  for (ContextState& state : mStyleStack) {
-    if (auto* obs = state.autoSVGFiltersObserver.get()) {
-      obs->Detach();
-    }
-  }
-
   sNumLivingContexts.set(sNumLivingContexts.get() - 1);
   if (sNumLivingContexts.get() == 0 && sErrorTarget.get()) {
     RefPtr<DrawTarget> target = dont_AddRef(sErrorTarget.get());
@@ -2389,9 +2389,13 @@ void CanvasRenderingContext2D::Save() {
     SetErrorState();
     return;
   }
-  mStyleStack[mStyleStack.Length() - 1].transform = GetCurrentTransform();
+  CurrentState().transform = GetCurrentTransform();
   mStyleStack.SetCapacity(mStyleStack.Length() + 1);
   mStyleStack.AppendElement(CurrentState());
+  if (auto* autoSVGFiltersObserver =
+          PreviousState().autoSVGFiltersObserver.get()) {
+    autoSVGFiltersObserver->SetIsActive(false);
+  }
 
   if (mStyleStack.Length() > MAX_STYLE_STACK_SIZE) {
     // This is not fast, but is better than OOMing and shouldn't be hit by
@@ -2417,6 +2421,10 @@ void CanvasRenderingContext2D::Restore() {
   }
 
   mStyleStack.RemoveLastElement();
+  if (auto* autoSVGFiltersObserver =
+          CurrentState().autoSVGFiltersObserver.get()) {
+    autoSVGFiltersObserver->SetIsActive(true);
+  }
 
   mPathTransformDirty = true;
 }
@@ -2985,7 +2993,7 @@ void CanvasRenderingContext2D::SetFilter(const nsACString& aFilter,
     CurrentState().filterChain = std::move(filterChain);
     if (mCanvasElement) {
       if (CurrentState().autoSVGFiltersObserver) {
-        CurrentState().autoSVGFiltersObserver->Detach();
+        CurrentState().autoSVGFiltersObserver->SetIsActive(false);
       }
       CurrentState().autoSVGFiltersObserver =
           SVGObserverUtils::ObserveFiltersForCanvasContext(
diff --git a/dom/canvas/CanvasRenderingContext2D.h b/dom/canvas/CanvasRenderingContext2D.h
index a73b7a35314..44260173ac9 100644
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -1083,7 +1083,7 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
     ContextState() = default;
     ContextState(const ContextState& aOther);
 
-    ~ContextState() = default;
+    ~ContextState();
 
     void SetColorStyle(Style aWhichStyle, nscolor aColor);
     void SetPatternStyle(Style aWhichStyle, CanvasPattern* aPat);
@@ -1181,12 +1181,10 @@ class CanvasRenderingContext2D : public nsICanvasRenderingContextInternal,
 
   AutoTArray<ContextState, 3> mStyleStack;
 
-  inline ContextState& CurrentState() {
-    return mStyleStack[mStyleStack.Length() - 1];
-  }
+  inline ContextState& CurrentState() { return mStyleStack.LastElement(); }
 
   inline const ContextState& CurrentState() const {
-    return mStyleStack[mStyleStack.Length() - 1];
+    return mStyleStack.LastElement();
   }
 
   inline const ContextState& PreviousState() const {
diff --git a/layout/svg/SVGObserverUtils.cpp b/layout/svg/SVGObserverUtils.cpp
index cab63a80839..190f5fc50e2 100644
--- a/layout/svg/SVGObserverUtils.cpp
+++ b/layout/svg/SVGObserverUtils.cpp
@@ -912,6 +912,8 @@ class SVGFilterObserverList : public ISVGFilterObserverList {
   virtual void OnRenderingChange(Element* aObservingElement) = 0;
 
  protected:
+  SVGFilterObserverList(const SVGFilterObserverList& aOther)
+      : mObservers(aOther.mObservers.Clone()) {}
   virtual ~SVGFilterObserverList();
 
   void DetachObservers() {
@@ -985,7 +987,14 @@ class SVGFilterObserverListForCSSProp final : public SVGFilterObserverList {
                               GetFrameContentAsElement(aFilteredFrame),
                               aFilteredFrame) {}
 
+  ISVGFilterObserverList* Clone() const override {
+    return new SVGFilterObserverListForCSSProp(*this);
+  }
+
  protected:
+  SVGFilterObserverListForCSSProp(const SVGFilterObserverListForCSSProp& aOther)
+      : SVGFilterObserverList(aOther) {}
+
   void OnRenderingChange(Element* aObservingElement) override;
 };
 
@@ -1023,18 +1032,25 @@ class SVGFilterObserverListForCanvasContext final
       : SVGFilterObserverList(aFilters, aCanvasElement), mContext(aContext) {}
 
   void OnRenderingChange(Element* aObservingElement) override;
-  void Detach() override { mContext = nullptr; }
+  void SetIsActive(bool aActive) override { mActive = aActive; }
+  ISVGFilterObserverList* Clone() const override {
+    return new SVGFilterObserverListForCanvasContext(*this);
+  }
 
  private:
+  SVGFilterObserverListForCanvasContext(
+      const SVGFilterObserverListForCanvasContext& aOther)
+      : SVGFilterObserverList(aOther),
+        mContext(aOther.mContext),
+        mActive(aOther.mActive) {}
+
   CanvasRenderingContext2D* mContext;
+  bool mActive = true;
 };
 
 void SVGFilterObserverListForCanvasContext::OnRenderingChange(
     Element* aObservingElement) {
-  if (!mContext) {
-    NS_WARNING(
-        "GFX: This should never be called without a context, except during "
-        "cycle collection (when Detach has been called)");
+  if (!mActive) {
     return;
   }
   // Refresh the cached FilterDescription in mContext->CurrentState().filter.
diff --git a/layout/svg/SVGObserverUtils.h b/layout/svg/SVGObserverUtils.h
index 9690998eba4..36e8fb6e0b1 100644
--- a/layout/svg/SVGObserverUtils.h
+++ b/layout/svg/SVGObserverUtils.h
@@ -54,7 +54,8 @@ class ISVGFilterObserverList : public nsISupports {
   NS_DECL_CYCLE_COLLECTION_CLASS(ISVGFilterObserverList)
 
   virtual const nsTArray<RefPtr<SVGFilterObserver>>& GetObservers() const = 0;
-  virtual void Detach() {}
+  virtual void SetIsActive(bool aActive) {}
+  virtual ISVGFilterObserverList* Clone() const = 0;
 
  protected:
   virtual ~ISVGFilterObserverList() = default;
Loading diff…