[php-src] PHP-8.5: Guard var_dump()/debug_zval_dump() against native stack overflow

Ilia Alshanetsky <[email protected]> Fri, 24 Jul 2026 12:49:19 +0000
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Date: 2026-07-24T08:28:16-04:00

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

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

Changed paths:
  A  ext/standard/tests/general_functions/var_dump_stack_limit.phpt
  M  ext/standard/var.c


Diff:

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 000000000000..a124e811fc9c
--- /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 acb605d2eabe..99f28366ffdb 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 */