@@ -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} |