[PHP-CVS] [php-src] PHP-8.5: Fix DOMXPath crash wrapping a foreign node after a nodeset callback

[email protected] (Ilia Alshanetsky)
Newsgroups php.cvs
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Date: 2026-08-17T06:53:45-04:00

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

Fix DOMXPath crash wrapping a foreign node after a nodeset callback

dom_xpath_intern_for_doc walked xpath_callbacks.node_list with
Z_DOMOBJ_P on every entry. That list also stores arrays (nodeset-mode
callback arguments). Same-document results take the early return; a
later php:function that returns a node from another document walks
the leftover array first. Skip non-objects and recurse into arrays
so the matching intern is found instead of type-confusing the array.

Closes GH-23253

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


Diff:

diff --git a/NEWS b/NEWS
index 1bac1989e7ed..baea08f757f5 100644
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,8 @@ PHP                                                                        NEWS
 - DOM:
   . Fixed a use-after-free when cloning a DOMNameSpaceNode after
     DOMDocument::xinclude(). (iliaal)
+  . Fixed a crash in DOMXPath when a php:function callback receives a nodeset
+    and a later callback returns a node from another document. (iliaal)
 
 - Opcache:
   . Fixed opcache.protect_memory race under ZTS. (realFlowControl)
diff --git a/ext/dom/tests/xpath_php_function_foreign_doc.phpt b/ext/dom/tests/xpath_php_function_foreign_doc.phpt
new file mode 100644
index 000000000000..4e4b2b2c2822
--- /dev/null
+++ b/ext/dom/tests/xpath_php_function_foreign_doc.phpt
@@ -0,0 +1,40 @@
+--TEST--
+DOMXPath: php:function nodeset args plus a foreign-document return must not treat arrays as DOM objects
+--EXTENSIONS--
+dom
+--FILE--
+<?php
+$doc1 = new DOMDocument();
+$doc1->loadXML('<root><a>1</a></root>');
+$doc2 = new DOMDocument();
+$doc2->loadXML('<root><b>2</b></root>');
+
+$xp = new DOMXPath($doc1);
+$xp->registerNamespace('php', 'http://php.net/xpath');
+$xp->registerPhpFunctions();
+
+function uses_nodeset($nodes) {
+    return true;
+}
+
+function foreign() {
+    global $doc2;
+    return $doc2->documentElement;
+}
+
+$xp->query('//a[php:function("uses_nodeset", //a)]');
+$res = $xp->query('php:function("foreign")');
+echo "count: ";
+var_dump($res->length);
+$n = $res->item(0);
+echo "name: ";
+var_dump($n->nodeName);
+echo "owner is doc2: ";
+var_dump($n->ownerDocument === $doc2);
+echo "done\n";
+?>
+--EXPECT--
+count: int(1)
+name: string(4) "root"
+owner is doc2: bool(true)
+done
diff --git a/ext/dom/xpath.c b/ext/dom/xpath.c
index 199dc96af40e..4688ed7743eb 100644
--- a/ext/dom/xpath.c
+++ b/ext/dom/xpath.c
@@ -35,6 +35,25 @@
 
 #ifdef LIBXML_XPATH_ENABLED
 
+static dom_object *dom_xpath_intern_from_entry(zval *entry, xmlDocPtr doc)
+{
+	if (Z_TYPE_P(entry) == IS_OBJECT) {
+		dom_object *obj = Z_DOMOBJ_P(entry);
+		if (obj->document && obj->document->ptr == doc) {
+			return obj;
+		}
+	} else if (Z_TYPE_P(entry) == IS_ARRAY) {
+		zval *inner;
+		ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(entry), inner) {
+			dom_object *obj = dom_xpath_intern_from_entry(inner, doc);
+			if (obj) {
+				return obj;
+			}
+		} ZEND_HASH_FOREACH_END();
+	}
+	return NULL;
+}
+
 static dom_object *dom_xpath_intern_for_doc(dom_xpath_object *xpath_obj, xmlDocPtr doc)
 {
 	if (xpath_obj->dom.document && xpath_obj->dom.document->ptr == doc) {
@@ -44,8 +63,8 @@ static dom_object *dom_xpath_intern_for_doc(dom_xpath_object *xpath_obj, xmlDocP
 	if (node_list) {
 		zval *entry;
 		ZEND_HASH_PACKED_FOREACH_VAL(node_list, entry) {
-			dom_object *obj = Z_DOMOBJ_P(entry);
-			if (obj->document && obj->document->ptr == doc) {
+			dom_object *obj = dom_xpath_intern_from_entry(entry, doc);
+			if (obj) {
 				return obj;
 			}
 		} ZEND_HASH_FOREACH_END();
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.