Commit 889ae478bdd for php.net

commit 889ae478bddaf123a219ab7db183b3acedb8c61f
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Thu Jul 23 14:37:09 2026 -0400

    Guard var_dump()/debug_zval_dump() against native stack overflow

    php_object_property_dump() and serialize() already check
    ZEND_CHECK_STACK_LIMIT before recursing, but php_array_element_dump()
    and both debug_zval_dump() element helpers do not, so a deeply nested
    array or object crashes the process instead of printing "nesting level
    too deep". Add the same guard to those three helpers. var_export()
    overflows the same way but needs a throw-based guard, left for a
    separate change.

    Closes GH-22871

diff --git a/ext/standard/tests/general_functions/var_dump_stack_limit.phpt b/ext/standard/tests/general_functions/var_dump_stack_limit.phpt
new file mode 100644
index 00000000000..a124e811fc9
--- /dev/null
+++ b/ext/standard/tests/general_functions/var_dump_stack_limit.phpt
@@ -0,0 +1,40 @@
+--TEST--
+var_dump() and debug_zval_dump() guard against native stack overflow on deep structures
+--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 < 50000; $i++) { $a = [$a]; }
+
+class Node { public $next; }
+$firstNode = new Node();
+$node = $firstNode;
+for ($i = 0; $i < 50000; $i++) { $newNode = new Node(); $node->next = $newNode; $node = $newNode; }
+
+function guarded(callable $fn): string {
+    ob_start();
+    $fn();
+    return str_contains(ob_get_clean(), 'nesting level too deep') ? "guarded\n" : "NO GUARD\n";
+}
+
+echo 'var_dump array: ', guarded(fn() => var_dump($a));
+echo 'debug_zval_dump array: ', guarded(fn() => debug_zval_dump($a));
+echo 'debug_zval_dump object: ', guarded(fn() => debug_zval_dump($firstNode));
+
+while (is_array($a) && isset($a[0])) { $a = $a[0]; }
+while ($next = $firstNode->next) { $firstNode->next = $next->next; }
+?>
+--EXPECT--
+var_dump array: guarded
+debug_zval_dump array: guarded
+debug_zval_dump object: guarded
diff --git a/ext/standard/var.c b/ext/standard/var.c
index acb605d2eab..99f28366ffd 100644
--- a/ext/standard/var.c
+++ b/ext/standard/var.c
@@ -41,6 +41,12 @@ struct php_serialize_data {

 static void php_array_element_dump(zval *zv, zend_ulong index, zend_string *key, int level) /* {{{ */
 {
+#ifdef ZEND_CHECK_STACK_LIMIT
+	if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) {
+		php_printf("%*cnesting level too deep", level + 1, ' ');
+		return;
+	}
+#endif
 	if (key == NULL) { /* numeric key */
 		php_printf("%*c[" ZEND_LONG_FMT "]=>\n", level + 1, ' ', index);
 	} else { /* string key */
@@ -255,6 +261,12 @@ PHP_FUNCTION(var_dump)

 static void zval_array_element_dump(zval *zv, zend_ulong index, zend_string *key, int level) /* {{{ */
 {
+#ifdef ZEND_CHECK_STACK_LIMIT
+	if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) {
+		php_printf("%*cnesting level too deep", level + 1, ' ');
+		return;
+	}
+#endif
 	if (key == NULL) { /* numeric key */
 		php_printf("%*c[" ZEND_LONG_FMT "]=>\n", level + 1, ' ', index);
 	} else { /* string key */
@@ -270,6 +282,12 @@ static void zval_object_property_dump(zend_property_info *prop_info, zval *zv, z
 {
 	const char *prop_name, *class_name;

+#ifdef ZEND_CHECK_STACK_LIMIT
+	if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) {
+		php_printf("%*cnesting level too deep", level + 1, ' ');
+		return;
+	}
+#endif
 	if (key == NULL) { /* numeric key */
 		php_printf("%*c[" ZEND_LONG_FMT "]=>\n", level + 1, ' ', index);
 	} else { /* string key */