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