Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions ext/standard/http.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ static void php_url_encode_scalar(zval *scalar, smart_str *form_str,
}
}

static zend_always_inline bool php_url_check_stack_limit(void)
{
#ifdef ZEND_CHECK_STACK_LIMIT
return zend_call_stack_overflowed(EG(stack_limit));
#else
return false;
#endif
}

/* {{{ php_url_encode_hash */
PHPAPI void php_url_encode_hash_ex(HashTable *ht, smart_str *formstr,
const char *num_prefix, size_t num_prefix_len,
Expand All @@ -110,6 +119,12 @@ PHPAPI void php_url_encode_hash_ex(HashTable *ht, smart_str *formstr,
return;
}

/* Very deeply structured data could trigger a stack overflow, even without recursion. */
if (UNEXPECTED(php_url_check_stack_limit())) {
zend_throw_error(NULL, "Maximum call stack size reached.");
return;
}

if (!arg_sep) {
arg_sep = zend_ini_str("arg_separator.output", strlen("arg_separator.output"), false);
if (ZSTR_LEN(arg_sep) == 0) {
Expand Down
27 changes: 27 additions & 0 deletions ext/standard/tests/http/http_build_query/gh20583.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--TEST--
GH-20583 (Stack overflow in http_build_query via deep structures)
--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=512K
--FILE--
<?php
$a = null;
for ($i = 0; $i < 5000; $i++) {
$a = [$i => $a];
}
try {
http_build_query($a, 'p');
} catch (Throwable $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
?>
--EXPECT--
Error: Maximum call stack size reached.
Loading