Fix GH-15169: stack overflow when var serialization in ext/standard/var

Adding a stack check here as I consider serialization to be a more
sensitive place where erroring out with an exception seems appropriate.

Closes GH-16159.
This commit is contained in:
Niels Dossche 2024-10-01 21:17:38 +02:00
parent 220c8828cc
commit bd724bdf42
No known key found for this signature in database
GPG key ID: B8A8AD166DF0E2E5
5 changed files with 51 additions and 28 deletions

2
NEWS
View file

@ -56,6 +56,8 @@ PHP NEWS
- Standard:
. Fixed bug GH-16053 (Assertion failure in Zend/zend_hash.c). (Arnaud)
. Fixed bug GH-15169 (stack overflow when var serialization in
ext/standard/var). (nielsdos)
- Streams:
. Fixed bugs GH-15908 and GH-15026 (leak / assertion failure in streams.c).

View file

@ -27,13 +27,6 @@ class Test2 {
}
}
class Test3 {
public function __sleep()
{
serialize($this);
}
}
function replace() {
return preg_replace_callback('#.#', function () {
return replace();
@ -52,12 +45,6 @@ try {
echo $e->getMessage(), "\n";
}
try {
serialize(new Test3);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
replace();
} catch (Error $e) {
@ -79,4 +66,3 @@ array(4) {
Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion?
Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion?
Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion?
Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion?

View file

@ -26,13 +26,6 @@ class Test2 {
}
}
class Test3 {
public function __sleep()
{
serialize($this);
}
}
function replace() {
return preg_replace_callback('#.#', function () {
return replace();
@ -52,12 +45,6 @@ $fiber = new Fiber(function (): void {
echo $e->getMessage(), "\n";
}
try {
serialize(new Test3);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
replace();
} catch (Error $e) {
@ -82,4 +69,3 @@ array(4) {
Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion?
Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion?
Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion?
Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion?

View file

@ -0,0 +1,35 @@
--TEST--
GH-15169 (stack overflow when var serialization in ext/standard/var)
--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
class Node
{
public $next;
}
$firstNode = new Node();
$node = $firstNode;
for ($i = 0; $i < 30000; $i++) {
$newNode = new Node();
$node->next = $newNode;
$node = $newNode;
}
try {
serialize($firstNode);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
Maximum call stack size reached. Infinite recursion?

View file

@ -986,6 +986,15 @@ static void php_var_serialize_class(smart_str *buf, zval *struc, HashTable *ht,
}
/* }}} */
static zend_always_inline bool php_serialize_check_stack_limit(void)
{
#ifdef ZEND_CHECK_STACK_LIMIT
return zend_call_stack_overflowed(EG(stack_limit));
#else
return false;
#endif
}
static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_data_t var_hash, bool in_rcn_array, bool is_root) /* {{{ */
{
zend_long var_already;
@ -995,6 +1004,11 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_
return;
}
if (UNEXPECTED(php_serialize_check_stack_limit())) {
zend_throw_error(NULL, "Maximum call stack size reached. Infinite recursion?");
return;
}
if (var_hash && (var_already = php_add_var_hash(var_hash, struc, in_rcn_array))) {
if (var_already == -1) {
/* Reference to an object that failed to serialize, replace with null. */