[php-src] PHP-8.4: Add a stack limit check in php_array_walk() (#23125)

Lazizbek Ergashev via GitHub <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Lazizbek Ergashev (lazerg)
Committer: GitHub (web-flow)
Pusher: arnaud-lb
Date: 2026-08-10T11:57:00+02:00

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

Add a stack limit check in php_array_walk() (#23125)

php_array_walk() recurses once per nesting level with no stack check, so array_walk_recursive() on a deeply nested array exhausts the native stack and the process dies with a segfault.

This adds the same stack limit check ext/standard already uses in var.c and http.c, so the call throws an Error instead of crashing. The existing GC_IS_RECURSIVE guard only covers self-referential arrays, not plain deep nesting.

Fixes GH-23111

Changed paths:
  A  ext/standard/tests/array/gh23111.phpt
  M  NEWS
  M  ext/standard/array.c


Diff:

diff --git a/NEWS b/NEWS
index 7a93e72de06f..01eb4160a20a 100644
--- a/NEWS
+++ b/NEWS
@@ -69,6 +69,10 @@ PHP                                                                        NEWS
 - SQLite:
   . Fix leak when trying to close db if blob stream is still open. (ndossche)
 
+- Standard:
+  . Fixed bug GH-23111 (Stack overflow in array_walk_recursive() with deeply
+    nested arrays). (Lazizbek Ergashev)
+
 - Streams:
   . Fixed bug GH-15836 (Use-after-free when a user stream filter accesses
     $this->stream during the close flush). (iliaal)
diff --git a/ext/standard/array.c b/ext/standard/array.c
index 4527d9a80df8..6863586c81ff 100644
--- a/ext/standard/array.c
+++ b/ext/standard/array.c
@@ -1465,6 +1465,13 @@ static zend_result php_array_walk(
 	 * levels of recursion. */
 	zend_fcall_info fci = context->fci;
 
+#ifdef ZEND_CHECK_STACK_LIMIT
+	if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) {
+		zend_call_stack_size_error();
+		return FAILURE;
+	}
+#endif
+
 	if (zend_hash_num_elements(target_hash) == 0) {
 		return result;
 	}
diff --git a/ext/standard/tests/array/gh23111.phpt b/ext/standard/tests/array/gh23111.phpt
new file mode 100644
index 000000000000..21db33a2fc75
--- /dev/null
+++ b/ext/standard/tests/array/gh23111.phpt
@@ -0,0 +1,27 @@
+--TEST--
+GH-23111 (Stack overflow in array_walk_recursive with deeply nested arrays)
+--SKIPIF--
+<?php
+if (ini_get('zend.max_allowed_stack_size') === false) {
+    die('skip No stack limit support');
+}
+if (getenv('SKIP_ASAN')) {
+    die('skip ASAN needs different stack limit setting due to more stack space usage');
+}
+?>
+--INI--
+zend.max_allowed_stack_size=256K
+--FILE--
+<?php
+$a = [];
+for ($i = 0; $i < 30000; $i++) {
+    $a = [$a];
+}
+try {
+    array_walk_recursive($a, function ($v) {});
+} catch (Throwable $e) {
+    echo $e::class, ": ", $e->getMessage(), "\n";
+}
+?>
+--EXPECTF--
+Error: Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion?
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.