From 2ceb3abc1d9f9e37827c32caeee75dcdd0ed4802 Mon Sep 17 00:00:00 2001 From: Atharv Mantri Date: Mon, 14 Sep 2026 08:35:42 +0530 Subject: [PATCH 1/3] guard recursive comparisons against stack exhaustion --- cJSON.c | 18 ++++++++++++++---- cJSON_Utils.c | 15 ++++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/cJSON.c b/cJSON.c index 88c2d95b..45f90325 100644 --- a/cJSON.c +++ b/cJSON.c @@ -3069,13 +3069,18 @@ CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) return (item->type & 0xFF) == cJSON_Raw; } -CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) +static cJSON_bool cJSON_Compare_rec(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive, size_t depth) { if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF))) { return false; } + if (depth >= CJSON_NESTING_LIMIT) + { + return false; + } + /* check if type is valid */ switch (a->type & 0xFF) { @@ -3134,7 +3139,7 @@ CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * cons for (; (a_element != NULL) && (b_element != NULL);) { - if (!cJSON_Compare(a_element, b_element, case_sensitive)) + if (!cJSON_Compare_rec(a_element, b_element, case_sensitive, depth + 1)) { return false; } @@ -3164,7 +3169,7 @@ CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * cons return false; } - if (!cJSON_Compare(a_element, b_element, case_sensitive)) + if (!cJSON_Compare_rec(a_element, b_element, case_sensitive, depth + 1)) { return false; } @@ -3180,7 +3185,7 @@ CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * cons return false; } - if (!cJSON_Compare(b_element, a_element, case_sensitive)) + if (!cJSON_Compare_rec(b_element, a_element, case_sensitive, depth + 1)) { return false; } @@ -3194,6 +3199,11 @@ CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * cons } } +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) +{ + return cJSON_Compare_rec(a, b, case_sensitive, 0); +} + CJSON_PUBLIC(void *) cJSON_malloc(size_t size) { return global_hooks.allocate(size); diff --git a/cJSON_Utils.c b/cJSON_Utils.c index 8b38eb25..bfa47c0b 100644 --- a/cJSON_Utils.c +++ b/cJSON_Utils.c @@ -601,13 +601,18 @@ static void sort_object(cJSON * const object, const cJSON_bool case_sensitive) object->child = sort_list(object->child, case_sensitive); } -static cJSON_bool compare_json(cJSON *a, cJSON *b, const cJSON_bool case_sensitive) +static cJSON_bool compare_json(cJSON *a, cJSON *b, const cJSON_bool case_sensitive, size_t depth) { if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF))) { /* mismatched type. */ return false; } + + if (depth >= CJSON_NESTING_LIMIT) + { + return false; + } switch (a->type & 0xFF) { case cJSON_Number: @@ -635,7 +640,7 @@ static cJSON_bool compare_json(cJSON *a, cJSON *b, const cJSON_bool case_sensiti case cJSON_Array: for ((void)(a = a->child), b = b->child; (a != NULL) && (b != NULL); (void)(a = a->next), b = b->next) { - cJSON_bool identical = compare_json(a, b, case_sensitive); + cJSON_bool identical = compare_json(a, b, case_sensitive, depth + 1); if (!identical) { return false; @@ -664,7 +669,7 @@ static cJSON_bool compare_json(cJSON *a, cJSON *b, const cJSON_bool case_sensiti /* missing member */ return false; } - identical = compare_json(a, b, case_sensitive); + identical = compare_json(a, b, case_sensitive, depth + 1); if (!identical) { return false; @@ -831,7 +836,7 @@ static int apply_patch(cJSON *object, const cJSON *patch, const cJSON_bool case_ else if (opcode == TEST) { /* compare value: {...} with the given path */ - status = !compare_json(get_item_from_pointer(object, path->valuestring, case_sensitive), get_object_item(patch, "value", case_sensitive), case_sensitive); + status = !compare_json(get_item_from_pointer(object, path->valuestring, case_sensitive), get_object_item(patch, "value", case_sensitive), case_sensitive, 0); goto cleanup; } @@ -1449,7 +1454,7 @@ static cJSON *generate_merge_patch(cJSON * const from, cJSON * const to, const c else { /* object key exists in both objects */ - if (!compare_json(from_child, to_child, case_sensitive)) + if (!compare_json(from_child, to_child, case_sensitive, 0)) { /* not identical --> generate a patch */ cJSON_AddItemToObject(patch, to_child->string, cJSONUtils_GenerateMergePatch(from_child, to_child)); From 00be6c69f1e17b82f7f04fb5fba120914a635c14 Mon Sep 17 00:00:00 2001 From: Atharv Mantri Date: Mon, 14 Sep 2026 08:41:34 +0530 Subject: [PATCH 2/3] Fix: bound merge patch recursion --- cJSON_Utils.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/cJSON_Utils.c b/cJSON_Utils.c index bfa47c0b..32e446ea 100644 --- a/cJSON_Utils.c +++ b/cJSON_Utils.c @@ -1393,7 +1393,7 @@ CJSON_PUBLIC(cJSON *) cJSONUtils_MergePatchCaseSensitive(cJSON *target, const cJ return merge_patch(target, patch, true); } -static cJSON *generate_merge_patch(cJSON * const from, cJSON * const to, const cJSON_bool case_sensitive) +static cJSON *generate_merge_patch(cJSON * const from, cJSON * const to, const cJSON_bool case_sensitive, size_t depth) { cJSON *from_child = NULL; cJSON *to_child = NULL; @@ -1403,6 +1403,11 @@ static cJSON *generate_merge_patch(cJSON * const from, cJSON * const to, const c /* patch to delete everything */ return cJSON_CreateNull(); } + if (depth >= CJSON_NESTING_LIMIT) + { + /* Do not recurse beyond the same limit used by cJSON comparisons. */ + return NULL; + } if (!cJSON_IsObject(to) || !cJSON_IsObject(from)) { return cJSON_Duplicate(to, 1); @@ -1457,7 +1462,7 @@ static cJSON *generate_merge_patch(cJSON * const from, cJSON * const to, const c if (!compare_json(from_child, to_child, case_sensitive, 0)) { /* not identical --> generate a patch */ - cJSON_AddItemToObject(patch, to_child->string, cJSONUtils_GenerateMergePatch(from_child, to_child)); + cJSON_AddItemToObject(patch, to_child->string, generate_merge_patch(from_child, to_child, case_sensitive, depth + 1)); } /* next key in the object */ @@ -1477,10 +1482,10 @@ static cJSON *generate_merge_patch(cJSON * const from, cJSON * const to, const c CJSON_PUBLIC(cJSON *) cJSONUtils_GenerateMergePatch(cJSON * const from, cJSON * const to) { - return generate_merge_patch(from, to, false); + return generate_merge_patch(from, to, false, 0); } CJSON_PUBLIC(cJSON *) cJSONUtils_GenerateMergePatchCaseSensitive(cJSON * const from, cJSON * const to) { - return generate_merge_patch(from, to, true); + return generate_merge_patch(from, to, true, 0); } From fe0b058be0bc8d7e547eec36e4c6665fa5b79e44 Mon Sep 17 00:00:00 2001 From: Atharv Mantri Date: Mon, 14 Sep 2026 08:48:21 +0530 Subject: [PATCH 3/3] Fix: propagate merge patch recursion failure --- cJSON_Utils.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cJSON_Utils.c b/cJSON_Utils.c index 32e446ea..8b8abff4 100644 --- a/cJSON_Utils.c +++ b/cJSON_Utils.c @@ -1398,6 +1398,7 @@ static cJSON *generate_merge_patch(cJSON * const from, cJSON * const to, const c cJSON *from_child = NULL; cJSON *to_child = NULL; cJSON *patch = NULL; + cJSON *child_patch = NULL; if (to == NULL) { /* patch to delete everything */ @@ -1462,7 +1463,13 @@ static cJSON *generate_merge_patch(cJSON * const from, cJSON * const to, const c if (!compare_json(from_child, to_child, case_sensitive, 0)) { /* not identical --> generate a patch */ - cJSON_AddItemToObject(patch, to_child->string, generate_merge_patch(from_child, to_child, case_sensitive, depth + 1)); + child_patch = generate_merge_patch(from_child, to_child, case_sensitive, depth + 1); + if ((child_patch == NULL) || !cJSON_AddItemToObject(patch, to_child->string, child_patch)) + { + cJSON_Delete(child_patch); + cJSON_Delete(patch); + return NULL; + } } /* next key in the object */