From 691ebad2661d84f757ad25e55937e755fba7d8d3 Mon Sep 17 00:00:00 2001 From: Valery Ivashchanka Date: Thu, 15 Oct 2026 19:20:49 +0000 Subject: [PATCH] Fix SoapClient not restoring EG(vm_stack_top) after a caught bailout SOAP_CLIENT_END_CODE() catches the bailout, frees the VM stack pages that were pushed during the call and then 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, which only matters for a page that is not the current one. EG(vm_stack_top) keeps pointing to where the stack was when the bailout happened: into a page that has just been freed if the call grew the stack beyond the current page. The next call frame release then trips the assertion in zend_vm_stack_free_call_frame_ex() on debug builds. Restore EG(vm_stack_top) instead, the same way zend_vm_stack_free_call_frame_ex() does when it pops a page. Co-Authored-By: Claude Opus 5.5 --- NEWS | 2 + ext/soap/soap.c | 2 +- .../tests/client_fatal_deep_vm_stack.phpt | 38 +++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 ext/soap/tests/client_fatal_deep_vm_stack.phpt diff --git a/NEWS b/NEWS index c695f56584c9..bb4a865d6beb 100644 --- a/NEWS +++ b/NEWS @@ -77,6 +77,8 @@ PHP NEWS - SOAP: . Fixed use of uninitialized func in do_request() on OOM bailout. (David Carlier) + . Fixed SoapClient not restoring EG(vm_stack_top) after catching a bailout + from a call that grew the VM stack. (Valery Ivashchanka) - Session: . Fixed session data loss in the files save handler when writing the session diff --git a/ext/soap/soap.c b/ext/soap/soap.c index 74e6422d70c6..a2bc1add6840 100644 --- a/ext/soap/soap.c +++ b/ext/soap/soap.c @@ -115,7 +115,7 @@ static void soap_error_handler(int error_num, zend_string *error_filename, uint3 EG(vm_stack) = tmp; \ EG(vm_stack_end) = tmp->end; \ } \ - EG(vm_stack)->top = _old_stack_top; \ + EG(vm_stack_top) = _old_stack_top; \ } \ } zend_end_try();\ SOAP_GLOBAL(use_soap_error_handler) = _old_handler;\ diff --git a/ext/soap/tests/client_fatal_deep_vm_stack.phpt b/ext/soap/tests/client_fatal_deep_vm_stack.phpt new file mode 100644 index 000000000000..26be5015cc66 --- /dev/null +++ b/ext/soap/tests/client_fatal_deep_vm_stack.phpt @@ -0,0 +1,38 @@ +--TEST-- +SoapClient: fatal error caught after the VM stack grew restores EG(vm_stack_top) +--EXTENSIONS-- +soap +--FILE-- + 0) { + return recurse($depth - 1); + } + eval("class Dup {} class Dup {}"); +} + +class TestClient extends SoapClient { + public function __doRequest($request, $location, $action, $version, $oneWay = false, $uriParserClass = null): ?string { + recurse(50000); + return ""; + } +} + +$client = new TestClient(null, ["location" => "test://", "uri" => "test://"]); +try { + $client->test(); +} catch (SoapFault $e) { + echo $e->getMessage(), "\n"; +} + +function f($a, $b, $c) { + return [$a, $b, $c]; +} +for ($i = 0; $i < 100000; $i++) { + f($i, [$i], "x$i"); +} +echo "Done\n"; +?> +--EXPECTF-- +Cannot redeclare class Dup (previously declared in %s) +Done