Commit e912c022fda for php.net

commit e912c022fdaa43a732143d666e3780f32dec997a
Author: Gina Peter Banyard <girgias@php.net>
Date:   Wed Mar 25 20:09:20 2026 +0000

    Revert 49b2ff5dbb94b76b265fd5909881997e1d95c6b3 to fix bug GH-21499

    The fix for this was to take hold of a pointer of the bucket, something that should not be done as it causes memory corruptions

    Closes GH-21532, GH-21520, GH-21499

diff --git a/NEWS b/NEWS
index e86c5132350..dc0e430775f 100644
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,10 @@ PHP                                                                        NEWS
   . Fixed bug GH-19983 (GC assertion failure with fibers, generators and
     destructors). (iliaal)

+- SPL:
+  . Fixed bug GH-21499 (RecursiveArrayIterator getChildren UAF after parent
+    free). (Girgias)
+
 09 Apr 2026, PHP 8.4.20

 - Bz2:
diff --git a/ext/spl/spl_array.c b/ext/spl/spl_array.c
index 3d9d2a9535c..01fdccf251b 100644
--- a/ext/spl/spl_array.c
+++ b/ext/spl/spl_array.c
@@ -44,8 +44,6 @@ typedef struct _spl_array_object {
 	uint32_t          ht_iter;
 	int               ar_flags;
 	unsigned char	  nApplyCount;
-	bool			  is_child;
-	Bucket			  *bucket;
 	zend_function     *fptr_offset_get;
 	zend_function     *fptr_offset_set;
 	zend_function     *fptr_offset_has;
@@ -166,8 +164,6 @@ static zend_object *spl_array_object_new_ex(zend_class_entry *class_type, zend_o
 	object_properties_init(&intern->std, class_type);

 	intern->ar_flags = 0;
-	intern->is_child = false;
-	intern->bucket = NULL;
 	intern->ce_get_iterator = spl_ce_ArrayIterator;
 	if (orig) {
 		spl_array_object *other = spl_array_from_obj(orig);
@@ -464,22 +460,6 @@ static zval *spl_array_read_dimension(zend_object *object, zval *offset, int typ
 	return spl_array_read_dimension_ex(1, object, offset, type, rv);
 } /* }}} */

-/*
- * The assertion(HT_ASSERT_RC1(ht)) failed because the refcount was increased manually when intern->is_child is true.
- * We have to set the refcount to 1 to make assertion success and restore the refcount to the original value after
- * modifying the array when intern->is_child is true.
- */
-static uint32_t spl_array_set_refcount(bool is_child, HashTable *ht, uint32_t refcount) /* {{{ */
-{
-	uint32_t old_refcount = 0;
-	if (is_child) {
-		old_refcount = GC_REFCOUNT(ht);
-		GC_SET_REFCOUNT(ht, refcount);
-	}
-
-	return old_refcount;
-} /* }}} */
-
 static void spl_array_write_dimension_ex(int check_inherited, zend_object *object, zval *offset, zval *value) /* {{{ */
 {
 	spl_array_object *intern = spl_array_from_obj(object);
@@ -503,19 +483,12 @@ static void spl_array_write_dimension_ex(int check_inherited, zend_object *objec
 	}

 	Z_TRY_ADDREF_P(value);
-
-	uint32_t refcount = 0;
 	if (!offset || Z_TYPE_P(offset) == IS_NULL) {
 		ht = spl_array_get_hash_table(intern);
 		if (UNEXPECTED(ht == intern->sentinel_array)) {
 			return;
 		}
-		refcount = spl_array_set_refcount(intern->is_child, ht, 1);
 		zend_hash_next_index_insert(ht, value);
-
-		if (refcount) {
-			spl_array_set_refcount(intern->is_child, ht, refcount);
-		}
 		return;
 	}

@@ -530,17 +503,13 @@ static void spl_array_write_dimension_ex(int check_inherited, zend_object *objec
 		spl_hash_key_release(&key);
 		return;
 	}
-	refcount = spl_array_set_refcount(intern->is_child, ht, 1);
+
 	if (key.key) {
 		zend_hash_update_ind(ht, key.key, value);
 		spl_hash_key_release(&key);
 	} else {
 		zend_hash_index_update(ht, key.h, value);
 	}
-
-	if (refcount) {
-		spl_array_set_refcount(intern->is_child, ht, refcount);
-	}
 } /* }}} */

 static void spl_array_write_dimension(zend_object *object, zval *offset, zval *value) /* {{{ */
@@ -570,8 +539,6 @@ static void spl_array_unset_dimension_ex(int check_inherited, zend_object *objec
 	}

 	ht = spl_array_get_hash_table(intern);
-	uint32_t refcount = spl_array_set_refcount(intern->is_child, ht, 1);
-
 	if (key.key) {
 		zval *data = zend_hash_find(ht, key.key);
 		if (data) {
@@ -596,10 +563,6 @@ static void spl_array_unset_dimension_ex(int check_inherited, zend_object *objec
 	} else {
 		zend_hash_index_del(ht, key.h);
 	}
-
-	if (refcount) {
-		spl_array_set_refcount(intern->is_child, ht, refcount);
-	}
 } /* }}} */

 static void spl_array_unset_dimension(zend_object *object, zval *offset) /* {{{ */
@@ -973,15 +936,6 @@ static void spl_array_set_array(zval *object, spl_array_object *intern, zval *ar
 		} else {
 			//??? TODO: try to avoid array duplication
 			ZVAL_ARR(&intern->array, zend_array_dup(Z_ARR_P(array)));
-
-			if (intern->is_child) {
-				Z_TRY_DELREF(intern->bucket->val);
-				/*
-				 * replace bucket->val with copied array, so the changes between
-				 * parent and child object can affect each other.
-				 */
-				ZVAL_COPY(&intern->bucket->val, &intern->array);
-			}
 		}
 	} else {
 		if (Z_OBJ_HT_P(array) == &spl_handler_ArrayObject) {
@@ -1854,15 +1808,6 @@ PHP_METHOD(RecursiveArrayIterator, hasChildren)
 static void spl_instantiate_child_arg(zend_class_entry *pce, zval *retval, zval *arg1, zval *arg2) /* {{{ */
 {
 	object_init_ex(retval, pce);
-	spl_array_object *new_intern = Z_SPLARRAY_P(retval);
-	/*
-	 * set new_intern->is_child is true to indicate that the object was created by
-	 * RecursiveArrayIterator::getChildren() method.
-	 */
-	new_intern->is_child = true;
-
-	/* find the bucket of parent object. */
-	new_intern->bucket = (Bucket *)((char *)(arg1) - XtOffsetOf(Bucket, val));;
 	zend_call_known_instance_method_with_2_params(pce->constructor, Z_OBJ_P(retval), NULL, arg1, arg2);
 }
 /* }}} */
diff --git a/ext/spl/tests/bug42654.phpt b/ext/spl/tests/bug42654.phpt
index eb72863e087..20aad74b734 100644
--- a/ext/spl/tests/bug42654.phpt
+++ b/ext/spl/tests/bug42654.phpt
@@ -110,11 +110,11 @@
     [2]=>
     array(2) {
       [2]=>
-      string(5) "alter"
+      string(4) "val2"
       [3]=>
       array(1) {
         [3]=>
-        string(5) "alter"
+        string(4) "val3"
       }
     }
     [4]=>
@@ -129,11 +129,11 @@
     [2]=>
     array(2) {
       [2]=>
-      string(5) "alter"
+      string(4) "val2"
       [3]=>
       array(1) {
         [3]=>
-        string(5) "alter"
+        string(4) "val3"
       }
     }
     [4]=>
@@ -146,11 +146,11 @@
   [2]=>
   array(2) {
     [2]=>
-    string(5) "alter"
+    string(4) "val2"
     [3]=>
     array(1) {
       [3]=>
-      string(5) "alter"
+      string(4) "val3"
     }
   }
   [4]=>
diff --git a/ext/spl/tests/bug42654_2.phpt b/ext/spl/tests/bug42654_2.phpt
deleted file mode 100644
index bd290247dbd..00000000000
--- a/ext/spl/tests/bug42654_2.phpt
+++ /dev/null
@@ -1,33 +0,0 @@
---TEST--
-Bug #42654 (RecursiveIteratorIterator modifies only part of leaves)
---FILE--
-<?php
-$data = array ('key1' => 'val1', array('key2' => 'val2'), 'key3' => 'val3');
-
-$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($data));
-foreach($iterator as $foo) {
-    $key = $iterator->key();
-    switch($key) {
-        case 'key1': // first level
-        case 'key2': // recursive level
-            echo "update $key".PHP_EOL;
-            $iterator->offsetSet($key, 'alter');
-    }
-}
-$copy = $iterator->getArrayCopy();
-var_dump($copy);
-?>
---EXPECT--
-update key1
-update key2
-array(3) {
-  ["key1"]=>
-  string(5) "alter"
-  [0]=>
-  array(1) {
-    ["key2"]=>
-    string(5) "alter"
-  }
-  ["key3"]=>
-  string(4) "val3"
-}
diff --git a/ext/spl/tests/gh10519.phpt b/ext/spl/tests/gh10519.phpt
index 1f7572d6e8c..87e7eae0cf0 100644
--- a/ext/spl/tests/gh10519.phpt
+++ b/ext/spl/tests/gh10519.phpt
@@ -1,5 +1,7 @@
 --TEST--
 Bug GH-10519 (Array Data Address Reference Issue)
+--XFAIL--
+The fix for this was bad
 --FILE--
 <?php
 interface DataInterface extends JsonSerializable, RecursiveIterator, Iterator
diff --git a/ext/spl/tests/gh21499.phpt b/ext/spl/tests/gh21499.phpt
new file mode 100644
index 00000000000..719da0094a6
--- /dev/null
+++ b/ext/spl/tests/gh21499.phpt
@@ -0,0 +1,17 @@
+--TEST--
+GH-21499 (RecursiveArrayIterator getChildren UAF after parent free)
+--FILE--
+<?php
+$it = new RecursiveArrayIterator([[1]]);
+$child = $it->getChildren();
+unset($it);
+$child->__construct([2, 3]);
+var_dump($child->getArrayCopy());
+?>
+--EXPECT--
+array(2) {
+  [0]=>
+  int(2)
+  [1]=>
+  int(3)
+}