Skip to content

Commit

Permalink
tests: Add fuzzing harness for ProcessMessage(...). Enables high-leve…
Browse files Browse the repository at this point in the history
…l fuzzing of the P2P layer.

Summary:
```
Add fuzzing harness for ProcessMessage(...). Enables high-level fuzzing
of the P2P layer.

All code paths reachable from this fuzzer can be assumed to be reachable
for an untrusted peer.

Seeded from thin air (an empty corpus) this fuzzer reaches roughly 20
000 lines of code.

To test this PR:

$ make distclean
$ ./autogen.sh
$ CC=clang CXX=clang++ ./configure --enable-fuzz \
      --with-sanitizers=address,fuzzer,undefined
$ make
$ src/test/fuzz/process_message
…

Worth noting about this fuzzing harness:

    To achieve a reasonable number of executions per seconds the state
of the fuzzer is unfortunately not entirely reset between test_one_input
calls. The set-up (FuzzingSetup ctor) and tear-down (~FuzzingSetup) work
is simply too costly to be run on every iteration. There is a trade-off
to handle here between a.) achieving high executions/second and b.)
giving the fuzzer a totally blank slate for each call. Please let me
know if you have any suggestion on how to improve this situation while
maintaining >1000 executions/second.
    To achieve optimal results when using coverage-guided fuzzing I've
chosen to create one specialised fuzzing binary per message type
(process_message_addr, process_message_block, process_message_blocktxn ,
etc.) and one general fuzzing binary (process_message) which handles all
messages types. The latter general fuzzer can be seeded with inputs
generated by the former specialised fuzzers.

Happy fuzzing friends!
```

