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