Skip to content

cJSON_Utils may dereference NULL valuestring after cJSON_IsString() checks #1011

@Yanhaoxi

Description

@Yanhaoxi

Summary

In cJSON_Utils.c, several code paths check whether a field is a string using cJSON_IsString(), and then directly use item->valuestring (e.g., via strcmp, strlen, or indexing).

However, cJSON_IsString() only checks the type tag and does not guarantee that valuestring is non-NULL.

It is possible to construct a cJSON_String item whose valuestring is NULL using public APIs such as cJSON_CreateStringReference(NULL).

Even if such input is considered invalid, the JSON Patch code path currently assumes that cJSON_IsString() is sufficient before dereferencing valuestring. This can lead to a crash instead of graceful rejection.

Affected version

  • cJSON 1.7.19
  • Code path: JSON Patch handling in cJSON_Utils.c

Relevant code

cJSON_IsString() only checks the type:

CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item)
{
    if (item == NULL)
    {
        return false;
    }

    return (item->type & 0xFF) == cJSON_String;
}

Example usage in decode_patch_operation():

cJSON *operation = get_object_item(patch, "op", case_sensitive);
if (!cJSON_IsString(operation))
{
    return INVALID;
}

if (strcmp(operation->valuestring, "add") == 0)
{
    return ADD;
}

Similar patterns exist in apply_patch() for fields like path and from, where valuestring is used without NULL checks.

Proof of concept

Case 1: NULL op->valuestring

#include "cJSON.h"
#include "cJSON_Utils.h"

int main(void)
{
    cJSON *target = cJSON_CreateObject();
    cJSON *patches = cJSON_CreateArray();
    cJSON *patch = cJSON_CreateObject();

    cJSON_AddItemToObject(patch, "op", cJSON_CreateStringReference(NULL));
    cJSON_AddItemToObject(patch, "path", cJSON_CreateString(""));
    cJSON_AddItemToObject(patch, "value", cJSON_CreateNumber(1));
    cJSON_AddItemToArray(patches, patch);

    return cJSONUtils_ApplyPatches(target, patches);
}

Case 2: NULL path->valuestring

#include "cJSON.h"
#include "cJSON_Utils.h"

int main(void)
{
    cJSON *target = cJSON_CreateObject();
    cJSON *patches = cJSON_CreateArray();
    cJSON *patch = cJSON_CreateObject();

    cJSON_AddItemToObject(patch, "op", cJSON_CreateString("remove"));
    cJSON_AddItemToObject(patch, "path", cJSON_CreateStringReference(NULL));
    cJSON_AddItemToArray(patches, patch);

    return cJSONUtils_ApplyPatches(target, patches);
}

Case 3: NULL from->valuestring

#include "cJSON.h"
#include "cJSON_Utils.h"

int main(void)
{
    cJSON *target = cJSON_Parse("{\"a\":1}");
    cJSON *patches = cJSON_CreateArray();
    cJSON *patch = cJSON_CreateObject();

    cJSON_AddItemToObject(patch, "op", cJSON_CreateString("move"));
    cJSON_AddItemToObject(patch, "path", cJSON_CreateString("/b"));
    cJSON_AddItemToObject(patch, "from", cJSON_CreateStringReference(NULL));
    cJSON_AddItemToArray(patches, patch);

    return cJSONUtils_ApplyPatches(target, patches);
}

Build and run

gcc poc.c cJSON.c cJSON_Utils.c -o poc
./poc

On Windows (MinGW), this results in access violation (0xC0000005).

Expected behavior

Invalid patch fields with NULL string values should be rejected with an error code instead of causing a crash.

Suggested fix

Add an additional NULL check before using valuestring. For example:

static cJSON_bool is_valid_patch_string(const cJSON * const item)
{
    return cJSON_IsString(item) && (item->valuestring != NULL);
}

Then use it for patch fields such as op, path, instead of relying on cJSON_IsString() alone

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