[php-src] PR #24363: Fix SoapClient not restoring EG(vm_stack_top) after a caught bailout

From: Date: Thu, 15 Oct 2026 19:22:23 +0000
Subject: [php-src] PR #24363: Fix SoapClient not restoring EG(vm_stack_top) after a caught bailout
Groups: php.git-pulls 
Request: Send a blank email to git-pulls+get-39488@lists.php.net to get a copy of this message
Pull Request: https://github.com/php/php-src/pull/24363 Author: likemusic SOAP_CLIENT_END_CODE() catches a bailout raised during a SoapClient call, frees the VM stack pages pushed during the call and restores the saved stack top. Since 0390cde428f (2014), the live stack top is EG(vm_stack_top), but the macro still writes the saved value to EG(vm_stack)->top. That field only matters for a page that is not the current one. So EG(vm_stack_top) keeps pointing to where the stack was when the bailout happened. If the call grew the stack beyond the current page, that is inside a page that has just been freed. This patch restores EG(vm_stack_top) instead, the same way zend_vm_stack_free_call_frame_ex() does when it pops a page. ### Reproducer The test included in this PR triggers it. __doRequest() recurses 50 000 levels deep, then raises a fatal error that the client turns into a SoapFault (eval("class Dup {} class Dup {}"), no memory exhaustion involved). After the catch, ordinary function calls continue. On a debug build, without the fix: ``` php: Zend/zend_execute.h:338: zend_vm_stack_free_call_frame_ex: Assertion `(executor_globals.vm_stack_top) > (zval *) (executor_globals.vm_stack) && (executor_globals.vm_stack_end) > (zval *) (executor_globals.vm_stack) && (executor_globals.vm_stack_top) <= (executor_globals.vm_stack_end)' failed. Termsig=6 ``` The same assertion fires on master (zend_execute.h:445). With the fix, the test passes on PHP-8.4 and on master. ### Scope I could only observe this on debug builds. A release build with ASAN (with and without USE_ZEND_ALLOC=0) stays silent on the same script: the next zend_vm_stack_free_call_frame_ex() takes the page-pop path and overwrites the stale pointer before anything reads through it. So I am not claiming a user-visible crash on release builds. The fix is to make the restore do what it was written to do. ### Testing - ext/soap + Zend/tests on PHP-8.4, debug build: 5345 passed, 0 failed, 1 expected fail (interface_constructor_prototype_002.phpt), 246 skipped. - ext/soap on master with this commit cherry-picked, debug build: 517 passed, 0 failed, 109 skipped. The cherry-pick conflicts only in NEWS. Found while auditing the zend_catch sites that continue execution after a bailout. The audit started from #10026. 🤖 Generated with [Claude Code](https://claude.com/claude-code)

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