Skip to content

Commit e458631

Browse files
author
MarcoFalke
committed
Merge bitcoin/bitcoin#21169: fuzz: Add RPC interface fuzzing. Increase fuzzing 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
2 parents edf6795 + 545404e commit e458631

File tree

2 files changed

+379
-0
lines changed

2 files changed

+379
-0
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ test_fuzz_fuzz_SOURCES = \
274274
test/fuzz/random.cpp \
275275
test/fuzz/rbf.cpp \
276276
test/fuzz/rolling_bloom_filter.cpp \
277+
test/fuzz/rpc.cpp \
277278
test/fuzz/script.cpp \
278279
test/fuzz/script_assets_test_minimizer.cpp \
279280
test/fuzz/script_bitcoin_consensus.cpp \

src/test/fuzz/rpc.cpp

Lines changed: 378 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,378 @@
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

Comments
 (0)