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 }