Description
When observer fcall handlers are used to observer PHP function calls and tracing JIT is enabled, then execute_data->opline pointers in the fcall handler may become unreliable (not NULL and not valid either) causing the PHP process to crash when these pointers are used.
A common scenario would be accessing execute_data->opline->lineno to get the line number. The following simple observer handler should print out the line number for every called user function:
static void observer_begin(zend_execute_data *ex)
{
if (ZEND_USER_CODE(ex->func->type) && ex->opline) {
printf("%u\n", ex->opline->lineno);
}
}
The actual result is that the PHP process crashes due to the execute_data->opline pointer being not NULL and not a valid pointer either:
Process 46323 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x19)
frame #0: 0x000000010146be44 php-observer.so`observer_begin(ex=0x0000000100c17150) at php_observer.c:58:36
55 static void observer_begin(zend_execute_data *ex)
56 {
57 if (ZEND_USER_CODE(ex->func->type) && ex->opline) {
-> 58 printf("%u\n", ex->opline->lineno);
59 }
60 }
> p ex->opline
(const zend_op *) 0x0000000000000001
Backtrace of the crashing PHP process:
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x19)
* frame #0: 0x000000010146be44 php-observer.so`observer_begin(ex=0x0000000100c17150) at php_observer.c:58:36
frame #1: 0x0000000100734340 php`_zend_observe_fcall_begin(execute_data=0x0000000100c17150) at zend_observer.c:244:3
frame #2: 0x00000001007343d4 php`zend_observer_fcall_begin(execute_data=0x0000000100c17150) at zend_observer.c:257:3
frame #3: 0x0000000138008638
frame #4: 0x00000001005aef34 php`zend_call_function(fci=0x000000016fdfdc68, fci_cache=0x000000016fdfdc40) at zend_execute_API.c:957:3
frame #5: 0x0000000100350d18 php`zif_call_user_func_array(execute_data=0x0000000100c16500, return_value=0x0000000100c164c0) at basic_functions.c:1514:6
frame #6: 0x0000000100698880 php`ZEND_DO_FCALL_BY_NAME_SPEC_OBSERVER_HANDLER(execute_data=0x0000000100c16450) at zend_vm_execute.h:1759:3
frame #7: 0x00000001006155f4 php`execute_ex(ex=0x0000000100c16020) at zend_vm_execute.h:57007:7
frame #8: 0x00000001006159f0 php`zend_execute(op_array=0x0000000100c9b000, return_value=0x0000000000000000) at zend_vm_execute.h:61604:2
frame #9: 0x00000001005cfac0 php`zend_execute_scripts(type=8, retval=0x0000000000000000, file_count=3) at zend.c:1883:4
frame #10: 0x00000001004fc61c php`php_execute_script(primary_file=0x000000016fdfe570) at main.c:2507:13
frame #11: 0x00000001007d1440 php`php_cli_server_dispatch_script(server=0x00000001009ab9e8, client=0x0000000114f45990) at php_cli_server.c:2122:3
frame #12: 0x00000001007cee20 php`php_cli_server_dispatch(server=0x00000001009ab9e8, client=0x0000000114f45990) at php_cli_server.c:2312:18
frame #13: 0x00000001007cd8d4 php`php_cli_server_recv_event_read_request(server=0x00000001009ab9e8, client=0x0000000114f45990) at php_cli_server.c:2641:11
frame #14: 0x00000001007cdf84 php`php_cli_server_do_event_for_each_fd_callback(_params=0x000000016fdfe870, fd=5, event=1) at php_cli_server.c:2726:5
frame #15: 0x00000001007cdbe8 php`php_cli_server_poller_iter_on_active(poller=0x00000001009ab9ec, opaque=0x000000016fdfe870, callback=(php`php_cli_server_do_event_for_each_fd_callback at php_cli_server.c:2688)) at php_cli_server.c:932:20
frame #16: 0x00000001007cd710 php`php_cli_server_do_event_for_each_fd(server=0x00000001009ab9e8, rhandler=(php`php_cli_server_recv_event_read_request at php_cli_server.c:2620), whandler=(php`php_cli_server_send_event at php_cli_server.c:2652)) at php_cli_server.c:2746:17
frame #17: 0x00000001007ca94c php`php_cli_server_do_event_loop(server=0x00000001009ab9e8) at php_cli_server.c:2758:4
frame #18: 0x00000001007ca408 php`do_cli_server(argc=3, argv=0x00006000033b3040) at php_cli_server.c:2890:17
frame #19: 0x00000001007c18b4 php`main(argc=3, argv=0x00006000033b3040) at php_cli.c:1343:18
frame #20: 0x000000018d6360e0 dyld`start + 2360
I haven't been able to isolate a simple PHP script that would trigger this issue. The crash above was observed when running an application using Yii framework. @dstogov confirmed the bug and has a zend_test patch that reproduces the issue by running bench.php.
PHP Version
PHP 8.3.4
Operating System
macOS 14.4
Description
When observer fcall handlers are used to observer PHP function calls and tracing JIT is enabled, then
execute_data->oplinepointers in the fcall handler may become unreliable (not NULL and not valid either) causing the PHP process to crash when these pointers are used.A common scenario would be accessing
execute_data->opline->linenoto get the line number. The following simple observer handler should print out the line number for every called user function:The actual result is that the PHP process crashes due to the
execute_data->oplinepointer being not NULL and not a valid pointer either:Backtrace of the crashing PHP process:
I haven't been able to isolate a simple PHP script that would trigger this issue. The crash above was observed when running an application using Yii framework. @dstogov confirmed the bug and has a
zend_testpatch that reproduces the issue by runningbench.php.PHP Version
PHP 8.3.4
Operating System
macOS 14.4