High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionUse-after-free in the XML component
ComponentDOM
Bug ClassLogic Error
Tracker2075996
Fix commit01c4f7862c6b (firefox) +93/-2
CISA KEVNot listed
CreditedMozilla
Disclosed2026-10-27

Changed Functions

FunctionChangeNotes
if
dom/xml/crashtests/2075996-1.html
modified
if
dom/xml/crashtests/2075996-2.html
modified
if
dom/xml/crashtests/2075996.sjs
modified

Files Changed

  • dom/xml/crashtests/2075996-1.html
  • dom/xml/crashtests/2075996-2.html
  • dom/xml/crashtests/2075996.sjs
  • dom/xml/crashtests/crashtests.list
  • dom/xml/nsXMLContentSink.cpp
  • dom/xml/nsXMLPrettyPrinter.cpp
diff --git a/dom/xml/nsXMLContentSink.cpp b/dom/xml/nsXMLContentSink.cpp
index 073827fdfa1..5d6caa7f1c8 100644
--- a/dom/xml/nsXMLContentSink.cpp
+++ b/dom/xml/nsXMLContentSink.cpp
@@ -158,6 +158,11 @@ nsresult nsXMLContentSink::MaybePrettyPrint() {
     nsAutoMicroTask mt;
   }
 
+  if (mDocument->GetRootElement() != mDocElement) {
+    mPrettyPrintXML = false;
+    return NS_OK;
+  }
+
   // Reenable the CSSLoader so that the prettyprinting stylesheets can load
   mDocument->EnsureCSSLoader().SetEnabled(true);
 
