[php-src] master: Merge branch '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:51+02:00
Commit: https://github.com/php/php-src/commit/b6bd738e451b4a52bba13a97bba9bbe0106ee30f
Raw diff: https://github.com/php/php-src/commit/b6bd738e451b4a52bba13a97bba9bbe0106ee30f.diff
Merge branch 'PHP-8.5'
* PHP-8.5:
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 adfc172af982..a980828cdcb2 100644
--- a/ext/spl/spl_observer.c
+++ b/ext/spl/spl_observer.c
@@ -747,7 +747,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 91d3932516eb..648b48479099 100644
--- a/ext/standard/array.c
+++ b/ext/standard/array.c
@@ -560,10 +560,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);
}
@@ -572,7 +580,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();
@@ -617,6 +630,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);
case IS_OBJECT: {
diff --git a/ext/standard/php_array.h b/ext/standard/php_array.h
index ab6c8494dd4e..a7e8afdec34b 100644
--- a/ext/standard/php_array.h
+++ b/ext/standard/php_array.h
@@ -27,6 +27,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