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 }