High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionUse-after-free in the DOM: Core & HTML component
ComponentDOM
Bug ClassLogic Error
Tracker2082086
Fix commitfeb58931ff87 (firefox) +37/-33
CISA KEVNot listed
CreditedOpenAI Codex Security (kylebot)
Disclosed2026-10-27

Changed Functions

FunctionChangeNotes
if
dom/base/Document.cpp
modified
for
dom/base/Document.cpp
modified
if
dom/base/FragmentOrElement.cpp
modified
if
dom/base/FragmentOrElement.h
modified
for
dom/base/RadioGroupContainer.cpp
modified
nsIContent
dom/base/RadioGroupContainer.h
modified
RadioGroupContainer
dom/base/RadioGroupContainer.h
modified
mRadioGroupContainer
dom/html/HTMLInputElement.cpp
modified
mIsUserInteracting
dom/html/HTMLInputElement.cpp
modified

Files Changed

  • dom/base/Document.cpp
  • dom/base/Document.h
  • dom/base/FragmentOrElement.cpp
  • dom/base/FragmentOrElement.h
  • dom/base/RadioGroupContainer.cpp
  • dom/base/RadioGroupContainer.h
  • dom/html/HTMLInputElement.cpp
  • dom/html/HTMLInputElement.h
diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp
index e9e2dadc775..3a3c149ffae 100644
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -2744,9 +2744,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document)
     cb.NoteXPCOMChild(registry);
   }
 
-  if (tmp->mRadioGroupContainer) {
-    RadioGroupContainer::Traverse(tmp->mRadioGroupContainer.get(), cb);
-  }
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRadioGroupContainer)
 
   for (auto& sheets : tmp->mAdditionalSheets) {
     tmp->TraverseStyleSheets(sheets, "mAdditionalSheets[<origin>][i]", cb);
@@ -2958,7 +2956,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document)
   DocumentOrShadowRoot::Unlink(tmp);
   CustomElementRegistry::RemoveScopedRegistry(*tmp);
 
-  tmp->mRadioGroupContainer = nullptr;
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mRadioGroupContainer)
 
   // Document has a pretty complex destructor, so we're going to
   // assume that *most* cycles you actually want to break somewhere
