[php-src] master: ext/dom: Use-after-free with namespace nodes from XSLTProcessor::registerPHPFunctions().

David Carlier <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: David Carlier (devnexen)
Date: 2026-07-10T20:41:57+01:00

Commit: https://github.com/php/php-src/commit/9073875eb98416595f1666df0f6a728bab2bc5c7
Raw diff: https://github.com/php/php-src/commit/9073875eb98416595f1666df0f6a728bab2bc5c7.diff

ext/dom: Use-after-free with namespace nodes from XSLTProcessor::registerPHPFunctions().

Fix #22621

Namespace parents from an external document() were wrapped directly
instead of through the proxy factory, so they skipped the copy that
keeps non-main-document nodes alive. A DOMNameSpaceNode retained by
userland then dangled once xsltFreeTransformContext() freed the external
document. Route the namespace parent through the proxy factory too.

close GH-22623

Changed paths:
  A  ext/xsl/tests/gh22621.phpt
  M  NEWS
  M  ext/dom/xpath_callbacks.c


Diff:

diff --git a/NEWS b/NEWS
index cd259f06660e..f40c6dfd8da6 100644
--- a/NEWS
+++ b/NEWS
@@ -33,6 +33,8 @@ PHP                                                                        NEWS
   . Fixed Dom\DtdNamedNodeMap integer dimension access so negative indexes
     return NULL and indexes outside the int range throw ValueError instead of
     returning the first entity or notation. (Weilin Du)
+  . Fixed bug GH-22623 (use after free with namespace nodes from
+    XSLTProcessor::registerFunctions())/ (David Carlier)
 
 - Exif:
   . Fixed bug GH-11020 (exif_read_data() emits a spurious "Illegal IFD size"
diff --git a/ext/dom/xpath_callbacks.c b/ext/dom/xpath_callbacks.c
index 622dd187dd84..349c304c9f25 100644
--- a/ext/dom/xpath_callbacks.c
+++ b/ext/dom/xpath_callbacks.c
@@ -347,15 +347,15 @@ static zval *php_dom_xpath_callback_fetch_args(xmlXPathParserContextPtr ctxt, ui
 							xmlNodePtr node = obj->nodesetval->nodeTab[j];
 							zval child;
 							if (UNEXPECTED(node->type == XML_NAMESPACE_DECL)) {
-								xmlNodePtr nsparent = node->_private;
 								xmlNsPtr original = (xmlNsPtr) node;
 
 								/* Make sure parent dom object exists, so we can take an extra reference. */
 								zval parent_zval; /* don't destroy me, my lifetime is transferred to the fake namespace decl */
-								php_dom_create_object(nsparent, &parent_zval, intern);
+								proxy_factory(node->_private, &parent_zval, intern, ctxt);
 								dom_object *parent_intern = Z_DOMOBJ_P(&parent_zval);
+								xmlNodePtr parent = dom_object_get_node(parent_intern);
 
-								php_dom_create_fake_namespace_decl(nsparent, original, &child, parent_intern);
+								php_dom_create_fake_namespace_decl(parent, original, &child, parent_intern);
 							} else {
 								proxy_factory(node, &child, intern, ctxt);
 							}
diff --git a/ext/xsl/tests/gh22621.phpt b/ext/xsl/tests/gh22621.phpt
new file mode 100644
index 000000000000..b31671eff630
--- /dev/null
+++ b/ext/xsl/tests/gh22621.phpt
@@ -0,0 +1,63 @@
+--TEST--
+GH-22621 (UAF via XSLTProcessor::registerPHPFunctions() retaining namespace nodes from an external document)
+--EXTENSIONS--
+dom
+xsl
+--CREDITS--
+ExPatch-LLC
+--FILE--
+<?php
+$cDIR = __DIR__;
+file_put_contents($cDIR . '/gh22621_external.xml', '<root xmlns:custom="urn:custom"><data>secret</data></root>');
+$uri = 'file:///' . ltrim(str_replace('\\', '/', $cDIR), '/') . '/gh22621_external.xml';
+
+$stored_ns = null;
+
+function capture_ns($nodes) {
+    global $stored_ns;
+    foreach ($nodes as $node) {
+        if ($node instanceof DOMNameSpaceNode) {
+            $stored_ns = $node;
+        }
+    }
+    return "captured";
+}
+
+$xsl = new DOMDocument();
+$xsl->loadXML(<<<XSL
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0"
+    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+    xmlns:php="http://php.net/xsl">
+  <xsl:template match="/">
+    <result>
+      <xsl:value-of select="php:function('capture_ns', document('$uri')/*/namespace::*)"/>
+    </result>
+  </xsl:template>
+</xsl:stylesheet>
+XSL);
+
+$doc = new DOMDocument();
+$doc->loadXML('<input/>');
+
+$proc = new XSLTProcessor();
+$proc->registerPHPFunctions();
+$proc->importStylesheet($xsl);
+echo $proc->transformToXml($doc);
+
+var_dump($stored_ns->localName);
+var_dump($stored_ns->namespaceURI);
+var_dump($stored_ns->parentNode->localName);
+var_dump($stored_ns->ownerDocument instanceof DOMDocument);
+?>
+--CLEAN--
+<?php
+@unlink(__DIR__ . '/gh22621_external.xml');
+?>
+--EXPECTF--
+<?xml version="1.0"?>
+<result xmlns:php="http://php.net/xsl">captured</result>
+string(6) "custom"
+string(10) "urn:custom"
+string(4) "root"
+bool(true)
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.