diff --git a/NEWS b/NEWS index 6fde7abcbc48..1b2e6d361294 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,10 @@ PHP NEWS - Curl: . Fixed curl_reset() retaining CURLOPT_PRIVATE after reset. (lacatoire) +- MBString: + . Fixed stack overflow in mb_check_encoding() and mb_convert_encoding() with + deeply nested arrays. (Eyüp Can Akman) + - Opcache: . Fixed bug GH-25165 (Tracing JIT loops forever once opcache.jit_buffer_size is exhausted while compiling side traces). (Ilia Alshanetsky) diff --git a/ext/mbstring/mbstring.c b/ext/mbstring/mbstring.c index fdcd2ef6199c..3df3367de07c 100644 --- a/ext/mbstring/mbstring.c +++ b/ext/mbstring/mbstring.c @@ -2776,6 +2776,17 @@ MBSTRING_API zend_string* php_mb_convert_encoding(const char *input, size_t leng return php_mb_convert_encoding_ex(input, length, to_encoding, from_encoding); } +static zend_always_inline bool mb_check_stack_limit(void) +{ +#ifdef ZEND_CHECK_STACK_LIMIT + if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) { + zend_call_stack_size_error(); + return true; + } +#endif + return false; +} + MBSTRING_API HashTable *php_mb_convert_encoding_recursive(HashTable *input, const mbfl_encoding *to_encoding, const mbfl_encoding **from_encodings, size_t num_from_encodings) { HashTable *output, *chash; @@ -2787,6 +2798,10 @@ MBSTRING_API HashTable *php_mb_convert_encoding_recursive(HashTable *input, cons return NULL; } + if (mb_check_stack_limit()) { + return NULL; + } + if (GC_IS_RECURSIVE(input)) { GC_UNPROTECT_RECURSION(input); php_error_docref(NULL, E_WARNING, "Cannot convert recursively referenced values"); @@ -2830,6 +2845,13 @@ MBSTRING_API HashTable *php_mb_convert_encoding_recursive(HashTable *input, cons Z_ARRVAL_P(entry), to_encoding, from_encodings, num_from_encodings); if (chash) { ZVAL_ARR(&entry_tmp, chash); + } else if (UNEXPECTED(EG(exception))) { + if (key) { + zend_string_release(key); + } + GC_TRY_UNPROTECT_RECURSION(input); + zend_array_destroy(output); + return NULL; } else { ZVAL_EMPTY_ARRAY(&entry_tmp); } @@ -2935,7 +2957,9 @@ PHP_FUNCTION(mb_convert_encoding) HashTable *tmp; tmp = php_mb_convert_encoding_recursive( input_ht, to_encoding, from_encodings, num_from_encodings); - RETVAL_ARR(tmp); + if (tmp) { + RETVAL_ARR(tmp); + } } if (free_from_encodings) { @@ -3737,17 +3761,6 @@ PHP_FUNCTION(mb_convert_kana) RETVAL_STR(jp_kana_convert(str, enc, opt)); } -static zend_always_inline bool mb_check_stack_limit(void) -{ -#ifdef ZEND_CHECK_STACK_LIMIT - if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) { - zend_call_stack_size_error(); - return true; - } -#endif - return false; -} - static unsigned int mb_recursive_count_strings(zval *var) { unsigned int count = 0; @@ -5677,6 +5690,10 @@ static bool php_mb_check_encoding_recursive(HashTable *vars, const mbfl_encoding (void)(idx); /* Suppress spurious compiler warning that `idx` is not used */ + if (mb_check_stack_limit()) { + return false; + } + if (GC_IS_RECURSIVE(vars)) { php_error_docref(NULL, E_WARNING, "Cannot not handle circular references"); return false; @@ -5699,6 +5716,10 @@ static bool php_mb_check_encoding_recursive(HashTable *vars, const mbfl_encoding break; case IS_ARRAY: if (!php_mb_check_encoding_recursive(Z_ARRVAL_P(entry), encoding)) { + if (UNEXPECTED(EG(exception))) { + GC_TRY_UNPROTECT_RECURSION(vars); + return false; + } valid = false; break; } diff --git a/ext/mbstring/tests/mb_encoding_recursive_stack_limit.phpt b/ext/mbstring/tests/mb_encoding_recursive_stack_limit.phpt new file mode 100644 index 000000000000..146dcffec74e --- /dev/null +++ b/ext/mbstring/tests/mb_encoding_recursive_stack_limit.phpt @@ -0,0 +1,52 @@ +--TEST-- +Stack overflow in mb_check_encoding() and mb_convert_encoding() with deeply nested arrays +--EXTENSIONS-- +mbstring +--SKIPIF-- + +--INI-- +zend.max_allowed_stack_size=256K +--FILE-- +getMessage(), "\n"; + var_dump($e->getPrevious()); + } + try { + mb_convert_encoding($input, 'UTF-8', 'ISO-8859-1'); + } catch (Error $e) { + echo $e->getMessage(), "\n"; + var_dump($e->getPrevious()); + } +} + +unset($input, $e); +while (is_array($deep)) { + $deep = $deep[0]; +} +?> +--EXPECTF-- +Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion? +NULL +Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion? +NULL +Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion? +NULL +Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion? +NULL