|
| 1 | +// Copyright (c) 2021 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 <chainparamsbase.h> |
| 7 | +#include <core_io.h> |
| 8 | +#include <interfaces/chain.h> |
| 9 | +#include <key.h> |
| 10 | +#include <key_io.h> |
| 11 | +#include <node/context.h> |
| 12 | +#include <primitives/block.h> |
| 13 | +#include <primitives/transaction.h> |
| 14 | +#include <psbt.h> |
| 15 | +#include <rpc/blockchain.h> |
| 16 | +#include <rpc/client.h> |
| 17 | +#include <rpc/request.h> |
| 18 | +#include <rpc/server.h> |
| 19 | +#include <rpc/util.h> |
| 20 | +#include <span.h> |
| 21 | +#include <streams.h> |
| 22 | +#include <test/fuzz/FuzzedDataProvider.h> |
| 23 | +#include <test/fuzz/fuzz.h> |
| 24 | +#include <test/fuzz/util.h> |
| 25 | +#include <test/util/setup_common.h> |
| 26 | +#include <tinyformat.h> |
| 27 | +#include <univalue.h> |
| 28 | +#include <util/strencodings.h> |
| 29 | +#include <util/string.h> |
| 30 | +#include <util/time.h> |
| 31 | + |
| 32 | +#include <cstdint> |
| 33 | +#include <iostream> |
| 34 | +#include <memory> |
| 35 | +#include <optional> |
| 36 | +#include <stdexcept> |
| 37 | +#include <string> |
| 38 | +#include <vector> |
| 39 | + |
| 40 | +namespace { |
| 41 | +struct RPCFuzzTestingSetup : public TestingSetup { |
| 42 | + RPCFuzzTestingSetup(const std::string& chain_name, const std::vector<const char*>& extra_args) : TestingSetup{chain_name, extra_args} |
| 43 | + { |
| 44 | + } |
| 45 | + |
| 46 | + UniValue CallRPC(const std::string& rpc_method, const std::vector<std::string>& arguments) |
| 47 | + { |
| 48 | + JSONRPCRequest request; |
| 49 | + request.context = &m_node; |
| 50 | + request.strMethod = rpc_method; |
| 51 | + request.params = RPCConvertValues(rpc_method, arguments); |
| 52 | + return tableRPC.execute(request); |
| 53 | + } |
| 54 | + |
| 55 | + std::vector<std::string> GetRPCCommands() const |
| 56 | + { |
| 57 | + return tableRPC.listCommands(); |
| 58 | + } |
| 59 | +}; |
| 60 | + |
| 61 | +RPCFuzzTestingSetup* rpc_testing_setup = nullptr; |
| 62 | +std::string g_limit_to_rpc_command; |
| 63 | + |
| 64 | +// RPC commands which are not appropriate for fuzzing: such as RPC commands |
| 65 | +// reading or writing to a filename passed as an RPC parameter, RPC commands |
| 66 | +// resulting in network activity, etc. |
| 67 | +const std::vector<std::string> RPC_COMMANDS_NOT_SAFE_FOR_FUZZING{ |
| 68 | + "addconnection", // avoid DNS lookups |
| 69 | + "addnode", // avoid DNS lookups |
| 70 | + "addpeeraddress", // avoid DNS lookups |
| 71 | + "analyzepsbt", // avoid signed integer overflow in CFeeRate::GetFee(unsigned long) (https://github.com/bitcoin/bitcoin/issues/20607) |
| 72 | + "dumptxoutset", // avoid writing to disk |
| 73 | +#ifdef ENABLE_WALLET |
| 74 | + "dumpwallet", // avoid writing to disk |
| 75 | +#endif |
| 76 | + "echoipc", // avoid assertion failure (Assertion `"EnsureAnyNodeContext(request.context).init" && check' failed.) |
| 77 | + "generatetoaddress", // avoid timeout |
| 78 | + "gettxoutproof", // avoid slow execution |
| 79 | +#ifdef ENABLE_WALLET |
| 80 | + "importwallet", // avoid reading from disk |
| 81 | + "loadwallet", // avoid reading from disk |
| 82 | +#endif |
| 83 | + "mockscheduler", // avoid assertion failure (Assertion `delta_seconds.count() > 0 && delta_seconds < std::chrono::hours{1}' failed.) |
| 84 | + "prioritisetransaction", // avoid signed integer overflow in CTxMemPool::PrioritiseTransaction(uint256 const&, long const&) (https://github.com/bitcoin/bitcoin/issues/20626) |
| 85 | + "setban", // avoid DNS lookups |
| 86 | + "stop", // avoid shutdown state |
| 87 | +}; |
| 88 | + |
| 89 | +// RPC commands which are safe for fuzzing. |
| 90 | +const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{ |
| 91 | + "clearbanned", |
| 92 | + "combinepsbt", |
| 93 | + "combinerawtransaction", |
| 94 | + "converttopsbt", |
| 95 | + "createmultisig", |
| 96 | + "createpsbt", |
| 97 | + "createrawtransaction", |
| 98 | + "decodepsbt", |
| 99 | + "decoderawtransaction", |
| 100 | + "decodescript", |
| 101 | + "deriveaddresses", |
| 102 | + "disconnectnode", |
| 103 | + "echo", |
| 104 | + "echojson", |
| 105 | + "estimaterawfee", |
| 106 | + "estimatesmartfee", |
| 107 | + "finalizepsbt", |
| 108 | + "generate", |
| 109 | + "generateblock", |
| 110 | + "generatetodescriptor", |
| 111 | + "getaddednodeinfo", |
| 112 | + "getbestblockhash", |
| 113 | + "getblock", |
| 114 | + "getblockchaininfo", |
| 115 | + "getblockcount", |
| 116 | + "getblockfilter", |
| 117 | + "getblockhash", |
| 118 | + "getblockheader", |
| 119 | + "getblockstats", |
| 120 | + "getblocktemplate", |
| 121 | + "getchaintips", |
| 122 | + "getchaintxstats", |
| 123 | + "getconnectioncount", |
| 124 | + "getdescriptorinfo", |
| 125 | + "getdifficulty", |
| 126 | + "getindexinfo", |
| 127 | + "getmemoryinfo", |
| 128 | + "getmempoolancestors", |
| 129 | + "getmempooldescendants", |
| 130 | + "getmempoolentry", |
| 131 | + "getmempoolinfo", |
| 132 | + "getmininginfo", |
| 133 | + "getnettotals", |
| 134 | + "getnetworkhashps", |
| 135 | + "getnetworkinfo", |
| 136 | + "getnodeaddresses", |
| 137 | + "getpeerinfo", |
| 138 | + "getrawmempool", |
| 139 | + "getrawtransaction", |
| 140 | + "getrpcinfo", |
| 141 | + "gettxout", |
| 142 | + "gettxoutsetinfo", |
| 143 | + "help", |
| 144 | + "invalidateblock", |
| 145 | + "joinpsbts", |
| 146 | + "listbanned", |
| 147 | + "logging", |
| 148 | + "ping", |
| 149 | + "preciousblock", |
| 150 | + "pruneblockchain", |
| 151 | + "reconsiderblock", |
| 152 | + "savemempool", |
| 153 | + "scantxoutset", |
| 154 | + "sendrawtransaction", |
| 155 | + "setmocktime", |
| 156 | + "setnetworkactive", |
| 157 | + "signmessagewithprivkey", |
| 158 | + "signrawtransactionwithkey", |
| 159 | + "submitblock", |
| 160 | + "submitheader", |
| 161 | + "syncwithvalidationinterfacequeue", |
| 162 | + "testmempoolaccept", |
| 163 | + "uptime", |
| 164 | + "utxoupdatepsbt", |
| 165 | + "validateaddress", |
| 166 | + "verifychain", |
| 167 | + "verifymessage", |
| 168 | + "verifytxoutproof", |
| 169 | + "waitforblock", |
| 170 | + "waitforblockheight", |
| 171 | + "waitfornewblock", |
| 172 | +}; |
| 173 | + |
| 174 | +std::string ConsumeScalarRPCArgument(FuzzedDataProvider& fuzzed_data_provider) |
| 175 | +{ |
| 176 | + const size_t max_string_length = 4096; |
| 177 | + std::string r; |
| 178 | + CallOneOf( |
| 179 | + fuzzed_data_provider, |
| 180 | + [&] { |
| 181 | + // string argument |
| 182 | + r = fuzzed_data_provider.ConsumeRandomLengthString(max_string_length); |
| 183 | + }, |
| 184 | + [&] { |
| 185 | + // base64 argument |
| 186 | + r = EncodeBase64(fuzzed_data_provider.ConsumeRandomLengthString(max_string_length)); |
| 187 | + }, |
| 188 | + [&] { |
| 189 | + // hex argument |
| 190 | + r = HexStr(fuzzed_data_provider.ConsumeRandomLengthString(max_string_length)); |
| 191 | + }, |
| 192 | + [&] { |
| 193 | + // bool argument |
| 194 | + r = fuzzed_data_provider.ConsumeBool() ? "true" : "false"; |
| 195 | + }, |
| 196 | + [&] { |
| 197 | + // range argument |
| 198 | + r = "[" + ToString(fuzzed_data_provider.ConsumeIntegral<int64_t>()) + "," + ToString(fuzzed_data_provider.ConsumeIntegral<int64_t>()) + "]"; |
| 199 | + }, |
| 200 | + [&] { |
| 201 | + // integral argument (int64_t) |
| 202 | + r = ToString(fuzzed_data_provider.ConsumeIntegral<int64_t>()); |
| 203 | + }, |
| 204 | + [&] { |
| 205 | + // integral argument (uint64_t) |
| 206 | + r = ToString(fuzzed_data_provider.ConsumeIntegral<uint64_t>()); |
| 207 | + }, |
| 208 | + [&] { |
| 209 | + // floating point argument |
| 210 | + r = strprintf("%f", fuzzed_data_provider.ConsumeFloatingPoint<double>()); |
| 211 | + }, |
| 212 | + [&] { |
| 213 | + // tx destination argument |
| 214 | + r = EncodeDestination(ConsumeTxDestination(fuzzed_data_provider)); |
| 215 | + }, |
| 216 | + [&] { |
| 217 | + // uint160 argument |
| 218 | + r = ConsumeUInt160(fuzzed_data_provider).ToString(); |
| 219 | + }, |
| 220 | + [&] { |
| 221 | + // uint256 argument |
| 222 | + r = ConsumeUInt256(fuzzed_data_provider).ToString(); |
| 223 | + }, |
| 224 | + [&] { |
| 225 | + // base32 argument |
| 226 | + r = EncodeBase32(fuzzed_data_provider.ConsumeRandomLengthString(max_string_length)); |
| 227 | + }, |
| 228 | + [&] { |
| 229 | + // base58 argument |
| 230 | + r = EncodeBase58(MakeUCharSpan(fuzzed_data_provider.ConsumeRandomLengthString(max_string_length))); |
| 231 | + }, |
| 232 | + [&] { |
| 233 | + // base58 argument with checksum |
| 234 | + r = EncodeBase58Check(MakeUCharSpan(fuzzed_data_provider.ConsumeRandomLengthString(max_string_length))); |
| 235 | + }, |
| 236 | + [&] { |
| 237 | + // hex encoded block |
| 238 | + std::optional<CBlock> opt_block = ConsumeDeserializable<CBlock>(fuzzed_data_provider); |
| 239 | + if (!opt_block) { |
| 240 | + return; |
| 241 | + } |
| 242 | + CDataStream data_stream{SER_NETWORK, PROTOCOL_VERSION}; |
| 243 | + data_stream << *opt_block; |
| 244 | + r = HexStr(data_stream); |
| 245 | + }, |
| 246 | + [&] { |
| 247 | + // hex encoded block header |
| 248 | + std::optional<CBlockHeader> opt_block_header = ConsumeDeserializable<CBlockHeader>(fuzzed_data_provider); |
| 249 | + if (!opt_block_header) { |
| 250 | + return; |
| 251 | + } |
| 252 | + CDataStream data_stream{SER_NETWORK, PROTOCOL_VERSION}; |
| 253 | + data_stream << *opt_block_header; |
| 254 | + r = HexStr(data_stream); |
| 255 | + }, |
| 256 | + [&] { |
| 257 | + // hex encoded tx |
| 258 | + std::optional<CMutableTransaction> opt_tx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider); |
| 259 | + if (!opt_tx) { |
| 260 | + return; |
| 261 | + } |
| 262 | + CDataStream data_stream{SER_NETWORK, fuzzed_data_provider.ConsumeBool() ? PROTOCOL_VERSION : (PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS)}; |
| 263 | + data_stream << *opt_tx; |
| 264 | + r = HexStr(data_stream); |
| 265 | + }, |
| 266 | + [&] { |
| 267 | + // base64 encoded psbt |
| 268 | + std::optional<PartiallySignedTransaction> opt_psbt = ConsumeDeserializable<PartiallySignedTransaction>(fuzzed_data_provider); |
| 269 | + if (!opt_psbt) { |
| 270 | + return; |
| 271 | + } |
| 272 | + CDataStream data_stream{SER_NETWORK, PROTOCOL_VERSION}; |
| 273 | + data_stream << *opt_psbt; |
| 274 | + r = EncodeBase64({data_stream.begin(), data_stream.end()}); |
| 275 | + }, |
| 276 | + [&] { |
| 277 | + // base58 encoded key |
| 278 | + const std::vector<uint8_t> random_bytes = fuzzed_data_provider.ConsumeBytes<uint8_t>(32); |
| 279 | + CKey key; |
| 280 | + key.Set(random_bytes.begin(), random_bytes.end(), fuzzed_data_provider.ConsumeBool()); |
| 281 | + if (!key.IsValid()) { |
| 282 | + return; |
| 283 | + } |
| 284 | + r = EncodeSecret(key); |
| 285 | + }, |
| 286 | + [&] { |
| 287 | + // hex encoded pubkey |
| 288 | + const std::vector<uint8_t> random_bytes = fuzzed_data_provider.ConsumeBytes<uint8_t>(32); |
| 289 | + CKey key; |
| 290 | + key.Set(random_bytes.begin(), random_bytes.end(), fuzzed_data_provider.ConsumeBool()); |
| 291 | + if (!key.IsValid()) { |
| 292 | + return; |
| 293 | + } |
| 294 | + r = HexStr(key.GetPubKey()); |
| 295 | + }); |
| 296 | + return r; |
| 297 | +} |
| 298 | + |
| 299 | +std::string ConsumeArrayRPCArgument(FuzzedDataProvider& fuzzed_data_provider) |
| 300 | +{ |
| 301 | + std::vector<std::string> scalar_arguments; |
| 302 | + while (fuzzed_data_provider.ConsumeBool()) { |
| 303 | + scalar_arguments.push_back(ConsumeScalarRPCArgument(fuzzed_data_provider)); |
| 304 | + } |
| 305 | + return "[\"" + Join(scalar_arguments, "\",\"") + "\"]"; |
| 306 | +} |
| 307 | + |
| 308 | +std::string ConsumeRPCArgument(FuzzedDataProvider& fuzzed_data_provider) |
| 309 | +{ |
| 310 | + return fuzzed_data_provider.ConsumeBool() ? ConsumeScalarRPCArgument(fuzzed_data_provider) : ConsumeArrayRPCArgument(fuzzed_data_provider); |
| 311 | +} |
| 312 | + |
| 313 | +RPCFuzzTestingSetup* InitializeRPCFuzzTestingSetup() |
| 314 | +{ |
| 315 | + static const auto setup = MakeNoLogFileContext<RPCFuzzTestingSetup>(); |
| 316 | + SetRPCWarmupFinished(); |
| 317 | + return setup.get(); |
| 318 | +} |
| 319 | +}; // namespace |
| 320 | + |
| 321 | +void initialize_rpc() |
| 322 | +{ |
| 323 | + rpc_testing_setup = InitializeRPCFuzzTestingSetup(); |
| 324 | + const std::vector<std::string> supported_rpc_commands = rpc_testing_setup->GetRPCCommands(); |
| 325 | + for (const std::string& rpc_command : supported_rpc_commands) { |
| 326 | + 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(); |
| 327 | + 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(); |
| 328 | + if (!(safe_for_fuzzing || not_safe_for_fuzzing)) { |
| 329 | + 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"; |
| 330 | + std::terminate(); |
| 331 | + } |
| 332 | + if (safe_for_fuzzing && not_safe_for_fuzzing) { |
| 333 | + 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"; |
| 334 | + std::terminate(); |
| 335 | + } |
| 336 | + } |
| 337 | + for (const std::string& rpc_command : RPC_COMMANDS_SAFE_FOR_FUZZING) { |
| 338 | + const bool supported_rpc_command = std::find(supported_rpc_commands.begin(), supported_rpc_commands.end(), rpc_command) != supported_rpc_commands.end(); |
| 339 | + if (!supported_rpc_command) { |
| 340 | + std::cerr << "Error: Unknown RPC command \"" << rpc_command << "\" found in RPC_COMMANDS_SAFE_FOR_FUZZING. Please update " << __FILE__ << ".\n"; |
| 341 | + std::terminate(); |
| 342 | + } |
| 343 | + } |
| 344 | + for (const std::string& rpc_command : RPC_COMMANDS_NOT_SAFE_FOR_FUZZING) { |
| 345 | + const bool supported_rpc_command = std::find(supported_rpc_commands.begin(), supported_rpc_commands.end(), rpc_command) != supported_rpc_commands.end(); |
| 346 | + if (!supported_rpc_command) { |
| 347 | + std::cerr << "Error: Unknown RPC command \"" << rpc_command << "\" found in RPC_COMMANDS_NOT_SAFE_FOR_FUZZING. Please update " << __FILE__ << ".\n"; |
| 348 | + std::terminate(); |
| 349 | + } |
| 350 | + } |
| 351 | + const char* limit_to_rpc_command_env = std::getenv("LIMIT_TO_RPC_COMMAND"); |
| 352 | + if (limit_to_rpc_command_env != nullptr) { |
| 353 | + g_limit_to_rpc_command = std::string{limit_to_rpc_command_env}; |
| 354 | + } |
| 355 | +} |
| 356 | + |
| 357 | +FUZZ_TARGET_INIT(rpc, initialize_rpc) |
| 358 | +{ |
| 359 | + FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; |
| 360 | + SetMockTime(ConsumeTime(fuzzed_data_provider)); |
| 361 | + const std::string rpc_command = fuzzed_data_provider.ConsumeRandomLengthString(64); |
| 362 | + if (!g_limit_to_rpc_command.empty() && rpc_command != g_limit_to_rpc_command) { |
| 363 | + return; |
| 364 | + } |
| 365 | + 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(); |
| 366 | + if (!safe_for_fuzzing) { |
| 367 | + return; |
| 368 | + } |
| 369 | + std::vector<std::string> arguments; |
| 370 | + while (fuzzed_data_provider.ConsumeBool()) { |
| 371 | + arguments.push_back(ConsumeRPCArgument(fuzzed_data_provider)); |
| 372 | + } |
| 373 | + try { |
| 374 | + rpc_testing_setup->CallRPC(rpc_command, arguments); |
| 375 | + } catch (const UniValue&) { |
| 376 | + } catch (const std::runtime_error&) { |
| 377 | + } |
| 378 | +} |
0 commit comments