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-- +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-- +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 diff --git a/ext/standard/string.c b/ext/standard/string.c index 006adc4467d3..006ec3509a67 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -23,10 +23,6 @@ # include #endif -#ifdef HAVE_LIBINTL -# include /* For LC_MESSAGES */ -#endif - #include "scanf.h" #include "zend_API.h" #include "zend_execute.h" @@ -5763,7 +5759,7 @@ PHP_FUNCTION(substr_count) static void php_str_pad_fill(zend_string *result, size_t pad_chars, const char *pad_str, size_t pad_str_len) { char *p = ZSTR_VAL(result) + ZSTR_LEN(result); - + if (pad_str_len == 1) { memset(p, pad_str[0], pad_chars); ZSTR_LEN(result) += pad_chars; @@ -5778,7 +5774,7 @@ static void php_str_pad_fill(zend_string *result, size_t pad_chars, const char * if (p < end) { memcpy(p, pad_str, end - p); } - + ZSTR_LEN(result) += pad_chars; }