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