fuzz.cpp raw
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
280