From c17fab5589ecd48240d720d98611090c0a5a71fa Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Thu, 23 Jul 2026 14:37:09 -0400 Subject: [PATCH] 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. --- .../var_dump_stack_limit.phpt | 40 +++++++++++++++++++ ext/standard/var.c | 18 +++++++++ 2 files changed, 58 insertions(+) create mode 100644 ext/standard/tests/general_functions/var_dump_stack_limit.phpt 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-- + +--INI-- +zend.max_allowed_stack_size=256K +--FILE-- +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 */