From 9d1b229086a7b069fb5f4e3be0226a22480a6707 Mon Sep 17 00:00:00 2001 From: Nicolas Badoux Date: Fri, 30 Aug 2024 13:36:22 +0200 Subject: [PATCH] Added max recusrion depth for cJSONDuplicate to prevent stack exhaustion in case of circular reference --- cJSON.c | 12 +++++++++++- cJSON.h | 6 ++++++ tests/misc_tests.c | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/cJSON.c b/cJSON.c index 56f65ef..9399c0d 100644 --- a/cJSON.c +++ b/cJSON.c @@ -2737,7 +2737,14 @@ CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int co } /* Duplication */ +cJSON * cJSON_Duplicate_rec(const cJSON *item, size_t depth, cJSON_bool recurse); + CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) +{ + return cJSON_Duplicate_rec(item, 0, recurse ); +} + +cJSON * cJSON_Duplicate_rec(const cJSON *item, size_t depth, cJSON_bool recurse) { cJSON *newitem = NULL; cJSON *child = NULL; @@ -2784,7 +2791,10 @@ CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) child = item->child; while (child != NULL) { - newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ + if(depth >= CJSON_CIRCULAR_LIMIT) { + goto fail; + } + newchild = cJSON_Duplicate_rec(child, ++depth, true); /* Duplicate (with recurse) each item in the ->next chain */ if (!newchild) { goto fail; diff --git a/cJSON.h b/cJSON.h index 88cf0bc..37520bb 100644 --- a/cJSON.h +++ b/cJSON.h @@ -137,6 +137,12 @@ typedef int cJSON_bool; #define CJSON_NESTING_LIMIT 1000 #endif +/* Limits the length of circular references can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_CIRCULAR_LIMIT +#define CJSON_CIRCULAR_LIMIT 10000 +#endif + /* returns the version of cJSON as a string */ CJSON_PUBLIC(const char*) cJSON_Version(void); diff --git a/tests/misc_tests.c b/tests/misc_tests.c index 606b460..a96c2fd 100644 --- a/tests/misc_tests.c +++ b/tests/misc_tests.c @@ -219,6 +219,23 @@ static void cjson_should_not_parse_to_deeply_nested_jsons(void) TEST_ASSERT_NULL_MESSAGE(cJSON_Parse(deep_json), "To deep JSONs should not be parsed."); } +static void cjson_should_not_follow_too_deep_circular_references(void) +{ + cJSON *o = cJSON_CreateArray(); + cJSON *a = cJSON_CreateArray(); + cJSON *b = cJSON_CreateArray(); + cJSON *x; + + cJSON_AddItemToArray(o, a); + cJSON_AddItemToArray(a, b); + cJSON_AddItemToArray(b, o); + + x = cJSON_Duplicate(o, 1); + TEST_ASSERT_NULL(x); + cJSON_DetachItemFromArray(b, 0); + cJSON_Delete(o); +} + static void cjson_set_number_value_should_set_numbers(void) { cJSON number[1] = {{NULL, NULL, NULL, cJSON_Number, NULL, 0, 0, NULL}}; @@ -777,6 +794,7 @@ int CJSON_CDECL main(void) RUN_TEST(cjson_get_object_item_case_sensitive_should_not_crash_with_array); RUN_TEST(typecheck_functions_should_check_type); RUN_TEST(cjson_should_not_parse_to_deeply_nested_jsons); + RUN_TEST(cjson_should_not_follow_too_deep_circular_references); RUN_TEST(cjson_set_number_value_should_set_numbers); RUN_TEST(cjson_detach_item_via_pointer_should_detach_items); RUN_TEST(cjson_detach_item_via_pointer_should_return_null_if_item_prev_is_null);