Medium firefox Logic Error 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionMemory safety bug fixed in Firefox 152
ComponentCore
Bug ClassLogic Error
Tracker2041856
Fix commitea04eba8aa60 (firefox) +4/-1
CISA KEVNot listed
Creditedsatyamasd
Disclosed2026-06-16

Background

`nsHtml5TreeOperation`
the class that applies queued HTML5 parser tree-building operations (append, create element, etc.) to the live DOM.
`AutoSetThrowOnDynamicMarkupInsertionCounter`
an RAII guard that increments a per-Document counter which makes reentrant dynamic markup insertion (e.g. document.write) throw instead of mutating the tree.
`AutoCEReaction`
an RAII scope that drives the custom element reaction stack, causing queued custom element constructors and reaction callbacks to execute when it unwinds.
`nsHtml5AutoPauseUpdate`
an RAII guard that pauses the parser’s content-update batching while script (such as a custom element constructor) is allowed to run.

Root Cause Analysis

When nsHtml5TreeOperation::Append inserted a node whose parent’s DocGroup had a custom element queue pushed for the current recursion depth, it emplaced an AutoCEReaction and nsHtml5AutoPauseUpdate so custom element constructors would run, but it never established an AutoSetThrowOnDynamicMarkupInsertionCounter on the document. This violated the invariant that any code path running custom element script during HTML5 parsing must forbid reentrant dynamic markup insertion, so a constructor could call document.write/document.open and reenter the parser while its tree-operation state was still live. Separately, in nsHtml5TreeOperation::CreateHTMLElement the guard was constructed against the local document variable rather than the parser builder’s document (aBuilder->GetDocument()), so when those two documents differed the guard protected the wrong document and left the parser’s actual document open to reentrancy.

The fix adds the missing Maybe<AutoSetThrowOnDynamicMarkupInsertionCounter> in Append, emplacing it on aBuilder->GetDocument() before the reaction/pause guards, and repoints the existing guard in CreateHTMLElement to aBuilder->GetDocument(). This works because the throw-on-insertion counter is now raised on the exact document the parser is mutating whenever custom element script can run, so reentrant markup insertion throws instead of corrupting parser state.

Key insight
The single core mistake was allowing custom element constructors to execute during tree operations without arming the throw-on-dynamic-markup-insertion counter on the parser’s real document; the fix raises that counter (via a new Maybe-wrapped guard in Append and a corrected document reference in CreateHTMLElement) so any reentrant markup insertion is rejected rather than mutating a document the parser assumes is stable.

Attack Path

  1. Register a custom element The attacker’s page defines a custom element whose constructor or reaction callback runs attacker-controlled script.
  2. Trigger reactive parsing Markup that causes the HTML5 parser to append/create an instance of that element enqueues a custom element reaction during nsHtml5TreeOperation processing.
  3. Run script mid-operation As AutoCEReaction unwinds, the constructor runs while the parser’s tree-operation state is still live and unprotected.
  4. Reenter the parser The constructor calls a dynamic markup insertion API (e.g. document.write) that would normally be blocked, mutating or tearing down the document the parser is operating on.
  5. Corrupt parser state The parser resumes against a now-inconsistent document, leading to a memory-safety violation.

Impact Assessment

An attacker who controls page content gains a memory-safety corruption in the content process by reentering the HTML5 parser through a custom element constructor, potentially enabling exploitation typical of parser use-after-free/state-confusion bugs. The precondition is a page that defines a custom element and arranges markup that triggers its reaction during parsing; no elevated privileges are required beyond loading attacker markup. The corruption occurs during document parsing in the process hosting the target document.

Changed Functions

FunctionChangeNotes
if
parser/html/nsHtml5TreeOperation.cpp
modified

Files Changed

  • parser/html/nsHtml5TreeOperation.cpp

Audit Directions

  • Custom element reaction scopes
    Audit every site that emplaces AutoCEReaction or nsHtml5AutoPauseUpdate during parsing to confirm an AutoSetThrowOnDynamicMarkupInsertionCounter is armed on the same document before script can run.
  • Document-reference mismatches
    Look for guards or counters constructed against a locally-derived document when a builder/parser exposes its own GetDocument(), since the two can diverge and leave the wrong document protected.
  • Parser reentrancy invariants
    Review other nsHtml5TreeOperation paths that can invoke script (constructors, callbacks, mutation observers) for missing protection against reentrant document.write/document.open.
diff --git a/parser/html/nsHtml5TreeOperation.cpp b/parser/html/nsHtml5TreeOperation.cpp
index 50e386c8fce..afee91c2050 100644
--- a/parser/html/nsHtml5TreeOperation.cpp
+++ b/parser/html/nsHtml5TreeOperation.cpp
@@ -296,6 +296,8 @@ nsresult nsHtml5TreeOperation::Append(nsIContent* aNode, nsIContent* aParent,
     return NS_OK;
   }
 
+  Maybe<AutoSetThrowOnDynamicMarkupInsertionCounter>
+      throwOnDynamicMarkupInsertionCounter;
   Maybe<nsHtml5AutoPauseUpdate> autoPause;
   Maybe<AutoCEReaction> autoCEReaction;
   DocGroup* docGroup = aParent->OwnerDoc()->GetDocGroup();
@@ -308,6 +310,7 @@ nsresult nsHtml5TreeOperation::Append(nsIContent* aNode, nsIContent* aParent,
   if (autoCEReaction.isSome() && docGroup &&
       docGroup->CustomElementReactionsStack()
           ->IsElementQueuePushedForCurrentRecursionDepth()) {
+    throwOnDynamicMarkupInsertionCounter.emplace(aBuilder->GetDocument());
     autoPause.emplace(aBuilder);
   }
   return rv;
@@ -582,7 +585,7 @@ nsIContent* nsHtml5TreeOperation::CreateHTMLElement(
   if (customElementDefinition) {
     // This will cause custom element constructors to run.
     AutoSetThrowOnDynamicMarkupInsertionCounter
-        throwOnDynamicMarkupInsertionCounter(document);
+        throwOnDynamicMarkupInsertionCounter(aBuilder->GetDocument());
     nsHtml5AutoPauseUpdate autoPauseContentUpdate(aBuilder);
     {
       nsAutoMicroTask mt;
Loading diff…