Commit fe8ce13ef7b for php
commit fe8ce13ef7bf19a7571e693400fd5e52742d493a
Author: Jorg Sowa <jorg.sowa@gmail.com>
Date: Thu Oct 22 18:54:49 2026 +0200
JIT: Fix use-after-free in ASSIGN_DIM_OP with CONCAT
Concatenation into an empty element shares the OP_DATA string, so its
refcount may be >1 when OP_DATA is freed.
Closes GH-24511.
diff --git a/NEWS b/NEWS
index 883c9d7dee3..a35e4ce8132 100644
--- a/NEWS
+++ b/NEWS
@@ -90,6 +90,8 @@ PHP NEWS
passed to a run-time-resolved function). (waroad)
. Fixed bug GH-24389 (Tracing JIT: type guard on entry of an inlined call
exits with an uninitialized opline). (waroad)
+ . Fixed use-after-free in JIT ASSIGN_DIM_OP when concatenating into an
+ empty array element. (jorgsowa)
- PDO:
. Fixed invalid ATTR_ORACLE_NULLS values being accepted and truncated.
diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c
index 830dbdf3272..536cef3dac7 100644
--- a/ext/opcache/jit/zend_jit_ir.c
+++ b/ext/opcache/jit/zend_jit_ir.c
@@ -13683,6 +13683,9 @@ static int zend_jit_assign_dim_op(zend_jit_ctx *jit,
ir_MERGE_list(end_inputs);
}
+ if (op1_data_info & (MAY_BE_REF|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
+ op1_data_info |= MAY_BE_RC1|MAY_BE_RCN;
+ }
jit_FREE_OP(jit, (opline+1)->op1_type, (opline+1)->op1, op1_data_info, NULL);
jit_FREE_OP(jit, opline->op2_type, opline->op2, op2_info, NULL);
if (may_throw) {
diff --git a/ext/opcache/tests/jit/assign_dim_op_008.phpt b/ext/opcache/tests/jit/assign_dim_op_008.phpt
new file mode 100644
index 00000000000..67179c0b128
--- /dev/null
+++ b/ext/opcache/tests/jit/assign_dim_op_008.phpt
@@ -0,0 +1,21 @@
+--TEST--
+JIT ASSIGN_DIM_OP: CONCAT of a temporary string into a null element
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_update_protection=0
+opcache.jit=function
+--FILE--
+<?php
+function f($n) {
+ $arr = [5 => null];
+ $arr[5] .= dechex($n);
+ return $arr;
+}
+var_dump(f(255));
+?>
+--EXPECT--
+array(1) {
+ [5]=>
+ string(2) "ff"
+}
diff --git a/ext/opcache/tests/jit/assign_dim_op_009.phpt b/ext/opcache/tests/jit/assign_dim_op_009.phpt
new file mode 100644
index 00000000000..479a4fa079c
--- /dev/null
+++ b/ext/opcache/tests/jit/assign_dim_op_009.phpt
@@ -0,0 +1,19 @@
+--TEST--
+JIT ASSIGN_DIM_OP: CONCAT of a temporary string into a referenced null element
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_update_protection=0
+opcache.jit=function
+--FILE--
+<?php
+function f($n) {
+ $arr = [5 => null];
+ $r = &$arr[5];
+ $arr[5] .= dechex($n);
+ return $r;
+}
+var_dump(f(255));
+?>
+--EXPECT--
+string(2) "ff"
diff --git a/ext/opcache/tests/jit/assign_dim_op_010.phpt b/ext/opcache/tests/jit/assign_dim_op_010.phpt
new file mode 100644
index 00000000000..2e50bef8577
--- /dev/null
+++ b/ext/opcache/tests/jit/assign_dim_op_010.phpt
@@ -0,0 +1,26 @@
+--TEST--
+JIT ASSIGN_DIM_OP: CONCAT of a temporary string into a typed reference
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_update_protection=0
+opcache.jit=function
+--FILE--
+<?php
+class C {
+ public ?string $p = null;
+}
+function f($n) {
+ $c = new C;
+ $arr = [5 => null];
+ $arr[5] = &$c->p;
+ $arr[5] .= dechex($n);
+ return $c;
+}
+var_dump(f(255));
+?>
+--EXPECT--
+object(C)#1 (1) {
+ ["p"]=>
+ string(2) "ff"
+}
diff --git a/ext/opcache/tests/jit/assign_dim_op_011.phpt b/ext/opcache/tests/jit/assign_dim_op_011.phpt
new file mode 100644
index 00000000000..5f95a9111a9
--- /dev/null
+++ b/ext/opcache/tests/jit/assign_dim_op_011.phpt
@@ -0,0 +1,28 @@
+--TEST--
+JIT ASSIGN_DIM_OP: CONCAT of a temporary string into an ArrayAccess offset
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_update_protection=0
+opcache.jit=function
+--FILE--
+<?php
+class A implements ArrayAccess {
+ public $d = [];
+ function offsetGet($o): mixed { return $this->d[$o] ?? null; }
+ function offsetSet($o, $v): void { $this->d[$o] = $v; }
+ function offsetExists($o): bool { return isset($this->d[$o]); }
+ function offsetUnset($o): void {}
+}
+function f($n) {
+ $a = new A;
+ $a[5] .= dechex($n);
+ return $a->d;
+}
+var_dump(f(255));
+?>
+--EXPECT--
+array(1) {
+ [5]=>
+ string(2) "ff"
+}
diff --git a/ext/opcache/tests/jit/assign_dim_op_012.phpt b/ext/opcache/tests/jit/assign_dim_op_012.phpt
new file mode 100644
index 00000000000..e2f16870804
--- /dev/null
+++ b/ext/opcache/tests/jit/assign_dim_op_012.phpt
@@ -0,0 +1,26 @@
+--TEST--
+JIT ASSIGN_DIM_OP: ADD of a temporary array
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_update_protection=0
+opcache.jit=function
+--FILE--
+<?php
+function f($n) {
+ $arr = [5 => []];
+ $arr[5] += $n ? [1, str_repeat("a", $n)] : 1;
+ return $arr;
+}
+var_dump(f(3));
+?>
+--EXPECT--
+array(1) {
+ [5]=>
+ array(2) {
+ [0]=>
+ int(1)
+ [1]=>
+ string(3) "aaa"
+ }
+}