diff --git a/CMakeLists.txt b/CMakeLists.txt index 45138c1..5936062 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 ) add_subdirectory(tests) +add_subdirectory(fuzz) endif() if (NOT MSVC) # cmd line apps don't built on Windows currently. diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt new file mode 100644 index 0000000..eecf030 --- /dev/null +++ b/fuzz/CMakeLists.txt @@ -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) diff --git a/fuzz/README.md b/fuzz/README.md index 237c1da..8962690 100644 --- a/fuzz/README.md +++ b/fuzz/README.md @@ -4,3 +4,23 @@ This directory contains fuzzers that target [llvm's LibFuzzer](https://llvm.org/docs/LibFuzzer.html). They are built and run automatically by 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/). diff --git a/fuzz/build.sh b/fuzz/build.sh index 7a42e0b..41c5d11 100755 --- a/fuzz/build.sh +++ b/fuzz/build.sh @@ -14,39 +14,22 @@ # 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 diff --git a/fuzz/standalone_runner.cc b/fuzz/standalone_runner.cc new file mode 100644 index 0000000..61631bc --- /dev/null +++ b/fuzz/standalone_runner.cc @@ -0,0 +1,25 @@ +#include +#include +#include +#include + +// 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 bytes(length); + in.read(bytes.data(), bytes.size()); + assert(in); + LLVMFuzzerTestOneInput(reinterpret_cast(bytes.data()), + bytes.size()); + std::cout << "Execution successful" << std::endl; + } +} diff --git a/fuzz/tests/valid.json b/fuzz/tests/valid.json new file mode 100644 index 0000000..bde58e7 --- /dev/null +++ b/fuzz/tests/valid.json @@ -0,0 +1 @@ +{"foo":123}