[php-src] PR #25014: Add stack-limit guards to var_export(), print_r() and unserialize()
| From: | Volkopat | Date: | Wed, 28 Oct 2026 17:48:43 +0000 |
| Subject: | [php-src] PR #25014: Add stack-limit guards to var_export(), print_r() and unserialize() | ||
| Groups: | php.git-pulls | ||
| Request: | Send a blank email to git-pulls+get-40033@lists.php.net to get a copy of this message | ||
Pull Request: https://github.com/php/php-src/pull/25014
Author: Volkopat
Fixes #24605.
ZEND_CHECK_STACK_LIMIT guards the recursion in serialize(),
var_dump() and debug_zval_dump(),
but not in var_export(), print_r() or unserialize(). On a
structure that fits the stack those
functions recurse until it is exhausted and the process dies with SIGSEGV - no error, no message.
## Reproduced first
Each function run in its own process, stock php:8.5-cli, --ulimit
stack=524288:524288, 3,000 levels:
| call | result before the fix |
|---|---|
| serialize() | throws Error (already guarded) |
| var_export() | **exit 139, SIGSEGV** |
| print_r() | **exit 139, SIGSEGV** |
| unserialize() | **exit 139, SIGSEGV** |
## Change
The same guard the already-fixed paths use, at each recursive entry point:
- ext/standard/var.c - php_var_export_ex() (return FAILURE;)
- Zend/zend.c - zend_print_zval_r_to_buf(), the recursion behind
print_r(), which lives in Zend
rather than ext/standard (return;)
- ext/standard/var_unserializer.re - php_var_unserialize_internal()
(return 0;)
Each site returns the type its function already uses, so no signatures change. 3 files, +18 lines.
```c
#ifdef ZEND_CHECK_STACK_LIMIT
if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) {
zend_call_stack_size_error();
return FAILURE;
}
#endif
```
## After
Built from current master with the patch applied, same structure, same 512 KB stack:
```
--- serialize (already guarded) ---
serialize threw: Maximum call stack size of 475136 bytes (zend.max_allowed_stack_size -
zend.reserved_stack_size) reached. Infinite recursion?
--- var_export ---
var_export threw: Maximum call stack size of 475136 bytes ... reached. Infinite recursion?
--- print_r ---
print_r threw: Maximum call stack size of 475136 bytes ... reached. Infinite recursion?
--- unserialize ---
unserialize threw: Maximum call stack size of 475136 bytes ... reached. Infinite recursion?
```
All four now raise the same catchable Error, and the surrounding behaviour is unchanged
for
structures that fit.
## Scope I did not test
unserialize()'s own max_depth option (default 4096) usually rejects
deep input first, so the new
guard only matters when the stack is small relative to that limit - which is the case tested here.
I did not test a very large max_depth against a large stack.