[php-src] PHP-8.5: Merge branch 'PHP-8.4' into PHP-8.5
Arnaud Le Blanc <[email protected]>
| Newsgroups | gmane.comp.php.cvs.general |
|---|---|
| Message-ID | <[email protected]> |
Author: Arnaud Le Blanc (arnaud-lb)
Date: 2026-08-11T19:01:37+02:00
Commit: https://github.com/php/php-src/commit/b5bca194e7df59eb93ae877c20b84ad92300dac4
Raw diff: https://github.com/php/php-src/commit/b5bca194e7df59eb93ae877c20b84ad92300dac4.diff
Merge branch 'PHP-8.4' into PHP-8.5
* PHP-8.4:
Add a stack limit check in php_count_recursive() (#23197)
Changed paths:
A ext/standard/tests/array/count_recursive_stack_limit.phpt
M ext/spl/spl_observer.c
M ext/standard/array.c
M ext/standard/php_array.h
Diff:
diff --git a/ext/spl/spl_observer.c b/ext/spl/spl_observer.c
index 95f872770766..d2b6235aa110 100644
--- a/ext/spl/spl_observer.c
+++ b/ext/spl/spl_observer.c
@@ -679,7 +679,11 @@ PHP_METHOD(SplObjectStorage, count)
}
if (mode == PHP_COUNT_RECURSIVE) {
- RETURN_LONG(php_count_recursive(&intern->storage));
+ zend_long count = php_count_recursive(&intern->storage);
+ if (UNEXPECTED(count < 0)) {
+ RETURN_THROWS();
+ }
+ RETURN_LONG(count);
}
RETURN_LONG(zend_hash_num_elements(&intern->storage));
diff --git a/ext/standard/array.c b/ext/standard/array.c
index 67ab7b2a1286..fcc6b3879666 100644
--- a/ext/standard/array.c
+++ b/ext/standard/array.c
@@ -611,10 +611,18 @@ PHPAPI zend_long php_count_recursive(HashTable *ht) /* {{{ */
zend_long cnt = 0;
zval *element;
+#ifdef ZEND_CHECK_STACK_LIMIT
+ if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) {
+ zend_call_stack_size_error();
+ return -1;
+ }
+#endif
+
if (!(GC_FLAGS(ht) & GC_IMMUTABLE)) {
if (GC_IS_RECURSIVE(ht)) {
php_error_docref(NULL, E_WARNING, "Recursion detected");
- return 0;
+ /* A user error handler may have thrown. */
+ return EG(exception) ? -1 : 0;
}
GC_PROTECT_RECURSION(ht);
}
@@ -623,7 +631,12 @@ PHPAPI zend_long php_count_recursive(HashTable *ht) /* {{{ */
ZEND_HASH_FOREACH_VAL(ht, element) {
ZVAL_DEREF(element);
if (Z_TYPE_P(element) == IS_ARRAY) {
- cnt += php_count_recursive(Z_ARRVAL_P(element));
+ zend_long sub_cnt = php_count_recursive(Z_ARRVAL_P(element));
+ if (UNEXPECTED(sub_cnt < 0)) {
+ cnt = -1;
+ break;
+ }
+ cnt += sub_cnt;
}
} ZEND_HASH_FOREACH_END();
@@ -668,6 +681,9 @@ PHP_FUNCTION(count)
cnt = zend_hash_num_elements(Z_ARRVAL_P(array));
} else {
cnt = php_count_recursive(Z_ARRVAL_P(array));
+ if (UNEXPECTED(cnt < 0)) {
+ RETURN_THROWS();
+ }
}
RETURN_LONG(cnt);
break;
diff --git a/ext/standard/php_array.h b/ext/standard/php_array.h
index 2a35af603808..24e320a5313c 100644
--- a/ext/standard/php_array.h
+++ b/ext/standard/php_array.h
@@ -29,6 +29,7 @@ PHPAPI int php_array_merge(HashTable *dest, HashTable *src);
PHPAPI int php_array_merge_recursive(HashTable *dest, HashTable *src);
PHPAPI int php_array_replace_recursive(HashTable *dest, HashTable *src);
PHPAPI int php_multisort_compare(const void *a, const void *b);
+/* Returns -1 and throws if the array is nested too deeply. */
PHPAPI zend_long php_count_recursive(HashTable *ht);
PHPAPI bool php_array_data_shuffle(php_random_algo_with_state engine, zval *array);
diff --git a/ext/standard/tests/array/count_recursive_stack_limit.phpt b/ext/standard/tests/array/count_recursive_stack_limit.phpt
new file mode 100644
index 000000000000..a7f3918ba350
--- /dev/null
+++ b/ext/standard/tests/array/count_recursive_stack_limit.phpt
@@ -0,0 +1,32 @@
+--TEST--
+Stack overflow in count() with COUNT_RECURSIVE and deeply nested arrays
+--SKIPIF--
+<?php
+if (ini_get('zend.max_allowed_stack_size') === false) {
+ die('skip No stack limit support');
+}
+if (getenv('SKIP_ASAN')) {
+ die('skip ASAN needs different stack limit setting due to more stack space usage');
+}
+?>
+--INI--
+zend.max_allowed_stack_size=256K
+--FILE--
+<?php
+/* Two elements per nesting level: the sibling must not be visited once the
+ * stack limit error has been thrown, so only one Error is thrown. */
+$a = [];
+for ($i = 0; $i < 30000; $i++) {
+ $a = [$a, []];
+}
+
+try {
+ count($a, COUNT_RECURSIVE);
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+ var_dump($e->getPrevious());
+}
+?>
+--EXPECTF--
+Error: Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion?
+NULL