Commit 69d5f1b5d20 for php.net

commit 69d5f1b5d20b160cdd80ea65ef7d8e7548fd9935
Author: Lazizbek Ergashev <lazerg2@gmail.com>
Date:   Mon Aug 10 14:57:00 2026 +0500

    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

diff --git a/NEWS b/NEWS
index 7a93e72de06..01eb4160a20 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 4527d9a80df..6863586c81f 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 00000000000..21db33a2fc7
--- /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?