[php-src] PHP-8.5: Merge branch 'PHP-8.4' into PHP-8.5

Ilia Alshanetsky <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Date: 2026-08-10T15:15:26-04:00

Commit: https://github.com/php/php-src/commit/46b40957b172eb7bafcdd9e50d804004e485b2db
Raw diff: https://github.com/php/php-src/commit/46b40957b172eb7bafcdd9e50d804004e485b2db.diff

Merge branch 'PHP-8.4' into PHP-8.5

* PHP-8.4:
  Fix use-after-free when __clone() retains the stylesheet copy

Changed paths:
  A  ext/xsl/tests/importStylesheet_clone_retained_document.phpt
  A  ext/xsl/tests/importStylesheet_clone_retained_node.phpt
  M  NEWS
  M  ext/xsl/xsltprocessor.c


Diff:

diff --git a/NEWS b/NEWS
index ade754593071..299678d0a008 100644
--- a/NEWS
+++ b/NEWS
@@ -90,6 +90,10 @@ PHP                                                                        NEWS
   . Fixed bug GH-15836 (Use-after-free when a user stream filter accesses
     $this->stream during the close flush). (iliaal)
 
+- XSL:
+  . Fixed use-after-free when a DOMDocument subclass __clone() retains the
+    stylesheet copy made by XSLTProcessor::importStylesheet(). (iliaal)
+
 30 Jul 2026, PHP 8.5.9
 
 - Core:
diff --git a/ext/xsl/tests/importStylesheet_clone_retained_document.phpt b/ext/xsl/tests/importStylesheet_clone_retained_document.phpt
new file mode 100644
index 000000000000..481925677d4e
--- /dev/null
+++ b/ext/xsl/tests/importStylesheet_clone_retained_document.phpt
@@ -0,0 +1,47 @@
+--TEST--
+XSLTProcessor::importStylesheet() rejects a stylesheet whose __clone() retains the cloned document
+--EXTENSIONS--
+dom
+xsl
+--FILE--
+<?php
+const STYLESHEET = <<<XML
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+  <xsl:template match="/"><out/></xsl:template>
+</xsl:stylesheet>
+XML;
+
+class Harmless extends DOMDocument {
+    public function __clone(): void {
+    }
+}
+
+class RetainsDocument extends DOMDocument {
+    public function __clone(): void {
+        $GLOBALS['stash'] = $this;
+    }
+}
+
+$doc = new Harmless;
+$doc->loadXML(STYLESHEET);
+$proc = new XSLTProcessor();
+var_dump($proc->importStylesheet($doc));
+unset($proc, $doc);
+
+$doc = new RetainsDocument;
+$doc->loadXML(STYLESHEET);
+$proc = new XSLTProcessor();
+try {
+    var_dump($proc->importStylesheet($doc));
+} catch (Error $e) {
+    echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+$kept = $GLOBALS['stash'];
+unset($GLOBALS['stash'], $proc, $doc);
+echo get_class($kept), " is still usable: ", $kept->documentElement->nodeName, PHP_EOL;
+?>
+--EXPECT--
+bool(true)
+ValueError: XSLTProcessor::importStylesheet(): Argument #1 ($stylesheet) must not have its clone retained by __clone()
+RetainsDocument is still usable: xsl:stylesheet
diff --git a/ext/xsl/tests/importStylesheet_clone_retained_node.phpt b/ext/xsl/tests/importStylesheet_clone_retained_node.phpt
new file mode 100644
index 000000000000..72c47b73b002
--- /dev/null
+++ b/ext/xsl/tests/importStylesheet_clone_retained_node.phpt
@@ -0,0 +1,34 @@
+--TEST--
+XSLTProcessor::importStylesheet() rejects a stylesheet whose __clone() retains a node of the cloned document
+--EXTENSIONS--
+dom
+xsl
+--FILE--
+<?php
+class RetainsElement extends DOMDocument {
+    public function __clone(): void {
+        $GLOBALS['stash'] = $this->documentElement;
+    }
+}
+
+$doc = new RetainsElement;
+$doc->loadXML(<<<XML
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+  <xsl:template match="/"><out/></xsl:template>
+</xsl:stylesheet>
+XML);
+
+$proc = new XSLTProcessor();
+try {
+    var_dump($proc->importStylesheet($doc));
+} catch (Error $e) {
+    echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+$kept = $GLOBALS['stash'];
+unset($GLOBALS['stash'], $proc, $doc);
+echo get_class($kept), " is still usable: ", $kept->nodeName, PHP_EOL;
+?>
+--EXPECT--
+ValueError: XSLTProcessor::importStylesheet(): Argument #1 ($stylesheet) must not have its clone retained by __clone()
+DOMElement is still usable: xsl:stylesheet
diff --git a/ext/xsl/xsltprocessor.c b/ext/xsl/xsltprocessor.c
index f7cffb2ed4ff..68ff9eb76ab6 100644
--- a/ext/xsl/xsltprocessor.c
+++ b/ext/xsl/xsltprocessor.c
@@ -227,6 +227,12 @@ PHP_METHOD(XSLTProcessor, importStylesheet)
 
 	php_libxml_node_object *clone_lxml_obj = Z_LIBXML_NODE_P(&clone_zv);
 
+	if (GC_REFCOUNT(clone) > 1 || clone_lxml_obj->document->refcount > 1) {
+		OBJ_RELEASE(clone);
+		zend_argument_value_error(1, "must not have its clone retained by __clone()");
+		RETURN_THROWS();
+	}
+
 	PHP_LIBXML_SANITIZE_GLOBALS(parse);
 	ZEND_DIAGNOSTIC_IGNORED_START("-Wdeprecated-declarations")
 	xmlSubstituteEntitiesDefault(1);
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.