Index: lib/fuzzer/FuzzerDriver.cpp =================================================================== --- lib/fuzzer/FuzzerDriver.cpp +++ lib/fuzzer/FuzzerDriver.cpp @@ -15,6 +15,7 @@ #include "FuzzerInterface.h" #include "FuzzerInternal.h" #include "FuzzerMutate.h" +#include "FuzzerOptions.h" #include "FuzzerRandom.h" #include "FuzzerShmem.h" #include "FuzzerTracePC.h" @@ -619,9 +620,9 @@ Options.PrintCorpusStats = Flags.print_corpus_stats; Options.PrintCoverage = Flags.print_coverage; Options.PrintUnstableStats = Flags.print_unstable_stats; - if (Flags.handle_unstable == TracePC::MinUnstable || - Flags.handle_unstable == TracePC::ZeroUnstable) - Options.HandleUnstable = Flags.handle_unstable; + if (Flags.handle_unstable > (int)UnstableMode::Start && + Flags.handle_unstable < (int)UnstableMode::End) + Options.HandleUnstable = static_cast(Flags.handle_unstable); Options.DumpCoverage = Flags.dump_coverage; if (Flags.exit_on_src_pos) Options.ExitOnSrcPos = Flags.exit_on_src_pos; Index: lib/fuzzer/FuzzerFlags.def =================================================================== --- lib/fuzzer/FuzzerFlags.def +++ lib/fuzzer/FuzzerFlags.def @@ -116,7 +116,10 @@ " If 1, we only use the minimum hit count from the 3 runs" " to determine whether an input is interesting." " If 2, we disregard edges that are found unstable for" - " feature collection.") + " feature collection." + " If 3, we collect features if a new stable edge is found." + " Unstable edges will only be taken into account if it is" + " found with a new stable edge.") FUZZER_FLAG_INT(print_unstable_stats, 0, "Experimental." " If 1, print unstable statistics at exit.") FUZZER_FLAG_INT(handle_segv, 1, "If 1, try to intercept SIGSEGV.") Index: lib/fuzzer/FuzzerInternal.h =================================================================== --- lib/fuzzer/FuzzerInternal.h +++ lib/fuzzer/FuzzerInternal.h @@ -67,7 +67,7 @@ static void StaticGracefulExitCallback(); void ExecuteCallback(const uint8_t *Data, size_t Size); - void CheckForUnstableCounters(const uint8_t *Data, size_t Size); + size_t CheckForUnstableCounters(const uint8_t *Data, size_t Size); bool RunOne(const uint8_t *Data, size_t Size, bool MayDeleteFile = false, InputInfo *II = nullptr, bool *FoundUniqFeatures = nullptr); Index: lib/fuzzer/FuzzerLoop.cpp =================================================================== --- lib/fuzzer/FuzzerLoop.cpp +++ lib/fuzzer/FuzzerLoop.cpp @@ -449,7 +449,7 @@ } } -void Fuzzer::CheckForUnstableCounters(const uint8_t *Data, size_t Size) { +size_t Fuzzer::CheckForUnstableCounters(const uint8_t *Data, size_t Size) { auto CBSetupAndRun = [&]() { ScopedEnableMsanInterceptorChecks S; UnitStartTime = system_clock::now(); @@ -465,16 +465,19 @@ // First Rerun CBSetupAndRun(); - TPC.UpdateUnstableCounters(Options.HandleUnstable); + size_t NumNewUnstableEdges = + TPC.UpdateUnstableCounters(Options.HandleUnstable); // Second Rerun CBSetupAndRun(); - TPC.UpdateUnstableCounters(Options.HandleUnstable); + NumNewUnstableEdges += TPC.UpdateUnstableCounters(Options.HandleUnstable); // Move minimum hit counts back to ModuleInline8bitCounters - if (Options.HandleUnstable == TracePC::MinUnstable || - Options.HandleUnstable == TracePC::ZeroUnstable) + if (Options.HandleUnstable == UnstableMode::Min || + Options.HandleUnstable == UnstableMode::Zero) TPC.ApplyUnstableCounters(); + + return NumNewUnstableEdges; } bool Fuzzer::RunOne(const uint8_t *Data, size_t Size, bool MayDeleteFile, @@ -488,14 +491,15 @@ size_t FoundUniqFeaturesOfII = 0; size_t NumUpdatesBefore = Corpus.NumFeatureUpdates(); bool NewFeaturesUnstable = false; + size_t NumNewUnstableEdges = 0; - if (Options.HandleUnstable || Options.PrintUnstableStats) { + if ((int)Options.HandleUnstable || Options.PrintUnstableStats) { TPC.CollectFeatures([&](size_t Feature) { if (Corpus.IsFeatureNew(Feature, Size, Options.Shrink)) NewFeaturesUnstable = true; }); if (NewFeaturesUnstable) - CheckForUnstableCounters(Data, Size); + NumNewUnstableEdges = CheckForUnstableCounters(Data, Size); } TPC.CollectFeatures([&](size_t Feature) { @@ -512,6 +516,11 @@ PrintPulseAndReportSlowInput(Data, Size); size_t NumNewFeatures = Corpus.NumFeatureUpdates() - NumUpdatesBefore; + if (Options.HandleUnstable == UnstableMode::Poison) { + assert(NumNewFeatures >= NumNewUnstableEdges); + NumNewFeatures -= NumNewUnstableEdges; + } + if (NumNewFeatures) { TPC.UpdateObservedPCs(); Corpus.AddToCorpus({Data, Data + Size}, NumNewFeatures, MayDeleteFile, Index: lib/fuzzer/FuzzerOptions.h =================================================================== --- lib/fuzzer/FuzzerOptions.h +++ lib/fuzzer/FuzzerOptions.h @@ -15,6 +15,14 @@ namespace fuzzer { +enum class UnstableMode { + Start = 0, + Min = 1, + Zero = 2, + Poison = 3, + End = 4, +}; + struct FuzzingOptions { int Verbosity = 1; size_t MaxLen = 0; @@ -56,7 +64,7 @@ bool PrintCorpusStats = false; bool PrintCoverage = false; bool PrintUnstableStats = false; - int HandleUnstable = 0; + UnstableMode HandleUnstable = UnstableMode::Start; bool DumpCoverage = false; bool DetectLeaks = true; int PurgeAllocatorIntervalSec = 1; Index: lib/fuzzer/FuzzerTracePC.h =================================================================== --- lib/fuzzer/FuzzerTracePC.h +++ lib/fuzzer/FuzzerTracePC.h @@ -14,6 +14,7 @@ #include "FuzzerDefs.h" #include "FuzzerDictionary.h" +#include "FuzzerOptions.h" #include "FuzzerValueBitMap.h" #include @@ -74,11 +75,6 @@ // How many bits of PC are used from __sanitizer_cov_trace_pc. static const size_t kTracePcBits = 18; - enum HandleUnstableOptions { - MinUnstable = 1, - ZeroUnstable = 2, - }; - void HandleInit(uint32_t *Start, uint32_t *Stop); void HandleInline8bitCountersInit(uint8_t *Start, uint8_t *Stop); void HandlePCsInit(const uintptr_t *Start, const uintptr_t *Stop); @@ -143,7 +139,7 @@ bool ObservedFocusFunction(); void InitializeUnstableCounters(); - void UpdateUnstableCounters(int UnstableMode); + size_t UpdateUnstableCounters(UnstableMode Mode); void ApplyUnstableCounters(); private: Index: lib/fuzzer/FuzzerTracePC.cpp =================================================================== --- lib/fuzzer/FuzzerTracePC.cpp +++ lib/fuzzer/FuzzerTracePC.cpp @@ -81,17 +81,21 @@ // Compares the current counters with counters from previous runs // and records differences as unstable edges. -void TracePC::UpdateUnstableCounters(int UnstableMode) { +size_t TracePC::UpdateUnstableCounters(UnstableMode Mode) { + size_t NumNewUnstableEdges = 0; IterateInline8bitCounters([&](int i, int j, int UnstableIdx) { - if (ModuleCounters[i].Start[j] != UnstableCounters[UnstableIdx].Counter) { - UnstableCounters[UnstableIdx].IsUnstable = true; - if (UnstableMode == ZeroUnstable) - UnstableCounters[UnstableIdx].Counter = 0; - else if (UnstableMode == MinUnstable) - UnstableCounters[UnstableIdx].Counter = std::min( - ModuleCounters[i].Start[j], UnstableCounters[UnstableIdx].Counter); - } + if (ModuleCounters[i].Start[j] == UnstableCounters[UnstableIdx].Counter) + return; + if (!UnstableCounters[UnstableIdx].IsUnstable) + NumNewUnstableEdges++; + UnstableCounters[UnstableIdx].IsUnstable = true; + if (Mode == UnstableMode::Zero) + UnstableCounters[UnstableIdx].Counter = 0; + else if (Mode == UnstableMode::Min) + UnstableCounters[UnstableIdx].Counter = std::min( + ModuleCounters[i].Start[j], UnstableCounters[UnstableIdx].Counter); }); + return NumNewUnstableEdges; } // Moves the minimum hit counts to ModuleCounters. Index: test/fuzzer/HandleUnstableTest.cpp =================================================================== --- /dev/null +++ test/fuzzer/HandleUnstableTest.cpp @@ -0,0 +1,73 @@ +#include +#include +#include +#include + +int x = 0; +int c = 0; +bool skip0 = false; +bool skip1 = false; +bool skip2 = false; + +__attribute__((noinline)) void det0() { x++; } +__attribute__((noinline)) void det1() { x++; } +__attribute__((noinline)) void det2() { x++; } + +__attribute__((noinline)) void ini0() { x++; } +__attribute__((noinline)) void ini1() { x++; } +__attribute__((noinline)) void ini2() { x++; } + +__attribute__((noinline)) void t0() { x++; } +__attribute__((noinline)) void t1() { x++; } +__attribute__((noinline)) void t2() { x++; } + +__attribute__((noinline)) void poison0() { x++; } +__attribute__((noinline)) void poison1() { x++; } +__attribute__((noinline)) void poison2() { x++; } + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + if (Size == 1 && Data[0] == 'A' && !skip0) { + skip0 = true; + ini0(); + } + if (Size == 1 && Data[0] == 'B' && !skip1) { + skip1 = true; + ini1(); + } + if (Size == 1 && Data[0] == 'C' && !skip2) { + skip2 = true; + ini2(); + } + + det0(); + det1(); + det2(); + + // c < 1000 on purpose so that when it enters the else condition, t0() gets + // collected since we detect new stable edge poison0(). + if (c < 1000) { + switch (c % 3) { + case 0: + t0(); + poison0(); + break; + case 1: + t1(); + poison1(); + break; + case 2: + t2(); + poison2(); + break; + default: + assert(false); + } + } else { + poison0(); + poison1(); + poison2(); + } + c++; + + return 0; +} Index: test/fuzzer/handle-unstable.test =================================================================== --- test/fuzzer/handle-unstable.test +++ test/fuzzer/handle-unstable.test @@ -1,42 +1,62 @@ # Tests -handle_unstable UNSUPPORTED: aarch64 -RUN: %cpp_compiler %S/PrintUnstableStatsTest.cpp -o %t-HandleUnstableTest +RUN: %cpp_compiler %S/HandleUnstableTest.cpp -o %t-HandleUnstableTest ; Normal RUN: %run %t-HandleUnstableTest -print_coverage=1 -runs=100000 2>&1 | FileCheck %s --check-prefix=NORMAL NORMAL-DAG: det0() NORMAL-DAG: det1() NORMAL-DAG: det2() -NORMAL-DAG: det3() -NORMAL-DAG: det4() NORMAL-DAG: ini0() NORMAL-DAG: ini1() NORMAL-DAG: ini2() NORMAL-DAG: t0() NORMAL-DAG: t1() NORMAL-DAG: t2() -NORMAL-DAG: t3() -NORMAL-DAG: t4() ; MinUnstable -RUN: %run %t-HandleUnstableTest -print_coverage=1 -handle_unstable=1 -runs=100000 2>&1 | FileCheck %s --check-prefix=MIN -MIN-NOT: ini0() -MIN-NOT: ini1() -MIN-NOT: ini2() +run: %run %t-handleunstabletest -print_coverage=1 -handle_unstable=1 -runs=100000 2>&1 | filecheck %s --check-prefix=min MIN: det0() MIN: det1() MIN: det2() -MIN: det3() -MIN: det4() +MIN: poison0() +MIN: poison1() +MIN: poison2() +MIN-NOT: ini0() +MIN-NOT: ini1() +MIN-NOT: ini2() +MIN-NOT: t0() +MIN-NOT: t1() +MIN-NOT: t2() ; ZeroUnstable -RUN: %run %t-HandleUnstableTest -print_coverage=1 -handle_unstable=2 -runs=1 2>&1 | FileCheck %s --check-prefix=ZERO -ZERO-NOT: ini0() -ZERO-NOT: ini1() -ZERO-NOT: ini2() +run: %run %t-handleunstabletest -print_coverage=1 -handle_unstable=2 -runs=100000 2>&1 | filecheck %s --check-prefix=zero ZERO: det0() ZERO: det1() ZERO: det2() -ZERO: det3() -ZERO: det4() +ZERO: poison0() +ZERO: poison1() +ZERO: poison2() +ZERO-NOT: ini0() +ZERO-NOT: ini1() +ZERO-NOT: ini2() +ZERO-NOT: t0() +ZERO-NOT: t1() +ZERO-NOT: t2() + +; PoisonUnstable +RUN: %run %t-HandleUnstableTest -print_coverage=1 -handle_unstable=3 -runs=100000 2>&1 | FileCheck %s --check-prefix=POISON +POISON: det0() +POISON: det1() +POISON: det2() +; t0 is triggered because last iteration hits poison0() and it's next iterations always hit poison0() causing the input to collect t0() as well. +POISON: t0() +POISON: poison0() +POISON: poison1() +POISON: poison2() +POISON-NOT: {{\b}}t1() +POISON-NOT: {{\b}}t2() +POISON-NOT: ini0() +POISON-NOT: ini1() +POISON-NOT: ini2()