diff --git a/NEWS b/NEWS index d7dadb551bcb..e3995c5664bd 100644 --- a/NEWS +++ b/NEWS @@ -74,6 +74,8 @@ PHP NEWS nested arrays). (Lazizbek Ergashev) . Fixed bug GH-23113 (Stack overflow in array_replace_recursive() with deeply nested arrays). (Lazizbek Ergashev) + . Fixed bug GH-23115 (Stack overflow in compact() with deeply nested + arrays). (Lazizbek Ergashev) - Streams: . Fixed bug GH-15836 (Use-after-free when a user stream filter accesses diff --git a/ext/standard/array.c b/ext/standard/array.c index 41123d43bcc6..bb23c99c5709 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -2699,7 +2699,7 @@ PHP_FUNCTION(extract) } /* }}} */ -static void php_compact_var(HashTable *eg_active_symbol_table, zval *return_value, zval *entry, uint32_t pos) /* {{{ */ +static zend_result php_compact_var(HashTable *eg_active_symbol_table, zval *return_value, zval *entry, uint32_t pos) /* {{{ */ { zval *value_ptr, data; @@ -2717,25 +2717,43 @@ static void php_compact_var(HashTable *eg_active_symbol_table, zval *return_valu } } else { php_error_docref_unchecked(NULL, E_WARNING, "Undefined variable $%S", Z_STR_P(entry)); + /* A user error handler may have thrown. */ + return EG(exception) ? FAILURE : SUCCESS; } } else if (Z_TYPE_P(entry) == IS_ARRAY) { + zend_result result = SUCCESS; + +#ifdef ZEND_CHECK_STACK_LIMIT + if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) { + zend_call_stack_size_error(); + return FAILURE; + } +#endif if (Z_REFCOUNTED_P(entry)) { if (Z_IS_RECURSIVE_P(entry)) { zend_throw_error(NULL, "Recursion detected"); - return; + return FAILURE; } Z_PROTECT_RECURSION_P(entry); } ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(entry), value_ptr) { - php_compact_var(eg_active_symbol_table, return_value, value_ptr, pos); + if (UNEXPECTED(php_compact_var(eg_active_symbol_table, return_value, value_ptr, pos) == FAILURE)) { + result = FAILURE; + break; + } } ZEND_HASH_FOREACH_END(); if (Z_REFCOUNTED_P(entry)) { Z_UNPROTECT_RECURSION_P(entry); } + + return result; } else { php_error_docref(NULL, E_WARNING, "Argument #%d must be string or array of strings, %s given", pos, zend_zval_value_name(entry)); - return; + /* A user error handler may have thrown. */ + return EG(exception) ? FAILURE : SUCCESS; } + + return SUCCESS; } /* }}} */ @@ -2767,7 +2785,9 @@ PHP_FUNCTION(compact) } for (i = 0; i < num_args; i++) { - php_compact_var(symbol_table, return_value, &args[i], i + 1); + if (UNEXPECTED(php_compact_var(symbol_table, return_value, &args[i], i + 1) == FAILURE)) { + RETURN_THROWS(); + } } } /* }}} */ diff --git a/ext/standard/tests/array/gh23115.phpt b/ext/standard/tests/array/gh23115.phpt new file mode 100644 index 000000000000..7854583f9ec6 --- /dev/null +++ b/ext/standard/tests/array/gh23115.phpt @@ -0,0 +1,41 @@ +--TEST-- +GH-23115 (Stack overflow in compact with deeply nested arrays) +--SKIPIF-- + +--INI-- +zend.max_allowed_stack_size=256K +--FILE-- +getMessage(), "\n"; + var_dump($e->getPrevious()); +} + +try { + compact($names, $names); +} 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 +Error: Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion? +NULL