From 97e09fcb2129b27c768fc86d5a1b4282ca6d716e Mon Sep 17 00:00:00 2001 From: Oculytic Date: Mon, 19 Oct 2026 09:55:40 +0100 Subject: [PATCH] Fix: heap-use-after-free in merge_patch when target is not an object and contains patch When cJSONUtils_MergePatch(target, patch) is called with an object patch that is a subtree of a non-object target (for example, target is the array [{"a":1}] and patch is its element), merge_patch() called cJSON_Delete(target), which also freed patch, and then read patch->child, triggering a heap-use-after-free. This is the same situation as #1060, fixed in 6d9f244 for non-object patches; that fix did not cover the branch where the patch is an object and the target is not. Fix: in that branch, duplicate the patch and create the new object before deleting target, then merge from the copy into the new object and free the copy. On allocation failure, free both and return NULL without touching target. Reproducer: cJSON *target = cJSON_Parse("[{\"a\":1}]"); cJSON *patch = target->child; cJSON *out = cJSONUtils_MergePatch(target, patch); --- cJSON_Utils.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/cJSON_Utils.c b/cJSON_Utils.c index 4f3a9ed8d..02040e9a3 100644 --- a/cJSON_Utils.c +++ b/cJSON_Utils.c @@ -1335,8 +1335,20 @@ static cJSON *merge_patch(cJSON *target, const cJSON * const patch, const cJSON_ if (!cJSON_IsObject(target)) { + /* The patch may be part of target: deleting target would free it. + * Work from a copy, as for non-object patches above. */ + cJSON *patch_copy = cJSON_Duplicate(patch, 1); + cJSON *result = cJSON_CreateObject(); + if ((patch_copy == NULL) || (result == NULL)) + { + cJSON_Delete(patch_copy); + cJSON_Delete(result); + return NULL; + } cJSON_Delete(target); - target = cJSON_CreateObject(); + result = merge_patch(result, patch_copy, case_sensitive); + cJSON_Delete(patch_copy); + return result; } patch_child = patch->child;