summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--llvm/lib/Fuzzer/FuzzerDriver.cpp1
-rw-r--r--llvm/lib/Fuzzer/FuzzerFlags.def2
-rw-r--r--llvm/lib/Fuzzer/FuzzerInternal.h5
-rw-r--r--llvm/lib/Fuzzer/FuzzerLoop.cpp75
-rw-r--r--llvm/lib/Fuzzer/test/LeakTest.cpp5
-rw-r--r--llvm/lib/Fuzzer/test/fuzzer-leak.test20
6 files changed, 103 insertions, 5 deletions
diff --git a/llvm/lib/Fuzzer/FuzzerDriver.cpp b/llvm/lib/Fuzzer/FuzzerDriver.cpp
index 57532b4da0a..b0dc8f889d3 100644
--- a/llvm/lib/Fuzzer/FuzzerDriver.cpp
+++ b/llvm/lib/Fuzzer/FuzzerDriver.cpp
@@ -294,6 +294,7 @@ static int FuzzerDriver(const std::vector<std::string> &Args,
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())
diff --git a/llvm/lib/Fuzzer/FuzzerFlags.def b/llvm/lib/Fuzzer/FuzzerFlags.def
index a9008fac6a6..261623277a4 100644
--- a/llvm/lib/Fuzzer/FuzzerFlags.def
+++ b/llvm/lib/Fuzzer/FuzzerFlags.def
@@ -79,6 +79,8 @@ FUZZER_FLAG_INT(handle_term, 1, "If 1, try to intercept SIGTERM.")
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)
diff --git a/llvm/lib/Fuzzer/FuzzerInternal.h b/llvm/lib/Fuzzer/FuzzerInternal.h
index 50402f8702f..c5443b07a33 100644
--- a/llvm/lib/Fuzzer/FuzzerInternal.h
+++ b/llvm/lib/Fuzzer/FuzzerInternal.h
@@ -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;
diff --git a/llvm/lib/Fuzzer/FuzzerLoop.cpp b/llvm/lib/Fuzzer/FuzzerLoop.cpp
index 3b00e47d233..5fe57bbb53d 100644
--- a/llvm/lib/Fuzzer/FuzzerLoop.cpp
+++ b/llvm/lib/Fuzzer/FuzzerLoop.cpp
@@ -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(uintptr_t **data);
__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_t *Data, size_t Size) {
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_t *Data, size_t Size) {
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::string> &Corpora) {
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);
}
}
diff --git a/llvm/lib/Fuzzer/test/LeakTest.cpp b/llvm/lib/Fuzzer/test/LeakTest.cpp
index 69ff10b2223..22e5164050e 100644
--- a/llvm/lib/Fuzzer/test/LeakTest.cpp
+++ b/llvm/lib/Fuzzer/test/LeakTest.cpp
@@ -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;
}
diff --git a/llvm/lib/Fuzzer/test/fuzzer-leak.test b/llvm/lib/Fuzzer/test/fuzzer-leak.test
index 3690068371f..25c1f45978c 100644
--- a/llvm/lib/Fuzzer/test/fuzzer-leak.test
+++ b/llvm/lib/Fuzzer/test/fuzzer-leak.test
@@ -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
OpenPOWER on IntegriCloud