@@ -21449,7 +21447,7 @@ FragmentDirective* Document::FragmentDirective() {
 
 RadioGroupContainer& Document::OwnedRadioGroupContainer() {
   if (!mRadioGroupContainer) {
-    mRadioGroupContainer = MakeUnique<RadioGroupContainer>();
+    mRadioGroupContainer = MakeRefPtr<RadioGroupContainer>();
   }
   return *mRadioGroupContainer;
 }
diff --git a/dom/base/Document.h b/dom/base/Document.h
index 8d9a21728b5..48350023b80 100644
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -6021,7 +6021,7 @@ class Document : public nsINode,
   RefPtr<ResizeObserver> mAutoSizeImageObserver;
 
   RefPtr<class FragmentDirective> mFragmentDirective;
-  UniquePtr<RadioGroupContainer> mRadioGroupContainer;
+  RefPtr<RadioGroupContainer> mRadioGroupContainer;
 
   nsCOMPtr<nsIURI> mTLSCertificateBindingURI;
 
diff --git a/dom/base/FragmentOrElement.cpp b/dom/base/FragmentOrElement.cpp
index 1d462b12469..35f2ff46671 100644
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -390,9 +390,8 @@ void FragmentOrElement::nsExtendedDOMSlots::TraverseExtendedSlots(
   if (mAnimations) {
     mAnimations->Traverse(aCb);
   }
-  if (mRadioGroupContainer) {
-    RadioGroupContainer::Traverse(mRadioGroupContainer.get(), aCb);
-  }
+  ImplCycleCollectionTraverse(aCb, mRadioGroupContainer,
+                              "mExtendedSlots->mRadioGroupContainer");
 }
 
 size_t FragmentOrElement::nsExtendedDOMSlots::SizeOfExcludingThis(
diff --git a/dom/base/FragmentOrElement.h b/dom/base/FragmentOrElement.h
index 6564d35edab..3ceaf4a237f 100644
--- a/dom/base/FragmentOrElement.h
+++ b/dom/base/FragmentOrElement.h
@@ -96,7 +96,7 @@ class FragmentOrElement : public nsIContent {
   RadioGroupContainer& OwnedRadioGroupContainer() {
     auto* slots = ExtendedDOMSlots();
     if (!slots->mRadioGroupContainer) {
-      slots->mRadioGroupContainer = MakeUnique<RadioGroupContainer>();
+      slots->mRadioGroupContainer = MakeRefPtr<RadioGroupContainer>();
     }
     return *slots->mRadioGroupContainer;
   }
@@ -203,7 +203,7 @@ class FragmentOrElement : public nsIContent {
      * RadioGroupContainer for radio buttons grouped under this disconnected
      * element.
      */
-    UniquePtr<RadioGroupContainer> mRadioGroupContainer;
+    RefPtr<RadioGroupContainer> mRadioGroupContainer;
 
     /**
      * Scroll state of this element's scroll container frame, saved while the
diff --git a/dom/base/RadioGroupContainer.cpp b/dom/base/RadioGroupContainer.cpp
index cfb8d23026f..c208c6aa5ca 100644
--- a/dom/base/RadioGroupContainer.cpp
+++ b/dom/base/RadioGroupContainer.cpp
@@ -29,22 +29,11 @@ struct nsRadioGroupStruct {
 
 RadioGroupContainer::RadioGroupContainer() = default;
 
-RadioGroupContainer::~RadioGroupContainer() {
-  for (const auto& group : mRadioGroups) {
-    for (const auto& button : group.GetData()->mRadioButtons.AsSpan()) {
-      // When the radio group container is being cycle-collected, any remaining
-      // connected buttons will also be in the process of being cycle-collected.
-      // Here, we unset the button's reference to the container so that when it
-      // is collected it does not attempt to remove itself from a potentially
-      // already deleted radio group.
-      button->DisconnectRadioGroupContainer();
-    }
-  }
-}
+RadioGroupContainer::~RadioGroupContainer() = default;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(RadioGroupContainer)
 
-/* static */
-void RadioGroupContainer::Traverse(RadioGroupContainer* tmp,
-                                   nsCycleCollectionTraversalCallback& cb) {
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(RadioGroupContainer)
   for (const auto& entry : tmp->mRadioGroups) {
     nsRadioGroupStruct* radioGroup = entry.GetWeak();
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
@@ -57,7 +46,20 @@ void RadioGroupContainer::Traverse(RadioGroupContainer* tmp,
       cb.NoteXPCOMChild(ToSupports(button));
     }
   }
-}
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(RadioGroupContainer)
+  for (const auto& group : tmp->mRadioGroups) {
+    for (const auto& button : group.GetData()->mRadioButtons.AsSpan()) {
+      // Any remaining connected buttons are also being cycle-collected. Unset
+      // the button's reference to the container so that when it is collected
+      // it does not attempt to remove itself from an already cleared radio
+      // group.
+      button->DisconnectRadioGroupContainer();
+    }
+  }
+  tmp->mRadioGroups.Clear();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 size_t RadioGroupContainer::SizeOfIncludingThis(
     MallocSizeOf aMallocSizeOf) const {
diff --git a/dom/base/RadioGroupContainer.h b/dom/base/RadioGroupContainer.h
index 24291fa2e83..4be15f8f97d 100644
--- a/dom/base/RadioGroupContainer.h
+++ b/dom/base/RadioGroupContainer.h
@@ -6,6 +6,8 @@
 #define mozilla_dom_nsRadioGroupStruct_h
 
 #include "nsClassHashtable.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupportsImpl.h"
 
 class nsIContent;
 
@@ -16,11 +18,11 @@ struct nsRadioGroupStruct;
 
 class RadioGroupContainer final {
  public:
+  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(RadioGroupContainer)
+  NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(RadioGroupContainer)
+
   RadioGroupContainer();
-  ~RadioGroupContainer();
 
-  static void Traverse(RadioGroupContainer* tmp,
-                       nsCycleCollectionTraversalCallback& cb);
   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
 
   template <typename VisitCallback>
@@ -53,6 +55,8 @@ class RadioGroupContainer final {
   nsRadioGroupStruct* GetOrCreateRadioGroup(const nsAString& aName);
 
  private:
+  ~RadioGroupContainer();
+
   Span<const RefPtr<HTMLInputElement>> GetButtonsInGroup(
       nsRadioGroupStruct* aGroup) const;
 
diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp
index 2c34f622855..4ee43d29d8c 100644
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -1160,8 +1160,7 @@ HTMLInputElement::HTMLInputElement(already_AddRefed<dom::NodeInfo> aNodeInfo,
       mHasBeenTypePassword(false),
       mHasPatternAttribute(false),
       mUserChangedSinceFocus(false),
-      mIsUserInteracting(false),
-      mRadioGroupContainer(nullptr) {
+      mIsUserInteracting(false) {
   // If size is above 512, mozjemalloc allocates 1kB, see
   // memory/build/mozjemalloc.cpp
   static_assert(sizeof(HTMLInputElement) <= 512,
@@ -1250,6 +1249,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLInputElement,
                                                   TextControlElement)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRadioGroupContainer)
   if (tmp->IsSingleLineTextControl(false) && tmp->mInputData.mState) {
     tmp->mInputData.mState->Traverse(cb);
   }
@@ -1263,6 +1263,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLInputElement,
                                                 TextControlElement)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mRadioGroupContainer)
   if (tmp->IsSingleLineTextControl(false) && tmp->mInputData.mState) {
     tmp->mInputData.mState->Unlink();
   }
diff --git a/dom/html/HTMLInputElement.h b/dom/html/HTMLInputElement.h
index a25389d24c6..3827cffbd6a 100644
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -1695,7 +1695,7 @@ class HTMLInputElement final : public TextControlElement,
    * The radio group container containing the group the element is a part of.
    * This allows the element to only access a container it has been added to.
    */
-  RadioGroupContainer* mRadioGroupContainer;
+  RefPtr<RadioGroupContainer> mRadioGroupContainer;
 
   struct nsFilePickerFilter {
     nsFilePickerFilter() : mFilterMask(0) {}
Loading diff…