Commit f36660d6450 for php.net
commit f36660d64507e0d5d9fce73739031faac8139ded
Author: David Carlier <devnexen@gmail.com>
Date: Thu Jul 2 20:10:35 2026 +0100
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
diff --git a/NEWS b/NEWS
index 0459373092f..9807e07945d 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 00000000000..c8db1d87390
--- /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 bd9947b4ad1..dc9e1b852fa 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)