diff --git a/ext/standard/tests/general_functions/gh24605_var_export_stack_guard.phpt b/ext/standard/tests/general_functions/gh24605_var_export_stack_guard.phpt new file mode 100644 index 000000000000..c6dc47b85bbc --- /dev/null +++ b/ext/standard/tests/general_functions/gh24605_var_export_stack_guard.phpt @@ -0,0 +1,57 @@ +--TEST-- +GH-24605: var_export() checks native stack limit for arrays and objects +--SKIPIF-- + +--INI-- +zend.max_allowed_stack_size=256K +--FILE-- +getMessage(), 'Maximum call stack size') + ? "guarded\n" + : "unexpected error\n"; + } +} + +$data = 0; +for ($i = 0; $i < 5000; $i++) { + $data = [$data]; +} +test_export('array', $data); + +// Detach the nested arrays without recursively destroying them. +while (is_array($data)) { + $data = $data[0]; +} + +class StackNode { public ?StackNode $next = null; } +$first = new StackNode(); +$node = $first; +for ($i = 0; $i < 5000; $i++) { + $node->next = new StackNode(); + $node = $node->next; +} +test_export('object', $first); + +// Break the chain iteratively to avoid teardown recursion. +$node = $first; +while ($node !== null) { + $next = $node->next; + $node->next = null; + $node = $next; +} +?> +--EXPECT-- +array: guarded +object: guarded diff --git a/ext/standard/var.c b/ext/standard/var.c index 8906d914d5dc..6ce623bc2a4a 100644 --- a/ext/standard/var.c +++ b/ext/standard/var.c @@ -542,6 +542,13 @@ PHPAPI zend_result php_var_export_ex(zval *struc, int level, smart_str *buf) /* zend_string *key; zval *val; +#ifdef ZEND_CHECK_STACK_LIMIT + if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) { + zend_call_stack_size_error(); + return FAILURE; + } +#endif + again: switch (Z_TYPE_P(struc)) { case IS_FALSE: @@ -672,10 +679,15 @@ PHPAPI zend_result php_var_export_ex(zval *struc, int level, smart_str *buf) /* return FAILURE; } } - php_object_element_export(val, index, key, level, buf); + zend_result result = php_object_element_export(val, index, key, level, buf); if (val == &tmp) { zval_ptr_dtor(val); } + if (UNEXPECTED(result == FAILURE)) { + ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, EXPORT, zobj); + zend_release_properties(myht); + return FAILURE; + } } ZEND_HASH_FOREACH_END(); } zend_release_properties(myht);