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