Skip to content

Commit 8975c59

Browse files
authored
JIT: fix vm_interrupt resumption in TAILCALL/CALL VMs (#24793)
In the CALL and TAILCALL VMs, opcode handlers called by function JIT can return the next opline tagged with ZEND_VM_ENTER_BIT after invoking a VM interrupt callback. Function JIT used that result directly as an opcode pointer, causing invalid accesses when execution continued.
1 parent 32b16a0 commit 8975c59

7 files changed

Lines changed: 105 additions & 2 deletions

File tree

‎NEWS‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ PHP NEWS
149149
. Fixed JIT segfault on switch over a string reference holding a
150150
non-string value. (jorgsowa)
151151
. Fixed AArch64 JIT encoding of invalid logical immediates. (jorgsowa)
152+
. Fixed JIT crashes when handling a VM interrupt with a stale saved opline
153+
in the tailcall VM. (Levi Morrison)
154+
. Fixed function JIT misinterpreting VM-enter results after an interrupt
155+
callback in the CALL and TAILCALL VMs. (Levi Morrison)
152156

153157
- PDO:
154158
. Fixed invalid ATTR_ORACLE_NULLS values being accepted and truncated.

‎Zend/zend_vm_execute.h‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Zend/zend_vm_gen.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2045,7 +2045,7 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name)
20452045
out($f,"# define ZEND_VM_ENTER() execute_data = EG(current_execute_data); LOAD_OPLINE(); ZEND_VM_ENTER_EX()\n");
20462046
out($f,"# define ZEND_VM_LEAVE() return (zend_op*)((uintptr_t)opline | ZEND_VM_ENTER_BIT)\n");
20472047
out($f,"#endif\n");
2048-
out($f,"#define ZEND_VM_INTERRUPT() ZEND_VM_TAIL_CALL(zend_interrupt_helper".($spec?"_SPEC":"")."(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));\n");
2048+
out($f,"#define ZEND_VM_INTERRUPT() ZEND_VM_KIND_TAILCALL_SAVE_OPLINE(); ZEND_VM_TAIL_CALL(zend_interrupt_helper".($spec?"_SPEC":"")."(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));\n");
20492049
out($f,"#ifdef ZEND_VM_FP_GLOBAL_REG\n");
20502050
out($f,"#define ZEND_VM_LOOP_INTERRUPT() zend_interrupt_helper".($spec?"_SPEC":"")."(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);\n");
20512051
out($f,"#else\n");

‎ext/opcache/jit/zend_jit_ir.c‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4165,10 +4165,16 @@ static int zend_jit_handler(zend_jit_ctx *jit, const zend_op *opline, int may_th
41654165
} else if (ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL) {
41664166
zend_vm_opcode_handler_func_t handler = (zend_vm_opcode_handler_func_t)zend_get_opcode_handler_func(opline);
41674167
ir_ref ip = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(handler), jit_FP(jit), jit_IP(jit));
4168+
if (zend_interrupt_function) {
4169+
ip = ir_AND_A(ip, ir_CONST_ADDR(~ZEND_VM_ENTER_BIT));
4170+
}
41684171
jit_STORE_IP(jit, ip);
41694172
} else {
41704173
zend_vm_opcode_handler_t handler = opline->handler;
41714174
ir_ref ip = ir_CALL_2(IR_ADDR, ir_CONST_FC_FUNC(handler), jit_FP(jit), jit_IP(jit));
4175+
if (zend_interrupt_function) {
4176+
ip = ir_AND_A(ip, ir_CONST_ADDR(~ZEND_VM_ENTER_BIT));
4177+
}
41724178
jit_STORE_IP(jit, ip);
41734179
}
41744180
if (may_throw) {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
Function JIT: decode the VM-enter bit after an interrupt callback
3+
--EXTENSIONS--
4+
opcache
5+
zend_test
6+
--INI--
7+
opcache.enable=1
8+
opcache.enable_cli=1
9+
opcache.file_update_protection=0
10+
opcache.jit=1201
11+
opcache.jit_buffer_size=64M
12+
zend_test.observer.set_vm_interrupt_on_begin=1
13+
--FILE--
14+
<?php
15+
function compare($left, $right) {
16+
if ($left == $right) {
17+
return 11;
18+
}
19+
return 22;
20+
}
21+
22+
var_dump(compare(new VmInterruptComparable(1), new VmInterruptComparable(2)));
23+
?>
24+
--EXPECT--
25+
int(22)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
--TEST--
2+
Tracing JIT: preserve the next opline when handling a VM interrupt
3+
--DESCRIPTION--
4+
The object comparison raises an interrupt; the observer INI setting installs
5+
an interrupt callback without enabling observer events. While recording the
6+
caller, the backward jump must save its target before the interrupt helper
7+
reloads the opline. Otherwise it revisits the already executed call opcode,
8+
whose pending call frame has been consumed.
9+
10+
At the time of writing this was failing on the TAILCALL VM, but this should
11+
pass on all VMs.
12+
--EXTENSIONS--
13+
opcache
14+
zend_test
15+
--INI--
16+
opcache.enable=1
17+
opcache.enable_cli=1
18+
opcache.file_update_protection=0
19+
opcache.jit=tracing
20+
opcache.jit_buffer_size=64M
21+
opcache.jit_hot_func=1
22+
opcache.jit_hot_loop=1
23+
zend_test.observer.set_vm_interrupt_on_begin=1
24+
--FILE--
25+
<?php
26+
function trigger(VmInterruptComparable $left, VmInterruptComparable $right): void {
27+
// Raise an interrupt without an internal call that would handle it immediately.
28+
$result = $left == $right;
29+
}
30+
31+
$left = new VmInterruptComparable(1);
32+
$right = new VmInterruptComparable(2);
33+
foreach ([1, 2, 3] as $i) {
34+
trigger($left, $right);
35+
}
36+
echo "Done\n";
37+
?>
38+
--EXPECT--
39+
Done
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
Function JIT: preserve the next opline when handling a VM interrupt
3+
--DESCRIPTION--
4+
The object comparison raises an interrupt while the minimal function JIT
5+
executes a callable opcode handler. The interrupt helper must resume at the
6+
branch target, not reload the saved comparison opcode. No interrupt callback
7+
is installed, isolating the saved-opline bug from callback resume-tag handling.
8+
--EXTENSIONS--
9+
opcache
10+
zend_test
11+
--INI--
12+
opcache.enable=1
13+
opcache.enable_cli=1
14+
opcache.file_update_protection=0
15+
opcache.jit=1201
16+
opcache.jit_buffer_size=64M
17+
--FILE--
18+
<?php
19+
function compare($left, $right) {
20+
if ($left == $right) {
21+
return 11;
22+
}
23+
return 22;
24+
}
25+
26+
var_dump(compare(new VmInterruptComparable(1), new VmInterruptComparable(2)));
27+
?>
28+
--EXPECT--
29+
int(22)

0 commit comments

Comments
 (0)