Skip to content

Commit 713439d

Browse files
committed
cJSON_Utils: add recursion depth limits to prevent stack overflow (#995)
- Add CJSON_NESTING_LIMIT depth check to merge_patch, generate_merge_patch, and compare_json. - Prevent stack exhaustion when processing deeply nested JSON structures. - Add unit tests in misc_utils_tests to verify safety against deep recursion.
1 parent fb16e5c commit 713439d

2 files changed

Lines changed: 87 additions & 13 deletions

File tree

cJSON_Utils.c

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -601,8 +601,13 @@ static void sort_object(cJSON * const object, const cJSON_bool case_sensitive)
601601
object->child = sort_list(object->child, case_sensitive);
602602
}
603603

604-
static cJSON_bool compare_json(cJSON *a, cJSON *b, const cJSON_bool case_sensitive)
604+
static cJSON_bool compare_json(cJSON *a, cJSON *b, size_t depth, const cJSON_bool case_sensitive)
605605
{
606+
/* Prevent stack overflow from deeply nested JSON comparison (#995) */
607+
if (depth >= CJSON_NESTING_LIMIT)
608+
{
609+
return false;
610+
}
606611
if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF)))
607612
{
608613
/* mismatched type. */
@@ -635,7 +640,7 @@ static cJSON_bool compare_json(cJSON *a, cJSON *b, const cJSON_bool case_sensiti
635640
case cJSON_Array:
636641
for ((void)(a = a->child), b = b->child; (a != NULL) && (b != NULL); (void)(a = a->next), b = b->next)
637642
{
638-
cJSON_bool identical = compare_json(a, b, case_sensitive);
643+
cJSON_bool identical = compare_json(a, b, depth + 1, case_sensitive);
639644
if (!identical)
640645
{
641646
return false;
@@ -664,7 +669,7 @@ static cJSON_bool compare_json(cJSON *a, cJSON *b, const cJSON_bool case_sensiti
664669
/* missing member */
665670
return false;
666671
}
667-
identical = compare_json(a, b, case_sensitive);
672+
identical = compare_json(a, b, depth + 1, case_sensitive);
668673
if (!identical)
669674
{
670675
return false;
@@ -831,7 +836,7 @@ static int apply_patch(cJSON *object, const cJSON *patch, const cJSON_bool case_
831836
else if (opcode == TEST)
832837
{
833838
/* compare value: {...} with the given path */
834-
status = !compare_json(get_item_from_pointer(object, path->valuestring, case_sensitive), get_object_item(patch, "value", case_sensitive), case_sensitive);
839+
status = !compare_json(get_item_from_pointer(object, path->valuestring, case_sensitive), get_object_item(patch, "value", case_sensitive), 0, case_sensitive);
835840
goto cleanup;
836841
}
837842

@@ -1318,10 +1323,17 @@ CJSON_PUBLIC(void) cJSONUtils_SortObjectCaseSensitive(cJSON * const object)
13181323
sort_object(object, true);
13191324
}
13201325

1321-
static cJSON *merge_patch(cJSON *target, const cJSON * const patch, const cJSON_bool case_sensitive)
1326+
static cJSON *merge_patch(cJSON *target, const cJSON * const patch, size_t depth, const cJSON_bool case_sensitive)
13221327
{
13231328
cJSON *patch_child = NULL;
13241329

1330+
/* Prevent stack overflow from deeply nested JSON merge patch (#995) */
1331+
if (depth >= CJSON_NESTING_LIMIT)
1332+
{
1333+
cJSON_Delete(target);
1334+
return NULL;
1335+
}
1336+
13251337
if (!cJSON_IsObject(patch))
13261338
{
13271339
/* scalar value, array or NULL, just duplicate */
@@ -1364,7 +1376,7 @@ static cJSON *merge_patch(cJSON *target, const cJSON * const patch, const cJSON_
13641376
replace_me = cJSON_DetachItemFromObject(target, patch_child->string);
13651377
}
13661378

1367-
replacement = merge_patch(replace_me, patch_child, case_sensitive);
1379+
replacement = merge_patch(replace_me, patch_child, depth + 1, case_sensitive);
13681380
if (replacement == NULL)
13691381
{
13701382
cJSON_Delete(target);
@@ -1380,19 +1392,25 @@ static cJSON *merge_patch(cJSON *target, const cJSON * const patch, const cJSON_
13801392

13811393
CJSON_PUBLIC(cJSON *) cJSONUtils_MergePatch(cJSON *target, const cJSON * const patch)
13821394
{
1383-
return merge_patch(target, patch, false);
1395+
return merge_patch(target, patch, 0, false);
13841396
}
13851397

13861398
CJSON_PUBLIC(cJSON *) cJSONUtils_MergePatchCaseSensitive(cJSON *target, const cJSON * const patch)
13871399
{
1388-
return merge_patch(target, patch, true);
1400+
return merge_patch(target, patch, 0, true);
13891401
}
13901402

1391-
static cJSON *generate_merge_patch(cJSON * const from, cJSON * const to, const cJSON_bool case_sensitive)
1403+
static cJSON *generate_merge_patch(cJSON * const from, cJSON * const to, size_t depth, const cJSON_bool case_sensitive)
13921404
{
13931405
cJSON *from_child = NULL;
13941406
cJSON *to_child = NULL;
13951407
cJSON *patch = NULL;
1408+
cJSON *sub_patch = NULL;
1409+
/* Prevent stack overflow from deeply nested JSON generate merge patch (#995) */
1410+
if (depth >= CJSON_NESTING_LIMIT)
1411+
{
1412+
return NULL;
1413+
}
13961414
if (to == NULL)
13971415
{
13981416
/* patch to delete everything */
@@ -1449,10 +1467,16 @@ static cJSON *generate_merge_patch(cJSON * const from, cJSON * const to, const c
14491467
else
14501468
{
14511469
/* object key exists in both objects */
1452-
if (!compare_json(from_child, to_child, case_sensitive))
1470+
if (!compare_json(from_child, to_child, 0, case_sensitive))
14531471
{
1472+
sub_patch = generate_merge_patch(from_child, to_child, depth + 1, case_sensitive);
1473+
if (sub_patch == NULL)
1474+
{
1475+
cJSON_Delete(patch);
1476+
return NULL;
1477+
}
14541478
/* not identical --> generate a patch */
1455-
cJSON_AddItemToObject(patch, to_child->string, cJSONUtils_GenerateMergePatch(from_child, to_child));
1479+
cJSON_AddItemToObject(patch, to_child->string, sub_patch);
14561480
}
14571481

14581482
/* next key in the object */
@@ -1472,10 +1496,10 @@ static cJSON *generate_merge_patch(cJSON * const from, cJSON * const to, const c
14721496

14731497
CJSON_PUBLIC(cJSON *) cJSONUtils_GenerateMergePatch(cJSON * const from, cJSON * const to)
14741498
{
1475-
return generate_merge_patch(from, to, false);
1499+
return generate_merge_patch(from, to, 0, false);
14761500
}
14771501

14781502
CJSON_PUBLIC(cJSON *) cJSONUtils_GenerateMergePatchCaseSensitive(cJSON * const from, cJSON * const to)
14791503
{
1480-
return generate_merge_patch(from, to, true);
1504+
return generate_merge_patch(from, to, 0, true);
14811505
}

tests/misc_utils_tests.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,25 @@
2929
#include "common.h"
3030
#include "../cJSON_Utils.h"
3131

32+
#define TEST_NESTING_LIMIT 2000
33+
34+
static cJSON *create_deeply_nested_object(size_t depth)
35+
{
36+
cJSON *root = cJSON_CreateObject();
37+
cJSON *curr = root;
38+
cJSON *next = NULL;
39+
size_t i = 0;
40+
41+
for (i = 0; (i < depth) && (curr != NULL); ++i)
42+
{
43+
next = cJSON_CreateObject();
44+
cJSON_AddItemToObject(curr, "a", next);
45+
curr = next;
46+
}
47+
48+
return root;
49+
}
50+
3251
static void cjson_utils_functions_shouldnt_crash_with_null_pointers(void)
3352
{
3453
cJSON *item = cJSON_CreateString("item");
@@ -70,11 +89,42 @@ static void cjson_utils_functions_shouldnt_crash_with_null_pointers(void)
7089
cJSON_Delete(item);
7190
}
7291

92+
static void cjson_utils_merge_patch_should_not_overflow_stack_on_deep_nesting(void)
93+
{
94+
cJSON *target = cJSON_CreateObject();
95+
cJSON *patch = create_deeply_nested_object(TEST_NESTING_LIMIT);
96+
97+
TEST_ASSERT_NOT_NULL(target);
98+
TEST_ASSERT_NOT_NULL(patch);
99+
100+
TEST_ASSERT_NULL(cJSONUtils_MergePatch(target, patch));
101+
102+
cJSON_Delete(patch);
103+
}
104+
105+
static void cjson_utils_generate_merge_patch_should_not_overflow_stack_on_deep_nesting(void)
106+
{
107+
cJSON *from = create_deeply_nested_object(TEST_NESTING_LIMIT);
108+
cJSON *to = create_deeply_nested_object(TEST_NESTING_LIMIT);
109+
110+
TEST_ASSERT_NOT_NULL(from);
111+
TEST_ASSERT_NOT_NULL(to);
112+
113+
cJSON_AddNumberToObject(to, "diff", 123);
114+
115+
TEST_ASSERT_NULL(cJSONUtils_GenerateMergePatch(from, to));
116+
117+
cJSON_Delete(from);
118+
cJSON_Delete(to);
119+
}
120+
73121
int main(void)
74122
{
75123
UNITY_BEGIN();
76124

77125
RUN_TEST(cjson_utils_functions_shouldnt_crash_with_null_pointers);
126+
RUN_TEST(cjson_utils_merge_patch_should_not_overflow_stack_on_deep_nesting);
127+
RUN_TEST(cjson_utils_generate_merge_patch_should_not_overflow_stack_on_deep_nesting);
78128

79129
return UNITY_END();
80130
}

0 commit comments

Comments
 (0)