[php-src] PR #25014: Add stack-limit guards to var_export(), print_r() and unserialize()

From: 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.

« previous php.git-pulls (#40033) next »