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