Skip to content

Fix potential infinite recursion in HYBRID/TAILCALL VMs - #25039

Merged
arnaud-lb merged 1 commit into
php:PHP-8.4from
morrisonlevi:vm-interrupt-loop
Oct 30, 2026
Merged

arnaud-lb merged 1 commit into
php:PHP-8.4from
morrisonlevi:vm-interrupt-loop

Conversation

@morrisonlevi

@morrisonlevi morrisonlevi commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

If an extension re-arms EG(vm_interrupt) inside of zend_interrupt_function (as-in, unconditionally, every time), then on some VM kinds there will be infinite recursion. For the HYBRID VM this will crash with stack overflow and for TAILCALL this is just regular infinite recursion (on 8.6, no problem on 8.5).

The VM should always make progress. This is a useful testing property (does my interrupt handler work properly under ASAN for every possible vm interrupt point that was provided?) but it's also a nice property to protect production against a misbehaving extension.

I've discussed this with @arnaud-lb previously (see his draft #24472) but I wanted to fix it more generically, backport it to PHP 8.4, and add a test to prevent regressions. The core of the fix is this:

-		ZEND_VM_ENTER();
+		execute_data = EG(current_execute_data);
+		LOAD_OPLINE();
+		ZEND_VM_LEAVE();

Comment thread Zend/zend_vm_def.h
Comment on lines +10449 to +10453
/* Like ZEND_VM_ENTER() but don't check for interrupts yet to avoid recursion if the
* interrupt handler re-sets the vm_interrupt flag. */
execute_data = EG(current_execute_data);
LOAD_OPLINE();
ZEND_VM_LEAVE();

@morrisonlevi morrisonlevi Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's how ZEND_VM_ENTER and ZEND_VM_LEAVE expand across VM kinds (courtesy of claude) so you can see how the change from ZEND_VM_ENTER(); to execute_data = EG(current_execute_data); LOAD_OPLINE(); ZEND_VM_LEAVE(); changes things:

Kind ZEND_VM_ENTER() ZEND_VM_LEAVE()
HYBRID (8.4–8.6) execute_data = EG(current_execute_data); LOAD_OPLINE(); then ZEND_VM_INTERRUPT_CHECK(); then ZEND_VM_CONTINUE() ZEND_VM_CONTINUE()
CALL, 8.4 return 1 return 2
CALL, 8.5–8.6 (and 8.5 TAILCALL) execute_data = EG(current_execute_data); LOAD_OPLINE(); return opline | ZEND_VM_ENTER_BIT return opline | ZEND_VM_ENTER_BIT
TAILCALL, 8.6 execute_data = EG(current_execute_data); LOAD_OPLINE(); then ZEND_VM_INTERRUPT_CHECK(); then ZEND_VM_CONTINUE() (musttail call to the next handler) ZEND_VM_CONTINUE()

I don't understand return 1 and return 2 on PHP 8.4's CALL VM but the rest seem correct to me.

@morrisonlevi
morrisonlevi marked this pull request as ready for review October 29, 2026 20:00
@morrisonlevi
morrisonlevi requested a review from dstogov as a code owner October 29, 2026 20:00
The recursion will result in a stack overflow on HYBRID VMs and
infinite recursion in TAILCALL VMs.

The property that the VM makes guaranteed forward progress is useful
for testing but it's also helpful to guard against for edge cases in
production as well.
@arnaud-lb
arnaud-lb merged commit 5f4d73a into php:PHP-8.4 Oct 30, 2026
18 checks passed
arnaud-lb added a commit that referenced this pull request Oct 30, 2026
* PHP-8.6:
  Fix potential infinite recursion in HYBRID/TAILCALL VMs (#25039)
@arnaud-lb

Copy link
Copy Markdown
Member

Thank you!

kairosci pushed a commit to kairosci/php-src that referenced this pull request Oct 30, 2026
* PHP-8.4:
  Fix potential infinite recursion in HYBRID/TAILCALL VMs (php#25039)
kairosci pushed a commit to kairosci/php-src that referenced this pull request Oct 30, 2026
* PHP-8.5:
  Fix potential infinite recursion in HYBRID/TAILCALL VMs (php#25039)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants