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