diff --git a/dom/xml/nsXMLPrettyPrinter.cpp b/dom/xml/nsXMLPrettyPrinter.cpp
index fa4dfc5fab2..a20ada9be82 100644
--- a/dom/xml/nsXMLPrettyPrinter.cpp
+++ b/dom/xml/nsXMLPrettyPrinter.cpp
@@ -49,8 +49,9 @@ nsresult nsXMLPrettyPrinter::PrettyPrint(Document* aDocument,
   NS_ENSURE_TRUE(rootElement, NS_ERROR_UNEXPECTED);
 
   // nsXMLContentSink should not ask us to pretty print an XML doc that comes
-  // with a CanAttachShadowDOM() == true root element, but just in case:
-  if (rootElement->CanAttachShadowDOM()) {
+  // with a CanAttachShadowDOM() == true root element or a root element that
+  // already has a shadow root, but just in case:
+  if (rootElement->CanAttachShadowDOM() || rootElement->GetShadowRoot()) {
     MOZ_DIAGNOSTIC_CRASH("We shouldn't be getting this root element");
     return NS_ERROR_UNEXPECTED;
   }
diff --git a/dom/xml/crashtests/2075996-1.html b/dom/xml/crashtests/2075996-1.html
new file mode 100644
index 00000000000..f07d1a39d07
--- /dev/null
+++ b/dom/xml/crashtests/2075996-1.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html class="reftest-wait">
+<body>
+<script>
+const KEY = "details";
+const url = `2075996.sjs?start&${KEY}`;
+const frame = document.createElement("iframe");
+frame.onload = () => {
+  const root = frame.contentDocument.documentElement;
+  root.setAttribute("data-a", "b");
+  root.getBoundingClientRect();
+  document.documentElement.removeAttribute("class");
+};
+frame.src = url;
+document.body.append(frame);
+const poll = setInterval(() => {
+  const doc = frame.contentDocument;
+  if (!doc?.URL.endsWith(url)) {
+    return;
+  }
+  clearInterval(poll);
+  doc.addEventListener("readystatechange", () => {
+    if (doc.readyState == "interactive") {
+      const el = doc.createElementNS("http://www.w3.org/1999/xhtml", KEY);
+      el.append("text");
+      doc.replaceChild(el, doc.documentElement);
+    }
+  });
+  fetch(`2075996.sjs?finish&${KEY}`);
+}, 0);
+</script>
+</body>
+</html>
diff --git a/dom/xml/crashtests/2075996-2.html b/dom/xml/crashtests/2075996-2.html
new file mode 100644
index 00000000000..9e10c74b93e
--- /dev/null
+++ b/dom/xml/crashtests/2075996-2.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html class="reftest-wait">
+<body>
+<script>
+const KEY = "div";
+const url = `2075996.sjs?start&${KEY}`;
+const frame = document.createElement("iframe");
+frame.onload = () => {
+  const root = frame.contentDocument.documentElement;
+  root.setAttribute("data-a", "b");
+  root.getBoundingClientRect();
+  document.documentElement.removeAttribute("class");
+};
+frame.src = url;
+document.body.append(frame);
+const poll = setInterval(() => {
+  const doc = frame.contentDocument;
+  if (!doc?.URL.endsWith(url)) {
+    return;
+  }
+  clearInterval(poll);
+  doc.addEventListener("readystatechange", () => {
+    if (doc.readyState == "interactive") {
+      const el = doc.createElementNS("http://www.w3.org/1999/xhtml", KEY);
+      el.append("text");
+      doc.replaceChild(el, doc.documentElement);
+    }
+  });
+  fetch(`2075996.sjs?finish&${KEY}`);
+}, 0);
+</script>
+</body>
+</html>
diff --git a/dom/xml/crashtests/2075996.sjs b/dom/xml/crashtests/2075996.sjs
new file mode 100644
index 00000000000..d8dcedee133
--- /dev/null
+++ b/dom/xml/crashtests/2075996.sjs
@@ -0,0 +1,17 @@
+function handleRequest(request, response) {
+  const [action, key] = request.queryString.split("&");
+  response.setHeader("Cache-Control", "no-cache", false);
+  if (action == "start") {
+    response.processAsync();
+    response.setHeader("Content-Type", "application/xml", false);
+    response.write("<root>");
+    setObjectState(key, response);
+    return;
+  }
+  getObjectState(key, xmlResponse => {
+    xmlResponse.write("</root>");
+    xmlResponse.finish();
+  });
+  response.setHeader("Content-Type", "text/plain", false);
+  response.write("ok");
+}
diff --git a/dom/xml/crashtests/crashtests.list b/dom/xml/crashtests/crashtests.list
index f53f69d6d69..8945f90c92d 100644
--- a/dom/xml/crashtests/crashtests.list
+++ b/dom/xml/crashtests/crashtests.list
@@ -14,3 +14,5 @@ load 1192544.xhtml
 load 1405878.xml
 load 1523655.xml
 load 2073692.html
+HTTP load 2075996-1.html
+HTTP load 2075996-2.html
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/dom/xml/crashtests/2075996-1.html b/dom/xml/crashtests/2075996-1.html
new file mode 100644
index 00000000000..f07d1a39d07
--- /dev/null
+++ b/dom/xml/crashtests/2075996-1.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html class="reftest-wait">
+<body>
+<script>
+const KEY = "details";
+const url = `2075996.sjs?start&${KEY}`;
+const frame = document.createElement("iframe");
+frame.onload = () => {
+  const root = frame.contentDocument.documentElement;
+  root.setAttribute("data-a", "b");
+  root.getBoundingClientRect();
+  document.documentElement.removeAttribute("class");
+};
+frame.src = url;
+document.body.append(frame);
+const poll = setInterval(() => {
+  const doc = frame.contentDocument;
+  if (!doc?.URL.endsWith(url)) {
+    return;
+  }
+  clearInterval(poll);
+  doc.addEventListener("readystatechange", () => {
+    if (doc.readyState == "interactive") {
+      const el = doc.createElementNS("http://www.w3.org/1999/xhtml", KEY);
+      el.append("text");
+      doc.replaceChild(el, doc.documentElement);
+    }
+  });
+  fetch(`2075996.sjs?finish&${KEY}`);
+}, 0);
+</script>
+</body>
+</html>
diff --git a/dom/xml/crashtests/2075996-2.html b/dom/xml/crashtests/2075996-2.html
new file mode 100644
index 00000000000..9e10c74b93e
--- /dev/null
+++ b/dom/xml/crashtests/2075996-2.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html class="reftest-wait">
+<body>
+<script>
+const KEY = "div";
+const url = `2075996.sjs?start&${KEY}`;
+const frame = document.createElement("iframe");
+frame.onload = () => {
+  const root = frame.contentDocument.documentElement;
+  root.setAttribute("data-a", "b");
+  root.getBoundingClientRect();
+  document.documentElement.removeAttribute("class");
+};
+frame.src = url;
+document.body.append(frame);
+const poll = setInterval(() => {
+  const doc = frame.contentDocument;
+  if (!doc?.URL.endsWith(url)) {
+    return;
+  }
+  clearInterval(poll);
+  doc.addEventListener("readystatechange", () => {
+    if (doc.readyState == "interactive") {
+      const el = doc.createElementNS("http://www.w3.org/1999/xhtml", KEY);
+      el.append("text");
+      doc.replaceChild(el, doc.documentElement);
+    }
+  });
+  fetch(`2075996.sjs?finish&${KEY}`);
+}, 0);
+</script>
+</body>
+</html>
diff --git a/dom/xml/crashtests/2075996.sjs b/dom/xml/crashtests/2075996.sjs
new file mode 100644
index 00000000000..d8dcedee133
--- /dev/null
+++ b/dom/xml/crashtests/2075996.sjs
@@ -0,0 +1,17 @@
+function handleRequest(request, response) {
+  const [action, key] = request.queryString.split("&");
+  response.setHeader("Cache-Control", "no-cache", false);
+  if (action == "start") {
+    response.processAsync();
+    response.setHeader("Content-Type", "application/xml", false);
+    response.write("<root>");
+    setObjectState(key, response);
+    return;
+  }
+  getObjectState(key, xmlResponse => {
+    xmlResponse.write("</root>");
+    xmlResponse.finish();
+  });
+  response.setHeader("Content-Type", "text/plain", false);
+  response.write("ok");
+}
diff --git a/dom/xml/crashtests/crashtests.list b/dom/xml/crashtests/crashtests.list
index f53f69d6d69..8945f90c92d 100644
--- a/dom/xml/crashtests/crashtests.list
+++ b/dom/xml/crashtests/crashtests.list
@@ -14,3 +14,5 @@ load 1192544.xhtml
 load 1405878.xml
 load 1523655.xml
 load 2073692.html
+HTTP load 2075996-1.html
+HTTP load 2075996-2.html
Loading diff…