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:
parent
6696baf203
commit
13bad441e8
@ -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())
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user