diff options
author | Kostya Serebryany <kcc@google.com> | 2019-02-08 21:27:23 +0000 |
---|---|---|
committer | Kostya Serebryany <kcc@google.com> | 2019-02-08 21:27:23 +0000 |
commit | f762a11544b77bcb5cbb45e9726562018cd5c8fa (patch) | |
tree | d9a22b41759cdc51c01d54feeb26a7d1a5a23044 /compiler-rt/lib/fuzzer | |
parent | 3bf72d7d64b8465acd4f4af1a469d68d9dc86058 (diff) | |
download | bcm5719-llvm-f762a11544b77bcb5cbb45e9726562018cd5c8fa.tar.gz bcm5719-llvm-f762a11544b77bcb5cbb45e9726562018cd5c8fa.zip |
[libFuzzer] introduce an experimental mode -fork=1, where fuzzing happens in a subprocess (still running multiple inputs per process), thus making the fuzzing more resilient to timeouts and OOMs. This is just a skeleton of the code, and some associated refactoring, not a fully working feature yet.
llvm-svn: 353570
Diffstat (limited to 'compiler-rt/lib/fuzzer')
-rw-r--r-- | compiler-rt/lib/fuzzer/FuzzerDriver.cpp | 68 | ||||
-rw-r--r-- | compiler-rt/lib/fuzzer/FuzzerFlags.def | 2 | ||||
-rw-r--r-- | compiler-rt/lib/fuzzer/FuzzerInternal.h | 9 | ||||
-rw-r--r-- | compiler-rt/lib/fuzzer/FuzzerLoop.cpp | 4 | ||||
-rw-r--r-- | compiler-rt/lib/fuzzer/FuzzerMerge.cpp | 42 | ||||
-rw-r--r-- | compiler-rt/lib/fuzzer/FuzzerMerge.h | 10 | ||||
-rw-r--r-- | compiler-rt/lib/fuzzer/tests/FuzzerUnittest.cpp | 6 |
7 files changed, 89 insertions, 52 deletions
diff --git a/compiler-rt/lib/fuzzer/FuzzerDriver.cpp b/compiler-rt/lib/fuzzer/FuzzerDriver.cpp index 2bc895d008c..418bebb9388 100644 --- a/compiler-rt/lib/fuzzer/FuzzerDriver.cpp +++ b/compiler-rt/lib/fuzzer/FuzzerDriver.cpp @@ -16,6 +16,7 @@ #include "FuzzerMutate.h" #include "FuzzerRandom.h" #include "FuzzerTracePC.h" +#include "FuzzerMerge.h" #include <algorithm> #include <atomic> #include <chrono> @@ -24,6 +25,7 @@ #include <mutex> #include <string> #include <thread> +#include <fstream> // This function should be present in the libFuzzer so that the client // binary can test for its existence. @@ -304,6 +306,11 @@ static std::string GetDedupTokenFromFile(const std::string &Path) { return S.substr(Beg, End - Beg); } +static std::string TempPath(const char *Extension) { + return DirPlusFile(TmpDir(), + "libFuzzerTemp." + std::to_string(GetPid()) + Extension); +} + int CleanseCrashInput(const Vector<std::string> &Args, const FuzzingOptions &Options) { if (Inputs->size() != 1 || !Flags.exact_artifact_path) { @@ -319,10 +326,8 @@ int CleanseCrashInput(const Vector<std::string> &Args, assert(Cmd.hasArgument(InputFilePath)); Cmd.removeArgument(InputFilePath); - auto LogFilePath = DirPlusFile( - TmpDir(), "libFuzzerTemp." + std::to_string(GetPid()) + ".txt"); - auto TmpFilePath = DirPlusFile( - TmpDir(), "libFuzzerTemp." + std::to_string(GetPid()) + ".repro"); + auto LogFilePath = TempPath(".txt"); + auto TmpFilePath = TempPath(".repro"); Cmd.addArgument(TmpFilePath); Cmd.setOutputFile(LogFilePath); Cmd.combineOutAndErr(); @@ -382,8 +387,7 @@ int MinimizeCrashInput(const Vector<std::string> &Args, BaseCmd.addFlag("max_total_time", "600"); } - auto LogFilePath = DirPlusFile( - TmpDir(), "libFuzzerTemp." + std::to_string(GetPid()) + ".txt"); + auto LogFilePath = TempPath(".txt"); BaseCmd.setOutputFile(LogFilePath); BaseCmd.combineOutAndErr(); @@ -467,6 +471,36 @@ int MinimizeCrashInputInternalStep(Fuzzer *F, InputCorpus *Corpus) { return 0; } +// This is just a sceleton of an experimental -fork=1 feature. +void FuzzWithFork(const FuzzingOptions &Options, + const Vector<std::string> &Args, + const Vector<std::string> &Corpora) { + auto CFPath = TempPath(".fork"); + Printf("INFO: -fork=1: doing fuzzing in a separate process in order to " + "be more resistant to crashes, timeouts, and OOMs\n"); + auto Files = + CrashResistantMerge(Args, Corpora, CFPath, nullptr, nullptr); + Printf("INFO: -fork=1: seed corpus analyzed, %zd seeds chosen, starting to " + "fuzz in separate processes\n", Files.size()); + + Command Cmd(Args); + Cmd.removeFlag("fork"); + if (Files.size() >= 2) + Cmd.addFlag("seed_inputs", + Files.back() + "," + Files[Files.size() - 2]); + Cmd.addFlag("runs", "1000000"); + Cmd.addFlag("max_total_time", "30"); + for (size_t i = 0; i < 1000; i++) { + Printf("RUN %s\n", Cmd.toString().c_str()); + int ExitCode = ExecuteCommand(Cmd); + // TODO: sniff the crash, ignore OOMs and timeouts. + if (ExitCode != 0) break; + } + + RemoveFile(CFPath); + exit(0); +} + int AnalyzeDictionary(Fuzzer *F, const Vector<Unit>& Dict, UnitVector& Corpus) { Printf("Started dictionary minimization (up to %d tests)\n", @@ -694,11 +728,25 @@ int FuzzerDriver(int *argc, char ***argv, UserCallback Callback) { exit(0); } + if (Flags.fork) + FuzzWithFork(Options, Args, *Inputs); + if (Flags.merge) { - F->CrashResistantMerge(Args, *Inputs, - Flags.load_coverage_summary, - Flags.save_coverage_summary, - Flags.merge_control_file); + if (Inputs->size() < 2) { + Printf("INFO: Merge requires two or more corpus dirs\n"); + exit(0); + } + std::string CFPath = + Flags.merge_control_file ? Flags.merge_control_file : TempPath(".txt"); + auto Files = + CrashResistantMerge(Args, *Inputs, CFPath, Flags.load_coverage_summary, + Flags.save_coverage_summary); + for (auto &Path : Files) + F->WriteToOutputCorpus(FileToVector(Path, Options.MaxLen)); + // We are done, delete the control file if it was a temporary one. + if (!Flags.merge_control_file) + RemoveFile(CFPath); + exit(0); } diff --git a/compiler-rt/lib/fuzzer/FuzzerFlags.def b/compiler-rt/lib/fuzzer/FuzzerFlags.def index c762bafa7ae..d59ee01ff04 100644 --- a/compiler-rt/lib/fuzzer/FuzzerFlags.def +++ b/compiler-rt/lib/fuzzer/FuzzerFlags.def @@ -41,6 +41,8 @@ FUZZER_FLAG_INT(timeout_exitcode, 77, "When libFuzzer reports a timeout " FUZZER_FLAG_INT(max_total_time, 0, "If positive, indicates the maximal total " "time in seconds to run the fuzzer.") FUZZER_FLAG_INT(help, 0, "Print help.") +FUZZER_FLAG_INT(fork, 0, "Experimental mode where fuzzing happens " + "in a subprocess") FUZZER_FLAG_INT(merge, 0, "If 1, the 2-nd, 3-rd, etc corpora will be " "merged into the 1-st corpus. Only interesting units will be taken. " "This flag can be used to minimize a corpus.") diff --git a/compiler-rt/lib/fuzzer/FuzzerInternal.h b/compiler-rt/lib/fuzzer/FuzzerInternal.h index e934e111488..ca4cdf81e2d 100644 --- a/compiler-rt/lib/fuzzer/FuzzerInternal.h +++ b/compiler-rt/lib/fuzzer/FuzzerInternal.h @@ -73,11 +73,6 @@ public: // Merge Corpora[1:] into Corpora[0]. void Merge(const Vector<std::string> &Corpora); - void CrashResistantMerge(const Vector<std::string> &Args, - const Vector<std::string> &Corpora, - const char *CoverageSummaryInputPathOrNull, - const char *CoverageSummaryOutputPathOrNull, - const char *MergeControlFilePathOrNull); void CrashResistantMergeInternalStep(const std::string &ControlFilePath); MutationDispatcher &GetMD() { return MD; } void PrintFinalStats(); @@ -91,19 +86,19 @@ public: bool DuringInitialCorpusExecution); void HandleMalloc(size_t Size); + static void MaybeExitGracefully(); + void WriteToOutputCorpus(const Unit &U); private: void AlarmCallback(); void CrashCallback(); void ExitCallback(); - void MaybeExitGracefully(); void CrashOnOverwrittenData(); void InterruptCallback(); void MutateAndTestOne(); void PurgeAllocator(); void ReportNewCoverage(InputInfo *II, const Unit &U); void PrintPulseAndReportSlowInput(const uint8_t *Data, size_t Size); - void WriteToOutputCorpus(const Unit &U); void WriteUnitToFileWithPrefix(const Unit &U, const char *Prefix); void PrintStats(const char *Where, const char *End = "\n", size_t Units = 0); void PrintStatusForNewUnit(const Unit &U, const char *Text); diff --git a/compiler-rt/lib/fuzzer/FuzzerLoop.cpp b/compiler-rt/lib/fuzzer/FuzzerLoop.cpp index 58fd5c3e97b..3b5e20e68bb 100644 --- a/compiler-rt/lib/fuzzer/FuzzerLoop.cpp +++ b/compiler-rt/lib/fuzzer/FuzzerLoop.cpp @@ -256,9 +256,9 @@ void Fuzzer::ExitCallback() { } void Fuzzer::MaybeExitGracefully() { - if (!GracefulExitRequested) return; + if (!F->GracefulExitRequested) return; Printf("==%lu== INFO: libFuzzer: exiting as requested\n", GetPid()); - PrintFinalStats(); + F->PrintFinalStats(); _Exit(0); } diff --git a/compiler-rt/lib/fuzzer/FuzzerMerge.cpp b/compiler-rt/lib/fuzzer/FuzzerMerge.cpp index 77b8a789a02..b4f05e3b8b0 100644 --- a/compiler-rt/lib/fuzzer/FuzzerMerge.cpp +++ b/compiler-rt/lib/fuzzer/FuzzerMerge.cpp @@ -122,7 +122,7 @@ size_t Merger::ApproximateMemoryConsumption() const { // Decides which files need to be merged (add thost to NewFiles). // Returns the number of new features added. -size_t Merger::Merge(const Set<uint32_t> &InitialFeatures, +size_t Merger::Merge(const Set<uint32_t> &InitialFeatures, Vector<std::string> *NewFiles) { NewFiles->clear(); assert(NumFilesInFirstCorpus <= Files.size()); @@ -223,7 +223,7 @@ void Fuzzer::CrashResistantMergeInternalStep(const std::string &CFPath) { std::ofstream OF(CFPath, std::ofstream::out | std::ofstream::app); Set<size_t> AllFeatures; for (size_t i = M.FirstNotProcessedFile; i < M.Files.size(); i++) { - MaybeExitGracefully(); + Fuzzer::MaybeExitGracefully(); auto U = FileToVector(M.Files[i].Name); if (U.size() > MaxInputLen) { U.resize(MaxInputLen); @@ -275,27 +275,18 @@ static void WriteNewControlFile(const std::string &CFPath, } // Outer process. Does not call the target code and thus sohuld not fail. -void Fuzzer::CrashResistantMerge(const Vector<std::string> &Args, - const Vector<std::string> &Corpora, - const char *CoverageSummaryInputPathOrNull, - const char *CoverageSummaryOutputPathOrNull, - const char *MergeControlFilePathOrNull) { - if (Corpora.size() <= 1) { - Printf("Merge requires two or more corpus dirs\n"); - return; - } - auto CFPath = - MergeControlFilePathOrNull - ? MergeControlFilePathOrNull - : DirPlusFile(TmpDir(), - "libFuzzerTemp." + std::to_string(GetPid()) + ".txt"); - +Vector<std::string> +CrashResistantMerge(const Vector<std::string> &Args, + const Vector<std::string> &Corpora, + const std::string &CFPath, + const char *CoverageSummaryInputPathOrNull, + const char *CoverageSummaryOutputPathOrNull) { size_t NumAttempts = 0; - if (MergeControlFilePathOrNull && FileSize(MergeControlFilePathOrNull)) { + if (FileSize(CFPath)) { Printf("MERGE-OUTER: non-empty control file provided: '%s'\n", - MergeControlFilePathOrNull); + CFPath.c_str()); Merger M; - std::ifstream IF(MergeControlFilePathOrNull); + std::ifstream IF(CFPath); if (M.Parse(IF, /*ParseCoverage=*/false)) { Printf("MERGE-OUTER: control file ok, %zd files total," " first not processed file %zd\n", @@ -334,9 +325,10 @@ void Fuzzer::CrashResistantMerge(const Vector<std::string> &Args, // Every inner process should execute at least one input. Command BaseCmd(Args); BaseCmd.removeFlag("merge"); + BaseCmd.removeFlag("fork"); bool Success = false; for (size_t Attempt = 1; Attempt <= NumAttempts; Attempt++) { - MaybeExitGracefully(); + Fuzzer::MaybeExitGracefully(); Printf("MERGE-OUTER: attempt %zd\n", Attempt); Command Cmd(BaseCmd); Cmd.addFlag("merge_control_file", CFPath); @@ -368,7 +360,6 @@ void Fuzzer::CrashResistantMerge(const Vector<std::string> &Args, std::ofstream SummaryOut(CoverageSummaryOutputPathOrNull); M.PrintSummary(SummaryOut); } - Vector<std::string> NewFiles; Set<uint32_t> InitialFeatures; if (CoverageSummaryInputPathOrNull) { std::ifstream SummaryIn(CoverageSummaryInputPathOrNull); @@ -376,14 +367,11 @@ void Fuzzer::CrashResistantMerge(const Vector<std::string> &Args, Printf("MERGE-OUTER: coverage summary loaded from %s, %zd features found\n", CoverageSummaryInputPathOrNull, InitialFeatures.size()); } + Vector<std::string> NewFiles; size_t NumNewFeatures = M.Merge(InitialFeatures, &NewFiles); Printf("MERGE-OUTER: %zd new files with %zd new features added\n", NewFiles.size(), NumNewFeatures); - for (auto &F: NewFiles) - WriteToOutputCorpus(FileToVector(F, MaxInputLen)); - // We are done, delete the control file if it was a temporary one. - if (!MergeControlFilePathOrNull) - RemoveFile(CFPath); + return NewFiles; } } // namespace fuzzer diff --git a/compiler-rt/lib/fuzzer/FuzzerMerge.h b/compiler-rt/lib/fuzzer/FuzzerMerge.h index 49031d3b408..917108e3f09 100644 --- a/compiler-rt/lib/fuzzer/FuzzerMerge.h +++ b/compiler-rt/lib/fuzzer/FuzzerMerge.h @@ -67,13 +67,17 @@ struct Merger { Set<uint32_t> ParseSummary(std::istream &IS); size_t Merge(const Set<uint32_t> &InitialFeatures, Vector<std::string> *NewFiles); - size_t Merge(Vector<std::string> *NewFiles) { - return Merge(Set<uint32_t>{}, NewFiles); - } size_t ApproximateMemoryConsumption() const; Set<uint32_t> AllFeatures() const; }; +Vector<std::string> +CrashResistantMerge(const Vector<std::string> &Args, + const Vector<std::string> &Corpora, + const std::string &CFPath, + const char *CoverageSummaryInputPathOrNull, + const char *CoverageSummaryOutputPathOrNull); + } // namespace fuzzer #endif // LLVM_FUZZER_MERGE_H diff --git a/compiler-rt/lib/fuzzer/tests/FuzzerUnittest.cpp b/compiler-rt/lib/fuzzer/tests/FuzzerUnittest.cpp index 16a1fc5cdf0..861eaeda6c5 100644 --- a/compiler-rt/lib/fuzzer/tests/FuzzerUnittest.cpp +++ b/compiler-rt/lib/fuzzer/tests/FuzzerUnittest.cpp @@ -647,7 +647,7 @@ static void Merge(const std::string &Input, EXPECT_TRUE(M.Parse(Input, true)); std::stringstream SS; M.PrintSummary(SS); - EXPECT_EQ(NumNewFeatures, M.Merge(&NewFiles)); + EXPECT_EQ(NumNewFeatures, M.Merge({}, &NewFiles)); EXPECT_EQ(M.AllFeatures(), M.ParseSummary(SS)); EQ(NewFiles, Result); } @@ -705,7 +705,7 @@ TEST(Merge, Good) { EQ(M.Files[0].Features, {1, 2, 3}); EQ(M.Files[1].Features, {4, 5, 6}); EQ(M.Files[2].Features, {1, 3, 6}); - EXPECT_EQ(0U, M.Merge(&NewFiles)); + EXPECT_EQ(0U, M.Merge({}, &NewFiles)); EQ(NewFiles, {}); EXPECT_TRUE(M.Parse("3\n1\nA\nB\nC\n" @@ -716,7 +716,7 @@ TEST(Merge, Good) { EQ(M.Files[0].Features, {1, 2, 3}); EQ(M.Files[1].Features, {4, 5, 6}); EQ(M.Files[2].Features, {1, 3, 6}); - EXPECT_EQ(3U, M.Merge(&NewFiles)); + EXPECT_EQ(3U, M.Merge({}, &NewFiles)); EQ(NewFiles, {"B"}); // Same as the above, but with InitialFeatures. |