/ src / test / fuzz / fuzz.cpp
fuzz.cpp
  1  // Copyright (c) 2009-present The Bitcoin Core developers
  2  // Distributed under the MIT software license, see the accompanying
  3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  4  
  5  #include <test/fuzz/fuzz.h>
  6  
  7  #include <netaddress.h>
  8  #include <netbase.h>
  9  #include <test/fuzz/util/check_globals.h>
 10  #include <test/util/coverage.h>
 11  #include <test/util/random.h>
 12  #include <test/util/setup_common.h>
 13  #include <util/check.h>
 14  #include <util/fs.h>
 15  #include <util/sock.h>
 16  #include <util/time.h>
 17  
 18  #include <algorithm>
 19  #include <csignal>
 20  #include <cstdint>
 21  #include <cstdio>
 22  #include <cstdlib>
 23  #include <cstring>
 24  #include <exception>
 25  #include <fstream>
 26  #include <functional>
 27  #include <iostream>
 28  #include <map>
 29  #include <memory>
 30  #include <random>
 31  #include <string>
 32  #include <tuple>
 33  #include <utility>
 34  #include <vector>
 35  
 36  #if defined(PROVIDE_FUZZ_MAIN_FUNCTION) && defined(__AFL_FUZZ_INIT)
 37  __AFL_FUZZ_INIT();
 38  #endif
 39  
 40  /**
 41   * A copy of the command line arguments that start with `--`.
 42   * First `LLVMFuzzerInitialize()` is called, which saves the arguments to `g_args`.
 43   * Later, depending on the fuzz test, `G_TEST_COMMAND_LINE_ARGUMENTS()` may be
 44   * called by `BasicTestingSetup` constructor to fetch those arguments and store
 45   * them in `BasicTestingSetup::m_node::args`.
 46   */
 47  static std::vector<const char*> g_args;
 48  
 49  static void SetArgs(int argc, char** argv) {
 50      for (int i = 1; i < argc; ++i) {
 51          // Only take into account arguments that start with `--`. The others are for the fuzz engine:
 52          // `fuzz -runs=1 fuzz_corpora/address_deserialize --checkaddrman=5`
 53          if (strlen(argv[i]) > 2 && argv[i][0] == '-' && argv[i][1] == '-') {
 54              g_args.push_back(argv[i]);
 55          }
 56      }
 57  }
 58  
 59  const std::function<std::vector<const char*>()> G_TEST_COMMAND_LINE_ARGUMENTS = []() {
 60      return g_args;
 61  };
 62  
 63  struct FuzzTarget {
 64      const TypeTestOneInput test_one_input;
 65      const FuzzTargetOptions opts;
 66  };
 67  
 68  auto& FuzzTargets()
 69  {
 70      static std::map<std::string_view, FuzzTarget> g_fuzz_targets;
 71      return g_fuzz_targets;
 72  }
 73  
 74  void FuzzFrameworkRegisterTarget(std::string_view name, TypeTestOneInput target, FuzzTargetOptions opts)
 75  {
 76      const auto [it, ins]{FuzzTargets().try_emplace(name, std::move(target), std::move(opts))};
 77      Assert(ins);
 78  }
 79  
 80  static std::string_view g_fuzz_target;
 81  static const TypeTestOneInput* g_test_one_input{nullptr};
 82  
 83  static void test_one_input(FuzzBufferType buffer)
 84  {
 85      CheckGlobals check{};
 86      (*Assert(g_test_one_input))(buffer);
 87  }
 88  
 89  const std::function<std::string()> G_TEST_GET_FULL_NAME{[]{
 90      return std::string{g_fuzz_target};
 91  }};
 92  
 93  static void initialize()
 94  {
 95      CheckGlobals check{};
 96      // By default, make the RNG deterministic with a fixed seed. This will affect all
 97      // randomness during the fuzz test, except:
 98      // - GetStrongRandBytes(), which is used for the creation of private key material.
 99      // - Randomness obtained before this call in g_rng_temp_path_init
100      SeedRandomStateForTest(SeedRand::ZEROS);
101  
102      // Set time to the genesis block timestamp for deterministic initialization.
103      SetMockTime(1231006505);
104  
105      // Terminate immediately if a fuzzing harness ever tries to create a socket.
106      // Individual tests can override this by pointing CreateSock to a mocked alternative.
107      CreateSock = [](int, int, int) -> std::unique_ptr<Sock> { std::terminate(); };
108  
109      // Terminate immediately if a fuzzing harness ever tries to perform a DNS lookup.
110      g_dns_lookup = [](const std::string& name, bool allow_lookup) {
111          if (allow_lookup) {
112              std::terminate();
113          }
114          return WrappedGetAddrInfo(name, false);
115      };
116  
117      bool should_exit{false};
118      if (std::getenv("PRINT_ALL_FUZZ_TARGETS_AND_ABORT")) {
119          for (const auto& [name, t] : FuzzTargets()) {
120              if (t.opts.hidden) continue;
121              std::cout << name << std::endl;
122          }
123          should_exit = true;
124      }
125      if (const char* out_path = std::getenv("WRITE_ALL_FUZZ_TARGETS_AND_ABORT")) {
126          std::cout << "Writing all fuzz target names to '" << out_path << "'." << std::endl;
127          std::ofstream out_stream{out_path, std::ios::binary};
128          for (const auto& [name, t] : FuzzTargets()) {
129              if (t.opts.hidden) continue;
130              out_stream << name << std::endl;
131          }
132          should_exit = true;
133      }
134      if (should_exit) {
135          std::exit(EXIT_SUCCESS);
136      }
137      if (const auto* env_fuzz{std::getenv("FUZZ")}) {
138          // To allow for easier fuzz executable binary modification,
139          static std::string g_copy{env_fuzz}; // create copy to avoid compiler optimizations, and
140          g_fuzz_target = g_copy.c_str();      // strip string after the first null-char.
141      } else {
142          std::cerr << "Must select fuzz target with the FUZZ env var." << std::endl;
143          std::cerr << "Hint: Set the PRINT_ALL_FUZZ_TARGETS_AND_ABORT=1 env var to see all compiled targets." << std::endl;
144          std::exit(EXIT_FAILURE);
145      }
146      const auto it = FuzzTargets().find(g_fuzz_target);
147      if (it == FuzzTargets().end()) {
148          std::cerr << "No fuzz target compiled for " << g_fuzz_target << "." << std::endl;
149          std::exit(EXIT_FAILURE);
150      }
151      if constexpr (!G_FUZZING_BUILD && !G_ABORT_ON_FAILED_ASSUME) {
152          std::cerr << "Must compile with -DBUILD_FOR_FUZZING=ON or in Debug mode to execute a fuzz target." << std::endl;
153          std::exit(EXIT_FAILURE);
154      }
155      if (!EnableFuzzDeterminism()) {
156          if (std::getenv("FUZZ_NONDETERMINISM")) {
157              std::cerr << "Warning: FUZZ_NONDETERMINISM env var set, results may be inconsistent with fuzz build" << std::endl;
158          } else {
159              g_enable_dynamic_fuzz_determinism = true;
160              assert(EnableFuzzDeterminism());
161          }
162      }
163      Assert(!g_test_one_input);
164      g_test_one_input = &it->second.test_one_input;
165      it->second.opts.init();
166  
167      ResetCoverageCounters();
168  }
169  
170  #if defined(PROVIDE_FUZZ_MAIN_FUNCTION)
171  static bool read_stdin(std::vector<uint8_t>& data)
172  {
173      std::istream::char_type buffer[1024];
174      std::streamsize length;
175      while ((std::cin.read(buffer, 1024), length = std::cin.gcount()) > 0) {
176          data.insert(data.end(), buffer, buffer + length);
177      }
178      return length == 0;
179  }
180  #endif
181  
182  #if defined(PROVIDE_FUZZ_MAIN_FUNCTION) && !defined(__AFL_LOOP)
183  static bool read_file(fs::path p, std::vector<uint8_t>& data)
184  {
185      uint8_t buffer[1024];
186      FILE* f = fsbridge::fopen(p, "rb");
187      if (f == nullptr) return false;
188      do {
189          const size_t length = fread(buffer, sizeof(uint8_t), sizeof(buffer), f);
190          if (ferror(f)) return false;
191          data.insert(data.end(), buffer, buffer + length);
192      } while (!feof(f));
193      fclose(f);
194      return true;
195  }
196  #endif
197  
198  #if defined(PROVIDE_FUZZ_MAIN_FUNCTION) && !defined(__AFL_LOOP)
199  static fs::path g_input_path;
200  void signal_handler(int signal)
201  {
202      if (signal == SIGABRT) {
203          std::cerr << "Error processing input " << g_input_path << std::endl;
204      } else {
205          std::cerr << "Unexpected signal " << signal << " received\n";
206      }
207      std::_Exit(EXIT_FAILURE);
208  }
209  #endif
210  
211  // This function is used by libFuzzer
212  extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
213  {
214      test_one_input({data, size});
215      return 0;
216  }
217  
218  // This function is used by libFuzzer
219  extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
220  {
221      SetArgs(*argc, *argv);
222      initialize();
223      return 0;
224  }
225  
226  #if defined(PROVIDE_FUZZ_MAIN_FUNCTION)
227  int main(int argc, char** argv)
228  {
229      initialize();
230  #ifdef __AFL_LOOP
231      // Enable AFL persistent mode. Requires compilation using afl-clang-fast++.
232      // See fuzzing.md for details.
233      const uint8_t* buffer = __AFL_FUZZ_TESTCASE_BUF;
234      while (__AFL_LOOP(100000)) {
235          size_t buffer_len = __AFL_FUZZ_TESTCASE_LEN;
236          test_one_input({buffer, buffer_len});
237      }
238  #else
239      std::vector<uint8_t> buffer;
240      if (argc <= 1) {
241          if (!read_stdin(buffer)) {
242              return 0;
243          }
244          test_one_input(buffer);
245          return 0;
246      }
247      std::signal(SIGABRT, signal_handler);
248      const auto start_time{Now<SteadySeconds>()};
249      int tested = 0;
250      for (int i = 1; i < argc; ++i) {
251          fs::path input_path(*(argv + i));
252          if (fs::is_directory(input_path)) {
253              std::vector<fs::path> files;
254              for (fs::directory_iterator it(input_path); it != fs::directory_iterator(); ++it) {
255                  if (!fs::is_regular_file(it->path())) continue;
256                  files.emplace_back(it->path());
257              }
258              std::ranges::shuffle(files, std::mt19937{std::random_device{}()});
259              for (const auto& input_path : files) {
260                  g_input_path = input_path;
261                  Assert(read_file(input_path, buffer));
262                  test_one_input(buffer);
263                  ++tested;
264                  buffer.clear();
265              }
266          } else {
267              g_input_path = input_path;
268              Assert(read_file(input_path, buffer));
269              test_one_input(buffer);
270              ++tested;
271              buffer.clear();
272          }
273      }
274      const auto end_time{Now<SteadySeconds>()};
275      std::cout << g_fuzz_target << ": succeeded against " << tested << " files in " << count_seconds(end_time - start_time) << "s." << std::endl;
276  #endif
277      return 0;
278  }
279  #endif