Commit 1cbd3b9ba90 for php.net

commit 1cbd3b9ba90c7296c10bae76a7cd7287ddc9ada3
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Fri Jun 26 08:15:04 2026 -0400

    Fix use-after-free in RecursiveIteratorIterator on reentry

    move_forward_ex() caches the active sub-iterator, then calls the inner
    iterator's move_forward(), which can re-enter userland. A next() that
    rewinds or advances the RecursiveIteratorIterator frees that sub-iterator,
    and the following validity check then reads freed memory. Re-fetch the
    sub-iterator after the call, the same way the no-more-elements branch
    already re-checks the level after endChildren().

    Closes GH-22466

diff --git a/ext/spl/spl_iterators.c b/ext/spl/spl_iterators.c
index db100d22834..eb068a50c91 100644
--- a/ext/spl/spl_iterators.c
+++ b/ext/spl/spl_iterators.c
@@ -273,6 +273,7 @@ static void spl_recursive_it_move_forward_ex(spl_recursive_it_object *object, zv
 						zend_clear_exception();
 					}
 				}
+				iterator = object->iterators[object->level].iterator;
 				ZEND_FALLTHROUGH;
 			case RS_START:
 				if (iterator->funcs->valid(iterator) == FAILURE) {
diff --git a/ext/spl/tests/recursiveiteratoriterator_rewind_during_next.phpt b/ext/spl/tests/recursiveiteratoriterator_rewind_during_next.phpt
new file mode 100644
index 00000000000..3c9863e81a3
--- /dev/null
+++ b/ext/spl/tests/recursiveiteratoriterator_rewind_during_next.phpt
@@ -0,0 +1,41 @@
+--TEST--
+RecursiveIteratorIterator: rewind() re-entered from an inner next() must not use-after-free
+--FILE--
+<?php
+class Reenter 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++;
+        if ($this->rii && $this->depth === 1 && $this->pos === 1 && !self::$fired) {
+            self::$fired = true;
+            $this->rii->rewind();
+        }
+    }
+    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 {
+        $c = new Reenter($this->current(), $this->depth + 1);
+        $c->rii = $this->rii;
+        return $c;
+    }
+}
+$root = new Reenter([[10, 11], [20, 21]]);
+$rii = new RecursiveIteratorIterator($root, RecursiveIteratorIterator::SELF_FIRST);
+$root->rii = $rii;
+$seen = [];
+foreach ($rii as $v) {
+    if (is_array($v)) { $v = '[' . implode(',', $v) . ']'; }
+    $seen[] = $v;
+    if (count($seen) > 20) { $seen[] = '...'; break; }
+}
+echo implode(' ', $seen), "\n";
+echo "done\n";
+?>
+--EXPECT--
+[10,11] 10 [10,11] 10 11 [20,21] 20 21
+done