Merge bitcoin/bitcoin#21169: fuzz: Add RPC interface fuzzing. Increase fuzzing coverage from 65% to 70%.
pull/826/head545404e7e1
fuzz: Add RPC interface fuzzing. Increase fuzzing coverage from 65% to 70%. (practicalswift) Pull request description: Add RPC interface fuzzing. This PR increases overall fuzzing line coverage from [~65%](https://marcofalke.github.io/btc_cov/fuzz.coverage/) to ~70% 🎉 To test this PR: ``` $ make distclean $ ./autogen.sh $ CC=clang CXX=clang++ ./configure --enable-fuzz --with-sanitizers=address,fuzzer,undefined $ make -C src/ test/fuzz/fuzz $ FUZZ=rpc src/test/fuzz/fuzz ``` See [`doc/fuzzing.md`](https://github.com/bitcoin/bitcoin/blob/master/doc/fuzzing.md) for more information on how to fuzz Bitcoin Core. Don't forget to contribute any coverage increasing inputs you find to the [Bitcoin Core fuzzing corpus repo](https://github.com/bitcoin-core/qa-assets). Happy fuzzing :) ACKs for top commit: MarcoFalke: Concept ACK545404e7e1
Tree-SHA512: 35fc1b508af42bf480ee3762326b09ff2eecdb7960a1917ad16345fadd5c0c21d666dafe736176e5a848ff6492483c782e4ea914cd9000faf50190df051950fd
commit
e45863166f
@ -0,0 +1,378 @@
|
|||||||
|
// Copyright (c) 2021 The Bitcoin Core developers
|
||||||
|
// Distributed under the MIT software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#include <base58.h>
|
||||||
|
#include <chainparamsbase.h>
|
||||||
|
#include <core_io.h>
|
||||||
|
#include <interfaces/chain.h>
|
||||||
|
#include <key.h>
|
||||||
|
#include <key_io.h>
|
||||||
|
#include <node/context.h>
|
||||||
|
#include <primitives/block.h>
|
||||||
|
#include <primitives/transaction.h>
|
||||||
|
#include <psbt.h>
|
||||||
|
#include <rpc/blockchain.h>
|
||||||
|
#include <rpc/client.h>
|
||||||
|
#include <rpc/request.h>
|
||||||
|
#include <rpc/server.h>
|
||||||
|
#include <rpc/util.h>
|
||||||
|
#include <span.h>
|
||||||
|
#include <streams.h>
|
||||||
|
#include <test/fuzz/FuzzedDataProvider.h>
|
||||||
|
#include <test/fuzz/fuzz.h>
|
||||||
|
#include <test/fuzz/util.h>
|
||||||
|
#include <test/util/setup_common.h>
|
||||||
|
#include <tinyformat.h>
|
||||||
|
#include <univalue.h>
|
||||||
|
#include <util/strencodings.h>
|
||||||
|
#include <util/string.h>
|
||||||
|
#include <util/time.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct RPCFuzzTestingSetup : public TestingSetup {
|
||||||
|
RPCFuzzTestingSetup(const std::string& chain_name, const std::vector<const char*>& extra_args) : TestingSetup{chain_name, extra_args}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
UniValue CallRPC(const std::string& rpc_method, const std::vector<std::string>& arguments)
|
||||||
|
{
|
||||||
|
JSONRPCRequest request;
|
||||||
|
request.context = &m_node;
|
||||||
|
request.strMethod = rpc_method;
|
||||||
|
request.params = RPCConvertValues(rpc_method, arguments);
|
||||||
|
return tableRPC.execute(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> GetRPCCommands() const
|
||||||
|
{
|
||||||
|
return tableRPC.listCommands();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
RPCFuzzTestingSetup* rpc_testing_setup = nullptr;
|
||||||
|
std::string g_limit_to_rpc_command;
|
||||||
|
|
||||||
|
// RPC commands which are not appropriate for fuzzing: such as RPC commands
|
||||||
|
// reading or writing to a filename passed as an RPC parameter, RPC commands
|
||||||
|
// resulting in network activity, etc.
|
||||||
|
const std::vector<std::string> RPC_COMMANDS_NOT_SAFE_FOR_FUZZING{
|
||||||
|
"addconnection", // avoid DNS lookups
|
||||||
|
"addnode", // avoid DNS lookups
|
||||||
|
"addpeeraddress", // avoid DNS lookups
|
||||||
|
"analyzepsbt", // avoid signed integer overflow in CFeeRate::GetFee(unsigned long) (https://github.com/bitcoin/bitcoin/issues/20607)
|
||||||
|
"dumptxoutset", // avoid writing to disk
|
||||||
|
#ifdef ENABLE_WALLET
|
||||||
|
"dumpwallet", // avoid writing to disk
|
||||||
|
#endif
|
||||||
|
"echoipc", // avoid assertion failure (Assertion `"EnsureAnyNodeContext(request.context).init" && check' failed.)
|
||||||
|
"generatetoaddress", // avoid timeout
|
||||||
|
"gettxoutproof", // avoid slow execution
|
||||||
|
#ifdef ENABLE_WALLET
|
||||||
|
"importwallet", // avoid reading from disk
|
||||||
|
"loadwallet", // avoid reading from disk
|
||||||
|
#endif
|
||||||
|
"mockscheduler", // avoid assertion failure (Assertion `delta_seconds.count() > 0 && delta_seconds < std::chrono::hours{1}' failed.)
|
||||||
|
"prioritisetransaction", // avoid signed integer overflow in CTxMemPool::PrioritiseTransaction(uint256 const&, long const&) (https://github.com/bitcoin/bitcoin/issues/20626)
|
||||||
|
"setban", // avoid DNS lookups
|
||||||
|
"stop", // avoid shutdown state
|
||||||
|
};
|
||||||
|
|
||||||
|
// RPC commands which are safe for fuzzing.
|
||||||
|
const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
|
||||||
|
"clearbanned",
|
||||||
|
"combinepsbt",
|
||||||
|
"combinerawtransaction",
|
||||||
|
"converttopsbt",
|
||||||
|
"createmultisig",
|
||||||
|
"createpsbt",
|
||||||
|
"createrawtransaction",
|
||||||
|
"decodepsbt",
|
||||||
|
"decoderawtransaction",
|
||||||
|
"decodescript",
|
||||||
|
"deriveaddresses",
|
||||||
|
"disconnectnode",
|
||||||
|
"echo",
|
||||||
|
"echojson",
|
||||||
|
"estimaterawfee",
|
||||||
|
"estimatesmartfee",
|
||||||
|
"finalizepsbt",
|
||||||
|
"generate",
|
||||||
|
"generateblock",
|
||||||
|
"generatetodescriptor",
|
||||||
|
"getaddednodeinfo",
|
||||||
|
"getbestblockhash",
|
||||||
|
"getblock",
|
||||||
|
"getblockchaininfo",
|
||||||
|
"getblockcount",
|
||||||
|
"getblockfilter",
|
||||||
|
"getblockhash",
|
||||||
|
"getblockheader",
|
||||||
|
"getblockstats",
|
||||||
|
"getblocktemplate",
|
||||||
|
"getchaintips",
|
||||||
|
"getchaintxstats",
|
||||||
|
"getconnectioncount",
|
||||||
|
"getdescriptorinfo",
|
||||||
|
"getdifficulty",
|
||||||
|
"getindexinfo",
|
||||||
|
"getmemoryinfo",
|
||||||
|
"getmempoolancestors",
|
||||||
|
"getmempooldescendants",
|
||||||
|
"getmempoolentry",
|
||||||
|
"getmempoolinfo",
|
||||||
|
"getmininginfo",
|
||||||
|
"getnettotals",
|
||||||
|
"getnetworkhashps",
|
||||||
|
"getnetworkinfo",
|
||||||
|
"getnodeaddresses",
|
||||||
|
"getpeerinfo",
|
||||||
|
"getrawmempool",
|
||||||
|
"getrawtransaction",
|
||||||
|
"getrpcinfo",
|
||||||
|
"gettxout",
|
||||||
|
"gettxoutsetinfo",
|
||||||
|
"help",
|
||||||
|
"invalidateblock",
|
||||||
|
"joinpsbts",
|
||||||
|
"listbanned",
|
||||||
|
"logging",
|
||||||
|
"ping",
|
||||||
|
"preciousblock",
|
||||||
|
"pruneblockchain",
|
||||||
|
"reconsiderblock",
|
||||||
|
"savemempool",
|
||||||
|
"scantxoutset",
|
||||||
|
"sendrawtransaction",
|
||||||
|
"setmocktime",
|
||||||
|
"setnetworkactive",
|
||||||
|
"signmessagewithprivkey",
|
||||||
|
"signrawtransactionwithkey",
|
||||||
|
"submitblock",
|
||||||
|
"submitheader",
|
||||||
|
"syncwithvalidationinterfacequeue",
|
||||||
|
"testmempoolaccept",
|
||||||
|
"uptime",
|
||||||
|
"utxoupdatepsbt",
|
||||||
|
"validateaddress",
|
||||||
|
"verifychain",
|
||||||
|
"verifymessage",
|
||||||
|
"verifytxoutproof",
|
||||||
|
"waitforblock",
|
||||||
|
"waitforblockheight",
|
||||||
|
"waitfornewblock",
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string ConsumeScalarRPCArgument(FuzzedDataProvider& fuzzed_data_provider)
|
||||||
|
{
|
||||||
|
const size_t max_string_length = 4096;
|
||||||
|
std::string r;
|
||||||
|
CallOneOf(
|
||||||
|
fuzzed_data_provider,
|
||||||
|
[&] {
|
||||||
|
// string argument
|
||||||
|
r = fuzzed_data_provider.ConsumeRandomLengthString(max_string_length);
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
// base64 argument
|
||||||
|
r = EncodeBase64(fuzzed_data_provider.ConsumeRandomLengthString(max_string_length));
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
// hex argument
|
||||||
|
r = HexStr(fuzzed_data_provider.ConsumeRandomLengthString(max_string_length));
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
// bool argument
|
||||||
|
r = fuzzed_data_provider.ConsumeBool() ? "true" : "false";
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
// range argument
|
||||||
|
r = "[" + ToString(fuzzed_data_provider.ConsumeIntegral<int64_t>()) + "," + ToString(fuzzed_data_provider.ConsumeIntegral<int64_t>()) + "]";
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
// integral argument (int64_t)
|
||||||
|
r = ToString(fuzzed_data_provider.ConsumeIntegral<int64_t>());
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
// integral argument (uint64_t)
|
||||||
|
r = ToString(fuzzed_data_provider.ConsumeIntegral<uint64_t>());
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
// floating point argument
|
||||||
|
r = strprintf("%f", fuzzed_data_provider.ConsumeFloatingPoint<double>());
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
// tx destination argument
|
||||||
|
r = EncodeDestination(ConsumeTxDestination(fuzzed_data_provider));
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
// uint160 argument
|
||||||
|
r = ConsumeUInt160(fuzzed_data_provider).ToString();
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
// uint256 argument
|
||||||
|
r = ConsumeUInt256(fuzzed_data_provider).ToString();
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
// base32 argument
|
||||||
|
r = EncodeBase32(fuzzed_data_provider.ConsumeRandomLengthString(max_string_length));
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
// base58 argument
|
||||||
|
r = EncodeBase58(MakeUCharSpan(fuzzed_data_provider.ConsumeRandomLengthString(max_string_length)));
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
// base58 argument with checksum
|
||||||
|
r = EncodeBase58Check(MakeUCharSpan(fuzzed_data_provider.ConsumeRandomLengthString(max_string_length)));
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
// hex encoded block
|
||||||
|
std::optional<CBlock> opt_block = ConsumeDeserializable<CBlock>(fuzzed_data_provider);
|
||||||
|
if (!opt_block) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CDataStream data_stream{SER_NETWORK, PROTOCOL_VERSION};
|
||||||
|
data_stream << *opt_block;
|
||||||
|
r = HexStr(data_stream);
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
// hex encoded block header
|
||||||
|
std::optional<CBlockHeader> opt_block_header = ConsumeDeserializable<CBlockHeader>(fuzzed_data_provider);
|
||||||
|
if (!opt_block_header) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CDataStream data_stream{SER_NETWORK, PROTOCOL_VERSION};
|
||||||
|
data_stream << *opt_block_header;
|
||||||
|
r = HexStr(data_stream);
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
// hex encoded tx
|
||||||
|
std::optional<CMutableTransaction> opt_tx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider);
|
||||||
|
if (!opt_tx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CDataStream data_stream{SER_NETWORK, fuzzed_data_provider.ConsumeBool() ? PROTOCOL_VERSION : (PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS)};
|
||||||
|
data_stream << *opt_tx;
|
||||||
|
r = HexStr(data_stream);
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
// base64 encoded psbt
|
||||||
|
std::optional<PartiallySignedTransaction> opt_psbt = ConsumeDeserializable<PartiallySignedTransaction>(fuzzed_data_provider);
|
||||||
|
if (!opt_psbt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CDataStream data_stream{SER_NETWORK, PROTOCOL_VERSION};
|
||||||
|
data_stream << *opt_psbt;
|
||||||
|
r = EncodeBase64({data_stream.begin(), data_stream.end()});
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
// base58 encoded key
|
||||||
|
const std::vector<uint8_t> random_bytes = fuzzed_data_provider.ConsumeBytes<uint8_t>(32);
|
||||||
|
CKey key;
|
||||||
|
key.Set(random_bytes.begin(), random_bytes.end(), fuzzed_data_provider.ConsumeBool());
|
||||||
|
if (!key.IsValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
r = EncodeSecret(key);
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
// hex encoded pubkey
|
||||||
|
const std::vector<uint8_t> random_bytes = fuzzed_data_provider.ConsumeBytes<uint8_t>(32);
|
||||||
|
CKey key;
|
||||||
|
key.Set(random_bytes.begin(), random_bytes.end(), fuzzed_data_provider.ConsumeBool());
|
||||||
|
if (!key.IsValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
r = HexStr(key.GetPubKey());
|
||||||
|
});
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ConsumeArrayRPCArgument(FuzzedDataProvider& fuzzed_data_provider)
|
||||||
|
{
|
||||||
|
std::vector<std::string> scalar_arguments;
|
||||||
|
while (fuzzed_data_provider.ConsumeBool()) {
|
||||||
|
scalar_arguments.push_back(ConsumeScalarRPCArgument(fuzzed_data_provider));
|
||||||
|
}
|
||||||
|
return "[\"" + Join(scalar_arguments, "\",\"") + "\"]";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ConsumeRPCArgument(FuzzedDataProvider& fuzzed_data_provider)
|
||||||
|
{
|
||||||
|
return fuzzed_data_provider.ConsumeBool() ? ConsumeScalarRPCArgument(fuzzed_data_provider) : ConsumeArrayRPCArgument(fuzzed_data_provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
RPCFuzzTestingSetup* InitializeRPCFuzzTestingSetup()
|
||||||
|
{
|
||||||
|
static const auto setup = MakeNoLogFileContext<RPCFuzzTestingSetup>();
|
||||||
|
SetRPCWarmupFinished();
|
||||||
|
return setup.get();
|
||||||
|
}
|
||||||
|
}; // namespace
|
||||||
|
|
||||||
|
void initialize_rpc()
|
||||||
|
{
|
||||||
|
rpc_testing_setup = InitializeRPCFuzzTestingSetup();
|
||||||
|
const std::vector<std::string> supported_rpc_commands = rpc_testing_setup->GetRPCCommands();
|
||||||
|
for (const std::string& rpc_command : supported_rpc_commands) {
|
||||||
|
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();
|
||||||
|
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();
|
||||||
|
if (!(safe_for_fuzzing || not_safe_for_fuzzing)) {
|
||||||
|
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";
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
if (safe_for_fuzzing && not_safe_for_fuzzing) {
|
||||||
|
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";
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const std::string& rpc_command : RPC_COMMANDS_SAFE_FOR_FUZZING) {
|
||||||
|
const bool supported_rpc_command = std::find(supported_rpc_commands.begin(), supported_rpc_commands.end(), rpc_command) != supported_rpc_commands.end();
|
||||||
|
if (!supported_rpc_command) {
|
||||||
|
std::cerr << "Error: Unknown RPC command \"" << rpc_command << "\" found in RPC_COMMANDS_SAFE_FOR_FUZZING. Please update " << __FILE__ << ".\n";
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const std::string& rpc_command : RPC_COMMANDS_NOT_SAFE_FOR_FUZZING) {
|
||||||
|
const bool supported_rpc_command = std::find(supported_rpc_commands.begin(), supported_rpc_commands.end(), rpc_command) != supported_rpc_commands.end();
|
||||||
|
if (!supported_rpc_command) {
|
||||||
|
std::cerr << "Error: Unknown RPC command \"" << rpc_command << "\" found in RPC_COMMANDS_NOT_SAFE_FOR_FUZZING. Please update " << __FILE__ << ".\n";
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const char* limit_to_rpc_command_env = std::getenv("LIMIT_TO_RPC_COMMAND");
|
||||||
|
if (limit_to_rpc_command_env != nullptr) {
|
||||||
|
g_limit_to_rpc_command = std::string{limit_to_rpc_command_env};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FUZZ_TARGET_INIT(rpc, initialize_rpc)
|
||||||
|
{
|
||||||
|
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
|
||||||
|
SetMockTime(ConsumeTime(fuzzed_data_provider));
|
||||||
|
const std::string rpc_command = fuzzed_data_provider.ConsumeRandomLengthString(64);
|
||||||
|
if (!g_limit_to_rpc_command.empty() && rpc_command != g_limit_to_rpc_command) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
if (!safe_for_fuzzing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::vector<std::string> arguments;
|
||||||
|
while (fuzzed_data_provider.ConsumeBool()) {
|
||||||
|
arguments.push_back(ConsumeRPCArgument(fuzzed_data_provider));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
rpc_testing_setup->CallRPC(rpc_command, arguments);
|
||||||
|
} catch (const UniValue&) {
|
||||||
|
} catch (const std::runtime_error&) {
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue