From a551b99b2ca85dddac2e4428a4df13769c46d6f8 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 25 Sep 2024 17:57:55 +0200 Subject: [PATCH] Fix GH-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 GH-16059. --- NEWS | 3 +++ ext/json/json_encoder.c | 17 +++++++++++++++++ ext/json/tests/gh15168.phpt | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 ext/json/tests/gh15168.phpt diff --git a/NEWS b/NEWS index 994cedc02ad..1911b8bdf5a 100644 --- a/NEWS +++ b/NEWS @@ -15,6 +15,9 @@ PHP NEWS . Fixed bug GH-16039 (Segmentation fault (access null pointer) in ext/dom/parentnode/tree.c). (nielsdos) +- JSON: + . Fixed bug GH-15168 (stack overflow in json_encode()). (nielsdos) + - LDAP: . Fixed bug GH-16032 (Various NULL pointer dereferencements in ldap_modify_batch()). (Girgias) diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c index 4709c0e2be4..869843589d4 100644 --- a/ext/json/json_encoder.c +++ b/ext/json/json_encoder.c @@ -31,6 +31,15 @@ static const char digits[] = "0123456789abcdef"; +static zend_always_inline bool php_json_check_stack_limit(void) +{ +#ifdef ZEND_CHECK_STACK_LIMIT + return zend_call_stack_overflowed(EG(stack_limit)); +#else + return false; +#endif +} + static int php_json_determine_array_type(zval *val) /* {{{ */ { 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, int i, r, need_comma = 0; HashTable *myht, *prop_ht; + if (php_json_check_stack_limit()) { + encoder->error_code = PHP_JSON_ERROR_DEPTH; + if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) { + smart_str_appendl(buf, "null", 4); + } + return FAILURE; + } + if (Z_TYPE_P(val) == IS_ARRAY) { myht = Z_ARRVAL_P(val); prop_ht = NULL; diff --git a/ext/json/tests/gh15168.phpt b/ext/json/tests/gh15168.phpt new file mode 100644 index 00000000000..bb17c0f7166 --- /dev/null +++ b/ext/json/tests/gh15168.phpt @@ -0,0 +1,35 @@ +--TEST-- +GH-15168 (stack overflow in json_encode()) +--SKIPIF-- + +--INI-- +zend.max_allowed_stack_size=512K +--FILE-- +next = $newNode; + $node = $newNode; +} + +var_dump(json_encode($firstNode, depth: 500000)); +var_dump(json_last_error()); +var_dump(json_last_error_msg()); + +?> +--EXPECT-- +bool(false) +int(1) +string(28) "Maximum stack depth exceeded"