/ 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 <test/util/time.h>
 21  #include <tinyformat.h>
 22  #include <uint256.h>
 23  #include <univalue.h>
 24  #include <util/strencodings.h>
 25  #include <util/string.h>
 26  #include <util/time.h>
 27  
 28  #include <algorithm>
 29  #include <cassert>
 30  #include <cstdint>
 31  #include <cstdlib>
 32  #include <exception>
 33  #include <iostream>
 34  #include <memory>
 35  #include <optional>
 36  #include <stdexcept>
 37  #include <vector>
 38  enum class ChainType;
 39  
 40  using util::Join;
 41  using util::ToString;
 42  
 43  namespace {
 44  struct RPCFuzzTestingSetup : public TestingSetup {
 45      RPCFuzzTestingSetup(const ChainType chain_type, TestOpts opts) : TestingSetup{chain_type, opts}
 46      {
 47      }
 48  
 49      void CallRPC(const std::string& rpc_method, const std::vector<std::string>& arguments)
 50      {
 51          JSONRPCRequest request;
 52          request.context = &m_node;
 53          request.strMethod = rpc_method;
 54          try {
 55              request.params = RPCConvertValues(rpc_method, arguments);
 56          } catch (const std::runtime_error&) {
 57              return;
 58          }
 59          tableRPC.execute(request);
 60      }
 61  
 62      std::vector<std::string> GetRPCCommands() const
 63      {
 64          return tableRPC.listCommands();
 65      }
 66  };
 67  
 68  RPCFuzzTestingSetup* rpc_testing_setup = nullptr;
 69  std::string g_limit_to_rpc_command;
 70  
 71  // RPC commands which are not appropriate for fuzzing: such as RPC commands
 72  // reading or writing to a filename passed as an RPC parameter, RPC commands
 73  // resulting in network activity, etc.
 74  const std::vector<std::string> RPC_COMMANDS_NOT_SAFE_FOR_FUZZING{
 75      "addconnection",  // avoid DNS lookups
 76      "addnode",        // avoid DNS lookups
 77      "addpeeraddress", // avoid DNS lookups
 78      "dumptxoutset",   // avoid writing to disk
 79      "enumeratesigners",
 80      "echoipc",              // avoid assertion failure (Assertion `"EnsureAnyNodeContext(request.context).init" && check' failed.)
 81      "exportasmap",          // avoid writing to disk
 82      "generatetoaddress",    // avoid prohibitively slow execution (when `num_blocks` is large)
 83      "generatetodescriptor", // avoid prohibitively slow execution (when `nblocks` is large)
 84      "gettxoutproof",        // avoid prohibitively slow execution
 85      "importmempool",        // 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      "abortprivatebroadcast",
 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      "generate",
116      "generateblock",
117      "getaddednodeinfo",
118      "getaddrmaninfo",
119      "getbestblockhash",
120      "getblock",
121      "getblockchaininfo",
122      "getblockcount",
123      "getblockfilter",
124      "getblockfrompeer", // when no peers are connected, no p2p message is sent
125      "getblockhash",
126      "getblockheader",
127      "getblockstats",
128      "getblocktemplate",
129      "getchaintips",
130      "getchainstates",
131      "getchaintxstats",
132      "getconnectioncount",
133      "getdeploymentinfo",
134      "getdescriptoractivity",
135      "getdescriptorinfo",
136      "getdifficulty",
137      "getindexinfo",
138      "getmemoryinfo",
139      "getmempoolancestors",
140      "getmempooldescendants",
141      "getmempoolentry",
142      "getmempoolfeeratediagram",
143      "getmempoolcluster",
144      "getmempoolinfo",
145      "getmininginfo",
146      "getnettotals",
147      "getnetworkhashps",
148      "getnetworkinfo",
149      "getnodeaddresses",
150      "getorphantxs",
151      "getpeerinfo",
152      "getprioritisedtransactions",
153      "getprivatebroadcastinfo",
154      "getrawaddrman",
155      "getrawmempool",
156      "getrawtransaction",
157      "getrpcinfo",
158      "gettxout",
159      "gettxoutsetinfo",
160      "gettxspendingprevout",
161      "help",
162      "invalidateblock",
163      "joinpsbts",
164      "listbanned",
165      "logging",
166      "mockscheduler",
167      "ping",
168      "preciousblock",
169      "prioritisetransaction",
170      "pruneblockchain",
171      "reconsiderblock",
172      "scanblocks",
173      "scantxoutset",
174      "sendmsgtopeer", // when no peers are connected, no p2p message is sent
175      "sendrawtransaction",
176      "setmocktime",
177      "setnetworkactive",
178      "signmessagewithprivkey",
179      "signrawtransactionwithkey",
180      "submitblock",
181      "submitheader",
182      "submitpackage",
183      "syncwithvalidationinterfacequeue",
184      "testmempoolaccept",
185      "uptime",
186      "utxoupdatepsbt",
187      "validateaddress",
188      "verifychain",
189      "verifymessage",
190      "verifytxoutproof",
191      "waitforblock",
192      "waitforblockheight",
193      "waitfornewblock",
194  };
195  
196  std::string ConsumeScalarRPCArgument(FuzzedDataProvider& fuzzed_data_provider, bool& good_data)
197  {
198      const size_t max_string_length = 4096;
199      const size_t max_base58_bytes_length{64};
200      std::string r;
201      CallOneOf(
202          fuzzed_data_provider,
203          [&] {
204              // string argument
205              r = fuzzed_data_provider.ConsumeRandomLengthString(max_string_length);
206          },
207          [&] {
208              // base64 argument
209              r = EncodeBase64(fuzzed_data_provider.ConsumeRandomLengthString(max_string_length));
210          },
211          [&] {
212              // hex argument
213              r = HexStr(fuzzed_data_provider.ConsumeRandomLengthString(max_string_length));
214          },
215          [&] {
216              // bool argument
217              r = fuzzed_data_provider.ConsumeBool() ? "true" : "false";
218          },
219          [&] {
220              // range argument
221              r = "[" + ToString(fuzzed_data_provider.ConsumeIntegral<int64_t>()) + "," + ToString(fuzzed_data_provider.ConsumeIntegral<int64_t>()) + "]";
222          },
223          [&] {
224              // integral argument (int64_t)
225              r = ToString(fuzzed_data_provider.ConsumeIntegral<int64_t>());
226          },
227          [&] {
228              // integral argument (uint64_t)
229              r = ToString(fuzzed_data_provider.ConsumeIntegral<uint64_t>());
230          },
231          [&] {
232              // floating point argument
233              r = strprintf("%f", fuzzed_data_provider.ConsumeFloatingPoint<double>());
234          },
235          [&] {
236              // tx destination argument
237              r = EncodeDestination(ConsumeTxDestination(fuzzed_data_provider));
238          },
239          [&] {
240              // uint160 argument
241              r = ConsumeUInt160(fuzzed_data_provider).ToString();
242          },
243          [&] {
244              // uint256 argument
245              r = ConsumeUInt256(fuzzed_data_provider).ToString();
246          },
247          [&] {
248              // base32 argument
249              r = EncodeBase32(fuzzed_data_provider.ConsumeRandomLengthString(max_string_length));
250          },
251          [&] {
252              // base58 argument
253              r = EncodeBase58(MakeUCharSpan(fuzzed_data_provider.ConsumeRandomLengthString(max_base58_bytes_length)));
254          },
255          [&] {
256              // base58 argument with checksum
257              r = EncodeBase58Check(MakeUCharSpan(fuzzed_data_provider.ConsumeRandomLengthString(max_base58_bytes_length)));
258          },
259          [&] {
260              // hex encoded block
261              std::optional<CBlock> opt_block = ConsumeDeserializable<CBlock>(fuzzed_data_provider, TX_WITH_WITNESS);
262              if (!opt_block) {
263                  good_data = false;
264                  return;
265              }
266              DataStream data_stream{};
267              data_stream << TX_WITH_WITNESS(*opt_block);
268              r = HexStr(data_stream);
269          },
270          [&] {
271              // hex encoded block header
272              std::optional<CBlockHeader> opt_block_header = ConsumeDeserializable<CBlockHeader>(fuzzed_data_provider);
273              if (!opt_block_header) {
274                  good_data = false;
275                  return;
276              }
277              DataStream data_stream{};
278              data_stream << *opt_block_header;
279              r = HexStr(data_stream);
280          },
281          [&] {
282              // hex encoded tx
283              std::optional<CMutableTransaction> opt_tx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
284              if (!opt_tx) {
285                  good_data = false;
286                  return;
287              }
288              DataStream data_stream;
289              auto allow_witness = (fuzzed_data_provider.ConsumeBool() ? TX_WITH_WITNESS : TX_NO_WITNESS);
290              data_stream << allow_witness(*opt_tx);
291              r = HexStr(data_stream);
292          },
293          [&] {
294              // base64 encoded psbt
295              std::optional<PartiallySignedTransaction> opt_psbt = ConsumeDeserializable<PartiallySignedTransaction>(fuzzed_data_provider);
296              if (!opt_psbt) {
297                  good_data = false;
298                  return;
299              }
300              DataStream data_stream{};
301              data_stream << *opt_psbt;
302              r = EncodeBase64(data_stream);
303          },
304          [&] {
305              // base58 encoded key
306              CKey key = ConsumePrivateKey(fuzzed_data_provider);
307              if (!key.IsValid()) {
308                  good_data = false;
309                  return;
310              }
311              r = EncodeSecret(key);
312          },
313          [&] {
314              // hex encoded pubkey
315              CKey key = ConsumePrivateKey(fuzzed_data_provider);
316              if (!key.IsValid()) {
317                  good_data = false;
318                  return;
319              }
320              r = HexStr(key.GetPubKey());
321          });
322      return r;
323  }
324  
325  std::string ConsumeArrayRPCArgument(FuzzedDataProvider& fuzzed_data_provider, bool& good_data)
326  {
327      std::vector<std::string> scalar_arguments;
328      LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 100)
329      {
330          scalar_arguments.push_back(ConsumeScalarRPCArgument(fuzzed_data_provider, good_data));
331      }
332      return "[\"" + Join(scalar_arguments, "\",\"") + "\"]";
333  }
334  
335  std::string ConsumeRPCArgument(FuzzedDataProvider& fuzzed_data_provider, bool& good_data)
336  {
337      return fuzzed_data_provider.ConsumeBool() ? ConsumeScalarRPCArgument(fuzzed_data_provider, good_data) : ConsumeArrayRPCArgument(fuzzed_data_provider, good_data);
338  }
339  
340  RPCFuzzTestingSetup* InitializeRPCFuzzTestingSetup()
341  {
342      static const auto setup = MakeNoLogFileContext<RPCFuzzTestingSetup>();
343      SetRPCWarmupFinished();
344      return setup.get();
345  }
346  }; // namespace
347  
348  void initialize_rpc()
349  {
350      rpc_testing_setup = InitializeRPCFuzzTestingSetup();
351      const std::vector<std::string> supported_rpc_commands = rpc_testing_setup->GetRPCCommands();
352      for (const std::string& rpc_command : supported_rpc_commands) {
353          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();
354          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();
355          if (!(safe_for_fuzzing || not_safe_for_fuzzing)) {
356              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";
357              std::terminate();
358          }
359          if (safe_for_fuzzing && not_safe_for_fuzzing) {
360              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";
361              std::terminate();
362          }
363      }
364      const char* limit_to_rpc_command_env = std::getenv("LIMIT_TO_RPC_COMMAND");
365      if (limit_to_rpc_command_env != nullptr) {
366          g_limit_to_rpc_command = std::string{limit_to_rpc_command_env};
367      }
368  }
369  
370  FUZZ_TARGET(rpc, .init = initialize_rpc)
371  {
372      SeedRandomStateForTest(SeedRand::ZEROS);
373      FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
374      bool good_data{true};
375      NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)};
376      const std::string rpc_command = fuzzed_data_provider.ConsumeRandomLengthString(64);
377      if (!g_limit_to_rpc_command.empty() && rpc_command != g_limit_to_rpc_command) {
378          return;
379      }
380      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();
381      if (!safe_for_fuzzing) {
382          return;
383      }
384      std::vector<std::string> arguments;
385      LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 100)
386      {
387          arguments.push_back(ConsumeRPCArgument(fuzzed_data_provider, good_data));
388      }
389      try {
390          std::optional<test_only_CheckFailuresAreExceptionsNotAborts> maybe_mock{};
391          if (rpc_command == "echo") {
392              // Avoid aborting fuzzing for this specific test-only RPC with an
393              // intentional trigger_internal_bug
394              maybe_mock.emplace();
395          }
396          rpc_testing_setup->CallRPC(rpc_command, arguments);
397      } catch (const UniValue& json_rpc_error) {
398          const std::string error_msg{json_rpc_error.find_value("message").get_str()};
399          if (error_msg.starts_with("Internal bug detected")) {
400              // Only allow the intentional internal bug
401              assert(error_msg.find("trigger_internal_bug") != std::string::npos);
402          }
403      }
404  }