diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 19b3ce125733a..0367888a3a46a 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -17091,6 +17091,27 @@ static int zend_jit_trace_handler(zend_jit_ctx *jit, const zend_op_array *op_arr ir_ref ref; zend_jit_set_ip(jit, opline); + /* FETCH_OBJ_FUNC_ARG/FETCH_OBJ_R may dispatch to a VM handler that + * pushes a call frame for SIMPLE_GET property hooks, which would + * corrupt the trace's call stack. Clear the SIMPLE_GET flag so the + * handler falls through to read_property instead. */ + if ((opline->opcode == ZEND_FETCH_OBJ_FUNC_ARG || opline->opcode == ZEND_FETCH_OBJ_R) + && opline->op2_type == IS_CONST) { + ir_ref run_time_cache = ir_LOAD_A(jit_EX(run_time_cache)); + ir_ref cache_slot_ref = ir_ADD_OFFSET(run_time_cache, + (opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*)); + ir_ref prop_offset_ref = ir_LOAD_A(cache_slot_ref); + /* Only clear SIMPLE_GET for hooked property offsets (range 1..15). + * Other offset types (dynamic = -1, valid >= 16) may have bit 3 + * set coincidentally and must not be modified. */ + ir_ref if_hooked_lo = ir_IF(ir_GE(prop_offset_ref, ir_CONST_ADDR(ZEND_PROPERTY_HOOK_SIMPLE_GET_BIT))); + ir_IF_TRUE(if_hooked_lo); + ir_ref if_hooked_hi = ir_IF(ir_LT(prop_offset_ref, ir_CONST_ADDR(ZEND_FIRST_PROPERTY_OFFSET))); + ir_IF_TRUE(if_hooked_hi); + ir_STORE(cache_slot_ref, ir_AND_A(prop_offset_ref, ir_CONST_ADDR(~(uintptr_t)ZEND_PROPERTY_HOOK_SIMPLE_GET_BIT))); + ir_MERGE_WITH_EMPTY_FALSE(if_hooked_hi); + ir_MERGE_WITH_EMPTY_FALSE(if_hooked_lo); + } if (GCC_GLOBAL_REGS) { ir_CALL(IR_VOID, ir_CONST_FUNC(handler)); } else { diff --git a/ext/opcache/tests/jit/gh21006.phpt b/ext/opcache/tests/jit/gh21006.phpt new file mode 100644 index 0000000000000..577b845e1edd5 --- /dev/null +++ b/ext/opcache/tests/jit/gh21006.phpt @@ -0,0 +1,59 @@ +--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=61 +opcache.jit_hot_func=127 +opcache.jit_hot_return=8 +opcache.jit_hot_side_exit=8 +--FILE-- + 'sha256'; + } + + public function sign() + { + return hash_hmac( + algo: $this->prop, + data: '', + key: '', + ); + } +} + +$obj = new C(); +for ($i = 0; $i < 100; $i++) { + $obj->sign(); +} + +// Dynamic property access through FETCH_OBJ_FUNC_ARG must not corrupt +// the ZEND_DYNAMIC_PROPERTY_OFFSET sentinel in the runtime cache. +#[\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