Skip to content

Heap-Use-After-Free in cJSONUtils_MergePatch via Recursive Deletion #1013

@JasonHonKL

Description

@JasonHonKL

Heap-Use-After-Free in cJSONUtils_MergePatch via Recursive Deletion

Summary

A Heap-Use-After-Free (UAF) vulnerability exists in the cJSON_Utils extension of the cJSON library. The cJSONUtils_MergePatch function fails to properly manage internal pointers when a recursive call deletes a target node. Specifically, after a node is detached and deleted, the parent function continues to use the stale pointer to update its linked list, leading to memory corruption or crashes.

Version

1.7.19

Description

The vulnerability occurs within the implementation of RFC 7396 (JSON Merge Patch). When patching an object, cJSONUtils_MergePatch iterates through the patch entries. If a patch value is a scalar (like a string or number) replacing an existing object, the library detaches the old item and calls merge_patch recursively.

Inside the recursive call, since the new value is not an object, cJSON_Delete is called on the original item. However, the parent caller still holds a pointer to this now-freed item and uses it as the "previous" node (prev) when attempting to insert the new replacement value into the parent object's linked list.

The vulnerable logic is located in cJSON_Utils.c:

/* Simplified logic flow */
if (cJSON_IsObject(patch_child)) {
    // ... recursion ...
} else {
    replace_me = cJSON_DetachItemFromObject(target, patch_child->string);
    merge_patch(replace_me, patch_child, case_sensitive); 
    cJSON_AddItemToObject(target, patch_child->string, cJSON_Duplicate(patch_child, 1));
}

The bug is triggered when:

  1. A target JSON contains an existing key.
  2. A patch JSON replaces that key with a non-object value.
  3. The library attempts to perform linked-list surgery using a pointer that was invalidated during the deletion of the old key.

PoC Code

#include <stdio.h>
#include "cjson/cJSON.h"
#include "cjson/cJSON_Utils.h"

int main() {
    cJSON *target = cJSON_CreateObject();
    cJSON_AddStringToObject(target, "vulnerable_node", "initial_value");
    cJSON *patch = cJSON_CreateObject();
    cJSON_AddStringToObject(patch, "vulnerable_node", "replacement_value");
    cJSONUtils_MergePatch(target, patch);
    cJSON_AddStringToObject(target, "trigger_node", "crash_now");
    cJSON_Delete(target);
    cJSON_Delete(patch);
    return 0;
}

Reproduction Step

  1. Compile the PoC with AddressSanitizer and direct inclusion of cJSON source files to ensure instrumentation:
clang++ -g -O0 -fsanitize=address,undefined \
    -I/path/to/cjson/headers \
    poc.cpp /path/to/cjson/cJSON.c /path/to/cjson/cJSON_Utils.c \
    -o poc
  1. Run the resulting binary:
./poc

Stack Trace

#0  suffix_object (prev=0x7c5ff6de0200, item=0x7c5ff6de0140) at /root/src/cjson/cJSON.c:2001:16
#1  add_item_to_array (array=0x7c5ff6de0020, item=0x7c5ff6de0140) at /root/src/cjson/cJSON.c:2052:13
#2  add_item_to_object (object=0x7c5ff6de0020, string=0x55555559f420 "str_val", item=0x7c5ff6de0140, hooks=0x5555556ec5e0 <global_hooks>, constant_key=0) at /root/src/cjson/cJSON.c:2116:12
#3  cJSON_AddStringToObject (object=0x7c5ff6de0020, name=0x55555559f420 "str_val", string=0x7c3ff6de0050 "\"\\mm\236 instead of co{\"\\mm\241 instea") at /root/src/cjson/cJSON.c:2213:12
#4  LLVMFuzzerTestOneInput::$_0::operator() (this=0x7bfff5c00040) at /root/FuzzAgent/output/cjson/harnesses/harness_002.cpp:45:13
#5  LLVMFuzzerTestOneInput (data=0x7bfff57f6800 "\"\\\\mm\236 instead of co{\"\\\\mm\241 instead ofeadlmm\241 instead of on\",", size=67) at /root/FuzzAgent/output/cjson/harnesses/harness_002.cpp:112:13
#6  ExecuteFilesOnyByOne (argc=2, argv=0x7fffffffdbb8, callback=callback@entry=0x5555556b7990 <LLVMFuzzerTestOneInput(uint8_t const*, size_t)>) at aflpp_driver.c:298:7

---> Signed-off-by: FuzzAnything fuzzanything@gmail.com

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions