Skip to content

cJSON_Utils: survive allocation failures in the patch/merge/pointer helpers - #1102

Open
bg0d-droid wants to merge 1 commit into
DaveGamble:masterfrom
bg0d-droid:fix-utils-oom-robustness
Open

bg0d-droid wants to merge 1 commit into
DaveGamble:masterfrom
bg0d-droid:fix-utils-oom-robustness

Conversation

@bg0d-droid

Copy link
Copy Markdown

Summary

Two failure classes in cJSON_Utils.c turn a simple allocation failure into a crash or a leak:

  1. NULL-pointer writes (crash). The path-building helpers use the result of cJSON_malloc without checking it and then write into it with sprintf/strcat. Any allocation failure segfaults the process. Affected: compose_patch(), create_patches() (array and object branches) and cJSONUtils_FindPointerFromObjectTo() (array and object branches).

  2. Leaked values on failed insertion. cJSON_AddItemToObject()/cJSON_AddItemToArray() leave the item caller-owned when they fail (e.g. when duplicating the key runs out of memory), but several call sites ignored the result and dropped or never took ownership of the value:

    • merge_patch() — the detached replacement subtree (and the recreated target object could be NULL and was used unchecked)
    • apply_patch() — the duplicated patch value (value = NULL was set unconditionally after the add)
    • generate_merge_patch() — the created null / duplicated / recursively generated patch entries
    • compose_patch() — the created op/path/value strings

Changes

All in cJSON_Utils.c: check every cJSON_malloc result and fail cleanly; only transfer ownership of a value when the add actually succeeded, releasing the orphaned value otherwise (NULL return from the generators, or the existing status-code convention with goto cleanup in apply_patch()).

tests/utils_oom_tests.c (plus one line in tests/CMakeLists.txt) is a regression test that routes all cJSON allocations through cJSON_InitHooks and injects a failure at every allocation step of the patch, merge, and pointer operations, asserting after each attempt that nothing remains allocated.

Reproduction (unpatched)

The regression test segfaults against the unpatched code:

$ ./tests/utils_oom_tests
Segmentation fault (exit 139)

Found with allocation-failure injection: a sweep that fails each allocation of a call in turn produced 41 crashing or leaking injection points across cJSONUtils_GeneratePatches/ApplyPatches/MergePatch/GenerateMergePatch/FindPointerFromObjectTo (LeakSanitizer traces show the leaked subtree in merge_patch/apply_patch/generate_merge_patch, and the NULL-write stacks point into sprintf on the unchecked cJSON_malloc results).

Testing

  • tests/utils_oom_tests passes after this fix (5 cases × 120 injection points each) and segfaults before it.
  • Full suite passes: 23/23 (ctest).
  • The same allocation-failure sweep is clean after the fix: 3,300 injection points across 11 scenarios (parse, invalid input, deep nesting, partial input, printing, builders, duplicate/compare, patch, merge, pointer) under ASan/LSan/UBSan with zero leaks and zero crashes.

…elpers

The path-building helpers in cJSON_Utils.c used the result of cJSON_malloc
without checking it and then wrote into it with sprintf/strcat, so any
allocation failure crashed the process with a NULL-pointer write.  This
affects compose_patch(), create_patches() and
cJSONUtils_FindPointerFromObjectTo().

Several sites also kept or dropped ownership of a value depending on an
unchecked cJSON_AddItemToObject()/cJSON_AddItemToArray() call.  Those
functions leave the item caller-owned when they fail (for example when
duplicating the key runs out of memory), so the item leaked: in
merge_patch() the detached subtree, in apply_patch() the duplicated patch
value (value was cleared unconditionally), in generate_merge_patch() the
created/duplicated/recursive patch entries, and in compose_patch() the
created op/path/value strings.

Fix both classes systematically in the affected functions: check every
allocation and only transfer ownership when the add actually succeeded,
otherwise release the orphaned value and fail cleanly (NULL return, or
the existing status-code convention in apply_patch()).

Found with allocation-failure injection over cJSONUtils_GeneratePatches/
ApplyPatches/MergePatch/GenerateMergePatch/FindPointerFromObjectTo: 41
injection points crashed or leaked (469 in the wider sweep across parse,
print and object APIs before triage).

Regression coverage: tests/utils_oom_tests.c routes all cJSON allocations
through cJSON_InitHooks and injects a failure at every step of the patch,
merge and pointer operations, asserting that nothing remains allocated
after each attempt.  It segfaults against the unpatched code and passes
with this fix; the rest of the suite (23 tests) still passes.
@bg0d-droid
bg0d-droid force-pushed the fix-utils-oom-robustness branch from 9a9cea0 to 287279e Compare October 15, 2026 07:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant