Commit cc8abaf9f10 for php.net

commit cc8abaf9f109a83c741b9fe5297f1c094c775811
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Wed Jul 15 16:20:48 2026 -0400

    Fix use-after-free serializing an array grown by an element's hook (#22714)

    The IS_ARRAY case of php_var_serialize_intern() walked the array's
    HashTable without holding a reference across
    php_var_serialize_nested_data(), which recurses into user hooks
    (__serialize, __sleep, Serializable::serialize). A hook that grows the
    same array through a by-reference alias reallocs the backing store
    mid-walk, so the iterator reads freed memory. Hold a ref across the walk,
    as the object path and var_dump/var_export already do, so the append
    separates a copy instead of reallocating in place.

diff --git a/ext/standard/tests/serialize/serialize_array_ref_reentrancy.phpt b/ext/standard/tests/serialize/serialize_array_ref_reentrancy.phpt
new file mode 100644
index 00000000000..490fa4f6c3b
--- /dev/null
+++ b/ext/standard/tests/serialize/serialize_array_ref_reentrancy.phpt
@@ -0,0 +1,21 @@
+--TEST--
+serialize(): a by-reference __serialize() that grows the array being walked must not free it
+--FILE--
+<?php
+class G {
+    public $ref;
+    public function __serialize(): array {
+        for ($i = 0; $i < 128; $i++) {
+            $this->ref[] = 'x' . $i;
+        }
+        return ['d' => 1];
+    }
+}
+$g = new G();
+$inner = [$g, 'tail'];
+$g->ref = &$inner;
+$top = [&$inner];
+var_dump(serialize($top));
+?>
+--EXPECT--
+string(59) "a:1:{i:0;a:2:{i:0;O:1:"G":1:{s:1:"d";i:1;}i:1;s:4:"tail";}}"
diff --git a/ext/standard/var.c b/ext/standard/var.c
index 3137a270f66..d79b8941634 100644
--- a/ext/standard/var.c
+++ b/ext/standard/var.c
@@ -1303,13 +1303,17 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_
 				zend_release_properties(myht);
 				return;
 			}
-		case IS_ARRAY:
+		case IS_ARRAY: {
 			smart_str_appendl(buf, "a:", 2);
 			myht = Z_ARRVAL_P(struc);
+			bool rcn = !is_root && (in_rcn_array || GC_REFCOUNT(myht) > 1);
+			GC_TRY_ADDREF(myht);
 			php_var_serialize_nested_data(
 				buf, struc, myht, zend_array_count(myht), /* incomplete_class */ false, var_hash,
-				!is_root && (in_rcn_array || GC_REFCOUNT(myht) > 1));
+				rcn);
+			GC_TRY_DTOR_NO_REF(myht);
 			return;
+		}
 		case IS_REFERENCE:
 			struc = Z_REFVAL_P(struc);
 			goto again;