[libFuzzer] Stop fuzzing if no new corpus were found within a specified period (#176177) This patch adds a new command-line option `-stale_corpus_timeout=N` which allows you to specify that the fuzzer should exit if it hasn't found a new corpus within a certain amount of time. It also adds to stats how many seconds a new path has not been found. An analog of `AFL_EXIT_ON_TIME`. GitOrigin-RevId: af2d76922b54fc347faac9727b0748387fea043f
diff --git a/FuzzerDriver.cpp b/FuzzerDriver.cpp index fda7880..d3b8852 100644 --- a/FuzzerDriver.cpp +++ b/FuzzerDriver.cpp
@@ -698,6 +698,7 @@ Options.IgnoreOOMs = Flags.ignore_ooms; Options.IgnoreCrashes = Flags.ignore_crashes; Options.MaxTotalTimeSec = Flags.max_total_time; + Options.StaleCorpusTimeoutSec = Flags.stale_corpus_timeout; Options.DoCrossOver = Flags.cross_over; Options.CrossOverUniformDist = Flags.cross_over_uniform_dist; Options.MutateDepth = Flags.mutate_depth; @@ -924,8 +925,9 @@ F->Loop(CorporaFiles); if (Flags.verbosity) - Printf("Done %zd runs in %zd second(s)\n", F->getTotalNumberOfRuns(), - F->secondsSinceProcessStartUp()); + Printf("Done %zd runs in %zd second(s), %zd second(s) without new corpus\n", + F->getTotalNumberOfRuns(), F->secondsSinceProcessStartUp(), + F->secondsSinceLastNewCorpus()); F->PrintFinalStats(); exit(0); // Don't let F destroy itself.
diff --git a/FuzzerFlags.def b/FuzzerFlags.def index 9b1da61..cb12419 100644 --- a/FuzzerFlags.def +++ b/FuzzerFlags.def
@@ -56,6 +56,9 @@ "this exit code will be used.") FUZZER_FLAG_INT(max_total_time, 0, "If positive, indicates the maximal total " "time in seconds to run the fuzzer.") +FUZZER_FLAG_INT(stale_corpus_timeout, 0, + "If positive, indicates the maximum time in seconds to run the " + "fuzzer after new input is added to the corpus.") FUZZER_FLAG_INT(help, 0, "Print help.") FUZZER_FLAG_INT(fork, 0, "Experimental mode where fuzzing happens " "in a subprocess.")
diff --git a/FuzzerFork.cpp b/FuzzerFork.cpp index e544cd8..b8d3639 100644 --- a/FuzzerFork.cpp +++ b/FuzzerFork.cpp
@@ -98,6 +98,7 @@ std::vector<std::size_t> FilesSizes; Random *Rand; std::chrono::system_clock::time_point ProcessStartTime; + std::chrono::system_clock::time_point LastNewCorpusTime; int Verbosity = 0; int Group = 0; int NumCorpuses = 8; @@ -117,11 +118,18 @@ .count(); } + size_t secondsSinceLastNewCorpus() const { + return std::chrono::duration_cast<std::chrono::seconds>( + std::chrono::system_clock::now() - LastNewCorpusTime) + .count(); + } + FuzzJob *CreateNewJob(size_t JobId) { Command Cmd(Args); Cmd.removeFlag("fork"); Cmd.removeFlag("runs"); Cmd.removeFlag("collect_data_flow"); + Cmd.removeFlag("stale_corpus_timeout"); for (auto &C : CorpusDirs) // Remove all corpora from the args. Cmd.removeArgument(C); Cmd.addFlag("reload", "0"); // working in an isolated dir, no reload. @@ -196,6 +204,15 @@ return Job; } + void PrintStats(Stats *Stats, FuzzJob *Job) { + Printf("#%zd: cov: %zd ft: %zd corp: %zd exec/s: %zd oom/timeout/crash: " + "%zd/%zd/%zd time: %zds job: %zd dft_time: %d stale: %zd\n", + NumRuns, Cov.size(), Features.size(), Files.size(), + Stats->average_exec_per_sec, NumOOMs, NumTimeouts, NumCrashes, + secondsSinceProcessStartUp(), Job->JobId, Job->DftTimeInSeconds, + secondsSinceLastNewCorpus()); + } + void RunOneMergeJob(FuzzJob *Job) { auto Stats = ParseFinalStatsFromLog(Job->LogPath); NumRuns += Stats.number_of_executed_units; @@ -219,14 +236,11 @@ } } } - // if (!FilesToAdd.empty() || Job->ExitCode != 0) - Printf("#%zd: cov: %zd ft: %zd corp: %zd exec/s: %zd " - "oom/timeout/crash: %zd/%zd/%zd time: %zds job: %zd dft_time: %d\n", - NumRuns, Cov.size(), Features.size(), Files.size(), - Stats.average_exec_per_sec, NumOOMs, NumTimeouts, NumCrashes, - secondsSinceProcessStartUp(), Job->JobId, Job->DftTimeInSeconds); - if (MergeCandidates.empty()) return; + if (MergeCandidates.empty()) { + PrintStats(&Stats, Job); + return; + } std::vector<std::string> FilesToAdd; std::set<uint32_t> NewFeatures, NewCov; @@ -257,6 +271,9 @@ if (TPC.PcIsFuncEntry(TE)) PrintPC(" NEW_FUNC: %p %F %L\n", "", TPC.GetNextInstructionPc(TE->PC)); + if (!FilesToAdd.empty()) + LastNewCorpusTime = std::chrono::system_clock::now(); + PrintStats(&Stats, Job); } void CollectDFT(const std::string &InputPath) { @@ -381,6 +398,8 @@ FuzzQ.Push(Env.CreateNewJob(JobId++)); } + Env.LastNewCorpusTime = std::chrono::system_clock::now(); + while (true) { std::unique_ptr<FuzzJob> Job(MergeQ.Pop()); if (!Job) @@ -462,12 +481,20 @@ // and we will wait while joining them. // We also don't stop instantly: other jobs need to finish. if (Options.MaxTotalTimeSec > 0 && - Env.secondsSinceProcessStartUp() >= (size_t)Options.MaxTotalTimeSec) { + Env.secondsSinceProcessStartUp() > (size_t)Options.MaxTotalTimeSec) { Printf("INFO: fuzzed for %zd seconds, wrapping up soon\n", Env.secondsSinceProcessStartUp()); StopJobs(); break; } + if (Options.StaleCorpusTimeoutSec > 0 && + Env.secondsSinceLastNewCorpus() > + (size_t)Options.StaleCorpusTimeoutSec) { + Printf("INFO: no new corpus for %zd seconds, wrapping up soon\n", + Env.secondsSinceLastNewCorpus()); + StopJobs(); + break; + } if (Env.NumRuns >= Options.MaxNumberOfRuns) { Printf("INFO: fuzzed for %zd iterations, wrapping up soon\n", Env.NumRuns);
diff --git a/FuzzerInternal.h b/FuzzerInternal.h index 8850470..e68cdf3 100644 --- a/FuzzerInternal.h +++ b/FuzzerInternal.h
@@ -44,10 +44,18 @@ .count(); } + size_t secondsSinceLastNewCorpus() { + return duration_cast<seconds>(system_clock::now() - LastNewCorpusTime) + .count(); + } + bool TimedOut() { - return Options.MaxTotalTimeSec > 0 && - secondsSinceProcessStartUp() > - static_cast<size_t>(Options.MaxTotalTimeSec); + return (Options.MaxTotalTimeSec > 0 && + secondsSinceProcessStartUp() > + static_cast<size_t>(Options.MaxTotalTimeSec)) || + (Options.StaleCorpusTimeoutSec > 0 && + secondsSinceLastNewCorpus() > + static_cast<size_t>(Options.StaleCorpusTimeoutSec)); } size_t execPerSec() { @@ -137,6 +145,7 @@ DataFlowTrace DFT; system_clock::time_point ProcessStartTime = system_clock::now(); + system_clock::time_point LastNewCorpusTime = system_clock::now(); system_clock::time_point UnitStartTime, UnitStopTime; long TimeOfLongestUnitInSeconds = 0; long EpochOfLastReadOfOutputCorpus = 0;
diff --git a/FuzzerLoop.cpp b/FuzzerLoop.cpp index 5511dfa..55f6f7b 100644 --- a/FuzzerLoop.cpp +++ b/FuzzerLoop.cpp
@@ -352,6 +352,8 @@ Printf(" exec/s: %zd", ExecPerSec); Printf(" rss: %zdMB", GetPeakRSSMb()); + if (Options.StaleCorpusTimeoutSec > 0) + Printf(" stale: %zd", secondsSinceLastNewCorpus()); Printf("%s", End); } @@ -535,6 +537,7 @@ size_t NumNewFeatures = Corpus.NumFeatureUpdates() - NumUpdatesBefore; if (NumNewFeatures || ForceAddToCorpus) { TPC.UpdateObservedPCs(); + LastNewCorpusTime = UnitStopTime; auto NewII = Corpus.AddToCorpus({Data, Data + Size}, NumNewFeatures, MayDeleteFile, TPC.ObservedFocusFunction(), ForceAddToCorpus, @@ -874,7 +877,8 @@ DFT.Clear(); // No need for DFT any more. TPC.SetPrintNewPCs(Options.PrintNewCovPcs); TPC.SetPrintNewFuncs(Options.PrintNewCovFuncs); - system_clock::time_point LastCorpusReload = system_clock::now(); + system_clock::time_point LastCorpusReload = LastNewCorpusTime = + system_clock::now(); TmpMaxMutationLen = Min(MaxMutationLen, Max(size_t(4), Corpus.MaxInputSize()));
diff --git a/FuzzerOptions.h b/FuzzerOptions.h index 6478b63..e54e0c7 100644 --- a/FuzzerOptions.h +++ b/FuzzerOptions.h
@@ -28,6 +28,7 @@ bool IgnoreOOMs = true; bool IgnoreCrashes = false; int MaxTotalTimeSec = 0; + int StaleCorpusTimeoutSec = 0; int RssLimitMb = 0; int MallocLimitMb = 0; bool DoCrossOver = true;