[php-src] PHP-8.5: Merge branch 'PHP-8.4' into PHP-8.5

Ilia Alshanetsky <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Date: 2026-06-27T08:24:28-04:00

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

Merge branch 'PHP-8.4' into PHP-8.5

* PHP-8.4:
  Fix use-after-free in XPath php:function argument nodes

Changed paths:
  A  ext/dom/tests/xpath_php_function_removes_argument_node.phpt
  M  ext/dom/xpath_callbacks.c


Diff:

diff --git a/ext/dom/tests/xpath_php_function_removes_argument_node.phpt b/ext/dom/tests/xpath_php_function_removes_argument_node.phpt
new file mode 100644
index 000000000000..e6696617be25
--- /dev/null
+++ b/ext/dom/tests/xpath_php_function_removes_argument_node.phpt
@@ -0,0 +1,33 @@
+--TEST--
+DOMXPath: a php:function callback that removes its argument node must not free it mid-evaluation
+--EXTENSIONS--
+dom
+--FILE--
+<?php
+$doc = new DOMDocument();
+$doc->loadXML('<root><a>1</a><a>2</a><a>3</a><a>4</a></root>');
+$xp = new DOMXPath($doc);
+$xp->registerNamespace('php', 'http://php.net/xpath');
+$xp->registerPhpFunctions();
+
+function cb($nodes) {
+    foreach ($nodes as $n) {
+        if ($n->parentNode) {
+            $n->parentNode->removeChild($n);
+        }
+    }
+    return true;
+}
+
+$res = $xp->query('//a[php:function("cb", .)]');
+foreach ($res as $r) {
+    var_dump($r->nodeName);
+}
+echo "done\n";
+?>
+--EXPECT--
+string(1) "a"
+string(1) "a"
+string(1) "a"
+string(1) "a"
+done
diff --git a/ext/dom/xpath_callbacks.c b/ext/dom/xpath_callbacks.c
index ee5df69e357a..ffac09a616f7 100644
--- a/ext/dom/xpath_callbacks.c
+++ b/ext/dom/xpath_callbacks.c
@@ -381,11 +381,19 @@ static zval *php_dom_xpath_callback_fetch_args(xmlXPathParserContextPtr ctxt, ui
 	return params;
 }
 
-static void php_dom_xpath_callback_cleanup_args(zval *params, uint32_t param_count)
+static void php_dom_xpath_callback_cleanup_args(php_dom_xpath_callbacks *xpath_callbacks, zval *params, uint32_t param_count)
 {
 	if (params) {
 		for (uint32_t i = 0; i < param_count; i++) {
-			zval_ptr_dtor(&params[i]);
+			zval *param = &params[i];
+			if (Z_TYPE_P(param) == IS_OBJECT || Z_TYPE_P(param) == IS_ARRAY) {
+				if (xpath_callbacks->node_list == NULL) {
+					xpath_callbacks->node_list = zend_new_array(0);
+				}
+				zend_hash_next_index_insert_new(xpath_callbacks->node_list, param);
+			} else {
+				zval_ptr_dtor(param);
+			}
 		}
 		efree(params);
 	}
@@ -484,7 +492,7 @@ PHP_DOM_EXPORT zend_result php_dom_xpath_callbacks_call_php_ns(php_dom_xpath_cal
 
 cleanup:
 	xmlXPathFreeObject(obj);
-	php_dom_xpath_callback_cleanup_args(params, param_count);
+	php_dom_xpath_callback_cleanup_args(xpath_callbacks, params, param_count);
 cleanup_no_obj:
 	if (UNEXPECTED(result != SUCCESS)) {
 		/* Push sentinel value */
@@ -512,7 +520,7 @@ PHP_DOM_EXPORT zend_result php_dom_xpath_callbacks_call_custom_ns(php_dom_xpath_
 
 	zend_result result = php_dom_xpath_callback_dispatch(xpath_callbacks, ns, ctxt, params, param_count, function_name, function_name_length);
 
-	php_dom_xpath_callback_cleanup_args(params, param_count);
+	php_dom_xpath_callback_cleanup_args(xpath_callbacks, params, param_count);
 	if (UNEXPECTED(result != SUCCESS)) {
 		/* Push sentinel value */
 		valuePush(ctxt, xmlXPathNewString((const xmlChar *) ""));
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.