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 <core_io.h>
7 : : #include <key.h>
8 : : #include <key_io.h>
9 : : #include <node/context.h>
10 : : #include <primitives/block.h>
11 : : #include <primitives/transaction.h>
12 : : #include <psbt.h>
13 : : #include <rpc/blockchain.h>
14 : : #include <rpc/client.h>
15 : : #include <rpc/request.h>
16 : : #include <rpc/server.h>
17 [ + - ]: 2 : #include <rpc/util.h>
18 [ + - ]: 2 : #include <span.h>
19 : : #include <streams.h>
20 : : #include <test/fuzz/FuzzedDataProvider.h>
21 : : #include <test/fuzz/fuzz.h>
22 : : #include <test/fuzz/util.h>
23 : : #include <test/util/setup_common.h>
24 : : #include <tinyformat.h>
25 : : #include <univalue.h>
26 : : #include <util/chaintype.h>
27 : 2 : #include <util/strencodings.h>
28 : : #include <util/string.h>
29 : : #include <util/time.h>
30 : :
31 : : #include <cstdint>
32 : : #include <iostream>
33 : : #include <memory>
34 : : #include <optional>
35 : : #include <stdexcept>
36 : : #include <string>
37 : : #include <vector>
38 : :
39 : : namespace {
40 : : 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 : 2 : 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 [ + - ]: 4 : "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)
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);
253 [ # # ]: 0 : if (!opt_block) {
254 : 0 : return;
255 : : }
256 [ # # ]: 0 : CDataStream data_stream{SER_NETWORK, PROTOCOL_VERSION};
257 [ # # # # ]: 0 : data_stream << *opt_block;
258 [ # # # # ]: 0 : r = HexStr(data_stream);
259 [ # # ]: 0 : },
260 : 0 : [&] {
261 : : // hex encoded block header
262 : 0 : std::optional<CBlockHeader> opt_block_header = ConsumeDeserializable<CBlockHeader>(fuzzed_data_provider);
263 [ # # ]: 0 : if (!opt_block_header) {
264 : 0 : return;
265 : : }
266 : 0 : DataStream data_stream{};
267 [ # # # # ]: 0 : data_stream << *opt_block_header;
268 [ # # # # ]: 0 : r = HexStr(data_stream);
269 : 0 : },
270 : 0 : [&] {
271 : : // hex encoded tx
272 : 0 : std::optional<CMutableTransaction> opt_tx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider);
273 [ # # ]: 0 : if (!opt_tx) {
274 : 0 : return;
275 : : }
276 [ # # # # ]: 0 : CDataStream data_stream{SER_NETWORK, fuzzed_data_provider.ConsumeBool() ? PROTOCOL_VERSION : (PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS)};
277 [ # # # # ]: 0 : data_stream << *opt_tx;
278 [ # # # # ]: 0 : r = HexStr(data_stream);
279 [ # # ]: 0 : },
280 : 0 : [&] {
281 : : // base64 encoded psbt
282 : 0 : std::optional<PartiallySignedTransaction> opt_psbt = ConsumeDeserializable<PartiallySignedTransaction>(fuzzed_data_provider);
283 [ # # ]: 0 : if (!opt_psbt) {
284 : 0 : return;
285 : : }
286 [ # # ]: 0 : CDataStream data_stream{SER_NETWORK, PROTOCOL_VERSION};
287 [ # # # # ]: 0 : data_stream << *opt_psbt;
288 [ # # # # ]: 0 : r = EncodeBase64(data_stream);
289 [ # # ]: 0 : },
290 : 0 : [&] {
291 : : // base58 encoded key
292 : 0 : CKey key = ConsumePrivateKey(fuzzed_data_provider);
293 [ # # # # ]: 0 : if (!key.IsValid()) {
294 : 0 : return;
295 : : }
296 [ # # ]: 0 : r = EncodeSecret(key);
297 [ # # ]: 0 : },
298 : 0 : [&] {
299 : : // hex encoded pubkey
300 : 0 : CKey key = ConsumePrivateKey(fuzzed_data_provider);
301 [ # # ]: 0 : if (!key.IsValid()) {
302 : 0 : return;
303 : : }
304 [ # # # # : 0 : r = HexStr(key.GetPubKey());
# # ]
305 [ # # ]: 0 : });
306 : 0 : return r;
307 [ # # ]: 0 : }
308 : :
309 : 0 : std::string ConsumeArrayRPCArgument(FuzzedDataProvider& fuzzed_data_provider)
310 : : {
311 : 0 : std::vector<std::string> scalar_arguments;
312 [ # # # # : 0 : LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100) {
# # ]
313 [ # # # # ]: 0 : scalar_arguments.push_back(ConsumeScalarRPCArgument(fuzzed_data_provider));
314 : 0 : }
315 [ # # # # : 0 : return "[\"" + Join(scalar_arguments, "\",\"") + "\"]";
# # ]
316 : 0 : }
317 : :
318 : 0 : std::string ConsumeRPCArgument(FuzzedDataProvider& fuzzed_data_provider)
319 : : {
320 [ # # ]: 0 : return fuzzed_data_provider.ConsumeBool() ? ConsumeScalarRPCArgument(fuzzed_data_provider) : ConsumeArrayRPCArgument(fuzzed_data_provider);
321 : : }
322 : :
323 : 0 : RPCFuzzTestingSetup* InitializeRPCFuzzTestingSetup()
324 : : {
325 [ # # # # : 0 : static const auto setup = MakeNoLogFileContext<RPCFuzzTestingSetup>();
# # ]
326 : 0 : SetRPCWarmupFinished();
327 : 0 : return setup.get();
328 : 0 : }
329 : : }; // namespace
330 : :
331 : 0 : void initialize_rpc()
332 : : {
333 : 0 : rpc_testing_setup = InitializeRPCFuzzTestingSetup();
334 : 0 : const std::vector<std::string> supported_rpc_commands = rpc_testing_setup->GetRPCCommands();
335 [ # # ]: 0 : for (const std::string& rpc_command : supported_rpc_commands) {
336 [ # # ]: 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();
337 [ # # ]: 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();
338 [ # # # # ]: 0 : if (!(safe_for_fuzzing || not_safe_for_fuzzing)) {
339 [ # # # # : 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";
# # # # #
# ]
340 : 0 : std::terminate();
341 : : }
342 [ # # # # ]: 0 : if (safe_for_fuzzing && not_safe_for_fuzzing) {
343 [ # # # # : 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";
# # # # #
# ]
344 : 0 : std::terminate();
345 : : }
346 : : }
347 : 0 : const char* limit_to_rpc_command_env = std::getenv("LIMIT_TO_RPC_COMMAND");
348 [ # # ]: 0 : if (limit_to_rpc_command_env != nullptr) {
349 [ # # ]: 0 : g_limit_to_rpc_command = std::string{limit_to_rpc_command_env};
350 : 0 : }
351 : 0 : }
352 : :
353 [ + - - + ]: 4 : FUZZ_TARGET(rpc, .init = initialize_rpc)
354 : : {
355 : 0 : FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
356 : 0 : SetMockTime(ConsumeTime(fuzzed_data_provider));
357 : 0 : const std::string rpc_command = fuzzed_data_provider.ConsumeRandomLengthString(64);
358 [ # # # # ]: 0 : if (!g_limit_to_rpc_command.empty() && rpc_command != g_limit_to_rpc_command) {
359 : 0 : return;
360 : : }
361 [ # # ]: 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();
362 [ # # ]: 0 : if (!safe_for_fuzzing) {
363 : 0 : return;
364 : : }
365 : 0 : std::vector<std::string> arguments;
366 [ # # # # ]: 0 : LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100) {
367 [ # # # # ]: 0 : arguments.push_back(ConsumeRPCArgument(fuzzed_data_provider));
368 : 0 : }
369 : : try {
370 [ # # ]: 0 : rpc_testing_setup->CallRPC(rpc_command, arguments);
371 [ # # ]: 0 : } catch (const UniValue& json_rpc_error) {
372 [ # # # # : 0 : const std::string error_msg{json_rpc_error.find_value("message").get_str()};
# # ]
373 : : // Once c++20 is allowed, starts_with can be used.
374 : : // if (error_msg.starts_with("Internal bug detected")) {
375 [ # # # # ]: 0 : if (0 == error_msg.rfind("Internal bug detected", 0)) {
376 : : // Only allow the intentional internal bug
377 [ # # ]: 0 : assert(error_msg.find("trigger_internal_bug") != std::string::npos);
378 : 0 : }
379 [ # # # # ]: 0 : }
380 [ # # ]: 0 : }
|