CVE-2026-12315
Overview
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 byCustomElementData. - `CustomElementData`
- per-element state, retrievable through
Element::GetCustomElementData, that records the element’s custom-element identity (including itsisvalue) 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.
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
- 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. - 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’sCustomElementDataattached. - Retain custom-element identity The sanitized element survives with its customized-built-in association intact rather than as inert markup.
- 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
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
| Function | Change | Notes |
|---|---|---|
ifdom/base/nsTreeSanitizer.cpp |
modified |
Files Changed
dom/base/nsTreeSanitizer.cpp
Audit Directions
- Out-of-band node stateAudit 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 invariantsVerify 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 lifecycleReview code that upgrades or runs reactions on elements after they pass through security-sensitive transforms, ensuring
CustomElementDatais cleared wherever an element is meant to be neutralized.
Patch
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;