diff --git a/lib/Fuzzer/CMakeLists.txt b/lib/Fuzzer/CMakeLists.txt index 2538375063c..499cf98a6b9 100644 --- a/lib/Fuzzer/CMakeLists.txt +++ b/lib/Fuzzer/CMakeLists.txt @@ -1,3 +1,5 @@ +# Disable the coverage instrumentation for the fuzzer itself. +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -fsanitize-coverage=0") if( LLVM_USE_SANITIZE_COVERAGE ) add_library(LLVMFuzzer STATIC EXCLUDE_FROM_ALL # Do not build if you are not building fuzzers. diff --git a/lib/Fuzzer/FuzzerFlags.def b/lib/Fuzzer/FuzzerFlags.def index e165ebdbf37..264c105950c 100644 --- a/lib/Fuzzer/FuzzerFlags.def +++ b/lib/Fuzzer/FuzzerFlags.def @@ -25,3 +25,7 @@ FUZZER_FLAG(int, help, 0, "Print help.") FUZZER_FLAG( int, save_minimized_corpus, 0, "If 1, the minimized corpus is saved into the first input directory") +FUZZER_FLAG(int, use_full_coverage_set, 0, + "Maximize the number of different full" + " coverage sets as opposed to maximizing the total coverage." + " This is potentially MUCH slower, but may discover more paths.") diff --git a/lib/Fuzzer/FuzzerInternal.h b/lib/Fuzzer/FuzzerInternal.h index c361ffba83a..45379711db4 100644 --- a/lib/Fuzzer/FuzzerInternal.h +++ b/lib/Fuzzer/FuzzerInternal.h @@ -14,6 +14,7 @@ #include #include #include +#include namespace fuzzer { typedef std::vector Unit; @@ -43,6 +44,7 @@ class Fuzzer { bool DoCrossOver = true; bool MutateDepth = 10; bool ExitOnFirst = false; + bool UseFullCoverageSet = false; std::string OutputCorpus; }; Fuzzer(FuzzingOptions Options) : Options(Options) { @@ -63,6 +65,8 @@ class Fuzzer { private: size_t MutateAndTestOne(Unit *U); size_t RunOne(const Unit &U); + size_t RunOneMaximizeTotalCoverage(const Unit &U); + size_t RunOneMaximizeFullCoverageSet(const Unit &U); void WriteToOutputCorpus(const Unit &U); static void WriteToCrash(const Unit &U, const char *Prefix); @@ -73,6 +77,7 @@ class Fuzzer { size_t TotalNumberOfRuns = 0; std::vector Corpus; + std::unordered_set FullCoverageSets; FuzzingOptions Options; system_clock::time_point ProcessStartTime = system_clock::now(); static system_clock::time_point UnitStartTime; diff --git a/lib/Fuzzer/FuzzerLoop.cpp b/lib/Fuzzer/FuzzerLoop.cpp index dcfc965f6bf..f3dee7c7f46 100644 --- a/lib/Fuzzer/FuzzerLoop.cpp +++ b/lib/Fuzzer/FuzzerLoop.cpp @@ -76,6 +76,35 @@ void Fuzzer::ShuffleAndMinimize() { size_t Fuzzer::RunOne(const Unit &U) { UnitStartTime = system_clock::now(); TotalNumberOfRuns++; + if (Options.UseFullCoverageSet) + return RunOneMaximizeFullCoverageSet(U); + return RunOneMaximizeTotalCoverage(U); +} + +static uintptr_t HashOfArrayOfPCs(uintptr_t *PCs, uintptr_t NumPCs) { + uintptr_t Res = 0; + for (uintptr_t i = 0; i < NumPCs; i++) { + Res = (Res + PCs[i]) * 7; + } + return Res; +} + +// Fuly reset the current coverage state, run a single unit, +// compute a hash function from the full coverage set, +// return non-zero if the hash value is new. +// This produces tons of new units and as is it's only suitable for small tests, +// e.g. test/FullCoverageSetTest.cpp. FIXME: make it scale. +size_t Fuzzer::RunOneMaximizeFullCoverageSet(const Unit &U) { + __sanitizer_reset_coverage(); + TestOneInput(U.data(), U.size()); + uintptr_t *PCs; + uintptr_t NumPCs =__sanitizer_get_coverage_guards(&PCs); + if (FullCoverageSets.insert(HashOfArrayOfPCs(PCs, NumPCs)).second) + return FullCoverageSets.size(); + return 0; +} + +size_t Fuzzer::RunOneMaximizeTotalCoverage(const Unit &U) { size_t OldCoverage = __sanitizer_get_total_unique_coverage(); TestOneInput(U.data(), U.size()); size_t NewCoverage = __sanitizer_get_total_unique_coverage(); diff --git a/lib/Fuzzer/FuzzerMain.cpp b/lib/Fuzzer/FuzzerMain.cpp index 6031fc835b9..03e0566049d 100644 --- a/lib/Fuzzer/FuzzerMain.cpp +++ b/lib/Fuzzer/FuzzerMain.cpp @@ -117,6 +117,7 @@ int main(int argc, char **argv) { Options.DoCrossOver = Flags.cross_over; Options.MutateDepth = Flags.mutate_depth; Options.ExitOnFirst = Flags.exit_on_first; + Options.UseFullCoverageSet = Flags.use_full_coverage_set; if (!inputs.empty()) Options.OutputCorpus = inputs[0]; Fuzzer F(Options); diff --git a/lib/Fuzzer/test/CMakeLists.txt b/lib/Fuzzer/test/CMakeLists.txt index 0c2118f31b0..17afd92c3b4 100644 --- a/lib/Fuzzer/test/CMakeLists.txt +++ b/lib/Fuzzer/test/CMakeLists.txt @@ -1,5 +1,11 @@ +# Build all these tests with -O0, otherwise optimizations may merge some +# basic blocks and we'll fail to discover the targets. +# Also enable the coverage instrumentation back (it is disabled +# for the Fuzzer lib) +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O0 -fsanitize-coverage=4") + set(Tests - ExactTest + FullCoverageSetTest InfiniteTest NullDerefTest SimpleTest diff --git a/lib/Fuzzer/test/ExactTest.cpp b/lib/Fuzzer/test/FullCoverageSetTest.cpp similarity index 97% rename from lib/Fuzzer/test/ExactTest.cpp rename to lib/Fuzzer/test/FullCoverageSetTest.cpp index bbfed3c1eb0..d4f8c115abc 100644 --- a/lib/Fuzzer/test/ExactTest.cpp +++ b/lib/Fuzzer/test/FullCoverageSetTest.cpp @@ -14,7 +14,7 @@ extern "C" void TestOneInput(const uint8_t *Data, size_t Size) { if (Size > 5 && Data[5] == 'R') bits |= 32; if (bits == 63) { std::cerr << "BINGO!\n"; - abort(); + exit(1); } } diff --git a/lib/Fuzzer/test/fuzzer.test b/lib/Fuzzer/test/fuzzer.test index 5f013109f62..51b42d67824 100644 --- a/lib/Fuzzer/test/fuzzer.test +++ b/lib/Fuzzer/test/fuzzer.test @@ -11,3 +11,6 @@ TimeoutTest: CRASHED; file written to timeout RUN: not ./LLVMFuzzer-NullDerefTest 2>&1 | FileCheck %s --check-prefix=NullDerefTest NullDerefTest: CRASHED; file written to crash- + +RUN: not ./LLVMFuzzer-FullCoverageSetTest -timeout=15 -mutate_depth=2 -use_full_coverage_set=1 2>&1 | FileCheck %s --check-prefix=FullCoverageSetTest +FullCoverageSetTest: BINGO