Commit 2210fda0e1c for php.net

commit 2210fda0e1c19ce610d430ba67914d503cc5c73e
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Sun Jun 14 11:56:50 2026 -0400

    Fix use-after-free when ArrayObject sort comparator replaces backing store

    spl_array_method() caches the backing HashTable pointer across a
    user-supplied comparator (uasort/uksort and the sort handlers). The
    comparator can re-enter __construct() or __unserialize(), which route
    through spl_array_set_array() and swap intern->array out from under the
    cached pointer, leaving the post-sort cleanup to release and dereference
    freed memory. Mirror the nApplyCount guard the other mutators already
    use so replacing the backing store during a sort throws instead.

    Closes GH-22310

diff --git a/ext/spl/spl_array.c b/ext/spl/spl_array.c
index 01fdccf251b..b60b3b9c0da 100644
--- a/ext/spl/spl_array.c
+++ b/ext/spl/spl_array.c
@@ -927,6 +927,10 @@ static zend_result spl_array_skip_protected(spl_array_object *intern, HashTable
 static void spl_array_set_array(zval *object, spl_array_object *intern, zval *array, zend_long ar_flags, bool just_array) {
 	/* Handled by ZPP prior to this, or for __unserialize() before passing to here */
 	ZEND_ASSERT(Z_TYPE_P(array) == IS_ARRAY || Z_TYPE_P(array) == IS_OBJECT);
+	if (intern->nApplyCount > 0) {
+		zend_throw_error(NULL, "Modification of ArrayObject during sorting is prohibited");
+		return;
+	}
 	zval garbage;
 	ZVAL_UNDEF(&garbage);
 	if (Z_TYPE_P(array) == IS_ARRAY) {
diff --git a/ext/spl/tests/ArrayObject_construct_during_sorting.phpt b/ext/spl/tests/ArrayObject_construct_during_sorting.phpt
new file mode 100644
index 00000000000..cec41dc92cd
--- /dev/null
+++ b/ext/spl/tests/ArrayObject_construct_during_sorting.phpt
@@ -0,0 +1,34 @@
+--TEST--
+Can't use __construct() to replace the backing store while ArrayObject is being sorted
+--FILE--
+<?php
+
+$ao = new ArrayObject([1, 2, 3]);
+$other = new ArrayObject([4, 5, 6]);
+$i = 0;
+$ao->uasort(function($a, $b) use ($ao, $other, &$i) {
+    if ($i++ == 0) {
+        try {
+            $ao->__construct($other);
+        } catch (Error $e) {
+            echo $e->getMessage(), "\n";
+        }
+    }
+    return $a <=> $b;
+});
+var_dump($ao);
+
+?>
+--EXPECT--
+Modification of ArrayObject during sorting is prohibited
+object(ArrayObject)#1 (1) {
+  ["storage":"ArrayObject":private]=>
+  array(3) {
+    [0]=>
+    int(1)
+    [1]=>
+    int(2)
+    [2]=>
+    int(3)
+  }
+}