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"
+  }
+}