| @@ -46,6 +46,7 @@ if (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING AND | |||||
| (NOT MSVC OR NOT (MSVC_VERSION LESS 1800)) # Tests need at least VS2013 | (NOT MSVC OR NOT (MSVC_VERSION LESS 1800)) # Tests need at least VS2013 | ||||
| ) | ) | ||||
| add_subdirectory(tests) | add_subdirectory(tests) | ||||
| add_subdirectory(fuzz) | |||||
| endif() | endif() | ||||
| if (NOT MSVC) # cmd line apps don't built on Windows currently. | if (NOT MSVC) # cmd line apps don't built on Windows currently. | ||||
| @@ -0,0 +1,36 @@ | |||||
| # https://cmake.org/cmake/help/v3.0/command/add_test.html | |||||
| # https://pabloariasal.github.io/2018/02/19/its-time-to-do-cmake-right/ | |||||
| enable_language(CXX) | |||||
| set (CMAKE_CXX_STANDARD 11) | |||||
| include_directories(PUBLIC ${CMAKE_SOURCE_DIR}) | |||||
| include_directories(PUBLIC ${PROJECT_BINARY_DIR}) | |||||
| # This sets up either the standalone runner or the user supplied libfuzzing | |||||
| # engine. We're using the standalone runner for unit testing alone at the | |||||
| # moment to make the sure the fuzzer builds and runs appropriately. | |||||
| if(DEFINED ENV{LIB_FUZZING_ENGINE}) | |||||
| set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} $ENV{LIB_FUZZING_ENGINE}") | |||||
| set(FUZZING_ENGINE "") | |||||
| else() | |||||
| add_library(fuzzing_engine standalone_runner.cc) | |||||
| set(FUZZING_ENGINE fuzzing_engine) | |||||
| endif() | |||||
| foreach(FUZZERNAME | |||||
| tokener_parse_ex_fuzzer) | |||||
| add_executable(${FUZZERNAME} ${FUZZERNAME}.cc) | |||||
| target_link_libraries(${FUZZERNAME} | |||||
| json-c | |||||
| ${FUZZING_ENGINE}) | |||||
| file(GLOB TESTFILES "${CMAKE_SOURCE_DIR}/fuzz/tests/*.json") | |||||
| foreach(TESTFILE ${TESTFILES}) | |||||
| get_filename_component(FILENAME ${TESTFILE} NAME_WE) | |||||
| add_test(NAME test_${FUZZERNAME}_${FILENAME} | |||||
| COMMAND ${CMAKE_BINARY_DIR}/fuzz/${FUZZERNAME} | |||||
| ${TESTFILE}) | |||||
| endforeach(TESTFILE) | |||||
| endforeach(FUZZERNAME) | |||||
| @@ -4,3 +4,23 @@ This directory contains fuzzers that | |||||
| target [llvm's LibFuzzer](https://llvm.org/docs/LibFuzzer.html). They are built | target [llvm's LibFuzzer](https://llvm.org/docs/LibFuzzer.html). They are built | ||||
| and run automatically by | and run automatically by | ||||
| Google's [OSS-Fuzz](https://github.com/google/oss-fuzz/) infrastructure. | Google's [OSS-Fuzz](https://github.com/google/oss-fuzz/) infrastructure. | ||||
| ## How do I test or run the fuzzers like oss-fuzz? | |||||
| ``` | |||||
| git clone https://github.com/google/oss-fuzz.git | |||||
| cd oss-fuzz | |||||
| python infra/helper.py build_image json-c | |||||
| python infra/helper.py build_fuzzers --sanitizer address --engine libfuzzer --architecture x86_64 json-c | |||||
| python infra/helper.py run_fuzzer json-c tokener_parse_ex_fuzzer | |||||
| ``` | |||||
| ## How do I add new unit or regression tests for the fuzzer? | |||||
| The tests directory contains json files that can be used to either test the fuzzer itself or be used as regression tests. As long as the files end in `.json`, cmake will pick them up and generate a Ctest test case. If/when oss-fuzz finds a bug with a fuzzer, simply pull that test case into the `./tests` directory and it will serve as a regression test. | |||||
| Note - the fuzzers are not being run with sanitizers in this repository's CI at the moment; we're strictly building them here to ensure that they function. | |||||
| ## How do I reproduce a failure form a fuzzer? | |||||
| Use [the steps detailed on OSS-fuzz](https://google.github.io/oss-fuzz/advanced-topics/reproducing/). | |||||
| @@ -14,39 +14,22 @@ | |||||
| # limitations under the License. | # limitations under the License. | ||||
| # | # | ||||
| ################################################################################ | ################################################################################ | ||||
| # this is expected to be run only by oss-fuzz. It will run from $SRC (above json-c) | |||||
| cp $SRC/json-c/fuzz/*.dict "$OUT/" | |||||
| # This should be run from the top of the json-c source tree. | |||||
| BUILD="$SRC/json-c/build" | |||||
| mkdir build | |||||
| cd build | |||||
| cmake -DBUILD_SHARED_LIBS=OFF .. | |||||
| make -j$(nproc) | |||||
| LIB=$(pwd)/libjson-c.a | |||||
| cd .. | |||||
| zip -j "$SRC/corpus.zip" "$SRC/go-fuzz-corpus/json/corpus" | |||||
| # These seem to be set externally, but let's assign defaults to | |||||
| # make it possible to at least partially test this standalone. | |||||
| : ${SRC:=$(dirname "$0")} | |||||
| : ${OUT:=$SRC/out} | |||||
| : ${CXX:=gcc} | |||||
| : ${CXXFLAGS:=} | |||||
| [ -d "$OUT" ] || mkdir "$OUT" | |||||
| cp $SRC/*.dict $OUT/. | |||||
| mkdir "$BUILD" | |||||
| cd "$BUILD" | |||||
| cmake -DCMAKE_C_FLAGS="$CFLAGS" -DCMAKE_CXX_FLAGS="$CXXFLAGS" -DBUILD_SHARED_LIBS=OFF .. | |||||
| make -j$(nproc) | |||||
| cp fuzz/*_fuzzer "$OUT/" | |||||
| # XXX this doesn't seem to make much sense, since $SRC is presumably | |||||
| # the "fuzz" directory, which is _inside_ the json-c repo, rather than | |||||
| # the other way around, but I'm just preserving existing behavior. -erh | |||||
| INCS=$SRC/json-c | |||||
| # Compat when testing standalone | |||||
| [ -e "${INCS}" ] || ln -s .. "${INCS}" | |||||
| fuzzerFiles=$(find fuzz/ -name "*_fuzzer") | |||||
| set -x | |||||
| set -v | |||||
| for f in $SRC/*_fuzzer.cc; do | |||||
| fuzzer=$(basename "$f" _fuzzer.cc) | |||||
| $CXX $CXXFLAGS -std=c++11 -I$INCS \ | |||||
| $SRC/${fuzzer}_fuzzer.cc -o $OUT/${fuzzer}_fuzzer \ | |||||
| -lFuzzingEngine $LIB | |||||
| for F in $fuzzerFiles; do | |||||
| FN=$(basename -- $F) | |||||
| cp "$SRC/corpus.zip" "$OUT/${FN}_seed_corpus.zip" | |||||
| done | done | ||||
| @@ -0,0 +1,25 @@ | |||||
| #include <cassert> | |||||
| #include <fstream> | |||||
| #include <iostream> | |||||
| #include <vector> | |||||
| // Forward declare the "fuzz target" interface. | |||||
| // We deliberately keep this inteface simple and header-free. | |||||
| extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); | |||||
| int main(int argc, char **argv) { | |||||
| for (int i = 1; i < argc; i++) { | |||||
| std::ifstream in(argv[i]); | |||||
| in.seekg(0, in.end); | |||||
| size_t length = in.tellg(); | |||||
| in.seekg(0, in.beg); | |||||
| std::cout << "Reading " << length << " bytes from " << argv[i] << std::endl; | |||||
| // Allocate exactly length bytes so that we reliably catch buffer overflows. | |||||
| std::vector<char> bytes(length); | |||||
| in.read(bytes.data(), bytes.size()); | |||||
| assert(in); | |||||
| LLVMFuzzerTestOneInput(reinterpret_cast<const uint8_t *>(bytes.data()), | |||||
| bytes.size()); | |||||
| std::cout << "Execution successful" << std::endl; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1 @@ | |||||
| {"foo":123} | |||||