Index: lib/fuzzer/FuzzerDefs.h =================================================================== --- lib/fuzzer/FuzzerDefs.h +++ lib/fuzzer/FuzzerDefs.h @@ -100,6 +100,11 @@ # define ATTRIBUTE_TARGET_POPCNT #endif +#if __has_attribute(noreturn) +# define ATTRIBUTE_NO_RETURN __attribute__((noreturn)) +#else +# define ATTRIBUTE_NO_RETURN +#endif #ifdef __clang__ // avoid gcc warning. # if __has_attribute(no_sanitize) Index: lib/fuzzer/FuzzerDriver.cpp =================================================================== --- lib/fuzzer/FuzzerDriver.cpp +++ lib/fuzzer/FuzzerDriver.cpp @@ -710,7 +710,7 @@ "*** executed the target code on a fixed set of inputs.\n" "***\n"); F->PrintFinalStats(); - exit(0); + F->ExitWithUserCallBack(0); } if (Flags.merge) { @@ -757,8 +757,7 @@ Printf("Done %zd runs in %zd second(s)\n", F->getTotalNumberOfRuns(), F->secondsSinceProcessStartUp()); F->PrintFinalStats(); - - exit(0); // Don't let F destroy itself. + F->ExitWithUserCallBack(0); // Don't let F destory itself. } // Storage for global ExternalFunctions object. Index: lib/fuzzer/FuzzerExtFunctions.def =================================================================== --- lib/fuzzer/FuzzerExtFunctions.def +++ lib/fuzzer/FuzzerExtFunctions.def @@ -24,6 +24,7 @@ const uint8_t * Data2, size_t Size2, uint8_t * Out, size_t MaxOutSize, unsigned int Seed), false); +EXT_FUNC(LLVMFuzzerOnExitHandler, void, (), false); // Sanitizer functions EXT_FUNC(__lsan_enable, void, (), false); Index: lib/fuzzer/FuzzerInterface.h =================================================================== --- lib/fuzzer/FuzzerInterface.h +++ lib/fuzzer/FuzzerInterface.h @@ -64,6 +64,10 @@ __attribute__((visibility("default"))) size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); +// Optional user-provided exit function. If provided, libFuzzer will try to +// call this function when libFuzzer exits, in most scenarios +__attribute__((visibility("default"))) void LLVMFuzzerOnExitHandler(); + #ifdef __cplusplus } // extern "C" #endif // __cplusplus Index: lib/fuzzer/FuzzerInternal.h =================================================================== --- lib/fuzzer/FuzzerInternal.h +++ lib/fuzzer/FuzzerInternal.h @@ -90,6 +90,8 @@ void HandleMalloc(size_t Size); void AnnounceOutput(const uint8_t *Data, size_t Size); + ATTRIBUTE_NO_RETURN static void ExitWithUserCallBack(int Status, + bool CleanUp = true); private: void AlarmCallback(); Index: lib/fuzzer/FuzzerLoop.cpp =================================================================== --- lib/fuzzer/FuzzerLoop.cpp +++ lib/fuzzer/FuzzerLoop.cpp @@ -134,7 +134,8 @@ DumpCurrentUnit("oom-"); Printf("SUMMARY: libFuzzer: out-of-memory\n"); PrintFinalStats(); - _Exit(Options.ErrorExitCode); // Stop right now. + ExitWithUserCallBack(Options.ErrorExitCode, + /*CleanUp=*/false); // Stop right now. } Fuzzer::Fuzzer(UserCallback CB, InputCorpus &Corpus, MutationDispatcher &MD, @@ -224,7 +225,10 @@ void Fuzzer::StaticFileSizeExceedCallback() { Printf("==%lu== ERROR: libFuzzer: file size exceeded\n", GetPid()); - exit(1); + // FIXME: Why is `exit()` being used rather than `_Exit()` + // here? + // FIXME: Exit code should really be `Options.ErrorExitCode`. + ExitWithUserCallBack(1, /*CleanUp=*/true); } void Fuzzer::CrashCallback() { @@ -237,7 +241,8 @@ Printf("SUMMARY: libFuzzer: deadly signal\n"); DumpCurrentUnit("crash-"); PrintFinalStats(); - _Exit(Options.ErrorExitCode); // Stop right now. + ExitWithUserCallBack(Options.ErrorExitCode, + /*CleanUp=*/false); // Stop right now } void Fuzzer::ExitCallback() { @@ -249,20 +254,22 @@ Printf("SUMMARY: libFuzzer: fuzz target exited\n"); DumpCurrentUnit("crash-"); PrintFinalStats(); - _Exit(Options.ErrorExitCode); + ExitWithUserCallBack(Options.ErrorExitCode, /*CleanUp=*/false); } void Fuzzer::MaybeExitGracefully() { if (!GracefulExitRequested) return; Printf("==%lu== INFO: libFuzzer: exiting as requested\n", GetPid()); PrintFinalStats(); - _Exit(0); + ExitWithUserCallBack(0, /*CleanUp=*/false); } void Fuzzer::InterruptCallback() { Printf("==%lu== libFuzzer: run interrupted; exiting\n", GetPid()); PrintFinalStats(); - _Exit(0); // Stop right now, don't perform any at-exit actions. + // Stop right now, don't perform any at-exit actions except the user defined + // callback. + ExitWithUserCallBack(0, /*CleanUp=*/false); } NO_SANITIZE_MEMORY @@ -292,7 +299,8 @@ EF->__sanitizer_print_stack_trace(); Printf("SUMMARY: libFuzzer: timeout\n"); PrintFinalStats(); - _Exit(Options.TimeoutExitCode); // Stop right now. + ExitWithUserCallBack(Options.TimeoutExitCode, + /*CleanUp=*/false); // Stop right now. } } @@ -306,7 +314,8 @@ DumpCurrentUnit("oom-"); Printf("SUMMARY: libFuzzer: out-of-memory\n"); PrintFinalStats(); - _Exit(Options.ErrorExitCode); // Stop right now. + ExitWithUserCallBack(Options.ErrorExitCode, + /*CleanUp=*/false); // Stop right now. } void Fuzzer::PrintStats(const char *Where, const char *End, size_t Units) { @@ -382,7 +391,7 @@ if (Descr.find(Options.ExitOnSrcPos) != std::string::npos) { Printf("INFO: found line matching '%s', exiting.\n", Options.ExitOnSrcPos.c_str()); - _Exit(0); + ExitWithUserCallBack(0, /*CleanUp=*/false); } }; TPC.ForEachObservedPC(HandlePC); @@ -391,7 +400,7 @@ if (Corpus.HasUnit(Options.ExitOnItem)) { Printf("INFO: found item with checksum '%s', exiting.\n", Options.ExitOnItem.c_str()); - _Exit(0); + ExitWithUserCallBack(0, /*CleanUp=*/false); } } } @@ -484,6 +493,8 @@ GetPid()); DumpCurrentUnit("crash-"); Printf("SUMMARY: libFuzzer: out-of-memory\n"); + // Don't use ExitWithUserCallBack(). The client code has abused our API + // so it's probably best to avoid calling `LLVMFuzzerOnExitHandler`. _Exit(Options.ErrorExitCode); // Stop right now. } @@ -612,7 +623,8 @@ CurrentUnitSize = Size; DumpCurrentUnit("leak-"); PrintFinalStats(); - _Exit(Options.ErrorExitCode); // not exit() to disable lsan further on. + // CleanUp=false to disable lsan further on. + ExitWithUserCallBack(Options.ErrorExitCode, /*CleanUp=*/false); } } @@ -820,6 +832,15 @@ } } +void Fuzzer::ExitWithUserCallBack(int Status, bool CleanUp) { + if (EF->LLVMFuzzerOnExitHandler) { + EF->LLVMFuzzerOnExitHandler(); + } + if (CleanUp) + exit(Status); + _Exit(Status); +} + } // namespace fuzzer extern "C" { Index: test/fuzzer/FuzzerOnExitHandlerTest.cpp =================================================================== --- /dev/null +++ test/fuzzer/FuzzerOnExitHandlerTest.cpp @@ -0,0 +1,63 @@ +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. + +// Test for a fuzzer: must find the case where a particular basic block is +// executed many times. +#include +#include +#include +#include +#include +#include + +__attribute__((noinline)) uint8_t *getNull() { return 0; } +uint8_t *v = NULL; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { +#if defined(TEST_MODE_LOOP) + return 0; +#elif defined(TEST_MODE_ABORT) + abort(); +#elif defined(TEST_MODE_CRASH) + uint8_t *v = getNull(); + v[0] = 'x'; // BOOM + return 0; +#elif defined(TEST_MODE_HANG) + uint8_t *v = 0; + while (true) { + v = getNull(); + } + return 0; +#elif defined(TEST_MODE_LARGE_ALLOC) + if (Size == 0) + return 0; // Don't alloc during INIT + v = (uint8_t *)malloc(30 << 20); // 30MiB + assert(v != NULL); + // Do something with the allocated memory to avoid Clang optimizing + // malloc/free out. + memcpy(v, Data, Size); + free(v); + return 0; +#elif defined(TEST_MODE_LEAK) + v = (uint8_t *)malloc(128); // Leak + return 0; +#elif defined(TEST_MODE_EXIT_ON_SRC_LOC) + if (Size > 0 && Data[0] == 'H') { + v = (uint8_t *)0x0; + if (Size > 1 && Data[1] == 'i') { + v = (uint8_t *)0x1; // This is the source location + if (Size > 2 && Data[2] == '!') { + v = (uint8_t *)0x2; + } + } + } + return 0; +#else +#error TEST_MODE must be set +#endif +} + +extern "C" void LLVMFuzzerOnExitHandler() { + printf("Custom LLVMFuzzerOnExitHandler called\n"); + fflush(stdout); +} Index: test/fuzzer/fuzzer-on-exit-handler-leaks.test =================================================================== --- /dev/null +++ test/fuzzer/fuzzer-on-exit-handler-leaks.test @@ -0,0 +1,13 @@ +// FIXME: Update as LSan support is implemented in more targets and LibFuzzer. +REQUIRES: linux +RUN: %cpp_compiler -fsanitize=leak -DTEST_MODE_LARGE_ALLOC %S/FuzzerOnExitHandlerTest.cpp -o %t-FuzzerAtExitHandlerTestLargeAllocLSan + +// Malloc limit +RUN: not %t-FuzzerAtExitHandlerTestLargeAllocLSan -malloc_limit_mb=1 2>&1 | FileCheck %s +// Clean exit + +// Leak check +RUN: %cpp_compiler -fsanitize=leak -DTEST_MODE_LEAK %S/FuzzerOnExitHandlerTest.cpp -o %t-FuzzerAtExitHandlerTestLeak +RUN: not %t-FuzzerAtExitHandlerTestLeak -detect_leaks=1 2>&1 | FileCheck %s + +CHECK: Custom LLVMFuzzerOnExitHandler called Index: test/fuzzer/fuzzer-on-exit-handler.test =================================================================== --- /dev/null +++ test/fuzzer/fuzzer-on-exit-handler.test @@ -0,0 +1,41 @@ +// Clean exit +RUN: %cpp_compiler -DTEST_MODE_LOOP %S/FuzzerOnExitHandlerTest.cpp -o %t-FuzzerAtExitHandlerTestLoop +RUN: %t-FuzzerAtExitHandlerTestLoop -runs=20 2>&1 | FileCheck %s + + +// Abort called +RUN: %cpp_compiler -DTEST_MODE_ABORT %S/FuzzerOnExitHandlerTest.cpp -o %t-FuzzerAtExitHandlerTestAbort +RUN: not %t-FuzzerAtExitHandlerTestAbort 2>&1 | FileCheck %s + +// Crash +RUN: %cpp_compiler -DTEST_MODE_CRASH %S/FuzzerOnExitHandlerTest.cpp -o %t-FuzzerAtExitHandlerTestCrash +RUN: not %t-FuzzerAtExitHandlerTestCrash 2>&1 | FileCheck %s + +// Total timeout +RUN: %t-FuzzerAtExitHandlerTestLoop -max_total_time=2 2>&1 | FileCheck %s + +// Per unit timeout +RUN: %cpp_compiler -DTEST_MODE_HANG %S/FuzzerOnExitHandlerTest.cpp -o %t-FuzzerAtExitHandlerTestHang +RUN: not %t-FuzzerAtExitHandlerTestHang -timeout=2 2>&1 | FileCheck %s + + +// Run on files only +RUN: echo "input one" >> %t-input-one.txt +RUN: echo "input two" >> %t-input-two.txt +RUN: %t-FuzzerAtExitHandlerTestLoop %t-input-one.txt %t-input-two.txt 2>&1 | FileCheck %s + +// RSS limit +RUN: %cpp_compiler -DTEST_MODE_LARGE_ALLOC %S/FuzzerOnExitHandlerTest.cpp -o %t-FuzzerAtExitHandlerTestLargeAlloc +RUN: not %t-FuzzerAtExitHandlerTestLargeAlloc -rss_limit_mb=30 2>&1 | FileCheck %s + +// Exit on source location +// NOTE: Compile flags taken from exit_on_src_pos.test +RUN: %cpp_compiler -O0 -DTEST_MODE_EXIT_ON_SRC_LOC %S/FuzzerOnExitHandlerTest.cpp -mllvm -use-unknown-locations=Disable -o %t-FuzzerAtExitHandlerTestSimpleTest +RUN: %t-FuzzerAtExitHandlerTestSimpleTest -max_len=2 -exit_on_src_pos=FuzzerOnExitHandlerTest.cpp:49 2>&1 | FileCheck %s + +// Exit on unit +RUN: %t-FuzzerAtExitHandlerTestSimpleTest -seed=1 -max_len=2 -exit_on_item=adc83b19e793491b1c6ea0fd8b46cd9f32e592fc 2>&1 | FileCheck %s + +// FIXME: We should really test SIGINT but there's no portable way to do that. + +CHECK: Custom LLVMFuzzerOnExitHandler called