Medium chrome Type Confusion 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactType Confusion in XML
DescriptionType Confusion in XML
ComponentXML
Bug ClassType Confusion
Tracker503879106
Fix commitf022d55a424e (chromium/src) +105/-3
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
if
third_party/libxslt/chromium/0006-Fix-type-confusion-in-xsltParseTemplateContent.patch
modified
if
third_party/libxslt/src/libxslt/xslt.c
modified

Files Changed

  • third_party/blink/web_tests/TestExpectations
  • third_party/blink/web_tests/external/wpt/xml/xslt/xslt-type-confusion-crash.html
  • third_party/blink/web_tests/external/wpt/xml/xslt/xslt-type-confusion.xml
  • third_party/blink/web_tests/external/wpt/xml/xslt/xslt-type-confusion.xsl
  • third_party/libxslt/chromium/0006-Fix-type-confusion-in-xsltParseTemplateContent.patch
  • third_party/libxslt/chromium/roll.py
  • third_party/libxslt/src/libxslt/xslt.c
From f022d55a424e6b5727681af0002b9e185b3b808b Mon Sep 17 00:00:00 2001
From: Andrew Paseltiner <apaseltiner@chromium.org>
Date: Mon, 27 Apr 2026 06:18:50 -0700
Subject: [PATCH] Fix type confusion in xsltParseTemplateContent

A type confusion vulnerability exists in libxslt's
xsltParseTemplateContent function. While many libxml2 node types share a
common structural header, the ns field is only valid for xmlNode and
xmlAttr. When traversing into a DTD, the code could process an
XML_ENTITY_DECL node as if it were an XML_ELEMENT_NODE. At the memory
offset where an element would have its ns pointer, an entity has its
orig string pointer. Treating this string pointer as a namespace
structure allows an attacker to control the prefix field, leading to a
heap-buffer-overflow (READ) in xsltCheckExtPrefix.

This patch adds a check to ensure that the node is an element before
accessing its namespace field and robustly skips all DTD-related
declarations during traversal.

Bug: 503879106
Change-Id: I34a4357a6790744adfd31c2e03181bee5e13f587
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7779759
Commit-Queue: Andrew Paseltiner <apaseltiner@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1621039}
---

diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 49a0ef2..6d9fad5 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1659,6 +1659,7 @@
 # disables the main XSLT flag, which should keep XSLT disabled.
 crbug.com/421650040 external/wpt/xml/xslt/* [ Failure ]
 crbug.com/503627446 external/wpt/xml/xslt/xslt-attribute-double-free-crash.html [ Pass ]
+crbug.com/503879106 external/wpt/xml/xslt/xslt-type-confusion-crash.html [ Pass ]
 crbug.com/421650040 virtual/xslt-enabled/external/wpt/xml/xslt/* [ Pass ]
 crbug.com/421650040 http/tests/xsl/xslt/* [ Failure ]
 crbug.com/421650040 virtual/xslt-enabled/http/tests/xsl/xslt/* [ Pass ]
diff --git a/third_party/blink/web_tests/external/wpt/xml/xslt/xslt-type-confusion-crash.html b/third_party/blink/web_tests/external/wpt/xml/xslt/xslt-type-confusion-crash.html
new file mode 100644
index 0000000..d2b753b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/xml/xslt/xslt-type-confusion-crash.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<title>XSLT type confusion crash test</title>
+<link rel="help" href="https://crbug.com/503879106">
+<body>
+    <p>Test passes if it does not crash.</p>
+    <iframe src="xslt-type-confusion.xml"></iframe>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/xml/xslt/xslt-type-confusion.xml b/third_party/blink/web_tests/external/wpt/xml/xslt/xslt-type-confusion.xml
new file mode 100644
index 0000000..35110f99
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/xml/xslt/xslt-type-confusion.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="xslt-type-confusion.xsl"?>
+<root/>
diff --git a/third_party/blink/web_tests/external/wpt/xml/xslt/xslt-type-confusion.xsl b/third_party/blink/web_tests/external/wpt/xml/xslt/xslt-type-confusion.xsl
new file mode 100644
index 0000000..671a193
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/xml/xslt/xslt-type-confusion.xsl
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<!DOCTYPE root [
+  <!-- Bypasses an insufficient check in libxslt that only skipped the DTD if
+       its first child was an entity. This comment forces the parser to
+       descend into the DTD and process entities as elements. -->
+  <!ENTITY exploit "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA">
+]>
+<root xsl:version="1.0"
+      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+      xsl:extension-element-prefixes="ext"
+      xmlns:ext="http://example.com/ext">
+  <xsl:template match="/">
+    <html>
+      <body>
+        <h1>Test</h1>
+      </body>
+    </html>
+  </xsl:template>
+</root>
diff --git a/third_party/libxslt/chromium/0006-Fix-type-confusion-in-xsltParseTemplateContent.patch b/third_party/libxslt/chromium/0006-Fix-type-confusion-in-xsltParseTemplateContent.patch
new file mode 100644
index 0000000..9d31bd1
--- /dev/null
+++ b/third_party/libxslt/chromium/0006-Fix-type-confusion-in-xsltParseTemplateContent.patch
@@ -0,0 +1,64 @@
+From 4c582b582b582b582b582b582b582b582b582b58 Mon Sep 17 00:00:00 2001
+From: Andrew Paseltiner <apaseltiner@chromium.org>
+Date: Thu, 23 Apr 2026 14:00:00 -0400
+Subject: [PATCH] Fix type confusion in xsltParseTemplateContent
+
+A potential type confusion vulnerability exists in libxslt within the
+xsltParseTemplateContent function. When a DTD contains a comment as its
+first child, the traversal logic can descend into the DTD and process
+an XML_ENTITY_DECL node as if it were an XML_ELEMENT_NODE.
+
+This leads to a type confusion where the 'ns' field of an xmlNode
+(offset 72) overlaps with the 'orig' string pointer of an xmlEntity.
+Treating the 'orig' pointer as an xmlNs struct allows an attacker to
+control the 'prefix' field (offset 24), which is then dereferenced in
+xsltCheckExtPrefix. This results in a heap-buffer-overflow (READ) and
+can lead to a deterministic crash (Denial of Service) or a constrained
+arbitrary read primitive within the renderer process.
+
+This patch adds a check to ensure that the node is an element before
+accessing its namespace field and robustly skips all DTD-related
+declarations during traversal. While DTD-related nodes share a common
+header with xmlNode, they lack the 'ns' field, which could lead to type
+confusion if their fields were accessed as if they were elements.
+
+Bug: 503879106
+---
+ libxslt/xslt.c | 11 ++++++++---
+ 1 file changed, 8 insertions(+), 3 deletions(-)
+
+diff --git a/libxslt/xslt.c b/libxslt/xslt.c
+index 6532f976b3a02..594696199b658 100644
+--- a/libxslt/xslt.c
++++ b/libxslt/xslt.c
+@@ -5019,7 +5019,8 @@ xsltParseTemplateContent(xsltStylesheetPtr style, xmlNodePtr templ) {
+                goto skip_children;
+            }
+        }
+-       else if ((cur->ns != NULL) && (style->nsDefs != NULL) &&
++       else if ((cur->type == XML_ELEMENT_NODE) && (cur->ns != NULL) &&
++           (style->nsDefs != NULL) &&
+            (xsltCheckExtPrefix(style, cur->ns->prefix)))
+        {
+            /*
+@@ -5047,10 +5048,16 @@ xsltParseTemplateContent(xsltStylesheetPtr style, xmlNodePtr templ) {
+            }
+        }
+        /*
+-        * Skip to next node
++        * Skip to next node. DTD-related nodes are explicitly skipped because
++        * they lack the `ns` field found in `xmlNode` and `xmlAttr`. While
++        * they share a common header with `xmlNode`, accessing their fields
++        * as if they were elements can lead to type confusion.
+         */
+        if (cur->children != NULL) {
+-           if (cur->children->type != XML_ENTITY_DECL) {
++           if ((cur->type != XML_ENTITY_DECL) &&
++               (cur->type != XML_DTD_NODE) &&
++               (cur->type != XML_ELEMENT_DECL) &&
++               (cur->type != XML_ATTRIBUTE_DECL)) {
+                cur = cur->children;
+                continue;
+            }
+-- 
+2.50.0.rc0.642.g800a2b2222-goog
diff --git a/third_party/libxslt/chromium/roll.py b/third_party/libxslt/chromium/roll.py
index 410762e..277e0f4 100755
--- a/third_party/libxslt/chromium/roll.py
+++ b/third_party/libxslt/chromium/roll.py
@@ -74,6 +74,7 @@
     'xslt-locale.patch',
     '0004-Use-a-dedicated-node-type-to-maintain-the-list-of-ca.patch',
     '0005-Verify-dictionary-ownership-before-aliasing-attribut.patch',
+    '0006-Fix-type-confusion-in-xsltParseTemplateContent.patch',
 ]
 
 
diff --git a/third_party/libxslt/src/libxslt/xslt.c b/third_party/libxslt/src/libxslt/xslt.c
index 6532f97..59469619 100644
--- a/third_party/libxslt/src/libxslt/xslt.c
+++ b/third_party/libxslt/src/libxslt/xslt.c
@@ -5019,7 +5019,8 @@
 		goto skip_children;
 	    }
 	}
