[php-src] master: Merge branch '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:51-04:00
Commit: https://github.com/php/php-src/commit/c8682fb61af207c09c71bcded7fe5ba7b17d4a5e
Raw diff: https://github.com/php/php-src/commit/c8682fb61af207c09c71bcded7fe5ba7b17d4a5e.diff
Merge branch 'PHP-8.5'
* PHP-8.5:
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 7bbc50fcccfe..4ed01591ea4c 100644
--- a/NEWS
+++ b/NEWS
@@ -108,6 +108,10 @@ PHP NEWS
. Io\Poll\Context::wait() now takes a Time\Duration object as a timeout.
(timwolla)
+- XSL:
+ . Fixed use-after-free when a DOMDocument subclass __clone() retains the
+ stylesheet copy made by XSLTProcessor::importStylesheet(). (iliaal)
+
30 Jul 2026, PHP 8.6.0alpha3
- 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 230c7bd39cc2..5d24ab1ec873 100644
--- a/ext/xsl/xsltprocessor.c
+++ b/ext/xsl/xsltprocessor.c
@@ -216,6 +216,12 @@ PHP_METHOD(XSLTProcessor, importStylesheet)
newdoc = nodep->doc;
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);