base: Add a fuzzer for JSON
This commit is contained in:
90
lib/base/FUZZING.md
Normal file
90
lib/base/FUZZING.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# Fuzzing lib/base
|
||||
|
||||
This directory contains a fuzzer for the JSON parser (`json.c`).
|
||||
|
||||
## fuzz_json
|
||||
|
||||
Fuzzes `heim_json_create_with_bytes()` and `heim_json_copy_serialize()` with
|
||||
various parsing flags and depth limits.
|
||||
|
||||
### Building
|
||||
|
||||
#### Standalone (for testing)
|
||||
|
||||
```bash
|
||||
cd build
|
||||
make -C lib/base fuzz_json
|
||||
```
|
||||
|
||||
#### With libFuzzer + AddressSanitizer (recommended)
|
||||
|
||||
```bash
|
||||
cd build
|
||||
CC=clang CXX=clang++ \
|
||||
CFLAGS="-fsanitize=fuzzer-no-link,address -g -O1" \
|
||||
LDFLAGS="-fsanitize=fuzzer,address" \
|
||||
../configure --enable-maintainer-mode --enable-developer
|
||||
|
||||
make -C lib/base fuzz_json
|
||||
```
|
||||
|
||||
#### With AFL++
|
||||
|
||||
```bash
|
||||
cd build
|
||||
CC=afl-clang-fast CXX=afl-clang-fast++ \
|
||||
../configure --enable-maintainer-mode --enable-developer
|
||||
|
||||
make -C lib/base fuzz_json
|
||||
```
|
||||
|
||||
### Running
|
||||
|
||||
#### Standalone mode (reads from files or stdin)
|
||||
|
||||
```bash
|
||||
# Test with corpus files
|
||||
./lib/base/fuzz_json ../lib/base/fuzz_json_corpus/*.json
|
||||
|
||||
# Test single input
|
||||
echo '{"test": [1,2,3]}' | ./lib/base/fuzz_json
|
||||
```
|
||||
|
||||
#### libFuzzer mode
|
||||
|
||||
```bash
|
||||
# Basic fuzzing
|
||||
./lib/base/fuzz_json ../lib/base/fuzz_json_corpus/
|
||||
|
||||
# With options
|
||||
./lib/base/fuzz_json ../lib/base/fuzz_json_corpus/ \
|
||||
-max_len=262144 \
|
||||
-timeout=10 \
|
||||
-jobs=4 \
|
||||
-workers=4
|
||||
```
|
||||
|
||||
#### AFL++ mode
|
||||
|
||||
```bash
|
||||
afl-fuzz -i ../lib/base/fuzz_json_corpus -o findings -- ./lib/base/fuzz_json @@
|
||||
```
|
||||
|
||||
### Seed Corpus
|
||||
|
||||
The `fuzz_json_corpus/` directory contains seed inputs covering:
|
||||
|
||||
- Basic JSON types (null, true, false, numbers, strings)
|
||||
- Unicode literals and escape sequences (`\uXXXX`)
|
||||
- Arrays and objects (empty, nested, deep)
|
||||
- Edge cases (empty keys, whitespace variations, huge integers)
|
||||
- Malformed inputs (unclosed brackets, missing values, trailing commas)
|
||||
- JWT-like payloads (common real-world use case)
|
||||
|
||||
### What it tests
|
||||
|
||||
1. **Default parsing** - `heim_json_create_with_bytes()` with depth limit 10
|
||||
2. **Strict mode** - `HEIM_JSON_F_STRICT` flag (rejects some permissive inputs)
|
||||
3. **Shallow depth** - Depth limit 2 (rejects deep nesting)
|
||||
4. **Null handling** - `HEIM_JSON_F_NO_C_NULL` flag
|
||||
5. **Round-trip** - Parse, serialize, re-parse to verify consistency
|
||||
@@ -82,6 +82,12 @@ libheimbase_la_DEPENDENCIES = version-script.map
|
||||
|
||||
test_base_LDADD = libheimbase.la $(LIB_roken)
|
||||
|
||||
# Fuzzer for JSON parser (build with: make fuzz_json CFLAGS="-fsanitize=fuzzer,address")
|
||||
noinst_PROGRAMS = fuzz_json
|
||||
fuzz_json_SOURCES = fuzz_json.c
|
||||
fuzz_json_LDADD = libheimbase.la $(LIB_roken)
|
||||
fuzz_json.$(OBJEXT): $(srcdir)/heimbase-protos.h heim_err.h
|
||||
|
||||
CLEANFILES = base64.c test_db.json heim_err.c heim_err.h
|
||||
|
||||
EXTRA_DIST = NTMakefile version-script.map config_reg.c heim_err.et
|
||||
|
||||
190
lib/base/fuzz_json.c
Normal file
190
lib/base/fuzz_json.c
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Kungliga Tekniska Högskolan
|
||||
* (Royal Institute of Technology, Stockholm, Sweden).
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the Institute nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* libFuzzer harness for JSON parser in lib/base/json.c
|
||||
*
|
||||
* Build with:
|
||||
* clang -g -O1 -fno-omit-frame-pointer -fsanitize=fuzzer,address \
|
||||
* fuzz_json.c -o fuzz_json -lheimbase -lroken
|
||||
*
|
||||
* Run with:
|
||||
* ./fuzz_json corpus_dir/
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <heimbase.h>
|
||||
|
||||
/* libFuzzer entry points */
|
||||
int LLVMFuzzerInitialize(int *argc, char ***argv);
|
||||
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
|
||||
|
||||
int LLVMFuzzerInitialize(int *argc, char ***argv)
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Main fuzzing entry point.
|
||||
* Input is treated as JSON to parse.
|
||||
*/
|
||||
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||
{
|
||||
heim_object_t obj = NULL;
|
||||
heim_string_t serialized = NULL;
|
||||
heim_object_t obj2 = NULL;
|
||||
heim_error_t error = NULL;
|
||||
|
||||
/* Limit input size to avoid OOM on deeply nested structures */
|
||||
if (size > 256 * 1024)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Test 1: Parse with default flags, various depth limits.
|
||||
* This exercises the core parsing logic.
|
||||
*/
|
||||
obj = heim_json_create_with_bytes(data, size, 10, 0, &error);
|
||||
if (obj) {
|
||||
/*
|
||||
* Test 2: Serialize back to JSON.
|
||||
* This exercises the serialization logic and ensures
|
||||
* the parsed structure is well-formed.
|
||||
*/
|
||||
serialized = heim_json_copy_serialize(obj, 0, NULL);
|
||||
if (serialized) {
|
||||
/*
|
||||
* Test 3: Re-parse the serialized output.
|
||||
* This is a sanity check - if we serialized it,
|
||||
* we should be able to parse it again.
|
||||
*/
|
||||
obj2 = heim_json_create(heim_string_get_utf8(serialized), 10, 0, NULL);
|
||||
heim_release(obj2);
|
||||
heim_release(serialized);
|
||||
}
|
||||
heim_release(obj);
|
||||
}
|
||||
heim_release(error);
|
||||
|
||||
/*
|
||||
* Test 4: Parse with HEIM_JSON_F_STRICT flag.
|
||||
* Strict mode rejects some inputs that permissive mode accepts.
|
||||
*/
|
||||
error = NULL;
|
||||
obj = heim_json_create_with_bytes(data, size, 10, HEIM_JSON_F_STRICT, &error);
|
||||
if (obj) {
|
||||
serialized = heim_json_copy_serialize(obj, HEIM_JSON_F_STRICT, NULL);
|
||||
heim_release(serialized);
|
||||
heim_release(obj);
|
||||
}
|
||||
heim_release(error);
|
||||
|
||||
/*
|
||||
* Test 5: Parse with shallow depth limit.
|
||||
* This should reject deeply nested structures.
|
||||
*/
|
||||
error = NULL;
|
||||
obj = heim_json_create_with_bytes(data, size, 2, 0, &error);
|
||||
heim_release(obj);
|
||||
heim_release(error);
|
||||
|
||||
/*
|
||||
* Test 6: Parse with HEIM_JSON_F_NO_C_NULL flag.
|
||||
* This affects handling of \u0000 escape sequences.
|
||||
*/
|
||||
error = NULL;
|
||||
obj = heim_json_create_with_bytes(data, size, 10, HEIM_JSON_F_NO_C_NULL, &error);
|
||||
if (obj) {
|
||||
serialized = heim_json_copy_serialize(obj, HEIM_JSON_F_NO_C_NULL, NULL);
|
||||
heim_release(serialized);
|
||||
heim_release(obj);
|
||||
}
|
||||
heim_release(error);
|
||||
|
||||
/*
|
||||
* Test 7: Try with one-line serialization flag.
|
||||
*/
|
||||
error = NULL;
|
||||
obj = heim_json_create_with_bytes(data, size, 10, 0, &error);
|
||||
if (obj) {
|
||||
serialized = heim_json_copy_serialize(obj, HEIM_JSON_F_ONE_LINE, NULL);
|
||||
heim_release(serialized);
|
||||
heim_release(obj);
|
||||
}
|
||||
heim_release(error);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef HAS_LIBFUZZER_MAIN
|
||||
/*
|
||||
* Standalone mode for testing without libFuzzer.
|
||||
* Reads input from stdin or file arguments.
|
||||
*/
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
uint8_t buf[256 * 1024];
|
||||
size_t len;
|
||||
FILE *fp;
|
||||
int i;
|
||||
|
||||
LLVMFuzzerInitialize(&argc, &argv);
|
||||
|
||||
if (argc < 2) {
|
||||
/* Read from stdin */
|
||||
len = fread(buf, 1, sizeof(buf), stdin);
|
||||
if (len > 0)
|
||||
LLVMFuzzerTestOneInput(buf, len);
|
||||
} else {
|
||||
/* Read from each file argument */
|
||||
for (i = 1; i < argc; i++) {
|
||||
fp = fopen(argv[i], "rb");
|
||||
if (fp == NULL)
|
||||
continue;
|
||||
len = fread(buf, 1, sizeof(buf), fp);
|
||||
fclose(fp);
|
||||
if (len > 0)
|
||||
LLVMFuzzerTestOneInput(buf, len);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
1
lib/base/fuzz_json_corpus/bad_escape.json
Normal file
1
lib/base/fuzz_json_corpus/bad_escape.json
Normal file
@@ -0,0 +1 @@
|
||||
"\x
|
||||
1
lib/base/fuzz_json_corpus/deep_nested.json
Normal file
1
lib/base/fuzz_json_corpus/deep_nested.json
Normal file
@@ -0,0 +1 @@
|
||||
[1,[2,[3,[4,[5,[6,[7,[8,[9,[10]]]]]]]]]]
|
||||
1
lib/base/fuzz_json_corpus/empty_array.json
Normal file
1
lib/base/fuzz_json_corpus/empty_array.json
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
||||
1
lib/base/fuzz_json_corpus/empty_key.json
Normal file
1
lib/base/fuzz_json_corpus/empty_key.json
Normal file
@@ -0,0 +1 @@
|
||||
{"":null}
|
||||
1
lib/base/fuzz_json_corpus/empty_object.json
Normal file
1
lib/base/fuzz_json_corpus/empty_object.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
1
lib/base/fuzz_json_corpus/empty_string.json
Normal file
1
lib/base/fuzz_json_corpus/empty_string.json
Normal file
@@ -0,0 +1 @@
|
||||
""
|
||||
1
lib/base/fuzz_json_corpus/escape_chars.json
Normal file
1
lib/base/fuzz_json_corpus/escape_chars.json
Normal file
@@ -0,0 +1 @@
|
||||
"\\n\\r\\t\\\\"
|
||||
1
lib/base/fuzz_json_corpus/false.json
Normal file
1
lib/base/fuzz_json_corpus/false.json
Normal file
@@ -0,0 +1 @@
|
||||
false
|
||||
1
lib/base/fuzz_json_corpus/huge_int.json
Normal file
1
lib/base/fuzz_json_corpus/huge_int.json
Normal file
@@ -0,0 +1 @@
|
||||
99999999999999999999999999999999
|
||||
1
lib/base/fuzz_json_corpus/int.json
Normal file
1
lib/base/fuzz_json_corpus/int.json
Normal file
@@ -0,0 +1 @@
|
||||
123456789
|
||||
1
lib/base/fuzz_json_corpus/int_array.json
Normal file
1
lib/base/fuzz_json_corpus/int_array.json
Normal file
@@ -0,0 +1 @@
|
||||
[1,2,3]
|
||||
1
lib/base/fuzz_json_corpus/invalid_utf8.json
Normal file
1
lib/base/fuzz_json_corpus/invalid_utf8.json
Normal file
@@ -0,0 +1 @@
|
||||
"<22><>"
|
||||
1
lib/base/fuzz_json_corpus/jwt_aud_array.json
Normal file
1
lib/base/fuzz_json_corpus/jwt_aud_array.json
Normal file
@@ -0,0 +1 @@
|
||||
{"aud":["a","b","c"],"iat":1234567890}
|
||||
1
lib/base/fuzz_json_corpus/jwt_claims.json
Normal file
1
lib/base/fuzz_json_corpus/jwt_claims.json
Normal file
@@ -0,0 +1 @@
|
||||
{"iss":"joe","exp":1300819380,"sub":"user@example.com"}
|
||||
1
lib/base/fuzz_json_corpus/jwt_header.json
Normal file
1
lib/base/fuzz_json_corpus/jwt_header.json
Normal file
@@ -0,0 +1 @@
|
||||
{"alg":"RS256","typ":"JWT"}
|
||||
1
lib/base/fuzz_json_corpus/lone_comma.json
Normal file
1
lib/base/fuzz_json_corpus/lone_comma.json
Normal file
@@ -0,0 +1 @@
|
||||
[,]
|
||||
1
lib/base/fuzz_json_corpus/max_int64.json
Normal file
1
lib/base/fuzz_json_corpus/max_int64.json
Normal file
@@ -0,0 +1 @@
|
||||
9223372036854775807
|
||||
1
lib/base/fuzz_json_corpus/min_int64.json
Normal file
1
lib/base/fuzz_json_corpus/min_int64.json
Normal file
@@ -0,0 +1 @@
|
||||
-9223372036854775808
|
||||
1
lib/base/fuzz_json_corpus/missing_comma.json
Normal file
1
lib/base/fuzz_json_corpus/missing_comma.json
Normal file
@@ -0,0 +1 @@
|
||||
{"a":1 "b":2}
|
||||
1
lib/base/fuzz_json_corpus/missing_key.json
Normal file
1
lib/base/fuzz_json_corpus/missing_key.json
Normal file
@@ -0,0 +1 @@
|
||||
{:1}
|
||||
1
lib/base/fuzz_json_corpus/missing_value.json
Normal file
1
lib/base/fuzz_json_corpus/missing_value.json
Normal file
@@ -0,0 +1 @@
|
||||
{"key":}
|
||||
1
lib/base/fuzz_json_corpus/mixed.json
Normal file
1
lib/base/fuzz_json_corpus/mixed.json
Normal file
@@ -0,0 +1 @@
|
||||
{"array":[1,2,3],"string":"hello","number":42}
|
||||
1
lib/base/fuzz_json_corpus/multi_object.json
Normal file
1
lib/base/fuzz_json_corpus/multi_object.json
Normal file
@@ -0,0 +1 @@
|
||||
{"a":1,"b":2,"c":3}
|
||||
1
lib/base/fuzz_json_corpus/neg.json
Normal file
1
lib/base/fuzz_json_corpus/neg.json
Normal file
@@ -0,0 +1 @@
|
||||
-1
|
||||
1
lib/base/fuzz_json_corpus/neg_zero.json
Normal file
1
lib/base/fuzz_json_corpus/neg_zero.json
Normal file
@@ -0,0 +1 @@
|
||||
-0
|
||||
1
lib/base/fuzz_json_corpus/nested_arrays.json
Normal file
1
lib/base/fuzz_json_corpus/nested_arrays.json
Normal file
@@ -0,0 +1 @@
|
||||
[[[]]]
|
||||
1
lib/base/fuzz_json_corpus/nested_object.json
Normal file
1
lib/base/fuzz_json_corpus/nested_object.json
Normal file
@@ -0,0 +1 @@
|
||||
{"nested":{"deep":{"value":true}}}
|
||||
4
lib/base/fuzz_json_corpus/newlines.json
Normal file
4
lib/base/fuzz_json_corpus/newlines.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"key":
|
||||
"value"
|
||||
}
|
||||
1
lib/base/fuzz_json_corpus/null.json
Normal file
1
lib/base/fuzz_json_corpus/null.json
Normal file
@@ -0,0 +1 @@
|
||||
null
|
||||
1
lib/base/fuzz_json_corpus/null_escape.json
Normal file
1
lib/base/fuzz_json_corpus/null_escape.json
Normal file
@@ -0,0 +1 @@
|
||||
"\u0000"
|
||||
BIN
lib/base/fuzz_json_corpus/null_in_key.json
Normal file
BIN
lib/base/fuzz_json_corpus/null_in_key.json
Normal file
Binary file not shown.
1
lib/base/fuzz_json_corpus/obj_trailing_comma.json
Normal file
1
lib/base/fuzz_json_corpus/obj_trailing_comma.json
Normal file
@@ -0,0 +1 @@
|
||||
{"a":1,}
|
||||
1
lib/base/fuzz_json_corpus/simple_object.json
Normal file
1
lib/base/fuzz_json_corpus/simple_object.json
Normal file
@@ -0,0 +1 @@
|
||||
{"a":1}
|
||||
1
lib/base/fuzz_json_corpus/single_array.json
Normal file
1
lib/base/fuzz_json_corpus/single_array.json
Normal file
@@ -0,0 +1 @@
|
||||
[1]
|
||||
1
lib/base/fuzz_json_corpus/space_key.json
Normal file
1
lib/base/fuzz_json_corpus/space_key.json
Normal file
@@ -0,0 +1 @@
|
||||
{"key with spaces":"value"}
|
||||
1
lib/base/fuzz_json_corpus/string.json
Normal file
1
lib/base/fuzz_json_corpus/string.json
Normal file
@@ -0,0 +1 @@
|
||||
"hello"
|
||||
1
lib/base/fuzz_json_corpus/string_array.json
Normal file
1
lib/base/fuzz_json_corpus/string_array.json
Normal file
@@ -0,0 +1 @@
|
||||
["a","b","c"]
|
||||
1
lib/base/fuzz_json_corpus/trailing_comma.json
Normal file
1
lib/base/fuzz_json_corpus/trailing_comma.json
Normal file
@@ -0,0 +1 @@
|
||||
[1,]
|
||||
1
lib/base/fuzz_json_corpus/true.json
Normal file
1
lib/base/fuzz_json_corpus/true.json
Normal file
@@ -0,0 +1 @@
|
||||
true
|
||||
1
lib/base/fuzz_json_corpus/unclosed_arr.json
Normal file
1
lib/base/fuzz_json_corpus/unclosed_arr.json
Normal file
@@ -0,0 +1 @@
|
||||
[
|
||||
1
lib/base/fuzz_json_corpus/unclosed_obj.json
Normal file
1
lib/base/fuzz_json_corpus/unclosed_obj.json
Normal file
@@ -0,0 +1 @@
|
||||
{
|
||||
1
lib/base/fuzz_json_corpus/unicode_escape.json
Normal file
1
lib/base/fuzz_json_corpus/unicode_escape.json
Normal file
@@ -0,0 +1 @@
|
||||
"\u0041\u0042\u0043"
|
||||
1
lib/base/fuzz_json_corpus/unicode_literal.json
Normal file
1
lib/base/fuzz_json_corpus/unicode_literal.json
Normal file
@@ -0,0 +1 @@
|
||||
"日本語"
|
||||
1
lib/base/fuzz_json_corpus/unterminated_string.json
Normal file
1
lib/base/fuzz_json_corpus/unterminated_string.json
Normal file
@@ -0,0 +1 @@
|
||||
"unterminated
|
||||
1
lib/base/fuzz_json_corpus/whitespace.json
Normal file
1
lib/base/fuzz_json_corpus/whitespace.json
Normal file
@@ -0,0 +1 @@
|
||||
{ "key" : "value" }
|
||||
1
lib/base/fuzz_json_corpus/zero.json
Normal file
1
lib/base/fuzz_json_corpus/zero.json
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
lib/base/fuzz_json_corpus/zero_num.json
Normal file
1
lib/base/fuzz_json_corpus/zero_num.json
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
Reference in New Issue
Block a user