Skip to content

Commit

Permalink
[libFuzzer] Extend the fuzz target intarface to allow -1 return value.
Browse files Browse the repository at this point in the history
With this change, fuzz targets may choose to return -1
to indicate that the input should not be added to the corpus
regardless of the coverage it generated.

Reviewed By: morehouse

Differential Revision: https://reviews.llvm.org/D128749
  • Loading branch information
kcc committed Jun 30, 2022
1 parent e633f8c commit 92fb310
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 12 deletions.
5 changes: 4 additions & 1 deletion compiler-rt/lib/fuzzer/FuzzerInternal.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ class Fuzzer {
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);
Expand Down
24 changes: 16 additions & 8 deletions compiler-rt/lib/fuzzer/FuzzerLoop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ bool Fuzzer::RunOne(const uint8_t *Data, size_t Size, bool MayDeleteFile,
// Largest input length should be INT_MAX.
assert(Size < std::numeric_limits<uint32_t>::max());

ExecuteCallback(Data, Size);
if(!ExecuteCallback(Data, Size)) return false;
auto TimeOfUnit = duration_cast<microseconds>(UnitStopTime - UnitStartTime);

UniqFeatureSetTmp.clear();
Expand Down Expand Up @@ -586,7 +586,7 @@ static bool LooseMemeq(const uint8_t *A, const uint8_t *B, size_t Size) {

// 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++;
Expand All @@ -602,23 +602,24 @@ ATTRIBUTE_NOINLINE void Fuzzer::ExecuteCallback(const uint8_t *Data,
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) {
Expand Down Expand Up @@ -843,9 +844,16 @@ void Fuzzer::ReadAndExecuteSeedCorpora(std::vector<SizedFile> &CorporaFiles) {
}

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<microseconds>(0s), {0}, DFT,
/*BaseII*/ nullptr);
}
}

Expand Down
23 changes: 23 additions & 0 deletions compiler-rt/test/fuzzer/Reject.cpp
Original file line number Diff line number Diff line change
@@ -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 <cstddef>
#include <cstdint>

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;
}
4 changes: 2 additions & 2 deletions compiler-rt/test/fuzzer/not-instrumented.test
Original file line number Diff line number Diff line change
@@ -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?
9 changes: 9 additions & 0 deletions compiler-rt/test/fuzzer/reject.test
Original file line number Diff line number Diff line change
@@ -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/*
24 changes: 23 additions & 1 deletion llvm/docs/LibFuzzer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Like this:
// 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
Expand Down Expand Up @@ -646,6 +646,28 @@ arguments and a callback. This callback is invoked just like
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
-----
Expand Down

0 comments on commit 92fb310

Please sign in to comment.