mirror of
https://github.com/nlohmann/json.git
synced 2026-09-30 14:05:18 +00:00
Add a fuzzer for json_document
fuzzer-parse_json_view.cpp checks for every input that json_document::accept agrees with json::accept, that an accepted input materializes to the value json::parse returns, and that a rejected input makes both throw the same exception with the same message. It is built like the other fuzzers (tests/Makefile, and the root Makefile's fuzz_testing_json_view target, which starts from the JSON test corpus) and listed in tests/fuzzing.md. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
9
Makefile
9
Makefile
@@ -40,6 +40,7 @@ all:
|
||||
@echo "fuzz_testing_bon8 - prepare fuzz testing of the BON8 parser"
|
||||
@echo "fuzz_testing_bson - prepare fuzz testing of the BSON parser"
|
||||
@echo "fuzz_testing_cbor - prepare fuzz testing of the CBOR parser"
|
||||
@echo "fuzz_testing_json_view - prepare fuzz testing of the json_document/json_view parser"
|
||||
@echo "fuzz_testing_msgpack - prepare fuzz testing of the MessagePack parser"
|
||||
@echo "fuzz_testing_ubjson - prepare fuzz testing of the UBJSON parser"
|
||||
@echo "pretty - beautify code with Artistic Style"
|
||||
@@ -97,6 +98,14 @@ fuzz_testing_cbor:
|
||||
find tests/data -size -5k -name *.cbor | xargs -I{} cp "{}" fuzz-testing/testcases
|
||||
@echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer"
|
||||
|
||||
fuzz_testing_json_view:
|
||||
rm -fr fuzz-testing
|
||||
mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out
|
||||
$(MAKE) parse_json_view_fuzzer -C tests CXX=afl-clang++
|
||||
mv tests/parse_json_view_fuzzer fuzz-testing/fuzzer
|
||||
find tests/data/json_tests -size -5k -name *json | xargs -I{} cp "{}" fuzz-testing/testcases
|
||||
@echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer"
|
||||
|
||||
fuzz_testing_msgpack:
|
||||
rm -fr fuzz-testing
|
||||
mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out
|
||||
|
||||
@@ -10,12 +10,15 @@ CXXFLAGS += -std=c++11
|
||||
CPPFLAGS += -I ../single_include
|
||||
|
||||
FUZZER_ENGINE = src/fuzzer-driver_afl.cpp
|
||||
FUZZERS = parse_afl_fuzzer parse_bson_fuzzer parse_cbor_fuzzer parse_msgpack_fuzzer parse_ubjson_fuzzer parse_bjdata_fuzzer parse_bon8_fuzzer
|
||||
FUZZERS = parse_afl_fuzzer parse_bson_fuzzer parse_cbor_fuzzer parse_msgpack_fuzzer parse_ubjson_fuzzer parse_bjdata_fuzzer parse_bon8_fuzzer parse_json_view_fuzzer
|
||||
fuzzers: $(FUZZERS)
|
||||
|
||||
parse_afl_fuzzer:
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(FUZZER_ENGINE) src/fuzzer-parse_json.cpp -o $@
|
||||
|
||||
parse_json_view_fuzzer:
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(FUZZER_ENGINE) src/fuzzer-parse_json_view.cpp -o $@
|
||||
|
||||
parse_bson_fuzzer:
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(FUZZER_ENGINE) src/fuzzer-parse_bson.cpp -o $@
|
||||
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
Each parser of the library (JSON, BJData, BON8, BSON, CBOR, MessagePack, and UBJSON) can be fuzz tested. Currently,
|
||||
[libFuzzer](https://llvm.org/docs/LibFuzzer.html) and [afl++](https://github.com/AFLplusplus/AFLplusplus) are supported.
|
||||
|
||||
Additionally, `parse_json_view_fuzzer` (`tests/src/fuzzer-parse_json_view.cpp`) cross-checks `json_document`/`json_view`
|
||||
(the zero-copy, read-only view declared in `json_view.hpp`) against `basic_json` on the same JSON text: it asserts that
|
||||
`json_document::accept` agrees with `json::accept`, that an accepted input materializes to the same value `json::parse`
|
||||
produces, and that a rejected input makes both parsers throw with an identical `what()`. It takes plain JSON text, so it
|
||||
reuses the `corpus_json` corpus (or, for the `make fuzz_testing_json_view` target below, `tests/data/json_tests`) rather
|
||||
than a format of its own.
|
||||
|
||||
## Corpus creation
|
||||
|
||||
For most effective fuzzing, a [corpus](https://llvm.org/docs/LibFuzzer.html#corpus) should be provided. A corpus is a
|
||||
|
||||
100
tests/src/fuzzer-parse_json_view.cpp
Normal file
100
tests/src/fuzzer-parse_json_view.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/*
|
||||
This file implements a parser test suitable for fuzz testing. It checks that
|
||||
json_document (the zero-copy, read-only view of a parsed JSON text declared in
|
||||
json_view.hpp) agrees with basic_json on every input:
|
||||
|
||||
- json_document::accept(data) must equal json::accept(data)
|
||||
- if the input is accepted, json_document::parse(data).root().materialize()
|
||||
must equal json::parse(data)
|
||||
- if the input is rejected, json_document::parse(data) (with exceptions
|
||||
enabled) must throw a json::parse_error or json::out_of_range whose what()
|
||||
is identical to the one json::parse(data) throws
|
||||
|
||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
// the checks below are assertions; NDEBUG would compile them away
|
||||
#ifdef NDEBUG
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
using json = nlohmann::json;
|
||||
using json_document = nlohmann::json_document;
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// json_document::accept only has a single-argument overload; wrap the raw
|
||||
// bytes in a (borrowed) std::string so the same bytes can be handed to it
|
||||
const std::string input(reinterpret_cast<const char*>(data), size); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
|
||||
const bool accepted_by_json = json::accept(data, data + size);
|
||||
const bool accepted_by_view = json_document::accept(input);
|
||||
|
||||
// json_document::accept must agree with json::accept on every input
|
||||
assert(accepted_by_json == accepted_by_view);
|
||||
|
||||
if (accepted_by_json)
|
||||
{
|
||||
// both parsers must agree on the resulting value
|
||||
json const j1 = json::parse(data, data + size);
|
||||
json_document const doc = json_document::parse(input);
|
||||
json const j2 = doc.root().materialize();
|
||||
assert(j1 == j2);
|
||||
}
|
||||
else
|
||||
{
|
||||
// both parsers must reject the input the same way when exceptions are used
|
||||
std::string expected_what;
|
||||
bool json_threw = false;
|
||||
try
|
||||
{
|
||||
static_cast<void>(json::parse(data, data + size));
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
expected_what = e.what();
|
||||
json_threw = true;
|
||||
}
|
||||
catch (const json::out_of_range& e)
|
||||
{
|
||||
expected_what = e.what();
|
||||
json_threw = true;
|
||||
}
|
||||
assert(json_threw);
|
||||
|
||||
bool view_threw = false;
|
||||
try
|
||||
{
|
||||
static_cast<void>(json_document::parse(input));
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
assert(e.what() == expected_what);
|
||||
view_threw = true;
|
||||
}
|
||||
catch (const json::out_of_range& e)
|
||||
{
|
||||
assert(e.what() == expected_what);
|
||||
view_threw = true;
|
||||
}
|
||||
assert(view_threw);
|
||||
}
|
||||
|
||||
// return 0 - non-zero return values are reserved for future use
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user