[php-src] Issue #24605: var_export(), print_r() and unserialize() segfault on deep nesting — the stack-limit guard from #22871 is incomplete
| From: | Volkopat | Date: | Fri, 23 Oct 2026 15:10:15 +0000 |
| Subject: | [php-src] Issue #24605: var_export(), print_r() and unserialize() segfault on deep nesting — the stack-limit guard from #22871 is incomplete | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-253131@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/24605
Author: Volkopat
### Description
#22871 added
ZEND_CHECK_STACK_LIMIT to
var_dump()/debug_zval_dump() so deep
nesting throws a catchable Error instead of segfaulting, and its description notes
var_export() "overflows the same way but needs a throw-based guard, left for a
separate change." That guard was never added. print_r()'s array path and
unserialize() have the same unguarded C recursion. serialize() (GH-15169)
and
DOMNode::normalize() (#23127) are already guarded.
### Reproduce (needs a real stack limit; a container makes it one line)
docker run --rm --ulimit stack=524288:524288 -v "$PWD:/w" php:8.5.12-cli php
/w/repro.php
# or locally: bash -c 'ulimit -s 512; php repro.php'
```php
<?php
$depth = 3000;
$a = 0;
for ($i = 0; $i < $depth; $i++) { $a = [$a]; }
try { serialize($a); } catch (\Error $e) { echo "serialize threw:
{$e->getMessage()}\n"; } // guarded
var_export($a); // SIGSEGV (exit 139)
// print_r($a); // same
// unserialize(str_repeat('a:1:{i:0;', $depth) . 'i:0;' .
str_repeat('}', $depth)); // same
```
**Expected:** a catchable Error ("Maximum call stack size reached"), as
serialize()
produces on the identical structure in the same process.
**Actual:** segmentation fault (exit 139).
Stack cost is ~233 B/level, so crash depth tracks the stack (~35,000 at 8 MB, ~2,200 at
512 KB). var_export() and print_r() have no depth backstop, so they crash
at ~35,000
on a stock 8 MB stack too; unserialize() throws at its default max_depth
of 4096 first,
so it only reaches the segfault when the stack is small enough that 4096 levels don't fit
(threaded SAPIs, musl/Alpine, fibers, lowered ulimit).
Fix: add ZEND_CHECK_STACK_LIMIT to the var_export, print_r
and unserialize recursion,
as the already-guarded paths do.
### PHP Version
```plain
8.5.12
```
### Operating System
_No response_