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 }