[compiler-rt] r353570 - [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.

Kostya Serebryany via llvm-commits llvm-commits at lists.llvm.org
Fri Feb 8 13:27:23 PST 2019


Author: kcc
Date: Fri Feb  8 13:27:23 2019
New Revision: 353570

URL: http://llvm.org/viewvc/llvm-project?rev=353570&view=rev
Log:
[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. 

Modified:
    compiler-rt/trunk/lib/fuzzer/FuzzerDriver.cpp
    compiler-rt/trunk/lib/fuzzer/FuzzerFlags.def
    compiler-rt/trunk/lib/fuzzer/FuzzerInternal.h
    compiler-rt/trunk/lib/fuzzer/FuzzerLoop.cpp
    compiler-rt/trunk/lib/fuzzer/FuzzerMerge.cpp
    compiler-rt/trunk/lib/fuzzer/FuzzerMerge.h
    compiler-rt/trunk/lib/fuzzer/tests/FuzzerUnittest.cpp

Modified: compiler-rt/trunk/lib/fuzzer/FuzzerDriver.cpp
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/fuzzer/FuzzerDriver.cpp?rev=353570&r1=353569&r2=353570&view=diff
==============================================================================
--- compiler-rt/trunk/lib/fuzzer/FuzzerDriver.cpp (original)
+++ compiler-rt/trunk/lib/fuzzer/FuzzerDriver.cpp Fri Feb  8 13:27:23 2019
@@ -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
   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::
   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:
     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(Fuzze
   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
     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);
   }
 

Modified: compiler-rt/trunk/lib/fuzzer/FuzzerFlags.def
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/fuzzer/FuzzerFlags.def?rev=353570&r1=353569&r2=353570&view=diff
==============================================================================
--- compiler-rt/trunk/lib/fuzzer/FuzzerFlags.def (original)
+++ compiler-rt/trunk/lib/fuzzer/FuzzerFlags.def Fri Feb  8 13:27:23 2019
@@ -41,6 +41,8 @@ FUZZER_FLAG_INT(timeout_exitcode, 77, "W
 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.")

Modified: compiler-rt/trunk/lib/fuzzer/FuzzerInternal.h
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/fuzzer/FuzzerInternal.h?rev=353570&r1=353569&r2=353570&view=diff
==============================================================================
--- compiler-rt/trunk/lib/fuzzer/FuzzerInternal.h (original)
+++ compiler-rt/trunk/lib/fuzzer/FuzzerInternal.h Fri Feb  8 13:27:23 2019
@@ -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);

Modified: compiler-rt/trunk/lib/fuzzer/FuzzerLoop.cpp
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/fuzzer/FuzzerLoop.cpp?rev=353570&r1=353569&r2=353570&view=diff
==============================================================================
--- compiler-rt/trunk/lib/fuzzer/FuzzerLoop.cpp (original)
+++ compiler-rt/trunk/lib/fuzzer/FuzzerLoop.cpp Fri Feb  8 13:27:23 2019
@@ -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);
 }
 

Modified: compiler-rt/trunk/lib/fuzzer/FuzzerMerge.cpp
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/fuzzer/FuzzerMerge.cpp?rev=353570&r1=353569&r2=353570&view=diff
==============================================================================
--- compiler-rt/trunk/lib/fuzzer/FuzzerMerge.cpp (original)
+++ compiler-rt/trunk/lib/fuzzer/FuzzerMerge.cpp Fri Feb  8 13:27:23 2019
@@ -122,7 +122,7 @@ size_t Merger::ApproximateMemoryConsumpt
 
 // 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::CrashResistantMergeInternal
   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 st
 }
 
 // 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 V
   // 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 V
     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 V
     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

Modified: compiler-rt/trunk/lib/fuzzer/FuzzerMerge.h
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/fuzzer/FuzzerMerge.h?rev=353570&r1=353569&r2=353570&view=diff
==============================================================================
--- compiler-rt/trunk/lib/fuzzer/FuzzerMerge.h (original)
+++ compiler-rt/trunk/lib/fuzzer/FuzzerMerge.h Fri Feb  8 13:27:23 2019
@@ -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

Modified: compiler-rt/trunk/lib/fuzzer/tests/FuzzerUnittest.cpp
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/fuzzer/tests/FuzzerUnittest.cpp?rev=353570&r1=353569&r2=353570&view=diff
==============================================================================
--- compiler-rt/trunk/lib/fuzzer/tests/FuzzerUnittest.cpp (original)
+++ compiler-rt/trunk/lib/fuzzer/tests/FuzzerUnittest.cpp Fri Feb  8 13:27:23 2019
@@ -647,7 +647,7 @@ static void Merge(const std::string &Inp
   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.




More information about the llvm-commits mailing list