[php-src] master: ext/dom: fix use-after-free with XPath callback returning foreign-document node.

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

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

ext/dom: fix use-after-free with XPath callback returning foreign-document node.

Fix GH-22554

A PHP XPath callback that returns a node belonging to a document created
inside the callback (e.g. $d->documentElement of a throwaway DOMDocument)
parks that node in the DOMXPath node_list to keep it alive. When a sibling
callback consumes a node navigated into that foreign document, the proxy
object was created with the DOMXPath's own dom as parent, so it took a
reference on the wrong document and none on the foreign one. On teardown the
foreign document could be freed while the proxy still referenced it.

Route the proxy factory through dom_xpath_intern_for_doc() so the created
object shares the ref_obj of the node's actual document, mirroring the
query-result path.

close GH-22562

Changed paths:
  A  ext/dom/tests/gh22554.phpt
  M  NEWS
  M  ext/dom/xpath.c


Diff:

diff --git a/NEWS b/NEWS
index 0459373092ff..9807e07945df 100644
--- a/NEWS
+++ b/NEWS
@@ -41,6 +41,8 @@ PHP                                                                        NEWS
     returning the first entity or notation. (Weilin Du)
   . Fixed bug GH-22623 (use after free with namespace nodes from
     XSLTProcessor::registerFunctions())/ (David Carlier)
+  . Fixed bug GH-22554 (use-after-free with XPath callback returning a node
+    from a foreign document). (David Carlier)
 
 - Exif:
   . Fixed bug GH-11020 (exif_read_data() emits a spurious "Illegal IFD size"
diff --git a/ext/dom/tests/gh22554.phpt b/ext/dom/tests/gh22554.phpt
new file mode 100644
index 000000000000..c8db1d87390b
--- /dev/null
+++ b/ext/dom/tests/gh22554.phpt
@@ -0,0 +1,38 @@
+--TEST--
+GH-22554 (Use-after-free when an XPath callback returns a node from a document created inside the callback)
+--CREDITS--
+waseem-cve
+--EXTENSIONS--
+dom
+--FILE--
+<?php
+
+$doc = new DOMDocument;
+$doc->loadXML('<root/>');
+
+$xp = new DOMXPath($doc);
+$xp->registerNamespace('my', 'my.ns');
+
+$xp->registerPHPFunctionNS('my.ns', 'include', function () {
+    $d = new DOMDocument;
+    $d->loadXML('<r><uaf/></r>');
+
+    return $d->documentElement;
+});
+
+$xp->registerPHPFunctionNS('my.ns', 'process', function ($arg) {
+    echo "process arg: ", get_class($arg[0]), " ", $arg[0]->nodeName, "\n";
+    return 'x';
+});
+
+$result = $xp->query('my:process(my:include()/uaf)');
+var_dump($result->length);
+unset($xp);
+
+echo "Done\n";
+
+?>
+--EXPECT--
+process arg: DOMElement uaf
+int(0)
+Done
diff --git a/ext/dom/xpath.c b/ext/dom/xpath.c
index bd9947b4ad10..dc9e1b852fae 100644
--- a/ext/dom/xpath.c
+++ b/ext/dom/xpath.c
@@ -77,7 +77,8 @@ static void dom_xpath_proxy_factory(xmlNodePtr node, zval *child, dom_object *in
 
 	ZEND_ASSERT(node->type != XML_NAMESPACE_DECL);
 
-	php_dom_create_object(node, child, intern);
+	dom_xpath_object *xobj = php_xpath_obj_from_obj(&intern->std);
+	php_dom_create_object(node, child, dom_xpath_intern_for_doc(xobj, node->doc));
 }
 
 static dom_xpath_object *dom_xpath_ext_fetch_intern(xmlXPathParserContextPtr ctxt)
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.