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