diff --git a/compiler-rt/lib/fuzzer/FuzzerInternal.h b/compiler-rt/lib/fuzzer/FuzzerInternal.h --- a/compiler-rt/lib/fuzzer/FuzzerInternal.h +++ b/compiler-rt/lib/fuzzer/FuzzerInternal.h @@ -65,7 +65,10 @@ static void StaticFileSizeExceedCallback(); static void StaticGracefulExitCallback(); - void ExecuteCallback(const uint8_t *Data, size_t Size); + // Executes the target callback on {Data, Size} once. + // Returns false if the input was rejected by the target (target returned -1), + // and true otherwise. + bool ExecuteCallback(const uint8_t *Data, size_t Size); bool RunOne(const uint8_t *Data, size_t Size, bool MayDeleteFile = false, InputInfo *II = nullptr, bool ForceAddToCorpus = false, bool *FoundUniqFeatures = nullptr); diff --git a/compiler-rt/lib/fuzzer/FuzzerLoop.cpp b/compiler-rt/lib/fuzzer/FuzzerLoop.cpp --- a/compiler-rt/lib/fuzzer/FuzzerLoop.cpp +++ b/compiler-rt/lib/fuzzer/FuzzerLoop.cpp @@ -511,7 +511,7 @@ // Largest input length should be INT_MAX. assert(Size < std::numeric_limits::max()); - ExecuteCallback(Data, Size); + if(!ExecuteCallback(Data, Size)) return false; auto TimeOfUnit = duration_cast(UnitStopTime - UnitStartTime); UniqFeatureSetTmp.clear(); @@ -586,7 +586,7 @@ // This method is not inlined because it would cause a test to fail where it // is part of the stack unwinding. See D97975 for details. -ATTRIBUTE_NOINLINE void Fuzzer::ExecuteCallback(const uint8_t *Data, +ATTRIBUTE_NOINLINE bool Fuzzer::ExecuteCallback(const uint8_t *Data, size_t Size) { TPC.RecordInitialStack(); TotalNumberOfRuns++; @@ -602,23 +602,24 @@ if (CurrentUnitData && CurrentUnitData != Data) memcpy(CurrentUnitData, Data, Size); CurrentUnitSize = Size; + int CBRes = 0; { ScopedEnableMsanInterceptorChecks S; AllocTracer.Start(Options.TraceMalloc); UnitStartTime = system_clock::now(); TPC.ResetMaps(); RunningUserCallback = true; - int Res = CB(DataCopy, Size); + CBRes = CB(DataCopy, Size); RunningUserCallback = false; UnitStopTime = system_clock::now(); - (void)Res; - assert(Res == 0); + assert(CBRes == 0 || CBRes == -1); HasMoreMallocsThanFrees = AllocTracer.Stop(); } if (!LooseMemeq(DataCopy, Data, Size)) CrashOnOverwrittenData(); CurrentUnitSize = 0; delete[] DataCopy; + return CBRes == 0; } std::string Fuzzer::WriteToOutputCorpus(const Unit &U) { @@ -843,9 +844,16 @@ } if (Corpus.empty() && Options.MaxNumberOfRuns) { - Printf("ERROR: no interesting inputs were found. " - "Is the code instrumented for coverage? Exiting.\n"); - exit(1); + Printf("WARNING: no interesting inputs were found so far. " + "Is the code instrumented for coverage?\n" + "This may also happen if the target rejected all inputs we tried so " + "far\n"); + // The remaining logic requires that the corpus is not empty, + // so we add one fake input to the in-memory corpus. + Corpus.AddToCorpus({'\n'}, /*NumFeatures=*/1, /*MayDeleteFile=*/true, + /*HasFocusFunction=*/false, /*NeverReduce=*/false, + /*TimeOfUnit=*/duration_cast(0s), {0}, DFT, + /*BaseII*/ nullptr); } } diff --git a/compiler-rt/test/fuzzer/Reject.cpp b/compiler-rt/test/fuzzer/Reject.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/fuzzer/Reject.cpp @@ -0,0 +1,23 @@ +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// Tests how the fuzzer rejects inputs if the target returns -1. +#include +#include + +static volatile int Sink; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + if (Size != 3) + return -1; // Reject anyting that's not 3 bytes long. + // Reject 'rej'. + if (Data[0] == 'r' && Data[1] == 'e' && Data[2] == 'j') + return -1; + // Accept 'acc'. + if (Data[0] == 'a' && Data[1] == 'c' && Data[2] == 'c') { + Sink = 1; + return 0; + } + return 0; +} diff --git a/compiler-rt/test/fuzzer/not-instrumented.test b/compiler-rt/test/fuzzer/not-instrumented.test --- a/compiler-rt/test/fuzzer/not-instrumented.test +++ b/compiler-rt/test/fuzzer/not-instrumented.test @@ -1,4 +1,4 @@ RUN: %cpp_compiler %S/NotinstrumentedTest.cpp -fsanitize-coverage=0 -o %t-NotinstrumentedTest-NoCoverage -RUN: not %run %t-NotinstrumentedTest-NoCoverage 2>&1 | FileCheck %s --check-prefix=NO_COVERAGE +RUN: %run %t-NotinstrumentedTest-NoCoverage -runs=100 2>&1 | FileCheck %s --check-prefix=NO_COVERAGE -NO_COVERAGE: ERROR: no interesting inputs were found. Is the code instrumented for coverage? Exiting +NO_COVERAGE: WARNING: no interesting inputs were found so far. Is the code instrumented for coverage? diff --git a/compiler-rt/test/fuzzer/reject.test b/compiler-rt/test/fuzzer/reject.test new file mode 100644 --- /dev/null +++ b/compiler-rt/test/fuzzer/reject.test @@ -0,0 +1,9 @@ +# Runs the Reject.cpp test, +# ensures that the input 'acc' is present in the corpus, +# and the input 'rej' is not. + +RUN: rm -rf %t-corpus && mkdir %t-corpus +RUN: %cpp_compiler %S/Reject.cpp -o %t-Reject +RUN: %run %t-Reject -runs=1000000 %t-corpus +RUN: grep 'acc' %t-corpus/* +RUN: not grep 'rej' %t-corpus/* diff --git a/llvm/docs/LibFuzzer.rst b/llvm/docs/LibFuzzer.rst --- a/llvm/docs/LibFuzzer.rst +++ b/llvm/docs/LibFuzzer.rst @@ -49,7 +49,7 @@ // fuzz_target.cc extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { DoSomethingInterestingWithMyAPI(Data, Size); - return 0; // Non-zero return values are reserved for future use. + return 0; // Values other than 0 and -1 are reserved for future use. } Note that this fuzz target does not depend on libFuzzer in any way @@ -646,6 +646,28 @@ int (*UserCb)(const uint8_t *Data, size_t Size)); +Rejecting unwanted inputs +------------------------- + +It may be desirable to reject some inputs, i.e. to not add them to the corpus. + +For example, when fuzzing an API consisting of parsing and other logic, +one may want to allow only those inputs into the corpus that parse successfully. + +If the fuzz target returns -1 on a given input, +libFuzzer will not add that input top the corpus, regardless of what coverage +it triggers. + + +.. code-block:: c++ + + extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + if (auto *Obj = ParseMe(Data, Size)) { + Obj->DoSomethingInteresting(); + return 0; // Accept. The input may be added to the corpus. + } + return -1; // Reject; The input will not be added to the corpus. + } Leaks -----