From a5c611d920644d2bcb47613c6d6e9009ab73d48b Mon Sep 17 00:00:00 2001 From: Volkopat Date: Wed, 28 Oct 2026 13:47:56 -0400 Subject: [PATCH 1/2] Add stack-limit guards to var_export(), print_r() and unserialize() These three recursion paths were missing ZEND_CHECK_STACK_LIMIT, so deeply nested input exhausted the stack and killed the process instead of raising the catchable Error that serialize() already produces. Guard each recursive entry point the same way the already-fixed paths do. --- Zend/zend.c | 6 ++++++ ext/standard/var.c | 6 ++++++ ext/standard/var_unserializer.re | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/Zend/zend.c b/Zend/zend.c index 17bc4ba0cbad..2ba34831d6e6 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -546,6 +546,12 @@ ZEND_API void zend_print_flat_zval_r(zval *expr) static void zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent) /* {{{ */ { +#ifdef ZEND_CHECK_STACK_LIMIT + if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) { + zend_call_stack_size_error(); + return; + } +#endif switch (Z_TYPE_P(expr)) { case IS_ARRAY: smart_str_appends(buf, "Array\n"); diff --git a/ext/standard/var.c b/ext/standard/var.c index 6d8fff970b2b..f50c82d72d84 100644 --- a/ext/standard/var.c +++ b/ext/standard/var.c @@ -538,6 +538,12 @@ static zend_result php_object_element_export(zval *zv, zend_ulong index, zend_st PHPAPI zend_result php_var_export_ex(zval *struc, int level, smart_str *buf) /* {{{ */ { +#ifdef ZEND_CHECK_STACK_LIMIT + if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) { + zend_call_stack_size_error(); + return FAILURE; + } +#endif HashTable *myht; zend_string *ztmp, *ztmp2; zend_ulong index; diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index 51c3293c63e2..89c6d5466e21 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -937,6 +937,12 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER) static int php_var_unserialize_internal(UNSERIALIZE_PARAMETER) { +#ifdef ZEND_CHECK_STACK_LIMIT + if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) { + zend_call_stack_size_error(); + return 0; + } +#endif const unsigned char *cursor, *limit, *marker, *start; zval *rval_ref; From a62f70ccee07378a7932cfc1f03575ae0f6b7220 Mon Sep 17 00:00:00 2001 From: Volkopat Date: Wed, 28 Oct 2026 14:00:14 -0400 Subject: [PATCH 2/2] Add a regression test for the var_export/print_r/unserialize stack guards Covers all three entry points with zend.max_allowed_stack_size lowered, asserting each raises the catchable Error instead of exhausting the stack. --- .../gh24605_stack_guard.phpt | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 ext/standard/tests/general_functions/gh24605_stack_guard.phpt diff --git a/ext/standard/tests/general_functions/gh24605_stack_guard.phpt b/ext/standard/tests/general_functions/gh24605_stack_guard.phpt new file mode 100644 index 000000000000..c86d86bc7a02 --- /dev/null +++ b/ext/standard/tests/general_functions/gh24605_stack_guard.phpt @@ -0,0 +1,42 @@ +--TEST-- +GH-24605 (var_export(), print_r() and unserialize() segfault on deep nesting) +--SKIPIF-- + +--INI-- +zend.max_allowed_stack_size=512K +--FILE-- + static fn() => var_export($a), + 'print_r' => static fn() => print_r($a, true), + 'unserialize' => static fn() => unserialize($u), +]; + +foreach ($calls as $name => $call) { + try { + $call(); + echo $name, ": no error\n"; + } catch (\Error $e) { + echo $name, ': ', + str_contains($e->getMessage(), 'Maximum call stack size') ? 'stack error' : 'other error', + "\n"; + } +} +?> +--EXPECT-- +var_export: stack error +print_r: stack error +unserialize: stack error \ No newline at end of file