[php-src] master: Merge branch 'PHP-8.5'

Ilia Alshanetsky <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Date: 2026-06-27T15:18:42-04:00

Commit: https://github.com/php/php-src/commit/8b39b012a22a68810833c573f80ab91ea3cc30d8
Raw diff: https://github.com/php/php-src/commit/8b39b012a22a68810833c573f80ab91ea3cc30d8.diff

Merge branch 'PHP-8.5'

* PHP-8.5:
  Fix use-after-free in RecursiveIteratorIterator on reentrant teardown

Changed paths:
  A  ext/spl/tests/recursiveiteratoriterator_next_during_endchildren.phpt
  A  ext/spl/tests/recursiveiteratoriterator_next_during_valid.phpt
  M  ext/spl/spl_iterators.c


Diff:

diff --git a/ext/spl/spl_iterators.c b/ext/spl/spl_iterators.c
index 6cbabff26a36..95fe2d7c0b56 100644
--- a/ext/spl/spl_iterators.c
+++ b/ext/spl/spl_iterators.c
@@ -250,6 +250,10 @@ static void spl_recursive_it_move_forward_ex(spl_recursive_it_object *object, zv
 	zend_class_entry          *ce;
 	zval                      retval, child;
 	zend_object_iterator      *sub_iter;
+	zend_object               *sub_object;
+	uint32_t                  prev_level;
+	zend_result               valid_result;
+	bool                      reentered;
 
 	SPL_FETCH_SUB_ITERATOR(iterator, object);
 
@@ -269,7 +273,17 @@ static void spl_recursive_it_move_forward_ex(spl_recursive_it_object *object, zv
 				iterator = object->iterators[object->level].iterator;
 				ZEND_FALLTHROUGH;
 			case RS_START:
-				if (iterator->funcs->valid(iterator) == FAILURE) {
+				sub_object = Z_OBJ(object->iterators[object->level].zobject);
+				prev_level = object->level;
+				GC_ADDREF(sub_object);
+				valid_result = iterator->funcs->valid(iterator);
+				reentered = object->level != prev_level
+					|| object->iterators[object->level].iterator != iterator;
+				OBJ_RELEASE(sub_object);
+				if (reentered) {
+					return;
+				}
+				if (valid_result == FAILURE) {
 					break;
 				}
 				object->iterators[object->level].state = RS_TEST;
@@ -417,7 +431,7 @@ static void spl_recursive_it_move_forward_ex(spl_recursive_it_object *object, zv
 					}
 				}
 			}
-			if (object->level > 0) {
+			if (object->level > 0 && object->iterators[object->level].iterator == iterator) {
 				zval garbage;
 				ZVAL_COPY_VALUE(&garbage, &object->iterators[object->level].zobject);
 				ZVAL_UNDEF(&object->iterators[object->level].zobject);
diff --git a/ext/spl/tests/recursiveiteratoriterator_next_during_endchildren.phpt b/ext/spl/tests/recursiveiteratoriterator_next_during_endchildren.phpt
new file mode 100644
index 000000000000..b3c80b2232a8
--- /dev/null
+++ b/ext/spl/tests/recursiveiteratoriterator_next_during_endchildren.phpt
@@ -0,0 +1,42 @@
+--TEST--
+RecursiveIteratorIterator: next() re-entered from endChildren() must not use-after-free
+--FILE--
+<?php
+class RIt implements RecursiveIterator {
+    public $data;
+    public $pos = 0;
+    function __construct(array $d) { $this->data = $d; }
+    function current(): mixed { return $this->data[$this->pos] ?? null; }
+    function key(): mixed { return $this->pos; }
+    function next(): void { $this->pos++; }
+    function rewind(): void { $this->pos = 0; }
+    function valid(): bool { return $this->pos < count($this->data); }
+    function hasChildren(): bool { return is_array($this->current()); }
+    function getChildren(): RecursiveIterator { return new RIt($this->current()); }
+}
+
+class ReenterRII extends RecursiveIteratorIterator {
+    public static int $fired = 0;
+    function endChildren(): void {
+        if (self::$fired++ === 0) {
+            $this->next();
+        }
+    }
+}
+
+$tree = [[[1, 2], 3], 4];
+$it = new ReenterRII(new RIt($tree), RecursiveIteratorIterator::SELF_FIRST);
+$seen = [];
+foreach ($it as $v) {
+    $seen[] = is_array($v) ? 'array' : $v;
+    if (count($seen) > 20) {
+        $seen[] = '...';
+        break;
+    }
+}
+echo implode(' ', $seen), "\n";
+echo "done\n";
+?>
+--EXPECT--
+array array 1 2 4
+done
diff --git a/ext/spl/tests/recursiveiteratoriterator_next_during_valid.phpt b/ext/spl/tests/recursiveiteratoriterator_next_during_valid.phpt
new file mode 100644
index 000000000000..a2dcf4695e36
--- /dev/null
+++ b/ext/spl/tests/recursiveiteratoriterator_next_during_valid.phpt
@@ -0,0 +1,47 @@
+--TEST--
+RecursiveIteratorIterator: next() re-entered from a sub-iterator valid() must not use-after-free
+--FILE--
+<?php
+class RIt implements RecursiveIterator {
+    public $data;
+    public $pos = 0;
+    public $rii;
+    public $depth;
+    public static bool $fired = false;
+    function __construct(array $d, $depth = 0) { $this->data = $d; $this->depth = $depth; }
+    function current(): mixed { return $this->data[$this->pos] ?? null; }
+    function key(): mixed { return $this->pos; }
+    function next(): void { $this->pos++; }
+    function rewind(): void { $this->pos = 0; }
+    function valid(): bool {
+        if ($this->rii && $this->depth === 2 && $this->pos === count($this->data) && !self::$fired) {
+            self::$fired = true;
+            $this->rii->next();
+        }
+        return $this->pos < count($this->data);
+    }
+    function hasChildren(): bool { return is_array($this->current()); }
+    function getChildren(): RecursiveIterator {
+        $c = new RIt($this->current(), $this->depth + 1);
+        $c->rii = $this->rii;
+        return $c;
+    }
+}
+
+$root = new RIt([[[1, 2], 3], 4]);
+$rii = new RecursiveIteratorIterator($root, RecursiveIteratorIterator::SELF_FIRST);
+$root->rii = $rii;
+$seen = [];
+foreach ($rii as $v) {
+    $seen[] = is_array($v) ? 'array' : $v;
+    if (count($seen) > 20) {
+        $seen[] = '...';
+        break;
+    }
+}
+echo implode(' ', $seen), "\n";
+echo "done\n";
+?>
+--EXPECT--
+array array 1 2 3 4
+done
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.