Fix GH-21006: JIT SEGV with FETCH_OBJ_FUNC_ARG and property hooks#21369
Fix GH-21006: JIT SEGV with FETCH_OBJ_FUNC_ARG and property hooks#21369iliaal wants to merge 1 commit intophp:masterfrom
Conversation
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.
5963656 to
a0251cf
Compare
|
I can reproduce the problem with "opcache.jit_hot_func=1 opcache.jit_hot_loop=1 opcache.jit_hot_return=1 opcache.jit_hot_side_exit=1". The second loop in the test seems irrelevant. It seems like this was partially fixed via 57c62eb (missing support for FETCH_OBJ_FUNC_ARG), but than was reverted... @ndossche @iluuu1994 can you take a look. |
The second loop guards against a regression in the fix itself: the SIMPLE_GET flag clear uses a range check (offset 1..15) to avoid corrupting ZEND_DYNAMIC_PROPERTY_OFFSET for non-hooked properties. Class D exercises that path. You are right, but it does safety check for current fix. |
|
Regarding the fix itself — clearing SIMPLE_GET before the handler call is a workaround that forces the slower diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c
--- a/ext/opcache/jit/zend_jit_ir.c
+++ b/ext/opcache/jit/zend_jit_ir.c
@@ -16850,12 +16850,15 @@ static int zend_jit_trace_handler(...)
ir_ref ref;
zend_jit_set_ip(jit, opline);
+ /* Save FP before handler call to detect SIMPLE_GET hook frame push */
+ ir_ref saved_fp = (opline->opcode == ZEND_FETCH_OBJ_R
+ || opline->opcode == ZEND_FETCH_OBJ_FUNC_ARG) ? jit_FP(jit) : IR_UNUSED;
+
if (GCC_GLOBAL_REGS) {
ir_CALL(IR_VOID, ir_CONST_FUNC(handler));
} else {
ref = jit_FP(jit);
ref = ir_CALL_1(IR_I32, ir_CONST_FC_FUNC(handler), ref);
}
+
+ if (saved_fp != IR_UNUSED) {
+ /* FETCH_OBJ_R handler may enter a property hook via SIMPLE_GET,
+ * pushing a call frame and changing EG(current_execute_data).
+ * Detect this and exit to the VM to execute the hook. */
+ ir_ref if_hook = ir_IF(ir_NE(ir_LOAD_A(jit_EG(current_execute_data)), saved_fp));
+ ir_IF_TRUE_cold(if_hook);
+ if (GCC_GLOBAL_REGS) {
+ ir_TAILCALL(IR_VOID, ir_LOAD_A(jit_IP(jit)));
+ } else {
+ jit_STORE_FP(jit, ir_LOAD_A(jit_EG(current_execute_data)));
+ ir_RETURN(ir_CONST_I32(1)); /* ZEND_VM_ENTER */
+ }
+ ir_IF_FALSE(if_hook);
+ }
+
if (may_throwHappy to implement this, or defer to @ndossche / @iluuu1994 if the proper fix should be at a different layer. |
Summary
FETCH_OBJ_FUNC_ARG(orFETCH_OBJ_R), the handler may find theSIMPLE_GETflag set in the runtime cache for a hooked property. This causes it to push a call frame for the hook function, changingexecute_data. When the trace resumes, it continues with the wrongexecute_data, leading to a segfault.zend_jit_trace_handler()to clear theSIMPLE_GETflag before calling the VM handler, so it falls through toread_propertyinstead.Closes GH-21006