[llvm] r266838 - [libFuzzer] added -detect_leaks flag (0 by default for now). When enabled, it will help finding leaks while fuzzing

Kostya Serebryany via llvm-commits llvm-commits at lists.llvm.org
Tue Apr 19 17:24:21 PDT 2016


Author: kcc
Date: Tue Apr 19 19:24:21 2016
New Revision: 266838

URL: http://llvm.org/viewvc/llvm-project?rev=266838&view=rev
Log:
[libFuzzer] added -detect_leaks flag (0 by default for now). When enabled, it will help finding leaks while fuzzing

Modified:
    llvm/trunk/lib/Fuzzer/FuzzerDriver.cpp
    llvm/trunk/lib/Fuzzer/FuzzerFlags.def
    llvm/trunk/lib/Fuzzer/FuzzerInternal.h
    llvm/trunk/lib/Fuzzer/FuzzerLoop.cpp
    llvm/trunk/lib/Fuzzer/test/LeakTest.cpp
    llvm/trunk/lib/Fuzzer/test/fuzzer-leak.test

Modified: llvm/trunk/lib/Fuzzer/FuzzerDriver.cpp
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Fuzzer/FuzzerDriver.cpp?rev=266838&r1=266837&r2=266838&view=diff
==============================================================================
--- llvm/trunk/lib/Fuzzer/FuzzerDriver.cpp (original)
+++ llvm/trunk/lib/Fuzzer/FuzzerDriver.cpp Tue Apr 19 19:24:21 2016
@@ -294,6 +294,7 @@ static int FuzzerDriver(const std::vecto
   Options.Reload = Flags.reload;
   Options.OnlyASCII = Flags.only_ascii;
   Options.OutputCSV = Flags.output_csv;
+  Options.DetectLeaks = Flags.detect_leaks;
   if (Flags.runs >= 0)
     Options.MaxNumberOfRuns = Flags.runs;
   if (!Inputs->empty())

Modified: llvm/trunk/lib/Fuzzer/FuzzerFlags.def
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Fuzzer/FuzzerFlags.def?rev=266838&r1=266837&r2=266838&view=diff
==============================================================================
--- llvm/trunk/lib/Fuzzer/FuzzerFlags.def (original)
+++ llvm/trunk/lib/Fuzzer/FuzzerFlags.def Tue Apr 19 19:24:21 2016
@@ -79,6 +79,8 @@ FUZZER_FLAG_INT(handle_term, 1, "If 1, t
 FUZZER_FLAG_INT(close_fd_mask, 0, "If 1, close stdout at startup; "
     "if 2, close stderr; if 3, close both. "
     "Be careful, this will also close e.g. asan's stderr/stdout.")
+FUZZER_FLAG_INT(detect_leaks, 0, "If 1, and if LeakSanitizer is enabled "
+    "try to detect memory leaks during fuzzing (i.e. not only at shut down).")
 
 FUZZER_DEPRECATED_FLAG(exit_on_first)
 FUZZER_DEPRECATED_FLAG(save_minimized_corpus)

Modified: llvm/trunk/lib/Fuzzer/FuzzerInternal.h
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Fuzzer/FuzzerInternal.h?rev=266838&r1=266837&r2=266838&view=diff
==============================================================================
--- llvm/trunk/lib/Fuzzer/FuzzerInternal.h (original)
+++ llvm/trunk/lib/Fuzzer/FuzzerInternal.h Tue Apr 19 19:24:21 2016
@@ -304,6 +304,7 @@ public:
     bool OutputCSV = false;
     bool PrintNewCovPcs = false;
     bool PrintFinalStats = false;
+    bool DetectLeaks = false;
   };
   Fuzzer(UserCallback CB, MutationDispatcher &MD, FuzzingOptions Options);
   void AddToCorpus(const Unit &U) {
@@ -366,6 +367,8 @@ private:
   void PrintStats(const char *Where, const char *End = "\n");
   void PrintStatusForNewUnit(const Unit &U);
   void ShuffleCorpus(UnitVector *V);
+  void TryDetectingAMemoryLeak(uint8_t *Data, size_t Size);
+  void CheckForMemoryLeaks();
 
   // Updates the probability distribution for the units in the corpus.
   // Must be called whenever the corpus or unit weights are changed.
@@ -398,6 +401,8 @@ private:
   size_t TotalNumberOfExecutedTraceBasedMutations = 0;
   size_t NumberOfNewUnitsAdded = 0;
 
+  bool HasMoreMallocsThanFrees = false;
+
   std::vector<Unit> Corpus;
   std::unordered_set<std::string> UnitHashesAddedToCorpus;
 

Modified: llvm/trunk/lib/Fuzzer/FuzzerLoop.cpp
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Fuzzer/FuzzerLoop.cpp?rev=266838&r1=266837&r2=266838&view=diff
==============================================================================
--- llvm/trunk/lib/Fuzzer/FuzzerLoop.cpp (original)
+++ llvm/trunk/lib/Fuzzer/FuzzerLoop.cpp Tue Apr 19 19:24:21 2016
@@ -18,6 +18,9 @@
 #if __has_include(<sanitizer / coverage_interface.h>)
 #include <sanitizer/coverage_interface.h>
 #endif
+#if __has_include(<sanitizer / lsan_interface.h>)
+#include <sanitizer/lsan_interface.h>
+#endif
 #endif
 
 #define NO_SANITIZE_MEMORY
@@ -47,6 +50,11 @@ __sanitizer_get_coverage_pc_buffer(uintp
 __attribute__((weak)) size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size,
                                                      size_t MaxSize,
                                                      unsigned int Seed);
+__attribute__((weak)) void __sanitizer_malloc_hook(void *ptr, size_t size);
+__attribute__((weak)) void __sanitizer_free_hook(void *ptr);
+__attribute__((weak)) void __lsan_enable();
+__attribute__((weak)) void __lsan_disable();
+__attribute__((weak)) int __lsan_do_recoverable_leak_check();
 }
 
 namespace fuzzer {
@@ -277,6 +285,7 @@ void Fuzzer::ShuffleAndMinimize() {
   for (auto &X : Corpus)
     UnitHashesAddedToCorpus.insert(Hash(X));
   PrintStats("INITED");
+  CheckForMemoryLeaks();
 }
 
 bool Fuzzer::RunOne(const uint8_t *Data, size_t Size) {
@@ -310,6 +319,26 @@ void Fuzzer::RunOneAndUpdateCorpus(uint8
     ReportNewCoverage({Data, Data + Size});
 }
 
+// Leak detection is expensive, so we first check if there were more mallocs
+// than frees (using the sanitizer malloc hooks) and only then try to call lsan.
+struct MallocFreeTracer {
+  void Start() {
+    Mallocs = 0;
+    Frees = 0;
+  }
+  // Returns true if there were more mallocs than frees.
+  bool Stop() { return Mallocs > Frees; }
+  size_t Mallocs;
+  size_t Frees;
+};
+
+static thread_local MallocFreeTracer AllocTracer;
+
+extern "C" {
+void __sanitizer_malloc_hook(void *ptr, size_t size) { AllocTracer.Mallocs++; }
+void __sanitizer_free_hook(void *ptr) { AllocTracer.Frees++; }
+}  // extern "C"
+
 void Fuzzer::ExecuteCallback(const uint8_t *Data, size_t Size) {
   UnitStartTime = system_clock::now();
   // We copy the contents of Unit into a separate heap buffer
@@ -319,10 +348,12 @@ void Fuzzer::ExecuteCallback(const uint8
   AssignTaintLabels(DataCopy.get(), Size);
   CurrentUnitData = DataCopy.get();
   CurrentUnitSize = Size;
+  AllocTracer.Start();
   int Res = CB(DataCopy.get(), Size);
+  (void)Res;
+  HasMoreMallocsThanFrees = AllocTracer.Stop();
   CurrentUnitSize = 0;
   CurrentUnitData = nullptr;
-  (void)Res;
   assert(Res == 0);
 }
 
@@ -498,6 +529,47 @@ void Fuzzer::Merge(const std::vector<std
   Printf("=== Merge: written %zd units\n", Res.size());
 }
 
+// Tries to call lsan, and if there are leaks exits. We call this right after
+// the initial corpus was read because if there are leaky inputs in the corpus
+// further fuzzing will likely hit OOMs.
+void Fuzzer::CheckForMemoryLeaks() {
+  if (!Options.DetectLeaks) return;
+  if (!__lsan_do_recoverable_leak_check)
+    return;
+  if (__lsan_do_recoverable_leak_check()) {
+    Printf("==%d== ERROR: libFuzzer: initial corpus triggers memory leaks.\n"
+           "Exiting now. Use -detect_leaks=0 to disable leak detection here.\n"
+           "LeakSanitizer will still check for leaks at the process exit.\n",
+           GetPid());
+    PrintFinalStats();
+    _Exit(Options.ErrorExitCode);
+  }
+}
+
+// Tries detecting a memory leak on the particular input that we have just
+// executed before calling this function.
+void Fuzzer::TryDetectingAMemoryLeak(uint8_t *Data, size_t Size) {
+  if (!HasMoreMallocsThanFrees) return;  // mallocs==frees, a leak is unlikely.
+  if (!Options.DetectLeaks) return;
+  if (!&__lsan_enable || !&__lsan_disable || !__lsan_do_recoverable_leak_check)
+    return;  // No lsan.
+  // Run the target once again, but with lsan disabled so that if there is
+  // a real leak we do not report it twice.
+  __lsan_disable();
+  RunOneAndUpdateCorpus(Data, Size);
+  __lsan_enable();
+  if (!HasMoreMallocsThanFrees) return;  // a leak is unlikely.
+  // Now perform the actual lsan pass. This is expensive and we must ensure
+  // we don't call it too often.
+  if (__lsan_do_recoverable_leak_check()) {  // Leak is found, report it.
+    CurrentUnitData = Data;
+    CurrentUnitSize = Size;
+    DumpCurrentUnit("leak-");
+    PrintFinalStats();
+    _Exit(Options.ErrorExitCode);  // not exit() to disable lsan further on.
+  }
+}
+
 void Fuzzer::MutateAndTestOne() {
   MD.StartMutationSequence();
 
@@ -522,6 +594,7 @@ void Fuzzer::MutateAndTestOne() {
       StartTraceRecording();
     RunOneAndUpdateCorpus(MutateInPlaceHere.data(), Size);
     StopTraceRecording();
+    TryDetectingAMemoryLeak(MutateInPlaceHere.data(), Size);
   }
 }
 

Modified: llvm/trunk/lib/Fuzzer/test/LeakTest.cpp
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Fuzzer/test/LeakTest.cpp?rev=266838&r1=266837&r2=266838&view=diff
==============================================================================
--- llvm/trunk/lib/Fuzzer/test/LeakTest.cpp (original)
+++ llvm/trunk/lib/Fuzzer/test/LeakTest.cpp Tue Apr 19 19:24:21 2016
@@ -8,7 +8,10 @@
 static volatile void *Sink;
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
-  Sink = new int;
+  if (Size > 0 && *Data == 'H') {
+    Sink = new int;
+    Sink = nullptr;
+  }
   return 0;
 }
 

Modified: llvm/trunk/lib/Fuzzer/test/fuzzer-leak.test
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Fuzzer/test/fuzzer-leak.test?rev=266838&r1=266837&r2=266838&view=diff
==============================================================================
--- llvm/trunk/lib/Fuzzer/test/fuzzer-leak.test (original)
+++ llvm/trunk/lib/Fuzzer/test/fuzzer-leak.test Tue Apr 19 19:24:21 2016
@@ -1,6 +1,20 @@
-RUN: not LLVMFuzzer-LeakTest -runs=10 2>&1 | FileCheck %s --check-prefix=LEAK
-LEAK: ERROR: LeakSanitizer: detected memory leaks
-LEAK-NOT: DEATH:
+RUN: not LLVMFuzzer-LeakTest -runs=100000 -detect_leaks=1 2>&1 | FileCheck %s --check-prefix=LEAK_DURING
+LEAK_DURING: ERROR: LeakSanitizer: detected memory leaks
+LEAK_DURING: Direct leak of 4 byte(s) in 1 object(s) allocated from:
+LEAK_DURING-NOT: DONE
+LEAK_DURING-NOT: Done
+LEAK_DURING-NOT: DEATH:
+
+RUN: not LLVMFuzzer-LeakTest -runs=0 -detect_leaks=1 %S 2>&1 | FileCheck %s --check-prefix=LEAK_IN_CORPUS
+LEAK_IN_CORPUS: ERROR: LeakSanitizer: detected memory leaks
+LEAK_IN_CORPUS: ERROR: libFuzzer: initial corpus triggers memory leaks.
+
+
+RUN: not LLVMFuzzer-LeakTest -runs=100000 -detect_leaks=0 2>&1 | FileCheck %s --check-prefix=LEAK_AFTER
+RUN: not LLVMFuzzer-LeakTest -runs=100000                 2>&1 | FileCheck %s --check-prefix=LEAK_AFTER
+LEAK_AFTER: Done 100000 runs in
+LEAK_AFTER: ERROR: LeakSanitizer: detected memory leaks
+LEAK_AFTER-NOT: DEATH:
 
 RUN: not LLVMFuzzer-LeakTimeoutTest -timeout=1 2>&1 | FileCheck %s --check-prefix=LEAK_TIMEOUT
 LEAK_TIMEOUT: ERROR: libFuzzer: timeout after




More information about the llvm-commits mailing list