[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);