1
0
mirror of https://github.com/RPCS3/llvm-mirror.git synced 2024-11-22 18:54:02 +01:00

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

llvm-svn: 266838
This commit is contained in:
Kostya Serebryany 2016-04-20 00:24:21 +00:00
parent 6696baf203
commit 13bad441e8
6 changed files with 103 additions and 5 deletions

View File

@ -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())

View File

@ -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)

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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