Skip to content

Commit a0251cf

Browse files
committed
Fix GH-21006: JIT SEGV with FETCH_OBJ_FUNC_ARG and property hooks
When the JIT falls back to the VM handler for FETCH_OBJ_FUNC_ARG (or FETCH_OBJ_R), the handler may find the SIMPLE_GET flag set in the runtime cache for a hooked property. This causes it to push a call frame for the hook function, changing execute_data. When the trace resumes after the handler returns, it continues with the wrong execute_data, leading to a segfault on the next opcode that accesses EX(call). Fix by emitting IR code in zend_jit_trace_handler() to clear the SIMPLE_GET flag in the runtime cache before calling the VM handler, so it falls through to read_property instead. The flag is only cleared when the cached property offset is in the hooked property range (1..15), to avoid corrupting other offset types like ZEND_DYNAMIC_PROPERTY_OFFSET.
1 parent f99ca63 commit a0251cf

File tree

2 files changed

+80
-0
lines changed

2 files changed

+80
-0
lines changed

ext/opcache/jit/zend_jit_ir.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17091,6 +17091,27 @@ static int zend_jit_trace_handler(zend_jit_ctx *jit, const zend_op_array *op_arr
1709117091
ir_ref ref;
1709217092

1709317093
zend_jit_set_ip(jit, opline);
17094+
/* FETCH_OBJ_FUNC_ARG/FETCH_OBJ_R may dispatch to a VM handler that
17095+
* pushes a call frame for SIMPLE_GET property hooks, which would
17096+
* corrupt the trace's call stack. Clear the SIMPLE_GET flag so the
17097+
* handler falls through to read_property instead. */
17098+
if ((opline->opcode == ZEND_FETCH_OBJ_FUNC_ARG || opline->opcode == ZEND_FETCH_OBJ_R)
17099+
&& opline->op2_type == IS_CONST) {
17100+
ir_ref run_time_cache = ir_LOAD_A(jit_EX(run_time_cache));
17101+
ir_ref cache_slot_ref = ir_ADD_OFFSET(run_time_cache,
17102+
(opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*));
17103+
ir_ref prop_offset_ref = ir_LOAD_A(cache_slot_ref);
17104+
/* Only clear SIMPLE_GET for hooked property offsets (range 1..15).
17105+
* Other offset types (dynamic = -1, valid >= 16) may have bit 3
17106+
* set coincidentally and must not be modified. */
17107+
ir_ref if_hooked_lo = ir_IF(ir_GE(prop_offset_ref, ir_CONST_ADDR(ZEND_PROPERTY_HOOK_SIMPLE_GET_BIT)));
17108+
ir_IF_TRUE(if_hooked_lo);
17109+
ir_ref if_hooked_hi = ir_IF(ir_LT(prop_offset_ref, ir_CONST_ADDR(ZEND_FIRST_PROPERTY_OFFSET)));
17110+
ir_IF_TRUE(if_hooked_hi);
17111+
ir_STORE(cache_slot_ref, ir_AND_A(prop_offset_ref, ir_CONST_ADDR(~(uintptr_t)ZEND_PROPERTY_HOOK_SIMPLE_GET_BIT)));
17112+
ir_MERGE_WITH_EMPTY_FALSE(if_hooked_hi);
17113+
ir_MERGE_WITH_EMPTY_FALSE(if_hooked_lo);
17114+
}
1709417115
if (GCC_GLOBAL_REGS) {
1709517116
ir_CALL(IR_VOID, ir_CONST_FUNC(handler));
1709617117
} else {

ext/opcache/tests/jit/gh21006.phpt

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
--TEST--
2+
GH-21006: JIT SEGV with FETCH_OBJ_FUNC_ARG and property hooks
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.jit=tracing
7+
opcache.jit_hot_loop=61
8+
opcache.jit_hot_func=127
9+
opcache.jit_hot_return=8
10+
opcache.jit_hot_side_exit=8
11+
--FILE--
12+
<?php
13+
namespace Test;
14+
15+
class C
16+
{
17+
public $prop {
18+
get => 'sha256';
19+
}
20+
21+
public function sign()
22+
{
23+
return hash_hmac(
24+
algo: $this->prop,
25+
data: '',
26+
key: '',
27+
);
28+
}
29+
}
30+
31+
$obj = new C();
32+
for ($i = 0; $i < 100; $i++) {
33+
$obj->sign();
34+
}
35+
36+
// Dynamic property access through FETCH_OBJ_FUNC_ARG must not corrupt
37+
// the ZEND_DYNAMIC_PROPERTY_OFFSET sentinel in the runtime cache.
38+
#[\AllowDynamicProperties]
39+
class D
40+
{
41+
public function test()
42+
{
43+
return hash_hmac(
44+
algo: $this->algo,
45+
data: '',
46+
key: '',
47+
);
48+
}
49+
}
50+
51+
$d = new D();
52+
$d->algo = 'sha256';
53+
for ($i = 0; $i < 100; $i++) {
54+
$d->test();
55+
}
56+
echo "OK\n";
57+
?>
58+
--EXPECT--
59+
OK

0 commit comments

Comments
 (0)