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