/root/bitcoin/src/test/fuzz/rpc.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) 2021-2022 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 <base58.h> |
6 | | #include <key.h> |
7 | | #include <key_io.h> |
8 | | #include <primitives/block.h> |
9 | | #include <primitives/transaction.h> |
10 | | #include <psbt.h> |
11 | | #include <rpc/client.h> |
12 | | #include <rpc/request.h> |
13 | | #include <rpc/server.h> |
14 | | #include <span.h> |
15 | | #include <streams.h> |
16 | | #include <test/fuzz/FuzzedDataProvider.h> |
17 | | #include <test/fuzz/fuzz.h> |
18 | | #include <test/fuzz/util.h> |
19 | | #include <test/util/setup_common.h> |
20 | | #include <tinyformat.h> |
21 | | #include <uint256.h> |
22 | | #include <univalue.h> |
23 | | #include <util/strencodings.h> |
24 | | #include <util/string.h> |
25 | | #include <util/time.h> |
26 | | |
27 | | #include <algorithm> |
28 | | #include <cassert> |
29 | | #include <cstdint> |
30 | | #include <cstdlib> |
31 | | #include <exception> |
32 | | #include <iostream> |
33 | | #include <memory> |
34 | | #include <optional> |
35 | | #include <stdexcept> |
36 | | #include <vector> |
37 | | enum class ChainType; |
38 | | |
39 | | using util::Join; |
40 | | using util::ToString; |
41 | | |
42 | | namespace { |
43 | | struct RPCFuzzTestingSetup : public TestingSetup { |
44 | 0 | RPCFuzzTestingSetup(const ChainType chain_type, TestOpts opts) : TestingSetup{chain_type, opts} |
45 | 0 | { |
46 | 0 | } |
47 | | |
48 | | void CallRPC(const std::string& rpc_method, const std::vector<std::string>& arguments) |
49 | 0 | { |
50 | 0 | JSONRPCRequest request; |
51 | 0 | request.context = &m_node; |
52 | 0 | request.strMethod = rpc_method; |
53 | 0 | try { |
54 | 0 | request.params = RPCConvertValues(rpc_method, arguments); |
55 | 0 | } catch (const std::runtime_error&) { |
56 | 0 | return; |
57 | 0 | } |
58 | 0 | tableRPC.execute(request); |
59 | 0 | } |
60 | | |
61 | | std::vector<std::string> GetRPCCommands() const |
62 | 0 | { |
63 | 0 | return tableRPC.listCommands(); |
64 | 0 | } |
65 | | }; |
66 | | |
67 | | RPCFuzzTestingSetup* rpc_testing_setup = nullptr; |
68 | | std::string g_limit_to_rpc_command; |
69 | | |
70 | | // RPC commands which are not appropriate for fuzzing: such as RPC commands |
71 | | // reading or writing to a filename passed as an RPC parameter, RPC commands |
72 | | // resulting in network activity, etc. |
73 | | const std::vector<std::string> RPC_COMMANDS_NOT_SAFE_FOR_FUZZING{ |
74 | | "addconnection", // avoid DNS lookups |
75 | | "addnode", // avoid DNS lookups |
76 | | "addpeeraddress", // avoid DNS lookups |
77 | | "dumptxoutset", // avoid writing to disk |
78 | | "dumpwallet", // avoid writing to disk |
79 | | "enumeratesigners", |
80 | | "echoipc", // avoid assertion failure (Assertion `"EnsureAnyNodeContext(request.context).init" && check' failed.) |
81 | | "generatetoaddress", // avoid prohibitively slow execution (when `num_blocks` is large) |
82 | | "generatetodescriptor", // avoid prohibitively slow execution (when `nblocks` is large) |
83 | | "gettxoutproof", // avoid prohibitively slow execution |
84 | | "importmempool", // avoid reading from disk |
85 | | "importwallet", // avoid reading from disk |
86 | | "loadtxoutset", // avoid reading from disk |
87 | | "loadwallet", // avoid reading from disk |
88 | | "savemempool", // disabled as a precautionary measure: may take a file path argument in the future |
89 | | "setban", // avoid DNS lookups |
90 | | "stop", // avoid shutdown state |
91 | | }; |
92 | | |
93 | | // RPC commands which are safe for fuzzing. |
94 | | const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{ |
95 | | "analyzepsbt", |
96 | | "clearbanned", |
97 | | "combinepsbt", |
98 | | "combinerawtransaction", |
99 | | "converttopsbt", |
100 | | "createmultisig", |
101 | | "createpsbt", |
102 | | "createrawtransaction", |
103 | | "decodepsbt", |
104 | | "decoderawtransaction", |
105 | | "decodescript", |
106 | | "deriveaddresses", |
107 | | "descriptorprocesspsbt", |
108 | | "disconnectnode", |
109 | | "echo", |
110 | | "echojson", |
111 | | "estimaterawfee", |
112 | | "estimatesmartfee", |
113 | | "finalizepsbt", |
114 | | "generate", |
115 | | "generateblock", |
116 | | "getaddednodeinfo", |
117 | | "getaddrmaninfo", |
118 | | "getbestblockhash", |
119 | | "getblock", |
120 | | "getblockchaininfo", |
121 | | "getblockcount", |
122 | | "getblockfilter", |
123 | | "getblockfrompeer", // when no peers are connected, no p2p message is sent |
124 | | "getblockhash", |
125 | | "getblockheader", |
126 | | "getblockstats", |
127 | | "getblocktemplate", |
128 | | "getchaintips", |
129 | | "getchainstates", |
130 | | "getchaintxstats", |
131 | | "getconnectioncount", |
132 | | "getdeploymentinfo", |
133 | | "getdescriptoractivity", |
134 | | "getdescriptorinfo", |
135 | | "getdifficulty", |
136 | | "getindexinfo", |
137 | | "getmemoryinfo", |
138 | | "getmempoolancestors", |
139 | | "getmempooldescendants", |
140 | | "getmempoolentry", |
141 | | "getmempoolinfo", |
142 | | "getmininginfo", |
143 | | "getnettotals", |
144 | | "getnetworkhashps", |
145 | | "getnetworkinfo", |
146 | | "getnodeaddresses", |
147 | | "getorphantxs", |
148 | | "getpeerinfo", |
149 | | "getprioritisedtransactions", |
150 | | "getrawaddrman", |
151 | | "getrawmempool", |
152 | | "getrawtransaction", |
153 | | "getrpcinfo", |
154 | | "gettxout", |
155 | | "gettxoutsetinfo", |
156 | | "gettxspendingprevout", |
157 | | "help", |
158 | | "invalidateblock", |
159 | | "joinpsbts", |
160 | | "listbanned", |
161 | | "logging", |
162 | | "mockscheduler", |
163 | | "ping", |
164 | | "preciousblock", |
165 | | "prioritisetransaction", |
166 | | "pruneblockchain", |
167 | | "reconsiderblock", |
168 | | "scanblocks", |
169 | | "scantxoutset", |
170 | | "sendmsgtopeer", // when no peers are connected, no p2p message is sent |
171 | | "sendrawtransaction", |
172 | | "setmocktime", |
173 | | "setnetworkactive", |
174 | | "signmessagewithprivkey", |
175 | | "signrawtransactionwithkey", |
176 | | "submitblock", |
177 | | "submitheader", |
178 | | "submitpackage", |
179 | | "syncwithvalidationinterfacequeue", |
180 | | "testmempoolaccept", |
181 | | "uptime", |
182 | | "utxoupdatepsbt", |
183 | | "validateaddress", |
184 | | "verifychain", |
185 | | "verifymessage", |
186 | | "verifytxoutproof", |
187 | | "waitforblock", |
188 | | "waitforblockheight", |
189 | | "waitfornewblock", |
190 | | }; |
191 | | |
192 | | std::string ConsumeScalarRPCArgument(FuzzedDataProvider& fuzzed_data_provider, bool& good_data) |
193 | 0 | { |
194 | 0 | const size_t max_string_length = 4096; |
195 | 0 | const size_t max_base58_bytes_length{64}; |
196 | 0 | std::string r; |
197 | 0 | CallOneOf( |
198 | 0 | fuzzed_data_provider, |
199 | 0 | [&] { |
200 | | // string argument |
201 | 0 | r = fuzzed_data_provider.ConsumeRandomLengthString(max_string_length); |
202 | 0 | }, |
203 | 0 | [&] { |
204 | | // base64 argument |
205 | 0 | r = EncodeBase64(fuzzed_data_provider.ConsumeRandomLengthString(max_string_length)); |
206 | 0 | }, |
207 | 0 | [&] { |
208 | | // hex argument |
209 | 0 | r = HexStr(fuzzed_data_provider.ConsumeRandomLengthString(max_string_length)); |
210 | 0 | }, |
211 | 0 | [&] { |
212 | | // bool argument |
213 | 0 | r = fuzzed_data_provider.ConsumeBool() ? "true" : "false"; Branch (213:17): [True: 0, False: 0]
|
214 | 0 | }, |
215 | 0 | [&] { |
216 | | // range argument |
217 | 0 | r = "[" + ToString(fuzzed_data_provider.ConsumeIntegral<int64_t>()) + "," + ToString(fuzzed_data_provider.ConsumeIntegral<int64_t>()) + "]"; |
218 | 0 | }, |
219 | 0 | [&] { |
220 | | // integral argument (int64_t) |
221 | 0 | r = ToString(fuzzed_data_provider.ConsumeIntegral<int64_t>()); |
222 | 0 | }, |
223 | 0 | [&] { |
224 | | // integral argument (uint64_t) |
225 | 0 | r = ToString(fuzzed_data_provider.ConsumeIntegral<uint64_t>()); |
226 | 0 | }, |
227 | 0 | [&] { |
228 | | // floating point argument |
229 | 0 | r = strprintf("%f", fuzzed_data_provider.ConsumeFloatingPoint<double>()); |
230 | 0 | }, |
231 | 0 | [&] { |
232 | | // tx destination argument |
233 | 0 | r = EncodeDestination(ConsumeTxDestination(fuzzed_data_provider)); |
234 | 0 | }, |
235 | 0 | [&] { |
236 | | // uint160 argument |
237 | 0 | r = ConsumeUInt160(fuzzed_data_provider).ToString(); |
238 | 0 | }, |
239 | 0 | [&] { |
240 | | // uint256 argument |
241 | 0 | r = ConsumeUInt256(fuzzed_data_provider).ToString(); |
242 | 0 | }, |
243 | 0 | [&] { |
244 | | // base32 argument |
245 | 0 | r = EncodeBase32(fuzzed_data_provider.ConsumeRandomLengthString(max_string_length)); |
246 | 0 | }, |
247 | 0 | [&] { |
248 | | // base58 argument |
249 | 0 | r = EncodeBase58(MakeUCharSpan(fuzzed_data_provider.ConsumeRandomLengthString(max_base58_bytes_length))); |
250 | 0 | }, |
251 | 0 | [&] { |
252 | | // base58 argument with checksum |
253 | 0 | r = EncodeBase58Check(MakeUCharSpan(fuzzed_data_provider.ConsumeRandomLengthString(max_base58_bytes_length))); |
254 | 0 | }, |
255 | 0 | [&] { |
256 | | // hex encoded block |
257 | 0 | std::optional<CBlock> opt_block = ConsumeDeserializable<CBlock>(fuzzed_data_provider, TX_WITH_WITNESS); |
258 | 0 | if (!opt_block) { Branch (258:17): [True: 0, False: 0]
|
259 | 0 | good_data = false; |
260 | 0 | return; |
261 | 0 | } |
262 | 0 | DataStream data_stream{}; |
263 | 0 | data_stream << TX_WITH_WITNESS(*opt_block); |
264 | 0 | r = HexStr(data_stream); |
265 | 0 | }, |
266 | 0 | [&] { |
267 | | // hex encoded block header |
268 | 0 | std::optional<CBlockHeader> opt_block_header = ConsumeDeserializable<CBlockHeader>(fuzzed_data_provider); |
269 | 0 | if (!opt_block_header) { Branch (269:17): [True: 0, False: 0]
|
270 | 0 | good_data = false; |
271 | 0 | return; |
272 | 0 | } |
273 | 0 | DataStream data_stream{}; |
274 | 0 | data_stream << *opt_block_header; |
275 | 0 | r = HexStr(data_stream); |
276 | 0 | }, |
277 | 0 | [&] { |
278 | | // hex encoded tx |
279 | 0 | std::optional<CMutableTransaction> opt_tx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS); |
280 | 0 | if (!opt_tx) { Branch (280:17): [True: 0, False: 0]
|
281 | 0 | good_data = false; |
282 | 0 | return; |
283 | 0 | } |
284 | 0 | DataStream data_stream; |
285 | 0 | auto allow_witness = (fuzzed_data_provider.ConsumeBool() ? TX_WITH_WITNESS : TX_NO_WITNESS); Branch (285:35): [True: 0, False: 0]
|
286 | 0 | data_stream << allow_witness(*opt_tx); |
287 | 0 | r = HexStr(data_stream); |
288 | 0 | }, |
289 | 0 | [&] { |
290 | | // base64 encoded psbt |
291 | 0 | std::optional<PartiallySignedTransaction> opt_psbt = ConsumeDeserializable<PartiallySignedTransaction>(fuzzed_data_provider); |
292 | 0 | if (!opt_psbt) { Branch (292:17): [True: 0, False: 0]
|
293 | 0 | good_data = false; |
294 | 0 | return; |
295 | 0 | } |
296 | 0 | DataStream data_stream{}; |
297 | 0 | data_stream << *opt_psbt; |
298 | 0 | r = EncodeBase64(data_stream); |
299 | 0 | }, |
300 | 0 | [&] { |
301 | | // base58 encoded key |
302 | 0 | CKey key = ConsumePrivateKey(fuzzed_data_provider); |
303 | 0 | if (!key.IsValid()) { Branch (303:17): [True: 0, False: 0]
|
304 | 0 | good_data = false; |
305 | 0 | return; |
306 | 0 | } |
307 | 0 | r = EncodeSecret(key); |
308 | 0 | }, |
309 | 0 | [&] { |
310 | | // hex encoded pubkey |
311 | 0 | CKey key = ConsumePrivateKey(fuzzed_data_provider); |
312 | 0 | if (!key.IsValid()) { Branch (312:17): [True: 0, False: 0]
|
313 | 0 | good_data = false; |
314 | 0 | return; |
315 | 0 | } |
316 | 0 | r = HexStr(key.GetPubKey()); |
317 | 0 | }); |
318 | 0 | return r; |
319 | 0 | } |
320 | | |
321 | | std::string ConsumeArrayRPCArgument(FuzzedDataProvider& fuzzed_data_provider, bool& good_data) |
322 | 0 | { |
323 | 0 | std::vector<std::string> scalar_arguments; |
324 | 0 | LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 100) |
325 | 0 | { |
326 | 0 | scalar_arguments.push_back(ConsumeScalarRPCArgument(fuzzed_data_provider, good_data)); |
327 | 0 | } |
328 | 0 | return "[\"" + Join(scalar_arguments, "\",\"") + "\"]"; |
329 | 0 | } |
330 | | |
331 | | std::string ConsumeRPCArgument(FuzzedDataProvider& fuzzed_data_provider, bool& good_data) |
332 | 0 | { |
333 | 0 | return fuzzed_data_provider.ConsumeBool() ? ConsumeScalarRPCArgument(fuzzed_data_provider, good_data) : ConsumeArrayRPCArgument(fuzzed_data_provider, good_data); Branch (333:12): [True: 0, False: 0]
|
334 | 0 | } |
335 | | |
336 | | RPCFuzzTestingSetup* InitializeRPCFuzzTestingSetup() |
337 | 0 | { |
338 | 0 | static const auto setup = MakeNoLogFileContext<RPCFuzzTestingSetup>(); |
339 | 0 | SetRPCWarmupFinished(); |
340 | 0 | return setup.get(); |
341 | 0 | } |
342 | | }; // namespace |
343 | | |
344 | | void initialize_rpc() |
345 | 0 | { |
346 | 0 | rpc_testing_setup = InitializeRPCFuzzTestingSetup(); |
347 | 0 | const std::vector<std::string> supported_rpc_commands = rpc_testing_setup->GetRPCCommands(); |
348 | 0 | for (const std::string& rpc_command : supported_rpc_commands) { Branch (348:41): [True: 0, False: 0]
|
349 | 0 | const bool safe_for_fuzzing = std::find(RPC_COMMANDS_SAFE_FOR_FUZZING.begin(), RPC_COMMANDS_SAFE_FOR_FUZZING.end(), rpc_command) != RPC_COMMANDS_SAFE_FOR_FUZZING.end(); |
350 | 0 | const bool not_safe_for_fuzzing = std::find(RPC_COMMANDS_NOT_SAFE_FOR_FUZZING.begin(), RPC_COMMANDS_NOT_SAFE_FOR_FUZZING.end(), rpc_command) != RPC_COMMANDS_NOT_SAFE_FOR_FUZZING.end(); |
351 | 0 | if (!(safe_for_fuzzing || not_safe_for_fuzzing)) { Branch (351:15): [True: 0, False: 0]
Branch (351:35): [True: 0, False: 0]
|
352 | 0 | std::cerr << "Error: RPC command \"" << rpc_command << "\" not found in RPC_COMMANDS_SAFE_FOR_FUZZING or RPC_COMMANDS_NOT_SAFE_FOR_FUZZING. Please update " << __FILE__ << ".\n"; |
353 | 0 | std::terminate(); |
354 | 0 | } |
355 | 0 | if (safe_for_fuzzing && not_safe_for_fuzzing) { Branch (355:13): [True: 0, False: 0]
Branch (355:33): [True: 0, False: 0]
|
356 | 0 | std::cerr << "Error: RPC command \"" << rpc_command << "\" found in *both* RPC_COMMANDS_SAFE_FOR_FUZZING and RPC_COMMANDS_NOT_SAFE_FOR_FUZZING. Please update " << __FILE__ << ".\n"; |
357 | 0 | std::terminate(); |
358 | 0 | } |
359 | 0 | } |
360 | 0 | const char* limit_to_rpc_command_env = std::getenv("LIMIT_TO_RPC_COMMAND"); |
361 | 0 | if (limit_to_rpc_command_env != nullptr) { Branch (361:9): [True: 0, False: 0]
|
362 | 0 | g_limit_to_rpc_command = std::string{limit_to_rpc_command_env}; |
363 | 0 | } |
364 | 0 | } |
365 | | |
366 | | FUZZ_TARGET(rpc, .init = initialize_rpc) |
367 | 0 | { |
368 | 0 | SeedRandomStateForTest(SeedRand::ZEROS); |
369 | 0 | FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; |
370 | 0 | bool good_data{true}; |
371 | 0 | SetMockTime(ConsumeTime(fuzzed_data_provider)); |
372 | 0 | const std::string rpc_command = fuzzed_data_provider.ConsumeRandomLengthString(64); |
373 | 0 | if (!g_limit_to_rpc_command.empty() && rpc_command != g_limit_to_rpc_command) { Branch (373:9): [True: 0, False: 0]
Branch (373:44): [True: 0, False: 0]
|
374 | 0 | return; |
375 | 0 | } |
376 | 0 | const bool safe_for_fuzzing = std::find(RPC_COMMANDS_SAFE_FOR_FUZZING.begin(), RPC_COMMANDS_SAFE_FOR_FUZZING.end(), rpc_command) != RPC_COMMANDS_SAFE_FOR_FUZZING.end(); |
377 | 0 | if (!safe_for_fuzzing) { Branch (377:9): [True: 0, False: 0]
|
378 | 0 | return; |
379 | 0 | } |
380 | 0 | std::vector<std::string> arguments; |
381 | 0 | LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 100) |
382 | 0 | { |
383 | 0 | arguments.push_back(ConsumeRPCArgument(fuzzed_data_provider, good_data)); |
384 | 0 | } |
385 | 0 | try { |
386 | 0 | rpc_testing_setup->CallRPC(rpc_command, arguments); |
387 | 0 | } catch (const UniValue& json_rpc_error) { |
388 | 0 | const std::string error_msg{json_rpc_error.find_value("message").get_str()}; |
389 | 0 | if (error_msg.starts_with("Internal bug detected")) { Branch (389:13): [True: 0, False: 0]
|
390 | | // Only allow the intentional internal bug |
391 | 0 | assert(error_msg.find("trigger_internal_bug") != std::string::npos); |
392 | 0 | } |
393 | 0 | } |
394 | 0 | } |