diff --git a/.gitignore b/.gitignore index 104befbe27..eedc964117 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ cmake-build-debug test/test-* .svn + +test/thirdparty/Fuzzer/libFuzzer.a diff --git a/Makefile b/Makefile index fcce453e29..8278dbf217 100644 --- a/Makefile +++ b/Makefile @@ -49,14 +49,10 @@ doctest: fuzz_testing: rm -fr fuzz-testing mkdir -p fuzz-testing fuzz-testing/testcases fuzz-testing/out - $(MAKE) fuzz CXX=afl-clang++ - mv fuzz fuzz-testing + $(MAKE) parse_afl_fuzzer -C test CXX=afl-clang++ + mv test/fuzzer parse_afl_fuzzer find test/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/fuzz" - -# the fuzzer binary -fuzz: test/src/fuzz.cpp src/json.hpp - $(CXX) -std=c++11 $(CXXFLAGS) $(FLAGS) $(CPPFLAGS) -I src $< $(LDFLAGS) -o $@ + @echo "Execute: afl-fuzz -i fuzz-testing/testcases -o fuzz-testing/out fuzz-testing/fuzzer" ########################################################################## diff --git a/test/Makefile b/test/Makefile index c1fb33f453..da67999850 100644 --- a/test/Makefile +++ b/test/Makefile @@ -78,3 +78,11 @@ TEST_PATTERN = "*" TEST_PREFIX = "" check: $(TESTCASES) @cd .. ; for testcase in $(TESTCASES); do echo "Executing $$testcase..."; $(TEST_PREFIX)test/$$testcase $(TEST_PATTERN) || exit 1; done + + +############################################################################## +# fuzzer +############################################################################## + +parse_afl_fuzzer: + $(CXX) $(CXXFLAGS) $(CPPFLAGS) src/fuzzer-driver_afl.cpp src/fuzzer-parse_json.cpp -o $@ diff --git a/test/src/fuzz.cpp b/test/src/fuzz.cpp deleted file mode 100644 index ef403ea8cd..0000000000 --- a/test/src/fuzz.cpp +++ /dev/null @@ -1,34 +0,0 @@ -/* - __ _____ _____ _____ - __| | __| | | | JSON for Modern C++ (fuzz test support) -| | |__ | | | | | | version 2.0.9 -|_____|_____|_____|_|___| https://github.com/nlohmann/json - -Run "make fuzz_testing" and follow the instructions. - -Licensed under the MIT License . -*/ - -#include - -using json = nlohmann::json; - -int main() -{ -#ifdef __AFL_HAVE_MANUAL_CONTROL - while (__AFL_LOOP(1000)) - { -#endif - try - { - json j(std::cin); - std::cout << j << std::endl; - } - catch (std::invalid_argument& e) - { - std::cout << "Invalid argument in parsing" << e.what() << '\n'; - } -#ifdef __AFL_HAVE_MANUAL_CONTROL - } -#endif -} diff --git a/test/src/fuzzer-driver_afl.cpp b/test/src/fuzzer-driver_afl.cpp new file mode 100644 index 0000000000..e386033a49 --- /dev/null +++ b/test/src/fuzzer-driver_afl.cpp @@ -0,0 +1,33 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ (fuzz test support) +| | |__ | | | | | | version 2.0.9 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +This file implements a driver for American Fuzzy Lop (afl-fuzz). It relies on +an implementation of the `LLVMFuzzerTestOneInput` function which processes a +passed byte array. + +Licensed under the MIT License . +*/ + +#include +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size); + +int main() +{ +#ifdef __AFL_HAVE_MANUAL_CONTROL + while (__AFL_LOOP(1000)) + { +#endif + // copy stdin to stringstream to pass it to fuzzer as byte array + std::stringstream ss; + ss << std::cin.rdbuf(); + LLVMFuzzerTestOneInput(reinterpret_cast(ss.str().c_str()), ss.str().size()); +#ifdef __AFL_HAVE_MANUAL_CONTROL + } +#endif +} diff --git a/test/src/fuzzer-parse_json.cpp b/test/src/fuzzer-parse_json.cpp index 20a824db90..51ac440df1 100644 --- a/test/src/fuzzer-parse_json.cpp +++ b/test/src/fuzzer-parse_json.cpp @@ -1,16 +1,23 @@ -// Copyright 2016 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ (fuzz test support) +| | |__ | | | | | | version 2.0.9 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +This file implements a parser test suitable for fuzz testing. Given a byte +array data, it performs the following steps: + +- j1 = parse(data) +- s1 = serialize(j1) +- j2 = parse(s1) +- s2 = serialize(j2) +- assert(s1 == s2) + +The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer +drivers. + +Licensed under the MIT License . +*/ #include #include @@ -18,25 +25,41 @@ using json = nlohmann::json; +// see http://llvm.org/docs/LibFuzzer.html extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { try { - std::stringstream s; - s << json::parse(data, data + size); + // step 1: parse input + json j1 = json::parse(data, data + size); + try { - auto j = json::parse(s.str()); - std::stringstream s2; - s2 << j; - assert(s.str() == s2.str()); - assert(j == json::parse(s.str())); + // step 2: round trip + + // first serialization + std::string s1 = j1.dump(); + + // parse serialization + json j2 = json::parse(s1); + + // second serialization + std::string s2 = j2.dump(); + + // serializations must match + assert(s1 == s2); } catch (const std::invalid_argument&) { - assert(0); + // parsing a JSON serialization must not fail + assert(false); } } - catch (const std::invalid_argument&) { } + catch (const std::invalid_argument&) + { + // parse errors are ok, because input may be random bytes + } + + // return 0 - non-zero return values are reserved for future use return 0; }