Undefined-Behavior (Float-Cast-Overflow) in cJSON_CreateNumber via NaN values

Open Beginner friendly
#999 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
68/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Quiet
Tech stack
c
Domain
security

Research direction

Start at cJSON.c:2524 in cJSON_CreateNumber and trace the cJSON_CreateFloatArray path described by the issue. Reproduce with the supplied NaN example under the shown Clang AddressSanitizer/UndefinedBehaviorSanitizer command; done means NaN and Infinity inputs no longer trigger the reported float-cast-overflow diagnostic.

Written by the indexing model from the issue text.

Description

Undefined-Behavior (Float-Cast-Overflow) in cJSON_CreateNumber via NaN values

Version

v1.7.15-52-gb2890c8

Description

An Undefined-Behavior bug exists in cJSON's cJSON_CreateNumber function located at cJSON.c:2524. This occurs when a NaN (Not-a-Number) or Infinity floating-point value is passed to the function (e.g. through array creation functions like cJSON_CreateFloatArray).

Vulnerability Type: Undefined-Behavior (Float-Cast-Overflow / Float out of range of int)

Root Cause: When a NaN floating-point value is passed into cJSON_CreateNumber(double num), it bypasses the two saturation checks (num >= INT_MAX and num <= (double)INT_MIN) because relational comparisons with NaN generally evaluate to false. It then falls through into the else branch, attempting to cast the NaN value into an int, which triggers undefined behavior in C/C++.

Vulnerable Code in cJSON.c:

CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num)
{
    cJSON *item = cJSON_New_Item(&global_hooks);
    if(item)
    {
        item->type = cJSON_Number;
        item->valuedouble = num;

        /* use saturation in case of overflow */
        if (num >= INT_MAX) // comparisons with `NaN` always return false
        {
            item->valueint = INT_MAX;
        }
        else if (num <= (double)INT_MIN) // comparisons with `NaN` always return false
        {
            item->valueint = INT_MIN;
        }
        else
        {
            item->valueint = (int)num; // BUG: attempts to cast NaN/Inf to int triggering UB
        }
    }

    return item;
}

PoC Code

#include <cmath>
#include <vector>
#include <fuzzer/FuzzedDataProvider.h>

extern "C" {
#include "cjson/cJSON.h"
}

extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, int size) {
  FuzzedDataProvider fdp(data, size);

  int array_size = 1;

  std::vector<float> float_values = { NAN };

  // This call triggers the Undefined Behavior (Float Cast Overflow) 
  // nested in cJSON_CreateNumber!
  cJSON *float_array = cJSON_CreateFloatArray(float_values.data(), array_size);

  return 0;
}

int main() {
    uint8_t data[10];
    LLVMFuzzerTestOneInput(data, 10);
    return 0;
}

Reproduction Steps

clang++ -g -O0 -fsanitize=address,undefined,fuzzer -fno-omit-frame-pointer \
    -I<include> \
    poc.cpp -o poc \
    libcjson.a

export UBSAN_OPTIONS=print_stacktrace=1
./poc

Stack Trace

./poc
/root/src/cjson/cJSON.c:2524:30: runtime error: nan is outside the range of representable values of type 'int'
    #0 0x55c6ee9ebc05 in cJSON_CreateNumber /root/src/cjson/cJSON.c:2524:30
    #1 0x55c6ee9ef7d3 in cJSON_CreateFloatArray /root/src/cjson/cJSON.c:2677:13
    #2 0x55c6ee9e0333 in LLVMFuzzerTestOneInput /root/FuzzAgent/output/cjson/crash_reports/crash_000/poc.cpp:18:24
    #3 0x55c6ee9e056f in main /root/FuzzAgent/output/cjson/crash_reports/crash_000/poc.cpp:25:5
    #4 0x7036a4be11c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #5 0x7036a4be128a in __libc_start_main csu/../csu/libc-start.c:360:3
    #6 0x55c6ee8f53f4 in _start (/root/FuzzAgent/output/cjson/crash_reports/crash_000/poc+0x3e3f4) (BuildId: b9bd84db748d3423672a09e5113f9577174c6452)

SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /root/src/cjson/cJSON.c:2524:30
Dominant language
C
Stars
13k
Forks
3.5k
PR merge metrics
No merged PRs in 30d

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from DaveGamble/cJSON

All issues in DaveGamble/cJSON

Similar issues

More C issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.