[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