diff --git a/NEWS b/NEWS index 6447fa7bc881..68eb850d62c5 100644 --- a/NEWS +++ b/NEWS @@ -78,6 +78,8 @@ PHP NEWS nested arrays). (Lazizbek Ergashev) . Fixed bug GH-23115 (Stack overflow in compact() with deeply nested arrays). (Lazizbek Ergashev) + . Fixed stack overflow in count() with COUNT_RECURSIVE and deeply nested + arrays. (Lazizbek Ergashev) - Streams: . Fixed bug GH-15836 (Use-after-free when a user stream filter accesses diff --git a/ext/spl/spl_observer.c b/ext/spl/spl_observer.c index 56cdbdd4b5f3..9a9710e069f2 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 bb23c99c5709..85a017eff7f9 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(); @@ -656,6 +669,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-- + +--INI-- +zend.max_allowed_stack_size=256K +--FILE-- +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