From f4175edb1d9cc3700f0f3b7c6de9fc8f3ce08d7f Mon Sep 17 00:00:00 2001 From: David Carlier Date: Thu, 12 Nov 2026 04:10:53 +0000 Subject: [PATCH] Zend: delayed OOM clears the opline of a frame mid-op. Fix #25304 zend_handle_delayed_oom() always reset EG(opline_before_exception) to NULL, even when called from zend_call_function() in the middle of an opcode. The JIT exception handler stubs then dereferenced it. Only the VM and JIT interrupt handlers run between two opcodes, so they now clear it instead. --- Zend/zend_alloc.c | 5 ----- Zend/zend_vm_def.h | 3 ++- Zend/zend_vm_execute.h | 3 ++- ext/opcache/jit/zend_jit_ir.c | 1 + ext/opcache/tests/jit/gh25304.phpt | 29 +++++++++++++++++++++++++++++ 5 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 ext/opcache/tests/jit/gh25304.phpt diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 2b0b2d62a195..8b0c2401b30c 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -3680,11 +3680,6 @@ ZEND_API void ZEND_FASTCALL zend_handle_delayed_oom(void) /* Bailout discards EG(current_execute_data). Would be better to avoid the bailout here. */ EG(current_execute_data) = execute_data; - /* This function may be called from the VM interrupt handler: opline is not - * executed yet. Set opline_before_exception to NULL so - * ZEND_HANDLE_EXCEPTION does not use it. */ - EG(opline_before_exception) = NULL; - zend_mm_oom_info_dtor(info); info->state = ZEND_OOM_STATE_HANDLED; diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index f9432cb65856..667f98c17166 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -8141,7 +8141,7 @@ ZEND_VM_HANDLER(149, ZEND_HANDLE_EXCEPTION, ANY, ANY) const zend_op *throw_op = EG(opline_before_exception); /* Exception was thrown before executing any op, or between two ops (see - * zend_handle_delayed_oom()). */ + * zend_interrupt_helper). */ if (UNEXPECTED(!throw_op)) { free_unfinished_calls(execute_data); ZEND_VM_DISPATCH_TO_HELPER(zend_dispatch_try_catch_finally_helper, try_catch_offset, -1, op_num, -1); @@ -10437,6 +10437,7 @@ ZEND_VM_HELPER(zend_interrupt_helper, ANY, ANY) zend_timeout(); } else if (ZEND_HAS_OOM()) { zend_handle_delayed_oom(); + EG(opline_before_exception) = NULL; HANDLE_EXCEPTION(); } else if (zend_interrupt_function) { zend_interrupt_function(execute_data); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 3137c55eac0d..f29d8313424f 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3256,7 +3256,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_HANDLE_EXCEPTION_SPEC_HANDLER( const zend_op *throw_op = EG(opline_before_exception); /* Exception was thrown before executing any op, or between two ops (see - * zend_handle_delayed_oom()). */ + * zend_interrupt_helper). */ if (UNEXPECTED(!throw_op)) { free_unfinished_calls(execute_data); ZEND_VM_TAIL_CALL(zend_dispatch_try_catch_finally_helper_SPEC(-1, -1 ZEND_OPCODE_HANDLER_ARGS_PASSTHRU_CC)); @@ -3892,6 +3892,7 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_interrupt_he zend_timeout(); } else if (ZEND_HAS_OOM()) { zend_handle_delayed_oom(); + EG(opline_before_exception) = NULL; HANDLE_EXCEPTION(); } else if (zend_interrupt_function) { zend_interrupt_function(execute_data); diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 1cdd92e1240f..e6029729bf48 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -2078,6 +2078,7 @@ static int zend_jit_interrupt_handler_stub(zend_jit_ctx *jit) ir_ref if_delayed_oom = ir_IF(ir_LOAD_U8(ir_ADD_OFFSET(jit_EG(delayed_oom), offsetof(zend_oom_info, state)))); ir_IF_TRUE(if_delayed_oom); ir_CALL(IR_VOID, ir_CONST_FC_FUNC(zend_handle_delayed_oom)); + ir_STORE(jit_EG(opline_before_exception), IR_NULL); // LOAD_OPLINE() jit_STORE_IP(jit, ir_LOAD_A(jit_EX(opline))); ir_ref if_delayed_oom_end = ir_END(); diff --git a/ext/opcache/tests/jit/gh25304.phpt b/ext/opcache/tests/jit/gh25304.phpt new file mode 100644 index 000000000000..3eddda9f4db9 --- /dev/null +++ b/ext/opcache/tests/jit/gh25304.phpt @@ -0,0 +1,29 @@ +--TEST-- +GH-25304 (JIT: SEGV in exception handler stub after OOM inside an output-buffer callback) +--CREDITS-- +YuanchengJiang +--EXTENSIONS-- +opcache +--SKIPIF-- + +--INI-- +memory_limit=16M +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit=function +opcache.jit_buffer_size=64M +--FILE-- + +--EXPECTF-- +Notice: Object of class stdClass could not be converted to int in %s on line %d + +Fatal error: Allowed memory size of %d bytes exhausted%S(tried to allocate %d bytes) in %s on line %d