forked from dashpay/dash
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge bitcoin#21169: fuzz: Add RPC interface fuzzing. Increase fuzzin…
…g coverage from 65% to 70%. 545404e 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 ACK 545404e Tree-SHA512: 35fc1b508af42bf480ee3762326b09ff2eecdb7960a1917ad16345fadd5c0c21d666dafe736176e5a848ff6492483c782e4ea914cd9000faf50190df051950fd
- Loading branch information
Showing
2 changed files
with
379 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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&) { | ||
} | ||
} |