[compiler-rt] 92fb310 - [libFuzzer] Extend the fuzz target intarface to allow -1 return value.

Kostya Serebryany via llvm-commits llvm-commits at lists.llvm.org
Thu Jun 30 13:21:43 PDT 2022


Author: Kostya Serebryany
Date: 2022-06-30T13:21:27-07:00
New Revision: 92fb310151d2b1e349695fc0f1c5d5d50afb3b52

URL: https://github.com/llvm/llvm-project/commit/92fb310151d2b1e349695fc0f1c5d5d50afb3b52
DIFF: https://github.com/llvm/llvm-project/commit/92fb310151d2b1e349695fc0f1c5d5d50afb3b52.diff

LOG: [libFuzzer] Extend the fuzz target intarface to allow -1 return value.

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

Added: 
    compiler-rt/test/fuzzer/Reject.cpp
    compiler-rt/test/fuzzer/reject.test

Modified: 
    compiler-rt/lib/fuzzer/FuzzerInternal.h
    compiler-rt/lib/fuzzer/FuzzerLoop.cpp
    compiler-rt/test/fuzzer/not-instrumented.test
    llvm/docs/LibFuzzer.rst

Removed: 
    


################################################################################
diff  --git a/compiler-rt/lib/fuzzer/FuzzerInternal.h b/compiler-rt/lib/fuzzer/FuzzerInternal.h
index 6637b0034e552..31f54eaa478a8 100644
--- a/compiler-rt/lib/fuzzer/FuzzerInternal.h
+++ b/compiler-rt/lib/fuzzer/FuzzerInternal.h
@@ -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);

diff  --git a/compiler-rt/lib/fuzzer/FuzzerLoop.cpp b/compiler-rt/lib/fuzzer/FuzzerLoop.cpp
index 3205942f6d84d..f095757229e9e 100644
--- a/compiler-rt/lib/fuzzer/FuzzerLoop.cpp
+++ b/compiler-rt/lib/fuzzer/FuzzerLoop.cpp
@@ -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();
@@ -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++;
@@ -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) {
@@ -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);
   }
 }
 

diff  --git a/compiler-rt/test/fuzzer/Reject.cpp b/compiler-rt/test/fuzzer/Reject.cpp
new file mode 100644
index 0000000000000..d67085cd50102
--- /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 <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;
+}

diff  --git a/compiler-rt/test/fuzzer/not-instrumented.test b/compiler-rt/test/fuzzer/not-instrumented.test
index 2330c47700675..6bf51f05c0f26 100644
--- 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
index 0000000000000..b7b7391ba89e1
--- /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
index d4374602e7c1a..89265ebfe35ec 100644
--- a/llvm/docs/LibFuzzer.rst
+++ b/llvm/docs/LibFuzzer.rst
@@ -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
@@ -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
 -----


        


More information about the llvm-commits mailing list