From 2350b8d6a3dba11c8a8e369adf675ff3c11c41a1 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Fri, 26 Jun 2026 20:34:38 -0400 Subject: [PATCH] Fix GH-21006: JIT SEGV with property hooks and FETCH_OBJ_FUNC_ARG FETCH_OBJ_FUNC_ARG reading a SIMPLE_GET property hook pushed the getter call frame mid-trace and corrupted the call being built, crashing at the following SEND_FUNC_ARG. Compile it inline like FETCH_OBJ_R, clearing the SIMPLE_GET flag so the read takes read_property. Positional arguments resolve their by-ref-ness at compile time through the preceding CHECK_FUNC_ARG; named arguments get a run-time guard that deoptimizes to the interpreter when the argument is passed by reference. Fixes GH-21006 --- ext/opcache/jit/zend_jit_ir.c | 20 ++++++++++ ext/opcache/jit/zend_jit_trace.c | 7 +++- ext/opcache/jit/zend_jit_vm_helpers.c | 2 +- ext/opcache/tests/jit/gh21006.phpt | 57 +++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 ext/opcache/tests/jit/gh21006.phpt diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 2fe0b1896a92..d62ef95b5513 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -14234,6 +14234,26 @@ static int zend_jit_class_guard(zend_jit_ctx *jit, const zend_op *opline, ir_ref return 1; } +static int zend_jit_func_arg_by_ref_guard(zend_jit_ctx *jit, const zend_op *opline) +{ + int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM); + const void *exit_addr = zend_jit_trace_get_exit_addr(exit_point); + ir_ref rx, call_info; + + if (!exit_addr) { + return 0; + } + if (jit->reuse_ip) { + rx = jit_IP(jit); + } else { + rx = ir_LOAD_A(jit_EX(call)); + } + call_info = ir_LOAD_U32(jit_CALL(rx, This.u1.type_info)); + ir_GUARD_NOT(ir_AND_U32(call_info, ir_CONST_U32(ZEND_CALL_SEND_ARG_BY_REF)), + ir_CONST_ADDR(exit_addr)); + return 1; +} + static int zend_jit_fetch_obj(zend_jit_ctx *jit, const zend_op *opline, const zend_op_array *op_array, diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index 024a5d0e194d..9b96adc52f52 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -6038,9 +6038,14 @@ static zend_vm_opcode_handler_t zend_jit_trace(zend_jit_trace_rec *trace_buffer, if (!JIT_G(current_frame) || !JIT_G(current_frame)->call || !JIT_G(current_frame)->call->func - || !TRACE_FRAME_IS_LAST_SEND_BY_VAL(JIT_G(current_frame)->call)) { + || TRACE_FRAME_IS_LAST_SEND_BY_REF(JIT_G(current_frame)->call)) { break; } + if (!TRACE_FRAME_IS_LAST_SEND_BY_VAL(JIT_G(current_frame)->call)) { + if (!zend_jit_func_arg_by_ref_guard(&ctx, opline)) { + goto jit_failure; + } + } ZEND_FALLTHROUGH; case ZEND_FETCH_OBJ_R: case ZEND_FETCH_OBJ_IS: diff --git a/ext/opcache/jit/zend_jit_vm_helpers.c b/ext/opcache/jit/zend_jit_vm_helpers.c index 271d923598d9..85a81c1573bc 100644 --- a/ext/opcache/jit/zend_jit_vm_helpers.c +++ b/ext/opcache/jit/zend_jit_vm_helpers.c @@ -978,6 +978,7 @@ zend_jit_trace_stop ZEND_FASTCALL zend_jit_trace_execute(zend_execute_data *ex, } } break; + case ZEND_FETCH_OBJ_FUNC_ARG: case ZEND_FETCH_OBJ_R: { if (opline->op2_type == IS_CONST) { /* Remove the SIMPLE_GET flag to avoid inlining hooks. */ @@ -992,7 +993,6 @@ zend_jit_trace_stop ZEND_FASTCALL zend_jit_trace_execute(zend_execute_data *ex, case ZEND_FETCH_OBJ_W: case ZEND_FETCH_OBJ_RW: case ZEND_FETCH_OBJ_IS: - case ZEND_FETCH_OBJ_FUNC_ARG: case ZEND_FETCH_OBJ_UNSET: case ZEND_ASSIGN_OBJ: case ZEND_ASSIGN_OBJ_OP: diff --git a/ext/opcache/tests/jit/gh21006.phpt b/ext/opcache/tests/jit/gh21006.phpt new file mode 100644 index 000000000000..a79c14efc99d --- /dev/null +++ b/ext/opcache/tests/jit/gh21006.phpt @@ -0,0 +1,57 @@ +--TEST-- +GH-21006: JIT SEGV with FETCH_OBJ_FUNC_ARG and property hooks +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit=tracing +opcache.jit_hot_loop=1 +opcache.jit_hot_func=1 +opcache.jit_hot_return=1 +opcache.jit_hot_side_exit=1 +--FILE-- + 'sha256'; + } + + public function sign() + { + return hash_hmac( + algo: $this->prop, + data: '', + key: '', + ); + } +} + +$obj = new C(); +for ($i = 0; $i < 100; $i++) { + $obj->sign(); +} + +#[\AllowDynamicProperties] +class D +{ + public function test() + { + return hash_hmac( + algo: $this->algo, + data: '', + key: '', + ); + } +} + +$d = new D(); +$d->algo = 'sha256'; +for ($i = 0; $i < 100; $i++) { + $d->test(); +} +echo "OK\n"; +?> +--EXPECT-- +OK