Skip to content

Commit 3c19ca2

Browse files
committed
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.
1 parent 742ff0e commit 3c19ca2

2 files changed

Lines changed: 58 additions & 0 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
--TEST--
2+
var_dump() and debug_zval_dump() guard against native stack overflow on deep structures
3+
--SKIPIF--
4+
<?php
5+
if (ini_get('zend.max_allowed_stack_size') === false) {
6+
die('skip No stack limit support');
7+
}
8+
if (getenv('SKIP_ASAN')) {
9+
die('skip ASAN needs different stack limit setting due to more stack space usage');
10+
}
11+
?>
12+
--INI--
13+
zend.max_allowed_stack_size=256K
14+
--FILE--
15+
<?php
16+
$a = [];
17+
for ($i = 0; $i < 50000; $i++) { $a = [$a]; }
18+
19+
class Node { public $next; }
20+
$firstNode = new Node();
21+
$node = $firstNode;
22+
for ($i = 0; $i < 50000; $i++) { $newNode = new Node(); $node->next = $newNode; $node = $newNode; }
23+
24+
function guarded(callable $fn): string {
25+
ob_start();
26+
$fn();
27+
return str_contains(ob_get_clean(), 'nesting level too deep') ? "guarded\n" : "NO GUARD\n";
28+
}
29+
30+
echo 'var_dump array: ', guarded(fn() => var_dump($a));
31+
echo 'debug_zval_dump array: ', guarded(fn() => debug_zval_dump($a));
32+
echo 'debug_zval_dump object: ', guarded(fn() => debug_zval_dump($firstNode));
33+
34+
while (is_array($a) && isset($a[0])) { $a = $a[0]; }
35+
while ($next = $firstNode->next) { $firstNode->next = $next->next; }
36+
?>
37+
--EXPECT--
38+
var_dump array: guarded
39+
debug_zval_dump array: guarded
40+
debug_zval_dump object: guarded

ext/standard/var.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ struct php_serialize_data {
4141

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

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

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

0 commit comments

Comments
 (0)