Backport od core [[bitcoin/bitcoin#17989 | PR17989]].

Depends on D8004 (test plan only, fixes a fuzz fixture issue).

Test Plan:
  ninja bitcoin-fuzzers
  ./src/test/fuzz/process_message
  ./src/test/fuzz/process_message_getheaders # Or any other message

Reviewers: #bitcoin_abc, deadalnix

Reviewed By: #bitcoin_abc, deadalnix

Subscribers: deadalnix

Differential Revision: https://reviews.bitcoinabc.org/D8005
  • Loading branch information
practicalswift authored and Fabcien committed Oct 20, 2020
1 parent c146a46 commit 946eb50
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 6 deletions.
9 changes: 4 additions & 5 deletions src/net_processing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2230,11 +2230,10 @@ void static ProcessOrphanTx(const Config &config, CConnman *connman,
}
}

static bool ProcessMessage(const Config &config, CNode *pfrom,
const std::string &strCommand, CDataStream &vRecv,
int64_t nTimeReceived, CConnman *connman,
BanMan *banman,
const std::atomic<bool> &interruptMsgProc) {
bool ProcessMessage(const Config &config, CNode *pfrom,
const std::string &strCommand, CDataStream &vRecv,
int64_t nTimeReceived, CConnman *connman, BanMan *banman,
const std::atomic<bool> &interruptMsgProc) {
const CChainParams &chainparams = config.GetChainParams();
LogPrint(BCLog::NET, "received: %s (%u bytes) peer=%d\n",
SanitizeString(strCommand), vRecv.size(), pfrom->GetId());
Expand Down
5 changes: 5 additions & 0 deletions src/net_processing.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,9 @@ void Misbehaving(NodeId nodeid, int howmuch, const std::string &reason = "");
/** Relay transaction to every node */
void RelayTransaction(const TxId &txid, const CConnman &connman);

bool ProcessMessage(const Config &config, CNode *pfrom,
const std::string &strCommand, CDataStream &vRecv,
int64_t nTimeReceived, CConnman *connman, BanMan *banman,
const std::atomic<bool> &interruptMsgProc);

#endif // BITCOIN_NET_PROCESSING_H
43 changes: 43 additions & 0 deletions src/test/fuzz/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ function(add_deserialize_fuzz_targets)
endforeach()
endfunction()

function(add_process_message_fuzz_targets)
foreach(_fuzz_test_name ${ARGN})
sanitize_target_name("fuzz-process_message_" ${_fuzz_test_name} _fuzz_target_name)
add_fuzz_target(
${_fuzz_target_name}
process_message_${_fuzz_test_name}

# Sources
process_message.cpp
)

target_compile_definitions(${_fuzz_target_name} PRIVATE MESSAGE_TYPE=${_fuzz_test_name})
endforeach()
endfunction()

add_regular_fuzz_targets(
addrdb
bloom_filter
Expand All @@ -53,6 +68,7 @@ add_regular_fuzz_targets(
eval_script
net_permissions
parse_iso8601
process_message
psbt
script
script_flags
Expand Down Expand Up @@ -101,3 +117,30 @@ add_deserialize_fuzz_targets(
txoutcompressor_deserialize
txundo_deserialize
)

add_process_message_fuzz_targets(
addr
block
blocktxn
cmpctblock
feefilter
filteradd
filterclear
filterload
getaddr
getblocks
getblocktxn
getdata
getheaders
headers
inv
mempool
notfound
ping
pong
sendcmpct
sendheaders
tx
verack
version
)
129 changes: 129 additions & 0 deletions src/test/fuzz/process_message.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright (c) 2020 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 <banman.h>
#include <chainparams.h>
#include <config.h>
#include <consensus/consensus.h>
#include <net.h>
#include <net_processing.h>
#include <protocol.h>
#include <scheduler.h>
#include <script/script.h>
#include <streams.h>
#include <validationinterface.h>
#include <version.h>

#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/util/mining.h>
#include <test/util/setup_common.h>

#include <algorithm>
#include <atomic>
#include <cassert>
#include <chrono>
#include <cstdint>
#include <iosfwd>
#include <iostream>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <vector>

namespace {

#ifdef MESSAGE_TYPE
#define TO_STRING_(s) #s
#define TO_STRING(s) TO_STRING_(s)
const std::string LIMIT_TO_MESSAGE_TYPE{TO_STRING(MESSAGE_TYPE)};
#else
const std::string LIMIT_TO_MESSAGE_TYPE;
#endif

const std::map<std::string, std::set<std::string>>
EXPECTED_DESERIALIZATION_EXCEPTIONS = {
{"CDataStream::read(): end of data: iostream error",
{"addr", "block", "blocktxn", "cmpctblock", "feefilter", "filteradd",
"filterload", "getblocks", "getblocktxn", "getdata", "getheaders",
"headers", "inv", "notfound", "ping", "sendcmpct", "tx"}},
{"CompactSize exceeds limit of type: iostream error", {"cmpctblock"}},
{"differential value overflow: iostream error", {"getblocktxn"}},
{"index overflowed 16 bits: iostream error", {"getblocktxn"}},
{"index overflowed 16-bits: iostream error", {"cmpctblock"}},
{"indexes overflowed 16 bits: iostream error", {"getblocktxn"}},
{"non-canonical ReadCompactSize(): iostream error",
{"addr", "block", "blocktxn", "cmpctblock", "filteradd", "filterload",
"getblocks", "getblocktxn", "getdata", "getheaders", "headers", "inv",
"notfound", "tx"}},
{"ReadCompactSize(): size too large: iostream error",
{"addr", "block", "blocktxn", "cmpctblock", "filteradd", "filterload",
"getblocks", "getblocktxn", "getdata", "getheaders", "headers", "inv",
"notfound", "tx"}},
{"Superfluous witness record: iostream error",
{"block", "blocktxn", "cmpctblock", "tx"}},
{"Unknown transaction optional data: iostream error",
{"block", "blocktxn", "cmpctblock", "tx"}},
};

const RegTestingSetup *g_setup;
} // namespace

void initialize() {
static RegTestingSetup setup{};
g_setup = &setup;

for (int i = 0; i < 2 * COINBASE_MATURITY; i++) {
MineBlock(GetConfig(), g_setup->m_node, CScript() << OP_TRUE);
}
SyncWithValidationInterfaceQueue();
}

void test_one_input(const std::vector<uint8_t> &buffer) {
const Config &config = GetConfig();
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
const std::string random_message_type{
fuzzed_data_provider.ConsumeBytesAsString(CMessageHeader::COMMAND_SIZE)
.c_str()};
if (!LIMIT_TO_MESSAGE_TYPE.empty() &&
random_message_type != LIMIT_TO_MESSAGE_TYPE) {
return;
}
CDataStream random_bytes_data_stream{
fuzzed_data_provider.ConsumeRemainingBytes<uint8_t>(), SER_NETWORK,
PROTOCOL_VERSION};
CNode p2p_node{0,
ServiceFlags(NODE_NETWORK | NODE_BLOOM),
0,
INVALID_SOCKET,
CAddress{CService{in_addr{0x0100007f}, 7777}, NODE_NETWORK},
0,
0,
CAddress{},
std::string{},
false};
p2p_node.fSuccessfullyConnected = true;
p2p_node.nVersion = PROTOCOL_VERSION;
p2p_node.SetSendVersion(PROTOCOL_VERSION);
g_setup->m_node.peer_logic->InitializeNode(config, &p2p_node);
try {
(void)ProcessMessage(
config, &p2p_node, random_message_type, random_bytes_data_stream,
GetTimeMillis(), g_setup->m_node.connman.get(),
g_setup->m_node.banman.get(), std::atomic<bool>{false});
} catch (const std::ios_base::failure &e) {
const std::string exception_message{e.what()};
const auto p =
EXPECTED_DESERIALIZATION_EXCEPTIONS.find(exception_message);
if (p == EXPECTED_DESERIALIZATION_EXCEPTIONS.cend() ||
p->second.count(random_message_type) == 0) {
std::cout << "Unexpected exception when processing message type \""
<< random_message_type << "\": " << exception_message
<< std::endl;
assert(false);
}
}
SyncWithValidationInterfaceQueue();
}
5 changes: 4 additions & 1 deletion src/test/util/setup_common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <logging.h>
#include <miner.h>
#include <net.h>
#include <net_processing.h>
#include <noui.h>
#include <pow/pow.h>
#include <rpc/blockchain.h>
Expand Down Expand Up @@ -85,7 +86,7 @@ std::ostream &operator<<(std::ostream &os, const ScriptError &err) {

BasicTestingSetup::BasicTestingSetup(const std::string &chainName)
: m_path_root{fs::temp_directory_path() / "test_common_" PACKAGE_NAME /
std::to_string(g_insecure_rand_ctx_temp_path.rand32())} {
g_insecure_rand_ctx_temp_path.rand256().ToString()} {
SetMockTime(0);
fs::create_directories(m_path_root);
gArgs.ForceSetArg("-datadir", m_path_root.string());
Expand Down Expand Up @@ -176,6 +177,8 @@ TestingSetup::TestingSetup(const std::string &chainName)
nullptr, DEFAULT_MISBEHAVING_BANTIME);
// Deterministic randomness for tests.
m_node.connman = std::make_unique<CConnman>(config, 0x1337, 0x1337);
m_node.peer_logic = std::make_unique<PeerLogicValidation>(
m_node.connman.get(), m_node.banman.get(), *m_node.scheduler);
}

TestingSetup::~TestingSetup() {
Expand Down

0 comments on commit 946eb50

Please sign in to comment.