From b621d734f64bf9544b6a24c4e84f7604d1aa03a1 Mon Sep 17 00:00:00 2001 From: Labish Bardiya Date: Sun, 25 Oct 2026 02:24:54 +0530 Subject: [PATCH 1/2] Guard var_export() against stack overflow (GH-24605) --- .../gh24605_var_export_stack_guard.phpt | 57 +++++++++++++++++++ ext/standard/var.c | 14 ++++- 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 ext/standard/tests/general_functions/gh24605_var_export_stack_guard.phpt 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..a0ed2442e5e5 --- /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 < 3000; $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 < 3000; $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); From 7508e440dc6d94f16cfa335d0b21662c53890aa8 Mon Sep 17 00:00:00 2001 From: Labish Bardiya Date: Mon, 26 Oct 2026 03:44:33 +0530 Subject: [PATCH 2/2] tests: Make GH-24605 stack guard coverage more robust --- .../general_functions/gh24605_var_export_stack_guard.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index a0ed2442e5e5..c6dc47b85bbc 100644 --- a/ext/standard/tests/general_functions/gh24605_var_export_stack_guard.phpt +++ b/ext/standard/tests/general_functions/gh24605_var_export_stack_guard.phpt @@ -25,7 +25,7 @@ function test_export(string $name, mixed $value): void { } $data = 0; -for ($i = 0; $i < 3000; $i++) { +for ($i = 0; $i < 5000; $i++) { $data = [$data]; } test_export('array', $data); @@ -38,7 +38,7 @@ while (is_array($data)) { class StackNode { public ?StackNode $next = null; } $first = new StackNode(); $node = $first; -for ($i = 0; $i < 3000; $i++) { +for ($i = 0; $i < 5000; $i++) { $node->next = new StackNode(); $node = $node->next; }