Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ PHP NEWS
after restart. (Pavel Gushcha)
. Fix opcache.interned_strings_buffer being 0 in threads started later (ZTS).
(Pavel Gushcha)
. Fixed potential infinite recursion/hang (HYBRID/TAILCALL respectively)
when zend_interrupt_function resets EG(vm_interrupt). (Levi Morrison)

- OpenSSL:
. Fixed bug GH-24784 (openssl_pkcs7_read() and openssl_cms_read() overwrite
Expand Down
26 changes: 26 additions & 0 deletions Zend/tests/vm_interrupt_rearm.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
VM interrupt re-armed by the interrupt handler must not stop execution from making progress
--DESCRIPTION--
zend_test.rearm_vm_interrupt installs a zend_interrupt_function that sets
EG(vm_interrupt) again every time it runs, so an interrupt is always pending.
After handling an interrupt the VM has to run the next opline before it checks
the flag again, otherwise it calls the handler forever (or recurses until the
stack overflows).
--EXTENSIONS--
zend_test
--INI--
zend_test.rearm_vm_interrupt=1
--FILE--
<?php
function inc(int $x): int {
return $x + 1;
}

$n = 0;
for ($i = 0; $i < 1000; $i++) {
$n = inc($n);
}
var_dump($n);
?>
--EXPECT--
int(1000)
6 changes: 5 additions & 1 deletion Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -10446,7 +10446,11 @@ ZEND_VM_HELPER(zend_interrupt_helper, ANY, ANY)

}
}
ZEND_VM_ENTER();
/* 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();
Comment on lines +10449 to +10453

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

}
ZEND_VM_CONTINUE();
}
6 changes: 5 additions & 1 deletion Zend/zend_vm_execute.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions ext/zend_test/php_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ ZEND_BEGIN_MODULE_GLOBALS(zend_test)
HashTable *global_weakmap;
int replace_zend_execute_ex;
int register_passes;
bool rearm_vm_interrupt;
bool print_stderr_mshutdown;
zend_long limit_copy_file_range;
int observe_opline_in_zendmm;
Expand Down
29 changes: 29 additions & 0 deletions ext/zend_test/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -1355,10 +1355,24 @@ static ZEND_METHOD(_ZendTestMagicCallForward, __call)
zval_ptr_dtor(&rv);
}

static void (*zend_test_prev_interrupt_function)(zend_execute_data *execute_data);

/* The VM clears EG(vm_interrupt) before calling this, so setting it again
* leaves an interrupt pending at all times. The VM must still make progress. */
static void zend_test_rearm_interrupt_function(zend_execute_data *execute_data)
{
zend_atomic_bool_store_ex(&EG(vm_interrupt), true);

if (zend_test_prev_interrupt_function) {
zend_test_prev_interrupt_function(execute_data);
}
}

PHP_INI_BEGIN()
PHP_INI_ENTRY("zend_test.register_system_entropy", "0", PHP_INI_SYSTEM, NULL)
STD_PHP_INI_BOOLEAN("zend_test.replace_zend_execute_ex", "0", PHP_INI_SYSTEM, OnUpdateBool, replace_zend_execute_ex, zend_zend_test_globals, zend_test_globals)
STD_PHP_INI_BOOLEAN("zend_test.register_passes", "0", PHP_INI_SYSTEM, OnUpdateBool, register_passes, zend_zend_test_globals, zend_test_globals)
STD_PHP_INI_BOOLEAN("zend_test.rearm_vm_interrupt", "0", PHP_INI_SYSTEM, OnUpdateBool, rearm_vm_interrupt, zend_zend_test_globals, zend_test_globals)
STD_PHP_INI_BOOLEAN("zend_test.print_stderr_mshutdown", "0", PHP_INI_SYSTEM, OnUpdateBool, print_stderr_mshutdown, zend_zend_test_globals, zend_test_globals)
#ifdef HAVE_COPY_FILE_RANGE
STD_PHP_INI_ENTRY("zend_test.limit_copy_file_range", "-1", PHP_INI_ALL, OnUpdateLong, limit_copy_file_range, zend_zend_test_globals, zend_test_globals)
Expand Down Expand Up @@ -1573,6 +1587,11 @@ PHP_MINIT_FUNCTION(zend_test)
zend_optimizer_register_pass(pass2);
}

if (ZT_G(rearm_vm_interrupt)) {
zend_test_prev_interrupt_function = zend_interrupt_function;
zend_interrupt_function = zend_test_rearm_interrupt_function;
}

zend_test_observer_init(INIT_FUNC_ARGS_PASSTHRU);
zend_test_mm_custom_handlers_minit(INIT_FUNC_ARGS_PASSTHRU);
zend_test_fiber_init();
Expand All @@ -1592,6 +1611,11 @@ PHP_MSHUTDOWN_FUNCTION(zend_test)

zend_test_observer_shutdown(SHUTDOWN_FUNC_ARGS_PASSTHRU);

if (zend_interrupt_function == zend_test_rearm_interrupt_function) {
zend_interrupt_function = zend_test_prev_interrupt_function;
zend_test_prev_interrupt_function = NULL;
}

if (ZT_G(print_stderr_mshutdown)) {
fprintf(stderr, "[zend_test] MSHUTDOWN\n");
}
Expand All @@ -1605,6 +1629,11 @@ PHP_RINIT_FUNCTION(zend_test)
zend_hash_init(ZT_G(global_weakmap), 8, NULL, ZVAL_PTR_DTOR, 0);
ZT_G(observer_nesting_depth) = 0;
zend_test_mm_custom_handlers_rinit();

if (ZT_G(rearm_vm_interrupt)) {
zend_atomic_bool_store_ex(&EG(vm_interrupt), true);
}

return SUCCESS;
}

Expand Down
Loading