Skip to content

var_export(), print_r() and unserialize() segfault on deep nesting — the stack-limit guard from #22871 is incomplete #24605

Open

Description

@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
$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

8.5.12

Operating System

No response

Activity

  1. self-assigned this
    on Oct 23, 2026
  2. ndossche commented on Oct 27, 2026

    @ndossche
    Member

    Only partially done

  3. added 4 commits that reference this issue on Oct 27, 2026
    afe5635
    71e8293
    4f8d7bb
    815aab5
  4. added a commit that references this issue on Oct 27, 2026
    a31751c
  5. Volkopat commented on Oct 27, 2026

    @Volkopat
    Author

    Confirming the reopen, with file/line detail on why the patch could not cover two of the three paths.

    On PHP-8.5, the six ZEND_CHECK_STACK_LIMIT sites in ext/standard/var.c are at lines 47, 68, 267, 288 (dump paths), 545 (php_var_export_ex), and 1123 (php_var_serialize_class). PR #24671's diff touches only var.c and tests/general_functions/gh24605_var_export_stack_guard.phpt.

    print_r is implemented in ext/standard/basic_functions.c (around line 2113), not var.c, and contains no stack check — so a var.c-only change cannot reach it.

    unserialize's parser is ext/standard/var_unserializer.re, which currently contains zero occurrences of ZEND_CHECK_STACK_LIMIT (and no stack token at all). It needs the check added in the .re and regenerated.

    I'd separate the fixes: basic_functions.c for print_r, var_unserializer.re for unserialize.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions