From 226b560837b90dea9b14431eca6e6fda8fb01ab4 Mon Sep 17 00:00:00 2001
From: Ariel Schon <ariel.schon12@gmail.com>
Date: Tue, 14 Apr 2026 21:57:15 +0000
Subject: [PATCH 1/2] parser: Pass userData to SAX text callbacks in
 xmlParseReference

When replaceEntities is on and the entity's cached tree is reused,
the first/last text-child optimization in xmlParseReference called
ctxt->sax->characters / cdataBlock with ctxt as the first argument
instead of ctxt->userData. Every other SAX callsite in parser.c
correctly passes ctxt->userData.

With xmlSchemaSAXPlug installed (as set up by
xmlTextReaderSetSchema / xmlTextReaderSchemaValidate), userData is
a _xmlSchemaSAXPlug*, so handing over a raw ctxt to the registered
charactersSplit handler produces a type-confused dereference:
charactersSplit reads ctxt->myDoc as user_sax and ctxt->myDoc->URL
as user_sax->characters, then calls that string pointer as code.
The result is a reliable SIGSEGV on the first entity reference in
any XSD-validated xmlTextReader stream (and a silent validation
bypass on the rare targets where the confused call doesn't crash).

Fix by passing ctxt->userData, matching the rest of the file.
---
 parser.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

Index: libxml2-2.13.8/parser.c
===================================================================
--- libxml2-2.13.8.orig/parser.c
+++ libxml2-2.13.8/parser.c
@@ -7488,10 +7488,10 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
             if ((cur->type == XML_TEXT_NODE) ||
                 (ctxt->sax->cdataBlock == NULL)) {
                 if (ctxt->sax->characters != NULL)
-                    ctxt->sax->characters(ctxt, cur->content, len);
+                    ctxt->sax->characters(ctxt->userData, cur->content, len);
             } else {
                 if (ctxt->sax->cdataBlock != NULL)
-                    ctxt->sax->cdataBlock(ctxt, cur->content, len);
+                    ctxt->sax->cdataBlock(ctxt->userData, cur->content, len);
             }
 
             cur = cur->next;
@@ -7511,10 +7511,12 @@ xmlParseReference(xmlParserCtxtPtr ctxt)
                 if ((cur->type == XML_TEXT_NODE) ||
                     (ctxt->sax->cdataBlock == NULL)) {
                     if (ctxt->sax->characters != NULL)
-                        ctxt->sax->characters(ctxt, cur->content, len);
+                        ctxt->sax->characters(ctxt->userData, cur->content,
+                                              len);
                 } else {
                     if (ctxt->sax->cdataBlock != NULL)
-                        ctxt->sax->cdataBlock(ctxt, cur->content, len);
+                        ctxt->sax->cdataBlock(ctxt->userData, cur->content,
+                                              len);
                 }
 
                 break;
Index: libxml2-2.13.8/testparser.c
===================================================================
--- libxml2-2.13.8.orig/testparser.c
+++ libxml2-2.13.8/testparser.c
@@ -11,6 +11,7 @@
 #include <libxml/xmlreader.h>
 #include <libxml/xmlwriter.h>
 #include <libxml/HTMLparser.h>
+#include <libxml/xmlschemas.h>
 
 #include <string.h>
 
@@ -552,6 +553,55 @@ testWriterClose(void){
     xmlFreeTextWriter(writer);
     return err;
 }
+
+/*
+ * Regression test for a type confusion in xmlParseReference that crashed
+ * a schema-validating xmlTextReader whenever the document expanded an
+ * internal entity.
+ */
+static int
+testReaderSchemaEntityExpansion(void) {
+    static const char xsd[] =
+        "<?xml version='1.0'?>\n"
+        "<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>\n"
+        "  <xs:element name='e' type='xs:integer'/>\n"
+        "</xs:schema>\n";
+    static const char xml[] =
+        "<!DOCTYPE e [<!ENTITY n \"not-an-int\">]>\n"
+        "<e>&n;</e>";
+    xmlSchemaParserCtxtPtr spc;
+    xmlSchemaPtr schema;
+    xmlTextReaderPtr reader;
+    int err = 0;
+    int ret;
+
+    spc = xmlSchemaNewMemParserCtxt(xsd, (int) sizeof(xsd) - 1);
+    schema = xmlSchemaParse(spc);
+    xmlSchemaFreeParserCtxt(spc);
+    if (schema == NULL) {
+        fprintf(stderr, "xmlSchemaParse failed\n");
+        return 1;
+    }
+
+    reader = xmlReaderForMemory(xml, (int) sizeof(xml) - 1, "doc.xml", NULL,
+                                XML_PARSE_NOENT | XML_PARSE_DTDLOAD);
+    xmlTextReaderSetSchema(reader, schema);
+
+    while ((ret = xmlTextReaderRead(reader)) == 1)
+        ;
+    if (ret != 0) {
+        fprintf(stderr, "reader failed on entity-expanded document\n");
+        err = 1;
+    }
+    if (xmlTextReaderIsValid(reader) != 0) {
+        fprintf(stderr, "schema missed invalid entity-expanded text\n");
+        err = 1;
+    }
+
+    xmlFreeTextReader(reader);
+    xmlSchemaFree(schema);
+    return err;
+}
 #endif
 
 typedef struct {
@@ -784,6 +834,9 @@ main(void) {
     err |= testReaderEncoding();
     err |= testReaderContent();
     err |= testReader();
+#ifdef LIBXML_SCHEMAS_ENABLED
+    err |= testReaderSchemaEntityExpansion();
+#endif
 #ifdef LIBXML_XINCLUDE_ENABLED
     err |= testReaderXIncludeError();
 #endif
