Skip to content

Commit a551b99

Browse files
committed
Fix phpGH-15168: stack overflow in json_encode()
The JSON encoder is recursive, and it's far from easy to make it iterative. Add a cheap stack limit check to prevent a segfault. This uses the PHP_JSON_ERROR_DEPTH error code that already talks about the stack depth. Previously this was only used for the $depth argument. Closes phpGH-16059.
1 parent d828308 commit a551b99

File tree

3 files changed

+55
-0
lines changed

3 files changed

+55
-0
lines changed

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ PHP NEWS
1515
. Fixed bug GH-16039 (Segmentation fault (access null pointer) in
1616
ext/dom/parentnode/tree.c). (nielsdos)
1717

18+
- JSON:
19+
. Fixed bug GH-15168 (stack overflow in json_encode()). (nielsdos)
20+
1821
- LDAP:
1922
. Fixed bug GH-16032 (Various NULL pointer dereferencements in
2023
ldap_modify_batch()). (Girgias)

ext/json/json_encoder.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@
3131

3232
static const char digits[] = "0123456789abcdef";
3333

34+
static zend_always_inline bool php_json_check_stack_limit(void)
35+
{
36+
#ifdef ZEND_CHECK_STACK_LIMIT
37+
return zend_call_stack_overflowed(EG(stack_limit));
38+
#else
39+
return false;
40+
#endif
41+
}
42+
3443
static int php_json_determine_array_type(zval *val) /* {{{ */
3544
{
3645
zend_array *myht = Z_ARRVAL_P(val);
@@ -115,6 +124,14 @@ static zend_result php_json_encode_array(smart_str *buf, zval *val, int options,
115124
int i, r, need_comma = 0;
116125
HashTable *myht, *prop_ht;
117126

127+
if (php_json_check_stack_limit()) {
128+
encoder->error_code = PHP_JSON_ERROR_DEPTH;
129+
if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
130+
smart_str_appendl(buf, "null", 4);
131+
}
132+
return FAILURE;
133+
}
134+
118135
if (Z_TYPE_P(val) == IS_ARRAY) {
119136
myht = Z_ARRVAL_P(val);
120137
prop_ht = NULL;

ext/json/tests/gh15168.phpt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
GH-15168 (stack overflow in json_encode())
3+
--SKIPIF--
4+
<?php
5+
if (ini_get('zend.max_allowed_stack_size') === false) {
6+
die('skip No stack limit support');
7+
}
8+
?>
9+
--INI--
10+
zend.max_allowed_stack_size=512K
11+
--FILE--
12+
<?php
13+
14+
class Node
15+
{
16+
public $next;
17+
}
18+
19+
$firstNode = new Node();
20+
$node = $firstNode;
21+
for ($i = 0; $i < 30000; $i++) {
22+
$newNode = new Node();
23+
$node->next = $newNode;
24+
$node = $newNode;
25+
}
26+
27+
var_dump(json_encode($firstNode, depth: 500000));
28+
var_dump(json_last_error());
29+
var_dump(json_last_error_msg());
30+
31+
?>
32+
--EXPECT--
33+
bool(false)
34+
int(1)
35+
string(28) "Maximum stack depth exceeded"

0 commit comments

Comments
 (0)