-	else if ((cur->ns != NULL) && (style->nsDefs != NULL) &&
+	else if ((cur->type == XML_ELEMENT_NODE) && (cur->ns != NULL) &&
+	    (style->nsDefs != NULL) &&
 	    (xsltCheckExtPrefix(style, cur->ns->prefix)))
 	{
 	    /*
@@ -5047,10 +5048,16 @@
 	    }
 	}
 	/*
-	 * Skip to next node
+	 * Skip to next node. DTD-related nodes are explicitly skipped because
+	 * they lack the `ns` field found in `xmlNode` and `xmlAttr`. While
+	 * they share a common header with `xmlNode`, accessing their fields
+	 * as if they were elements can lead to type confusion.
 	 */
 	if (cur->children != NULL) {
-	    if (cur->children->type != XML_ENTITY_DECL) {
+	    if ((cur->type != XML_ENTITY_DECL) &&
+		(cur->type != XML_DTD_NODE) &&
+		(cur->type != XML_ELEMENT_DECL) &&
+		(cur->type != XML_ATTRIBUTE_DECL)) {
 		cur = cur->children;
 		continue;
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 49a0ef2..6d9fad5 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1659,6 +1659,7 @@
 # disables the main XSLT flag, which should keep XSLT disabled.
 crbug.com/421650040 external/wpt/xml/xslt/* [ Failure ]
 crbug.com/503627446 external/wpt/xml/xslt/xslt-attribute-double-free-crash.html [ Pass ]
+crbug.com/503879106 external/wpt/xml/xslt/xslt-type-confusion-crash.html [ Pass ]
 crbug.com/421650040 virtual/xslt-enabled/external/wpt/xml/xslt/* [ Pass ]
 crbug.com/421650040 http/tests/xsl/xslt/* [ Failure ]
 crbug.com/421650040 virtual/xslt-enabled/http/tests/xsl/xslt/* [ Pass ]
diff --git a/third_party/blink/web_tests/external/wpt/xml/xslt/xslt-type-confusion-crash.html b/third_party/blink/web_tests/external/wpt/xml/xslt/xslt-type-confusion-crash.html
new file mode 100644
index 0000000..d2b753b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/xml/xslt/xslt-type-confusion-crash.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<title>XSLT type confusion crash test</title>
+<link rel="help" href="https://crbug.com/503879106">
+<body>
+    <p>Test passes if it does not crash.</p>
+    <iframe src="xslt-type-confusion.xml"></iframe>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/xml/xslt/xslt-type-confusion.xml b/third_party/blink/web_tests/external/wpt/xml/xslt/xslt-type-confusion.xml
new file mode 100644
index 0000000..35110f99
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/xml/xslt/xslt-type-confusion.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="xslt-type-confusion.xsl"?>
+<root/>
diff --git a/third_party/blink/web_tests/external/wpt/xml/xslt/xslt-type-confusion.xsl b/third_party/blink/web_tests/external/wpt/xml/xslt/xslt-type-confusion.xsl
new file mode 100644
index 0000000..671a193
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/xml/xslt/xslt-type-confusion.xsl
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<!DOCTYPE root [
+  <!-- Bypasses an insufficient check in libxslt that only skipped the DTD if
+       its first child was an entity. This comment forces the parser to
+       descend into the DTD and process entities as elements. -->
+  <!ENTITY exploit "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA">
+]>
+<root xsl:version="1.0"
+      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+      xsl:extension-element-prefixes="ext"
+      xmlns:ext="http://example.com/ext">
+  <xsl:template match="/">
+    <html>
+      <body>
+        <h1>Test</h1>
+      </body>
+    </html>
+  </xsl:template>
+</root>
Loading diff…

Original Bug Report

reported by rj...@google.com

Potential type confusion and OOB read in libxslt xsltParseTemplateContent

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential type confusion vulnerability in libxslt’s xsltParseTemplateContent occurs when an xmlEntity (specifically XML_ENTITY_DECL) is processed as an xmlNode. Because the structs share a common memory layout up to offset 64, accessing the ns field of the node actually reads the orig string pointer of the entity. This pointer is then treated as an xmlNs struct, leading to a heap buffer overflow (READ) when the prefix field is accessed and subsequently dereferenced in xmlStrEqual.

Affected files:

  • third_party/libxslt/src/libxslt/xslt.c

Estimated timestamp from git blame: 2008-07-26

Description

A potential type confusion vulnerability exists in libxslt within the xsltParseTemplateContent function (third_party/libxslt/src/libxslt/xslt.c). When a simplified XSLT stylesheet (Literal Result Element as Stylesheet) is processed, the entire document (doc) is passed to xsltParseTemplateContent to be parsed as template content. This causes the function to traverse the document’s children, including the xmlDtd node and its declarations.

While the traversal logic attempts to avoid descending into entity declarations with the check if (cur->children->type != XML_ENTITY_DECL) at line 5053, this check is insufficient. If an internal DTD contains a comment (or any other node type) as its first child, followed by an XML_ENTITY_DECL, the check passes. The traversal then enters the DTD’s children list and eventually processes the XML_ENTITY_DECL node.

At line 5022, the code evaluates else if ((cur->ns != NULL) && ...) assuming cur is an xmlNode. However, cur is an xmlEntity. On 64-bit systems, the ns field in struct _xmlNode and the orig field (which stores the raw entity string) in struct _xmlEntity both reside at offset 72. Consequently, cur->ns reads the string pointer provided by the attacker.

Because this pointer is not NULL, the code proceeds to evaluate xsltCheckExtPrefix(style, cur->ns->prefix). It treats the attacker-controlled string as a struct _xmlNs and attempts to read its prefix field, which is located at offset 24. This reads 8 bytes from the string buffer. These 8 bytes are then passed to xsltCheckExtPrefix and dereferenced by xmlStrEqual.

Impact

This results in a heap-buffer-overflow (READ). Depending on the length of the entity string, it either reads out of bounds of the string allocation, or an attacker can fully control the 8-byte value read. This controlled value is then dereferenced, leading to a deterministic crash (Denial of Service) or a constrained arbitrary read primitive within the renderer process.

Potential Reproduction Steps

(Note: These are potential steps based on code analysis; a working Proof of Concept has not been run.)

  1. Construct a malicious XML document designed to trigger the simplified XSLT stylesheet processing path (e.g., using an xsl:version attribute on the root element).
  2. Include an internal DTD in the document: <!DOCTYPE root [ ... ]>.
  3. Inside the DTD, add a comment as the first child to bypass the entity check: <!-- bypass -->.
  4. Follow the comment with an entity declaration containing a string longer than 32 bytes: <!ENTITY exploit "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA">.
  5. Serve this document to the Chrome renderer via an <?xml-stylesheet ...?> processing instruction.

Suggested Fix

  1. Modify the traversal logic in xsltParseTemplateContent to robustly ignore all DTD-related declarations rather than only checking the first child.
  2. Add an explicit check ensuring cur->type == XML_ELEMENT_NODE (or a macro like IS_XSLT_ELEM) before accessing cur->ns, preventing type confusion with other node types that overlap struct fields.

Evaluated with Chrome root at commit: 2b349e31cb87959d6a548625986c65e0a2d2e380


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker