Medium firefox Logic Error 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionMitigation bypass in the DOM: Security component
ComponentDOM
Bug ClassLogic Error
Tracker2042058
Fix commitbb9c61e77641 (firefox) +5/-0
CISA KEVNot listed
CreditedNguyen Minh
Disclosed2026-06-16

Background

`nsTreeSanitizer`
Gecko’s DOM sanitizer that walks a subtree and strips elements and attributes not on its allow-lists before untrusted markup is inserted into a document.
Custom element (`is` attribute)
a customized built-in element created via is="...", which associates a standard HTML element with a custom element definition and is tracked on the node by CustomElementData.
`CustomElementData`
per-element state, retrievable through Element::GetCustomElementData, that records the element’s custom-element identity (including its is value) and drives later upgrade/reaction callbacks.
Mitigation bypass
a defect where a security control (here, HTML sanitization) runs but its output can still reach a dangerous state, defeating the protection.

Root Cause Analysis

nsTreeSanitizer::SanitizeChildren in dom/base/nsTreeSanitizer.cpp scrubbed each element’s attributes against the allow-lists but left any attached CustomElementData intact, so a sanitized element retained its customized-built-in identity established through the is attribute. The sanitizer’s invariant is that its output contains only inert, allow-listed markup, but a node still carrying CustomElementData could later be upgraded against a custom element definition and run author-defined reaction callbacks, giving the element behavior the sanitizer intended to remove. Because the is attribute (and thus the custom-element association) survived sanitization, the security control was effectively bypassed for customized built-in elements.

The fix restores the invariant by detecting elt->HasCustomElementData() and calling elt->ClearCustomElementData(), severing the sanitized node from any custom-element definition so no later upgrade or callback can occur. The added MOZ_ASSERT on GetCustomElementData()->GetIs(elt) documents that the only path reaching this code is the customized-built-in (is-based) case.

Key insight
The single mistake was that sanitization normalized attributes but never cleared the separately-stored CustomElementData, leaving a sanitized element still bound to a custom-element definition; the fix clears that data during the sanitize walk so the node cannot later be upgraded into custom behavior.

Attack Path

  1. Craft customized built-in markup An attacker supplies HTML containing an element with an is="..." attribute referencing a custom element definition present in the target page.
  2. Route through the sanitizer The markup is inserted via a path that runs nsTreeSanitizer (e.g. a sanitized fragment), which strips disallowed attributes but leaves the element’s CustomElementData attached.
  3. Retain custom-element identity The sanitized element survives with its customized-built-in association intact rather than as inert markup.
  4. Trigger upgrade Once inserted into the document, the element is upgraded against the custom element definition, invoking author-defined reaction callbacks the sanitizer was meant to neutralize.

Impact Assessment

An attacker who can feed markup through nsTreeSanitizer can preserve a customized-built-in element’s link to a custom element definition, defeating the sanitizer’s guarantee and letting the element gain custom-element behavior in the content process/DOM context of the embedding page. Exploitation requires that the target environment already have a matching custom element definition and that the attacker-controlled markup reach a sanitizer-guarded insertion path. Rated moderate/medium impact as a mitigation bypass rather than direct memory corruption.

Changed Functions

FunctionChangeNotes
if
dom/base/nsTreeSanitizer.cpp
modified

Files Changed

  • dom/base/nsTreeSanitizer.cpp

Audit Directions

  • Out-of-band node state
    Audit other sanitization and cloning paths for per-node state (custom element data, shadow roots, reaction queues, expandos) that is not reset when attributes are scrubbed.
  • Sanitizer output invariants
    Verify every element the sanitizer emits is fully inert, treating retained identity/association state as a bypass even when the visible attributes are allow-listed.
  • Custom element lifecycle
    Review code that upgrades or runs reactions on elements after they pass through security-sensitive transforms, ensuring CustomElementData is cleared wherever an element is meant to be neutralized.
diff --git a/dom/base/nsTreeSanitizer.cpp b/dom/base/nsTreeSanitizer.cpp
index 8ee5120bf6d..c13ff63b026 100644
--- a/dom/base/nsTreeSanitizer.cpp
+++ b/dom/base/nsTreeSanitizer.cpp
@@ -1450,6 +1450,11 @@ void nsTreeSanitizer::SanitizeChildren(nsINode* aRoot) {
       NS_ASSERTION(ns == kNameSpaceID_XHTML || ns == kNameSpaceID_SVG ||
                        ns == kNameSpaceID_MathML,
                    "Should have only HTML, MathML or SVG here!");
+      if (elt->HasCustomElementData()) {
+        MOZ_ASSERT(elt->GetCustomElementData()->GetIs(elt),
+                   "CustomElementData without an |is| attribute?");
+        elt->ClearCustomElementData();
+      }
       AllowedAttributes allowed;
       if (ns == kNameSpaceID_XHTML) {
         allowed.mNames = sAttributesHTML;
Loading diff…