Compare commits

...

5 Commits

Author SHA1 Message Date
Jakub Zwolakowski 4ad0d9f5a0
Merge 11de03fbd2 into 87d8f0961a 2024-02-13 21:40:18 +04:00
Alanscut 87d8f0961a Release 1.7.17
update version to 1.7.17
2023-12-26 10:24:36 +08:00
Lee f66cbab4bf
fix error in null checkings (#810)
fixes #802 and #803
2023-12-20 11:05:23 +08:00
Lee 60ff122ef5
add NULL checkings (#809)
* add NULL checks in cJSON_SetValuestring

Fixes #803(CVE-2023-50472)

* add NULL check in cJSON_InsertItemInArray

Fixes #802(CVE-2023-50471)

* add tests for NULL checks

add tests for NULL checks in cJSON_InsertItemInArray and cJSON_SetValuestring
2023-12-18 11:47:52 +08:00
guillaumemillot 11de03fbd2 Initial TrustInSoft CI configuration 2020-11-25 16:40:31 +01:00
12 changed files with 663 additions and 6 deletions

View File

@ -1,3 +1,10 @@
1.7.17 (Dec 26, 2023)
======
Fixes:
------
* Fix null reference in cJSON_SetValuestring(CVE-2023-50472), see #809
* Fix null reference in cJSON_InsertItemInArray(CVE-2023-50471), see #809 and #810
1.7.16 (Jul 5, 2023)
======
Features:

View File

@ -2,7 +2,7 @@ set(CMAKE_LEGACY_CYGWIN_WIN32 0)
cmake_minimum_required(VERSION 3.0)
project(cJSON
VERSION 1.7.16
VERSION 1.7.17
LANGUAGES C)
cmake_policy(SET CMP0054 NEW) # set CMP0054 policy

View File

@ -8,7 +8,7 @@ CJSON_TEST_SRC = cJSON.c test.c
LDLIBS = -lm
LIBVERSION = 1.7.16
LIBVERSION = 1.7.17
CJSON_SOVERSION = 1
UTILS_SOVERSION = 1

View File

@ -555,6 +555,9 @@ cJSON is written in ANSI C (or C89, C90). If your compiler or C library doesn't
NOTE: ANSI C is not C++ therefore it shouldn't be compiled with a C++ compiler. You can compile it with a C compiler and link it with your C++ code however. Although compiling with a C++ compiler might work, correct behavior is not guaranteed.
Absence of Undefined Behaviors on the test suite guaranteed by [TrustInSoft CI](https://ci.trust-in-soft.com/projects/DaveGamble/cJSON/latest):
[![TrustInSoft CI](https://ci.trust-in-soft.com/projects/DaveGamble/cJSON.svg)](https://ci.trust-in-soft.com/projects/DaveGamble/cJSON)
#### Floating Point Numbers
cJSON does not officially support any `double` implementations other than IEEE754 double precision floating point numbers. It might still work with other implementations but bugs with these will be considered invalid.

16
cJSON.c
View File

@ -117,7 +117,7 @@ CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item)
}
/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */
#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 16)
#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 17)
#error cJSON.h and cJSON.c have different versions. Make sure that both have the same.
#endif
@ -401,7 +401,12 @@ CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring)
{
char *copy = NULL;
/* if object's type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */
if (!(object->type & cJSON_String) || (object->type & cJSON_IsReference))
if ((object == NULL) || !(object->type & cJSON_String) || (object->type & cJSON_IsReference))
{
return NULL;
}
/* return NULL if the object is corrupted */
if (object->valuestring == NULL)
{
return NULL;
}
@ -2264,7 +2269,7 @@ CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON
{
cJSON *after_inserted = NULL;
if (which < 0)
if (which < 0 || newitem == NULL)
{
return false;
}
@ -2275,6 +2280,11 @@ CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON
return add_item_to_array(array, newitem);
}
if (after_inserted != array->child && after_inserted->prev == NULL) {
/* return false if after_inserted is a corrupted array item */
return false;
}
newitem->next = after_inserted;
newitem->prev = after_inserted->prev;
after_inserted->prev = newitem;

View File

@ -81,7 +81,7 @@ then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJ
/* project version */
#define CJSON_VERSION_MAJOR 1
#define CJSON_VERSION_MINOR 7
#define CJSON_VERSION_PATCH 16
#define CJSON_VERSION_PATCH 17
#include <stddef.h>

View File

@ -352,6 +352,19 @@ static void cjson_functions_should_not_crash_with_null_pointers(void)
{
char buffer[10];
cJSON *item = cJSON_CreateString("item");
cJSON *array = cJSON_CreateArray();
cJSON *item1 = cJSON_CreateString("item1");
cJSON *item2 = cJSON_CreateString("corrupted array item3");
cJSON *corruptedString = cJSON_CreateString("corrupted");
struct cJSON *originalPrev;
add_item_to_array(array, item1);
add_item_to_array(array, item2);
originalPrev = item2->prev;
item2->prev = NULL;
free(corruptedString->valuestring);
corruptedString->valuestring = NULL;
cJSON_InitHooks(NULL);
TEST_ASSERT_NULL(cJSON_Parse(NULL));
@ -411,6 +424,8 @@ static void cjson_functions_should_not_crash_with_null_pointers(void)
cJSON_DeleteItemFromObject(item, NULL);
cJSON_DeleteItemFromObjectCaseSensitive(NULL, "item");
cJSON_DeleteItemFromObjectCaseSensitive(item, NULL);
TEST_ASSERT_FALSE(cJSON_InsertItemInArray(array, 0, NULL));
TEST_ASSERT_FALSE(cJSON_InsertItemInArray(array, 1, item));
TEST_ASSERT_FALSE(cJSON_InsertItemInArray(NULL, 0, item));
TEST_ASSERT_FALSE(cJSON_InsertItemInArray(item, 0, NULL));
TEST_ASSERT_FALSE(cJSON_ReplaceItemViaPointer(NULL, item, item));
@ -427,10 +442,16 @@ static void cjson_functions_should_not_crash_with_null_pointers(void)
TEST_ASSERT_NULL(cJSON_Duplicate(NULL, true));
TEST_ASSERT_FALSE(cJSON_Compare(item, NULL, false));
TEST_ASSERT_FALSE(cJSON_Compare(NULL, item, false));
TEST_ASSERT_NULL(cJSON_SetValuestring(NULL, "test"));
TEST_ASSERT_NULL(cJSON_SetValuestring(corruptedString, "test"));
cJSON_Minify(NULL);
/* skipped because it is only used via a macro that checks for NULL */
/* cJSON_SetNumberHelper(NULL, 0); */
/* restore corrupted item2 to delete it */
item2->prev = originalPrev;
cJSON_Delete(corruptedString);
cJSON_Delete(array);
cJSON_Delete(item);
}
@ -642,7 +663,9 @@ static void cjson_set_valuestring_to_object_should_not_leak_memory(void)
ptr1 = item1->valuestring;
return_value = cJSON_SetValuestring(cJSON_GetObjectItem(root, "one"), long_valuestring);
TEST_ASSERT_NOT_NULL(return_value);
#ifndef __TRUSTINSOFT_ANALYZER__ /* ptr1 was already freed, comparing it with another pointer is Undefined Behavior */
TEST_ASSERT_NOT_EQUAL_MESSAGE(ptr1, return_value, "new valuestring longer than old should reallocate memory")
#endif /* __TRUSTINSOFT_ANALYZER__ */
TEST_ASSERT_EQUAL_STRING(long_valuestring, cJSON_GetObjectItem(root, "one")->valuestring);
return_value = cJSON_SetValuestring(cJSON_GetObjectItem(root, "two"), long_valuestring);

View File

@ -34,7 +34,12 @@ static void parse_hex4_should_parse_all_combinations(void)
unsigned char digits_lower[6];
unsigned char digits_upper[6];
/* test all combinations */
#if defined(__TRUSTINSOFT_ANALYZER__)
/* Reduce the test's size for TIS CI. */
for (number = 0; number <= 0xFFF; number++)
#else
for (number = 0; number <= 0xFFFF; number++)
#endif
{
TEST_ASSERT_EQUAL_INT_MESSAGE(4, sprintf((char*)digits_lower, "%.4x", number), "sprintf failed.");
TEST_ASSERT_EQUAL_INT_MESSAGE(4, sprintf((char*)digits_upper, "%.4X", number), "sprintf failed.");

233
tis.config Normal file
View File

@ -0,0 +1,233 @@
[
{
"name": "cjson_add.c",
"files": [
"tests/cjson_add.c"
],
"include": "trustinsoft/common.config"
},
{
"name": "compare_tests.c",
"files": [
"tests/compare_tests.c"
],
"include": "trustinsoft/common.config"
},
{
"name": "json_patch_tests.c",
"files": [
"tests/json_patch_tests.c"
],
"include": "trustinsoft/common.config"
},
{
"name": "minify_tests.c",
"files": [
"tests/minify_tests.c"
],
"include": "trustinsoft/common.config"
},
{
"name": "misc_tests.c",
"files": [
"tests/misc_tests.c"
],
"include": "trustinsoft/common.config"
},
{
"name": "misc_utils_tests.c",
"files": [
"tests/misc_utils_tests.c"
],
"include": "trustinsoft/common.config"
},
{
"name": "old_utils_tests.c",
"files": [
"tests/old_utils_tests.c"
],
"include": "trustinsoft/common.config"
},
{
"name": "parse_array.c",
"files": [
"tests/parse_array.c"
],
"include": "trustinsoft/common.config"
},
{
"name": "parse_examples.c",
"files": [
"tests/parse_examples.c"
],
"include": "trustinsoft/common.config"
},
{
"name": "parse_hex4.c",
"files": [
"tests/parse_hex4.c"
],
"include": "trustinsoft/common.config"
},
{
"name": "parse_number.c",
"files": [
"tests/parse_number.c"
],
"include": "trustinsoft/common.config"
},
{
"name": "parse_object.c",
"files": [
"tests/parse_object.c"
],
"include": "trustinsoft/common.config"
},
{
"name": "parse_string.c",
"files": [
"tests/parse_string.c"
],
"include": "trustinsoft/common.config"
},
{
"name": "parse_value.c",
"files": [
"tests/parse_value.c"
],
"include": "trustinsoft/common.config"
},
{
"name": "parse_with_opts.c",
"files": [
"tests/parse_with_opts.c"
],
"include": "trustinsoft/common.config"
},
{
"name": "print_array.c",
"files": [
"tests/print_array.c"
],
"include": "trustinsoft/common.config"
},
{
"name": "print_number.c",
"files": [
"tests/print_number.c"
],
"include": "trustinsoft/common.config"
},
{
"name": "print_object.c",
"files": [
"tests/print_object.c"
],
"include": "trustinsoft/common.config"
},
{
"name": "print_string.c",
"files": [
"tests/print_string.c"
],
"include": "trustinsoft/common.config"
},
{
"name": "print_value.c",
"files": [
"tests/print_value.c"
],
"include": "trustinsoft/common.config"
},
{
"name": "readme_examples.c",
"files": [
"tests/readme_examples.c"
],
"include": "trustinsoft/common.config"
},
{
"name": "afl.c test1",
"val-args": " fuzzing/inputs/test1",
"include": "trustinsoft/common.config",
"include": "trustinsoft/fuzz.config"
},
{
"name": "afl.c test10",
"val-args": " fuzzing/inputs/test10",
"include": "trustinsoft/common.config",
"include": "trustinsoft/fuzz.config"
},
{
"name": "afl.c test11",
"val-args": " fuzzing/inputs/test11",
"include": "trustinsoft/common.config",
"include": "trustinsoft/fuzz.config"
},
{
"name": "afl.c test2",
"val-args": " fuzzing/inputs/test2",
"include": "trustinsoft/common.config",
"include": "trustinsoft/fuzz.config"
},
{
"name": "afl.c test3",
"val-args": " fuzzing/inputs/test3",
"include": "trustinsoft/common.config",
"include": "trustinsoft/fuzz.config"
},
{
"name": "afl.c test3.bu",
"val-args": " fuzzing/inputs/test3.bu",
"include": "trustinsoft/common.config",
"include": "trustinsoft/fuzz.config"
},
{
"name": "afl.c test3.uf",
"val-args": " fuzzing/inputs/test3.uf",
"include": "trustinsoft/common.config",
"include": "trustinsoft/fuzz.config"
},
{
"name": "afl.c test3.uu",
"val-args": " fuzzing/inputs/test3.uu",
"include": "trustinsoft/common.config",
"include": "trustinsoft/fuzz.config"
},
{
"name": "afl.c test4",
"val-args": " fuzzing/inputs/test4",
"include": "trustinsoft/common.config",
"include": "trustinsoft/fuzz.config"
},
{
"name": "afl.c test5",
"val-args": " fuzzing/inputs/test5",
"include": "trustinsoft/common.config",
"include": "trustinsoft/fuzz.config"
},
{
"name": "afl.c test6",
"val-args": " fuzzing/inputs/test6",
"include": "trustinsoft/common.config",
"include": "trustinsoft/fuzz.config"
},
{
"name": "afl.c test7",
"val-args": " fuzzing/inputs/test7",
"include": "trustinsoft/common.config",
"include": "trustinsoft/fuzz.config"
},
{
"name": "afl.c test8",
"val-args": " fuzzing/inputs/test8",
"include": "trustinsoft/common.config",
"include": "trustinsoft/fuzz.config"
},
{
"name": "afl.c test9",
"val-args": " fuzzing/inputs/test9",
"include": "trustinsoft/common.config",
"include": "trustinsoft/fuzz.config"
}
]

112
trustinsoft/common.config Normal file
View File

@ -0,0 +1,112 @@
{
"files": [
"../cJSON_Utils.c",
"../tests/unity/src/unity.c"
],
"compilation_cmd": "-DUNITY_EXCLUDE_SETJMP_H",
"val-clone-on-recursive-calls-max-depth": 10000,
"filesystem": {
"files": [
{
"name": "json-patch-tests/cjson-utils-tests.json",
"from": "../tests/json-patch-tests/cjson-utils-tests.json"
},
{
"name": "json-patch-tests/package.json",
"from": "../tests/json-patch-tests/package.json"
},
{
"name": "json-patch-tests/spec_tests.json",
"from": "../tests/json-patch-tests/spec_tests.json"
},
{
"name": "json-patch-tests/tests.json",
"from": "../tests/json-patch-tests/tests.json"
},
{
"name": "inputs/test1",
"from": "../tests/inputs/test1"
},
{
"name": "inputs/test1.expected",
"from": "../tests/inputs/test1.expected"
},
{
"name": "inputs/test10",
"from": "../tests/inputs/test10"
},
{
"name": "inputs/test10.expected",
"from": "../tests/inputs/test10.expected"
},
{
"name": "inputs/test11",
"from": "../tests/inputs/test11"
},
{
"name": "inputs/test11.expected",
"from": "../tests/inputs/test11.expected"
},
{
"name": "inputs/test2",
"from": "../tests/inputs/test2"
},
{
"name": "inputs/test2.expected",
"from": "../tests/inputs/test2.expected"
},
{
"name": "inputs/test3",
"from": "../tests/inputs/test3"
},
{
"name": "inputs/test3.expected",
"from": "../tests/inputs/test3.expected"
},
{
"name": "inputs/test4",
"from": "../tests/inputs/test4"
},
{
"name": "inputs/test4.expected",
"from": "../tests/inputs/test4.expected"
},
{
"name": "inputs/test5",
"from": "../tests/inputs/test5"
},
{
"name": "inputs/test5.expected",
"from": "../tests/inputs/test5.expected"
},
{
"name": "inputs/test6",
"from": "../tests/inputs/test6"
},
{
"name": "inputs/test7",
"from": "../tests/inputs/test7"
},
{
"name": "inputs/test7.expected",
"from": "../tests/inputs/test7.expected"
},
{
"name": "inputs/test8",
"from": "../tests/inputs/test8"
},
{
"name": "inputs/test8.expected",
"from": "../tests/inputs/test8.expected"
},
{
"name": "inputs/test9",
"from": "../tests/inputs/test9"
},
{
"name": "inputs/test9.expected",
"from": "../tests/inputs/test9.expected"
}
]
}
}

66
trustinsoft/fuzz.config Normal file
View File

@ -0,0 +1,66 @@
{
"files": [
"../cJSON.c",
"../fuzzing/afl.c"
],
"filesystem": {
"files": [
{
"name": "fuzzing/inputs/test1",
"from": "../fuzzing/inputs/test1"
},
{
"name": "fuzzing/inputs/test10",
"from": "../fuzzing/inputs/test10"
},
{
"name": "fuzzing/inputs/test11",
"from": "../fuzzing/inputs/test11"
},
{
"name": "fuzzing/inputs/test2",
"from": "../fuzzing/inputs/test2"
},
{
"name": "fuzzing/inputs/test3",
"from": "../fuzzing/inputs/test3"
},
{
"name": "fuzzing/inputs/test3.bu",
"from": "../fuzzing/inputs/test3.bu"
},
{
"name": "fuzzing/inputs/test3.uf",
"from": "../fuzzing/inputs/test3.uf"
},
{
"name": "fuzzing/inputs/test3.uu",
"from": "../fuzzing/inputs/test3.uu"
},
{
"name": "fuzzing/inputs/test4",
"from": "../fuzzing/inputs/test4"
},
{
"name": "fuzzing/inputs/test5",
"from": "../fuzzing/inputs/test5"
},
{
"name": "fuzzing/inputs/test6",
"from": "../fuzzing/inputs/test6"
},
{
"name": "fuzzing/inputs/test7",
"from": "../fuzzing/inputs/test7"
},
{
"name": "fuzzing/inputs/test8",
"from": "../fuzzing/inputs/test8"
},
{
"name": "fuzzing/inputs/test9",
"from": "../fuzzing/inputs/test9"
}
]
}
}

198
trustinsoft/regenerate.py Normal file
View File

@ -0,0 +1,198 @@
#! /usr/bin/env python3
# This script regenerates TrustInSoft CI configuration.
# Run from the root of the cJSON project:
# $ python3 trustinsoft/regenerate.py
import re # sub
import json # dumps, load
from os import path # basename, isdir, join
import glob # iglob
# Outputting JSON.
def string_of_json(obj):
# Output standard pretty-printed JSON (RFC 7159) with 4-space indentation.
s = json.dumps(obj, indent=4)
# Sometimes we need to have multiple "include" fields in the outputted
# JSON, which is unfortunately impossible in the internal python
# representation (OK, it is technically possible, but too cumbersome to
# bother implementing it here), so we can name these fields 'include_',
# 'include__', etc, and they are all converted to 'include' before
# outputting as JSON.
s = re.sub(r'"include_+"', '"include"', s)
return s
# Make a command line from a dictionary of lists.
def string_of_options(options):
elts = []
for opt_prefix in options: # e.g. opt_prefix == "-D"
for opt_value in options[opt_prefix]: # e.g. opt_value == "HAVE_OPEN"
elts.append(opt_prefix + opt_value) # e.g. "-DHAVE_OPEN"
return " ".join(elts)
# Directories.
test_files_dir = "tests"
fuzz_input_dir = path.join("fuzzing", "inputs")
# --------------------------------------------------------------------------- #
# ---------------------------------- CHECKS --------------------------------- #
# --------------------------------------------------------------------------- #
def check_dir(dir):
if path.isdir(dir):
print(" > OK! Directory '%s' exists." % dir)
else:
exit("Directory '%s' not found." % dir)
# Initial check.
print("1. Check if all necessary directories and files exist...")
check_dir("trustinsoft")
check_dir(test_files_dir)
check_dir(fuzz_input_dir)
# --------------------------------------------------------------------------- #
# -------------------- GENERATE trustinsoft/common.config ------------------- #
# --------------------------------------------------------------------------- #
common_config_path = path.join("trustinsoft", "common.config")
def make_common_config():
# C files.
c_files = [
"cJSON_Utils.c",
path.join("tests", "unity", "src", "unity.c"),
]
# Compilation options.
compilation_cmd = (
{
"-I": [],
"-D": [
"UNITY_EXCLUDE_SETJMP_H"
],
"-U": [],
}
)
# Filesystem.
json_patch_tests = list(
map(lambda file:
{
"name": path.join("json-patch-tests", path.basename(file)),
"from": path.join("..", file),
},
sorted(glob.iglob(path.join("tests", "json-patch-tests", "*.json"),
recursive=False)))
)
tests_and_expected = list(
map(lambda file:
{
"name": path.join("inputs", path.basename(file)),
"from": path.join("..", file),
},
sorted(glob.iglob(path.join("tests", "inputs", "test*"),
recursive=False)))
)
# Whole common.config JSON.
config = (
{
"files": list(map(lambda file: path.join("..", file), c_files)),
"compilation_cmd": string_of_options(compilation_cmd),
"val-clone-on-recursive-calls-max-depth": 10000,
"filesystem": { "files": json_patch_tests + tests_and_expected },
}
)
# Done.
return config
common_config = make_common_config()
with open(common_config_path, "w") as file:
print("2. Generate the '%s' file." % common_config_path)
file.write(string_of_json(common_config))
# --------------------------------------------------------------------------- #
# -------------------- GENERATE trustinsoft/fuzz.config --------------------- #
# --------------------------------------------------------------------------- #
fuzz_config_path = path.join("trustinsoft", "fuzz.config")
def make_fuzz_config():
# C files.
c_files = [
"cJSON.c",
path.join("fuzzing", "afl.c"),
]
# Filesystem.
fuzzing_files = list(
map(lambda file:
{
"name": path.join(fuzz_input_dir, path.basename(file)),
"from": path.join("..", file),
},
sorted(glob.iglob(path.join(fuzz_input_dir, "test*"),
recursive=False)))
)
# Whole fuzz.config JSON.
config = (
{
"files": list(map(lambda file: path.join("..", file), c_files)),
"filesystem": { "files": fuzzing_files },
}
)
# Done.
return config
fuzz_config = make_fuzz_config()
with open(fuzz_config_path, "w") as file:
print("3. Generate the '%s' file." % fuzz_config_path)
file.write(string_of_json(fuzz_config))
# --------------------------------------------------------------------------- #
# -------------------------------- tis.config ------------------------------- #
# --------------------------------------------------------------------------- #
exclude_tests = [
"unity_setup.c"
]
def test_files():
test_files = sorted(
glob.iglob(path.join(test_files_dir, "*.c"), recursive=False)
)
for exclude_test in exclude_tests:
test_files.remove(path.join("tests", exclude_test))
return test_files
def make_test(test_file):
basename = path.basename(test_file)
return (
{
"name": basename,
"files": [ test_file ],
"include": common_config_path,
}
)
def fuzz_input_files():
return sorted(
glob.iglob(path.join(fuzz_input_dir, "test*"), recursive=False)
)
def make_fuzz_test(fuzz_input_file):
basename = path.basename(fuzz_input_file)
return (
{
"name": ("afl.c " + basename),
"val-args": " " + path.join(fuzz_input_dir, basename),
"include": common_config_path,
"include_": fuzz_config_path,
}
)
tis_config = (
list(map(make_test, test_files())) +
list(map(make_fuzz_test, fuzz_input_files()))
)
with open("tis.config", "w") as file:
print("4. Generate the 'tis.config' file.")
file.write(string_of_json(tis_config))