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 : : #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 : 0 : struct RPCFuzzTestingSetup : public TestingSetup {
44 [ # # ]: 0 : RPCFuzzTestingSetup(const ChainType chain_type, TestOpts opts) : TestingSetup{chain_type, opts}
45 : : {
46 : 0 : }
47 : :
48 : 0 : void CallRPC(const std::string& rpc_method, const std::vector<std::string>& arguments)
49 : : {
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 : 0 : std::vector<std::string> GetRPCCommands() const
62 : : {
63 : 0 : return tableRPC.listCommands();
64 : : }
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 : : "getdescriptorinfo",
134 : : "getdifficulty",
135 : : "getindexinfo",
136 : : "getmemoryinfo",
137 : : "getmempoolancestors",
138 : : "getmempooldescendants",
139 : : "getmempoolentry",
140 : : "getmempoolinfo",
141 : : "getmininginfo",
142 : : "getnettotals",
143 : : "getnetworkhashps",
144 : : "getnetworkinfo",
145 : : "getnodeaddresses",
146 : : "getpeerinfo",
147 : : "getprioritisedtransactions",
148 : : "getrawaddrman",
149 : : "getrawmempool",
150 : : "getrawtransaction",
151 : : "getrpcinfo",
152 : : "gettxout",
153 : : "gettxoutsetinfo",
154 : : "gettxspendingprevout",
155 : : "help",
156 : : "invalidateblock",
157 : : "joinpsbts",
158 : : "listbanned",
159 : : "logging",
160 : : "mockscheduler",
161 : : "ping",
162 : : "preciousblock",
163 : : "prioritisetransaction",
164 : : "pruneblockchain",
165 : : "reconsiderblock",
166 : : "scanblocks",
167 : : "scantxoutset",
168 : : "sendmsgtopeer", // when no peers are connected, no p2p message is sent
169 : : "sendrawtransaction",
170 : : "setmocktime",
171 : : "setnetworkactive",
172 : : "signmessagewithprivkey",
173 : : "signrawtransactionwithkey",
174 : : "submitblock",
175 : : "submitheader",
176 : : "submitpackage",
177 : : "syncwithvalidationinterfacequeue",
178 : : "testmempoolaccept",
179 : : "uptime",
180 : : "utxoupdatepsbt",
181 : : "validateaddress",
182 : : "verifychain",
183 : : "verifymessage",
184 : : "verifytxoutproof",
185 : : "waitforblock",
186 : : "waitforblockheight",
187 : : "waitfornewblock",
188 : : };
189 : :
190 : 0 : std::string ConsumeScalarRPCArgument(FuzzedDataProvider& fuzzed_data_provider, bool& good_data)
191 : : {
192 : 0 : const size_t max_string_length = 4096;
193 : 0 : const size_t max_base58_bytes_length{64};
194 [ # # ]: 0 : std::string r;
195 [ # # ]: 0 : CallOneOf(
196 : : fuzzed_data_provider,
197 : 0 : [&] {
198 : : // string argument
199 : 0 : r = fuzzed_data_provider.ConsumeRandomLengthString(max_string_length);
200 : 0 : },
201 : 0 : [&] {
202 : : // base64 argument
203 [ # # ]: 0 : r = EncodeBase64(fuzzed_data_provider.ConsumeRandomLengthString(max_string_length));
204 : 0 : },
205 : 0 : [&] {
206 : : // hex argument
207 [ # # ]: 0 : r = HexStr(fuzzed_data_provider.ConsumeRandomLengthString(max_string_length));
208 : 0 : },
209 : 0 : [&] {
210 : : // bool argument
211 [ # # ]: 0 : r = fuzzed_data_provider.ConsumeBool() ? "true" : "false";
212 : 0 : },
213 : 0 : [&] {
214 : : // range argument
215 [ # # # # : 0 : r = "[" + ToString(fuzzed_data_provider.ConsumeIntegral<int64_t>()) + "," + ToString(fuzzed_data_provider.ConsumeIntegral<int64_t>()) + "]";
# # ]
216 : 0 : },
217 : 0 : [&] {
218 : : // integral argument (int64_t)
219 : 0 : r = ToString(fuzzed_data_provider.ConsumeIntegral<int64_t>());
220 : 0 : },
221 : 0 : [&] {
222 : : // integral argument (uint64_t)
223 : 0 : r = ToString(fuzzed_data_provider.ConsumeIntegral<uint64_t>());
224 : 0 : },
225 : 0 : [&] {
226 : : // floating point argument
227 : 0 : r = strprintf("%f", fuzzed_data_provider.ConsumeFloatingPoint<double>());
228 : 0 : },
229 : 0 : [&] {
230 : : // tx destination argument
231 [ # # ]: 0 : r = EncodeDestination(ConsumeTxDestination(fuzzed_data_provider));
232 : 0 : },
233 : 0 : [&] {
234 : : // uint160 argument
235 : 0 : r = ConsumeUInt160(fuzzed_data_provider).ToString();
236 : 0 : },
237 : 0 : [&] {
238 : : // uint256 argument
239 : 0 : r = ConsumeUInt256(fuzzed_data_provider).ToString();
240 : 0 : },
241 : 0 : [&] {
242 : : // base32 argument
243 [ # # ]: 0 : r = EncodeBase32(fuzzed_data_provider.ConsumeRandomLengthString(max_string_length));
244 : 0 : },
245 : 0 : [&] {
246 : : // base58 argument
247 [ # # ]: 0 : r = EncodeBase58(MakeUCharSpan(fuzzed_data_provider.ConsumeRandomLengthString(max_base58_bytes_length)));
248 : 0 : },
249 : 0 : [&] {
250 : : // base58 argument with checksum
251 [ # # ]: 0 : r = EncodeBase58Check(MakeUCharSpan(fuzzed_data_provider.ConsumeRandomLengthString(max_base58_bytes_length)));
252 : 0 : },
253 : 0 : [&] {
254 : : // hex encoded block
255 : 0 : std::optional<CBlock> opt_block = ConsumeDeserializable<CBlock>(fuzzed_data_provider, TX_WITH_WITNESS);
256 [ # # ]: 0 : if (!opt_block) {
257 : 0 : good_data = false;
258 : 0 : return;
259 : : }
260 : 0 : DataStream data_stream{};
261 [ # # ]: 0 : data_stream << TX_WITH_WITNESS(*opt_block);
262 [ # # ]: 0 : r = HexStr(data_stream);
263 : 0 : },
264 : 0 : [&] {
265 : : // hex encoded block header
266 : 0 : std::optional<CBlockHeader> opt_block_header = ConsumeDeserializable<CBlockHeader>(fuzzed_data_provider);
267 [ # # ]: 0 : if (!opt_block_header) {
268 : 0 : good_data = false;
269 : 0 : return;
270 : : }
271 : 0 : DataStream data_stream{};
272 [ # # ]: 0 : data_stream << *opt_block_header;
273 [ # # ]: 0 : r = HexStr(data_stream);
274 : 0 : },
275 : 0 : [&] {
276 : : // hex encoded tx
277 : 0 : std::optional<CMutableTransaction> opt_tx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
278 [ # # ]: 0 : if (!opt_tx) {
279 : 0 : good_data = false;
280 [ # # ]: 0 : return;
281 : : }
282 : 0 : DataStream data_stream;
283 [ # # ]: 0 : auto allow_witness = (fuzzed_data_provider.ConsumeBool() ? TX_WITH_WITNESS : TX_NO_WITNESS);
284 [ # # ]: 0 : data_stream << allow_witness(*opt_tx);
285 [ # # ]: 0 : r = HexStr(data_stream);
286 [ # # ]: 0 : },
287 : 0 : [&] {
288 : : // base64 encoded psbt
289 : 0 : std::optional<PartiallySignedTransaction> opt_psbt = ConsumeDeserializable<PartiallySignedTransaction>(fuzzed_data_provider);
290 [ # # ]: 0 : if (!opt_psbt) {
291 : 0 : good_data = false;
292 : 0 : return;
293 : : }
294 : 0 : DataStream data_stream{};
295 [ # # ]: 0 : data_stream << *opt_psbt;
296 [ # # ]: 0 : r = EncodeBase64(data_stream);
297 : 0 : },
298 : 0 : [&] {
299 : : // base58 encoded key
300 : 0 : CKey key = ConsumePrivateKey(fuzzed_data_provider);
301 [ # # ]: 0 : if (!key.IsValid()) {
302 : 0 : good_data = false;
303 : 0 : return;
304 : : }
305 [ # # ]: 0 : r = EncodeSecret(key);
306 : 0 : },
307 : 0 : [&] {
308 : : // hex encoded pubkey
309 : 0 : CKey key = ConsumePrivateKey(fuzzed_data_provider);
310 [ # # ]: 0 : if (!key.IsValid()) {
311 : 0 : good_data = false;
312 : 0 : return;
313 : : }
314 [ # # # # ]: 0 : r = HexStr(key.GetPubKey());
315 : 0 : });
316 : 0 : return r;
317 : 0 : }
318 : :
319 : 0 : std::string ConsumeArrayRPCArgument(FuzzedDataProvider& fuzzed_data_provider, bool& good_data)
320 : : {
321 : 0 : std::vector<std::string> scalar_arguments;
322 [ # # # # : 0 : LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 100)
# # ]
323 : : {
324 [ # # ]: 0 : scalar_arguments.push_back(ConsumeScalarRPCArgument(fuzzed_data_provider, good_data));
325 : : }
326 [ # # # # ]: 0 : return "[\"" + Join(scalar_arguments, "\",\"") + "\"]";
327 : 0 : }
328 : :
329 : 0 : std::string ConsumeRPCArgument(FuzzedDataProvider& fuzzed_data_provider, bool& good_data)
330 : : {
331 [ # # ]: 0 : return fuzzed_data_provider.ConsumeBool() ? ConsumeScalarRPCArgument(fuzzed_data_provider, good_data) : ConsumeArrayRPCArgument(fuzzed_data_provider, good_data);
332 : : }
333 : :
334 : 0 : RPCFuzzTestingSetup* InitializeRPCFuzzTestingSetup()
335 : : {
336 [ # # # # ]: 0 : static const auto setup = MakeNoLogFileContext<RPCFuzzTestingSetup>();
337 : 0 : SetRPCWarmupFinished();
338 : 0 : return setup.get();
339 [ # # ]: 0 : }
340 : : }; // namespace
341 : :
342 : 0 : void initialize_rpc()
343 : : {
344 : 0 : rpc_testing_setup = InitializeRPCFuzzTestingSetup();
345 : 0 : const std::vector<std::string> supported_rpc_commands = rpc_testing_setup->GetRPCCommands();
346 [ # # ]: 0 : for (const std::string& rpc_command : supported_rpc_commands) {
347 [ # # ]: 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();
348 : 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();
349 [ # # ]: 0 : if (!(safe_for_fuzzing || not_safe_for_fuzzing)) {
350 [ # # # # : 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";
# # # # #
# ]
351 : 0 : std::terminate();
352 : : }
353 [ # # ]: 0 : if (safe_for_fuzzing && not_safe_for_fuzzing) {
354 [ # # # # : 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";
# # # # #
# ]
355 : 0 : std::terminate();
356 : : }
357 : : }
358 : 0 : const char* limit_to_rpc_command_env = std::getenv("LIMIT_TO_RPC_COMMAND");
359 [ # # ]: 0 : if (limit_to_rpc_command_env != nullptr) {
360 [ # # ]: 0 : g_limit_to_rpc_command = std::string{limit_to_rpc_command_env};
361 : : }
362 : 0 : }
363 : :
364 [ # # ]: 0 : FUZZ_TARGET(rpc, .init = initialize_rpc)
365 : : {
366 : 0 : FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
367 : 0 : bool good_data{true};
368 : 0 : SetMockTime(ConsumeTime(fuzzed_data_provider));
369 : 0 : const std::string rpc_command = fuzzed_data_provider.ConsumeRandomLengthString(64);
370 [ # # # # ]: 0 : if (!g_limit_to_rpc_command.empty() && rpc_command != g_limit_to_rpc_command) {
371 : : return;
372 : : }
373 [ # # ]: 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();
374 [ # # ]: 0 : if (!safe_for_fuzzing) {
375 : : return;
376 : : }
377 : 0 : std::vector<std::string> arguments;
378 [ # # # # : 0 : LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 100)
# # ]
379 : : {
380 [ # # ]: 0 : arguments.push_back(ConsumeRPCArgument(fuzzed_data_provider, good_data));
381 : : }
382 : 0 : try {
383 [ # # ]: 0 : rpc_testing_setup->CallRPC(rpc_command, arguments);
384 [ - - ]: 0 : } catch (const UniValue& json_rpc_error) {
385 [ - - - - : 0 : const std::string error_msg{json_rpc_error.find_value("message").get_str()};
- - ]
386 [ - - ]: 0 : if (error_msg.starts_with("Internal bug detected")) {
387 : : // Only allow the intentional internal bug
388 [ - - ]: 0 : assert(error_msg.find("trigger_internal_bug") != std::string::npos);
389 : : }
390 : 0 : }
391 : 0 : }
|