Skip to content

Commit 8b39b01

Browse files
committed
Merge branch 'PHP-8.5'
* PHP-8.5: Fix use-after-free in RecursiveIteratorIterator on reentrant teardown
2 parents 5ff2a95 + 79e66f1 commit 8b39b01

3 files changed

Lines changed: 105 additions & 2 deletions

File tree

‎ext/spl/spl_iterators.c‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,10 @@ static void spl_recursive_it_move_forward_ex(spl_recursive_it_object *object, zv
250250
zend_class_entry *ce;
251251
zval retval, child;
252252
zend_object_iterator *sub_iter;
253+
zend_object *sub_object;
254+
uint32_t prev_level;
255+
zend_result valid_result;
256+
bool reentered;
253257

254258
SPL_FETCH_SUB_ITERATOR(iterator, object);
255259

@@ -269,7 +273,17 @@ static void spl_recursive_it_move_forward_ex(spl_recursive_it_object *object, zv
269273
iterator = object->iterators[object->level].iterator;
270274
ZEND_FALLTHROUGH;
271275
case RS_START:
272-
if (iterator->funcs->valid(iterator) == FAILURE) {
276+
sub_object = Z_OBJ(object->iterators[object->level].zobject);
277+
prev_level = object->level;
278+
GC_ADDREF(sub_object);
279+
valid_result = iterator->funcs->valid(iterator);
280+
reentered = object->level != prev_level
281+
|| object->iterators[object->level].iterator != iterator;
282+
OBJ_RELEASE(sub_object);
283+
if (reentered) {
284+
return;
285+
}
286+
if (valid_result == FAILURE) {
273287
break;
274288
}
275289
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
417431
}
418432
}
419433
}
420-
if (object->level > 0) {
434+
if (object->level > 0 && object->iterators[object->level].iterator == iterator) {
421435
zval garbage;
422436
ZVAL_COPY_VALUE(&garbage, &object->iterators[object->level].zobject);
423437
ZVAL_UNDEF(&object->iterators[object->level].zobject);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
--TEST--
2+
RecursiveIteratorIterator: next() re-entered from endChildren() must not use-after-free
3+
--FILE--
4+
<?php
5+
class RIt implements RecursiveIterator {
6+
public $data;
7+
public $pos = 0;
8+
function __construct(array $d) { $this->data = $d; }
9+
function current(): mixed { return $this->data[$this->pos] ?? null; }
10+
function key(): mixed { return $this->pos; }
11+
function next(): void { $this->pos++; }
12+
function rewind(): void { $this->pos = 0; }
13+
function valid(): bool { return $this->pos < count($this->data); }
14+
function hasChildren(): bool { return is_array($this->current()); }
15+
function getChildren(): RecursiveIterator { return new RIt($this->current()); }
16+
}
17+
18+
class ReenterRII extends RecursiveIteratorIterator {
19+
public static int $fired = 0;
20+
function endChildren(): void {
21+
if (self::$fired++ === 0) {
22+
$this->next();
23+
}
24+
}
25+
}
26+
27+
$tree = [[[1, 2], 3], 4];
28+
$it = new ReenterRII(new RIt($tree), RecursiveIteratorIterator::SELF_FIRST);
29+
$seen = [];
30+
foreach ($it as $v) {
31+
$seen[] = is_array($v) ? 'array' : $v;
32+
if (count($seen) > 20) {
33+
$seen[] = '...';
34+
break;
35+
}
36+
}
37+
echo implode(' ', $seen), "\n";
38+
echo "done\n";
39+
?>
40+
--EXPECT--
41+
array array 1 2 4
42+
done
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
--TEST--
2+
RecursiveIteratorIterator: next() re-entered from a sub-iterator valid() must not use-after-free
3+
--FILE--
4+
<?php
5+
class RIt implements RecursiveIterator {
6+
public $data;
7+
public $pos = 0;
8+
public $rii;
9+
public $depth;
10+
public static bool $fired = false;
11+
function __construct(array $d, $depth = 0) { $this->data = $d; $this->depth = $depth; }
12+
function current(): mixed { return $this->data[$this->pos] ?? null; }
13+
function key(): mixed { return $this->pos; }
14+
function next(): void { $this->pos++; }
15+
function rewind(): void { $this->pos = 0; }
16+
function valid(): bool {
17+
if ($this->rii && $this->depth === 2 && $this->pos === count($this->data) && !self::$fired) {
18+
self::$fired = true;
19+
$this->rii->next();
20+
}
21+
return $this->pos < count($this->data);
22+
}
23+
function hasChildren(): bool { return is_array($this->current()); }
24+
function getChildren(): RecursiveIterator {
25+
$c = new RIt($this->current(), $this->depth + 1);
26+
$c->rii = $this->rii;
27+
return $c;
28+
}
29+
}
30+
31+
$root = new RIt([[[1, 2], 3], 4]);
32+
$rii = new RecursiveIteratorIterator($root, RecursiveIteratorIterator::SELF_FIRST);
33+
$root->rii = $rii;
34+
$seen = [];
35+
foreach ($rii as $v) {
36+
$seen[] = is_array($v) ? 'array' : $v;
37+
if (count($seen) > 20) {
38+
$seen[] = '...';
39+
break;
40+
}
41+
}
42+
echo implode(' ', $seen), "\n";
43+
echo "done\n";
44+
?>
45+
--EXPECT--
46+
array array 1 2 3 4
47+
done

0 commit comments

Comments
 (0)