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