Skip to content

Commit 71e8293

Browse files
committed
Merge branch 'PHP-8.4' into PHP-8.5
* PHP-8.4: Fix GH-24605: Guard var_export() against native stack overflow (#24671)
2 parents 2546fe9 + afe5635 commit 71e8293

3 files changed

Lines changed: 72 additions & 1 deletion

File tree

‎NEWS‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ PHP NEWS
167167
Windows). (Nicolas Grekas)
168168
. Fixed glob() not negating bracket expressions that start with "^".
169169
(Nicolas Grekas)
170+
. Fixed bug GH-24605 (var_export() crashes on deeply nested arrays and
171+
objects). (Labish Bardiya)
170172

171173
- Streams:
172174
. Fixed bug GH-24508 (HTTP wrapper leaks the dechunk filter when header
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
--TEST--
2+
GH-24605: var_export() checks native stack limit for arrays and objects
3+
--SKIPIF--
4+
<?php
5+
if (ini_get('zend.max_allowed_stack_size') === false) {
6+
die('skip No stack limit support');
7+
}
8+
if (getenv('SKIP_ASAN')) {
9+
die('skip ASAN needs different stack limit setting');
10+
}
11+
?>
12+
--INI--
13+
zend.max_allowed_stack_size=256K
14+
--FILE--
15+
<?php
16+
function test_export(string $name, mixed $value): void {
17+
try {
18+
var_export($value, true);
19+
echo "$name: NOT GUARDED\n";
20+
} catch (Error $e) {
21+
echo $name, ': ', str_contains($e->getMessage(), 'Maximum call stack size')
22+
? "guarded\n"
23+
: "unexpected error\n";
24+
}
25+
}
26+
27+
$data = 0;
28+
for ($i = 0; $i < 5000; $i++) {
29+
$data = [$data];
30+
}
31+
test_export('array', $data);
32+
33+
// Detach the nested arrays without recursively destroying them.
34+
while (is_array($data)) {
35+
$data = $data[0];
36+
}
37+
38+
class StackNode { public ?StackNode $next = null; }
39+
$first = new StackNode();
40+
$node = $first;
41+
for ($i = 0; $i < 5000; $i++) {
42+
$node->next = new StackNode();
43+
$node = $node->next;
44+
}
45+
test_export('object', $first);
46+
47+
// Break the chain iteratively to avoid teardown recursion.
48+
$node = $first;
49+
while ($node !== null) {
50+
$next = $node->next;
51+
$node->next = null;
52+
$node = $next;
53+
}
54+
?>
55+
--EXPECT--
56+
array: guarded
57+
object: guarded

‎ext/standard/var.c‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,13 @@ PHPAPI zend_result php_var_export_ex(zval *struc, int level, smart_str *buf) /*
542542
zend_string *key;
543543
zval *val;
544544

545+
#ifdef ZEND_CHECK_STACK_LIMIT
546+
if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) {
547+
zend_call_stack_size_error();
548+
return FAILURE;
549+
}
550+
#endif
551+
545552
again:
546553
switch (Z_TYPE_P(struc)) {
547554
case IS_FALSE:
@@ -672,10 +679,15 @@ PHPAPI zend_result php_var_export_ex(zval *struc, int level, smart_str *buf) /*
672679
return FAILURE;
673680
}
674681
}
675-
php_object_element_export(val, index, key, level, buf);
682+
zend_result result = php_object_element_export(val, index, key, level, buf);
676683
if (val == &tmp) {
677684
zval_ptr_dtor(val);
678685
}
686+
if (UNEXPECTED(result == FAILURE)) {
687+
ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, EXPORT, zobj);
688+
zend_release_properties(myht);
689+
return FAILURE;
690+
}
679691
} ZEND_HASH_FOREACH_END();
680692
}
681693
zend_release_properties(myht);

0 commit comments

Comments
 (0)