CVE-2026-117936
Overview
Background
- `ElementInternals`
- the object exposing a form-associated custom element’s internals; it references the containing form via
mForm. - Cycle collection
- Gecko’s GC for refcounted DOM graphs; a member only participates (and is safely cleared) if it is traversed/unlinked.
Root Cause Analysis
ElementInternals::mForm was a raw HTMLFormElement*, justified by a comment claiming it would always be reset via CustomElementData::Unlink when the target was released or unlinked. That assumption did not hold in all teardown orders, so mForm could outlive the form and dangle — a use-after-free.
The fix makes mForm a RefPtr<HTMLFormElement> and adds it to the cycle-collection traverse (NS_IMPL_CYCLE_COLLECTION_TRAVERSE(..., mForm)), removing the raw-pointer init; the form is now kept alive and participates in cycle collection correctly.
RefPtr removes the dangling-pointer window.Attack Path
- ENTRY
From content, create a form-associated custom element and attach
ElementInternalsreferencing a containing form. - TRIGGER
Tear down / move the element or form in an order where
CustomElementData::Unlinkdoes not resetmFormfirst. - MISSING CHECK
mFormis a raw pointer not traversed by cycle collection, so it is neither kept alive nor cleared. - IMPACT
A later access to
mFormtouches freed memory — use-after-free. (Exact teardown order inferred; the raw→RefPtr + traverse change is the proof.)
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
fordom/html/ElementInternals.cpp |
modified | |
mControlNumberdom/html/ElementInternals.cpp |
modified |
Files Changed
dom/html/ElementInternals.cppdom/html/ElementInternals.h
Audit Directions
- Raw DOM back-pointers "safe via Unlink"Audit members documented as safe-because-unlink; verify every teardown/move path actually resets them, or convert to cycle-collected
RefPtr.
Patch
diff --git a/dom/html/ElementInternals.cpp b/dom/html/ElementInternals.cpp
index 101e17623a2..c4c70a64195 100644
--- a/dom/html/ElementInternals.cpp
+++ b/dom/html/ElementInternals.cpp
@@ -40,7 +40,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ElementInternals)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTarget, mSubmissionValue, mState,
mValidity, mValidationAnchor,
- mCustomStateSet);
+ mCustomStateSet, mForm);
for (auto& tableEntry : tmp->mAttrElementsMap) {
auto& [explicitlySetElements, cachedAttrElements] =
@@ -62,7 +62,6 @@ NS_INTERFACE_MAP_END
ElementInternals::ElementInternals(HTMLElement* aTarget)
: nsIFormControl(FormControlType::FormAssociatedCustomElement),
mTarget(aTarget),
- mForm(nullptr),
mFieldSet(nullptr),
mControlNumber(-1) {}
diff --git a/dom/html/ElementInternals.h b/dom/html/ElementInternals.h
index d77db940e61..c77734ef60f 100644
--- a/dom/html/ElementInternals.h
+++ b/dom/html/ElementInternals.h
@@ -230,9 +230,7 @@ class ElementInternals final : public nsIFormControl,
RefPtr<HTMLElement> mTarget;
// The form that contains the target element.
- // It's safe to use raw pointer because it will be reset via
- // CustomElementData::Unlink when mTarget is released or unlinked.
- HTMLFormElement* mForm;
+ RefPtr<HTMLFormElement> mForm;
// This is a pointer to the target element's closest fieldset parent if any.
// It's safe to use raw pointer because it will be reset via