diff --git a/NEWS b/NEWS index a654cec2f398..57d6e4694d07 100644 --- a/NEWS +++ b/NEWS @@ -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 diff --git a/Zend/tests/vm_interrupt_rearm.phpt b/Zend/tests/vm_interrupt_rearm.phpt new file mode 100644 index 000000000000..605cb59086d7 --- /dev/null +++ b/Zend/tests/vm_interrupt_rearm.phpt @@ -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-- + +--EXPECT-- +int(1000) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index df255c5a868f..53f33e0ce5a0 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -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(); } ZEND_VM_CONTINUE(); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 4b313f20cd39..258567bb34ed 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3901,7 +3901,11 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_interrupt_he } } - 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(); } ZEND_VM_CONTINUE(); } diff --git a/ext/zend_test/php_test.h b/ext/zend_test/php_test.h index 9ab27f5a54f6..ee2e334fb55c 100644 --- a/ext/zend_test/php_test.h +++ b/ext/zend_test/php_test.h @@ -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; diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index d9d80bf6ccf1..a11fc54af465 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -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) @@ -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(); @@ -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"); } @@ -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; }