Merge bitcoin/bitcoin#28584: Fuzz: extend CConnman tests

0802398e74 fuzz: make it possible to mock (fuzz) CThreadInterrupt (Vasil Dimov)
6d9e5d130d fuzz: add CConnman::SocketHandler() to the tests (Vasil Dimov)
3265df63a4 fuzz: add CConnman::InitBinds() to the tests (Vasil Dimov)
91cbf4dbd8 fuzz: add CConnman::CreateNodeFromAcceptedSocket() to the tests (Vasil Dimov)
50da7432ec fuzz: add CConnman::OpenNetworkConnection() to the tests (Vasil Dimov)
e6a917c8f8 fuzz: add Fuzzed NetEventsInterface and use it in connman tests (Vasil Dimov)
e883b37768 fuzz: set the output argument of FuzzedSock::Accept() (Vasil Dimov)

Pull request description:

  Extend `CConnman` fuzz tests to also exercise the methods `OpenNetworkConnection()`, `CreateNodeFromAcceptedSocket()`, `InitBinds()` and `SocketHandler()`.

  Previously fuzzing those methods would have resulted in real socket functions being called in the operating system which is undesirable during fuzzing. Now that https://github.com/bitcoin/bitcoin/pull/21878 is complete all those are mocked to a fuzzed socket and a fuzzed DNS resolver (see how `CreateSock` and `g_dns_lookup` are replaced in the first commit).

ACKs for top commit:
  achow101:
    ACK 0802398e74
  jonatack:
    Review re-ACK 0802398e74
  dergoegge:
    Code review ACK 0802398e74

Tree-SHA512: a717d4e79f42bacf2b029c821fdc265e10e4e5c41af77cd4cb452cc5720ec83c62789d5b3dfafd39a22cc8c0500b18169aa7864d497dded729a32ab863dd6c4d
This commit is contained in:
Ava Chow
2025-09-30 15:59:09 -07:00
15 changed files with 303 additions and 65 deletions

View File

@@ -6,12 +6,14 @@
#include <chainparams.h>
#include <common/args.h>
#include <net.h>
#include <net_processing.h>
#include <netaddress.h>
#include <protocol.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
#include <test/fuzz/util/net.h>
#include <test/fuzz/util/threadinterrupt.h>
#include <test/util/setup_common.h>
#include <util/translation.h>
@@ -51,19 +53,36 @@ FUZZ_TARGET(connman, .init = initialize_connman)
}
}
AddrManDeterministic& addr_man{*addr_man_ptr};
auto net_events{ConsumeNetEvents(fuzzed_data_provider)};
// Mock CreateSock() to create FuzzedSock.
auto CreateSockOrig = CreateSock;
CreateSock = [&fuzzed_data_provider](int, int, int) {
return std::make_unique<FuzzedSock>(fuzzed_data_provider);
};
// Mock g_dns_lookup() to return a fuzzed address.
auto g_dns_lookup_orig = g_dns_lookup;
g_dns_lookup = [&fuzzed_data_provider](const std::string&, bool) {
return std::vector<CNetAddr>{ConsumeNetAddr(fuzzed_data_provider)};
};
ConnmanTestMsg connman{fuzzed_data_provider.ConsumeIntegral<uint64_t>(),
fuzzed_data_provider.ConsumeIntegral<uint64_t>(),
addr_man,
netgroupman,
Params(),
fuzzed_data_provider.ConsumeBool()};
fuzzed_data_provider.ConsumeBool(),
ConsumeThreadInterrupt(fuzzed_data_provider)};
const uint64_t max_outbound_limit{fuzzed_data_provider.ConsumeIntegral<uint64_t>()};
CConnman::Options options;
options.m_msgproc = &net_events;
options.nMaxOutboundLimit = max_outbound_limit;
connman.Init(options);
CNetAddr random_netaddr;
CAddress random_address;
CNode random_node = ConsumeNode(fuzzed_data_provider);
CSubNet random_subnet;
std::string random_string;
@@ -79,6 +98,9 @@ FUZZ_TARGET(connman, .init = initialize_connman)
[&] {
random_netaddr = ConsumeNetAddr(fuzzed_data_provider);
},
[&] {
random_address = ConsumeAddress(fuzzed_data_provider);
},
[&] {
random_subnet = ConsumeSubNet(fuzzed_data_provider);
},
@@ -143,6 +165,52 @@ FUZZ_TARGET(connman, .init = initialize_connman)
},
[&] {
connman.SetTryNewOutboundPeer(fuzzed_data_provider.ConsumeBool());
},
[&] {
ConnectionType conn_type{
fuzzed_data_provider.PickValueInArray(ALL_CONNECTION_TYPES)};
if (conn_type == ConnectionType::INBOUND) { // INBOUND is not allowed
conn_type = ConnectionType::OUTBOUND_FULL_RELAY;
}
connman.OpenNetworkConnection(
/*addrConnect=*/random_address,
/*fCountFailure=*/fuzzed_data_provider.ConsumeBool(),
/*grant_outbound=*/{},
/*strDest=*/fuzzed_data_provider.ConsumeBool() ? nullptr : random_string.c_str(),
/*conn_type=*/conn_type,
/*use_v2transport=*/fuzzed_data_provider.ConsumeBool());
},
[&] {
connman.SetNetworkActive(fuzzed_data_provider.ConsumeBool());
const auto peer = ConsumeAddress(fuzzed_data_provider);
connman.CreateNodeFromAcceptedSocketPublic(
/*sock=*/CreateSock(AF_INET, SOCK_STREAM, IPPROTO_TCP),
/*permissions=*/ConsumeWeakEnum(fuzzed_data_provider, ALL_NET_PERMISSION_FLAGS),
/*addr_bind=*/ConsumeAddress(fuzzed_data_provider),
/*addr_peer=*/peer);
},
[&] {
CConnman::Options options;
options.vBinds = ConsumeServiceVector(fuzzed_data_provider);
options.vWhiteBinds = std::vector<NetWhitebindPermissions>{
fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 5)};
for (auto& wb : options.vWhiteBinds) {
wb.m_flags = ConsumeWeakEnum(fuzzed_data_provider, ALL_NET_PERMISSION_FLAGS);
wb.m_service = ConsumeService(fuzzed_data_provider);
}
options.onion_binds = ConsumeServiceVector(fuzzed_data_provider);
options.bind_on_any = options.vBinds.empty() && options.vWhiteBinds.empty() &&
options.onion_binds.empty();
connman.InitBindsPublic(options);
},
[&] {
connman.SocketHandlerPublic();
});
}
(void)connman.GetAddedNodeInfo(fuzzed_data_provider.ConsumeBool());
@@ -162,4 +230,6 @@ FUZZ_TARGET(connman, .init = initialize_connman)
(void)connman.ASMapHealthCheck();
connman.ClearTestNodes();
g_dns_lookup = g_dns_lookup_orig;
CreateSock = CreateSockOrig;
}

View File

@@ -10,6 +10,7 @@
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
#include <test/fuzz/util/net.h>
#include <test/fuzz/util/threadinterrupt.h>
#include <test/util/setup_common.h>
#include <util/fs_helpers.h>
#include <util/threadinterrupt.h>
@@ -35,15 +36,15 @@ FUZZ_TARGET(i2p, .init = initialize_i2p)
const fs::path private_key_path = gArgs.GetDataDirNet() / "fuzzed_i2p_private_key";
const CService addr{in6_addr(IN6ADDR_LOOPBACK_INIT), 7656};
const Proxy sam_proxy{addr, /*tor_stream_isolation=*/false};
CThreadInterrupt interrupt;
auto interrupt{ConsumeThreadInterrupt(fuzzed_data_provider)};
i2p::sam::Session session{private_key_path, sam_proxy, &interrupt};
i2p::sam::Session session{private_key_path, sam_proxy, interrupt};
i2p::Connection conn;
if (session.Listen(conn)) {
if (session.Accept(conn)) {
try {
(void)conn.sock->RecvUntilTerminator('\n', 10ms, interrupt, i2p::sam::MAX_MSG_SIZE);
(void)conn.sock->RecvUntilTerminator('\n', 10ms, *interrupt, i2p::sam::MAX_MSG_SIZE);
} catch (const std::runtime_error&) {
}
}
@@ -53,7 +54,7 @@ FUZZ_TARGET(i2p, .init = initialize_i2p)
if (session.Connect(CService{}, conn, proxy_error)) {
try {
conn.sock->SendComplete("verack\n", 10ms, interrupt);
conn.sock->SendComplete("verack\n", 10ms, *interrupt);
} catch (const std::runtime_error&) {
}
}

View File

@@ -7,6 +7,7 @@ add_library(test_fuzz STATIC EXCLUDE_FROM_ALL
descriptor.cpp
mempool.cpp
net.cpp
threadinterrupt.cpp
../fuzz.cpp
../util.cpp
)

View File

@@ -312,6 +312,33 @@ std::unique_ptr<Sock> FuzzedSock::Accept(sockaddr* addr, socklen_t* addr_len) co
SetFuzzedErrNo(m_fuzzed_data_provider, accept_errnos);
return std::unique_ptr<FuzzedSock>();
}
if (addr != nullptr) {
// Set a fuzzed address in the output argument addr.
memset(addr, 0x00, *addr_len);
if (m_fuzzed_data_provider.ConsumeBool()) {
// IPv4
const socklen_t write_len = static_cast<socklen_t>(sizeof(sockaddr_in));
if (*addr_len >= write_len) {
*addr_len = write_len;
auto addr4 = reinterpret_cast<sockaddr_in*>(addr);
addr4->sin_family = AF_INET;
const auto sin_addr_bytes = m_fuzzed_data_provider.ConsumeBytes<uint8_t>(sizeof(addr4->sin_addr));
memcpy(&addr4->sin_addr, sin_addr_bytes.data(), sin_addr_bytes.size());
addr4->sin_port = m_fuzzed_data_provider.ConsumeIntegralInRange<uint16_t>(1, 65535);
}
} else {
// IPv6
const socklen_t write_len = static_cast<socklen_t>(sizeof(sockaddr_in6));
if (*addr_len >= write_len) {
*addr_len = write_len;
auto addr6 = reinterpret_cast<sockaddr_in6*>(addr);
addr6->sin6_family = AF_INET6;
const auto sin_addr_bytes = m_fuzzed_data_provider.ConsumeBytes<uint8_t>(sizeof(addr6->sin6_addr));
memcpy(&addr6->sin6_addr, sin_addr_bytes.data(), sin_addr_bytes.size());
addr6->sin6_port = m_fuzzed_data_provider.ConsumeIntegralInRange<uint16_t>(1, 65535);
}
}
}
return std::make_unique<FuzzedSock>(m_fuzzed_data_provider);
}

View File

@@ -139,6 +139,25 @@ public:
}
};
class FuzzedNetEvents : public NetEventsInterface
{
public:
FuzzedNetEvents(FuzzedDataProvider& fdp) : m_fdp(fdp) {}
virtual void InitializeNode(const CNode&, ServiceFlags) override {}
virtual void FinalizeNode(const CNode&) override {}
virtual bool HasAllDesirableServiceFlags(ServiceFlags) const override { return m_fdp.ConsumeBool(); }
virtual bool ProcessMessages(CNode*, std::atomic<bool>&) override { return m_fdp.ConsumeBool(); }
virtual bool SendMessages(CNode*) override { return m_fdp.ConsumeBool(); }
private:
FuzzedDataProvider& m_fdp;
};
class FuzzedSock : public Sock
{
FuzzedDataProvider& m_fuzzed_data_provider;
@@ -203,6 +222,11 @@ public:
bool IsConnected(std::string& errmsg) const override;
};
[[nodiscard]] inline FuzzedNetEvents ConsumeNetEvents(FuzzedDataProvider& fdp) noexcept
{
return FuzzedNetEvents{fdp};
}
[[nodiscard]] inline FuzzedSock ConsumeSock(FuzzedDataProvider& fuzzed_data_provider)
{
return FuzzedSock{fuzzed_data_provider};
@@ -225,6 +249,18 @@ inline CService ConsumeService(FuzzedDataProvider& fuzzed_data_provider) noexcep
return {ConsumeNetAddr(fuzzed_data_provider), fuzzed_data_provider.ConsumeIntegral<uint16_t>()};
}
inline std::vector<CService> ConsumeServiceVector(FuzzedDataProvider& fuzzed_data_provider,
size_t max_vector_size = 5) noexcept
{
std::vector<CService> ret;
const size_t size = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, max_vector_size);
ret.reserve(size);
for (size_t i = 0; i < size; ++i) {
ret.emplace_back(ConsumeService(fuzzed_data_provider));
}
return ret;
}
CAddress ConsumeAddress(FuzzedDataProvider& fuzzed_data_provider) noexcept;
template <bool ReturnUniquePtr = false>

View File

@@ -0,0 +1,22 @@
// Copyright (c) 2024-present 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 <test/fuzz/util.h>
#include <test/fuzz/util/threadinterrupt.h>
FuzzedThreadInterrupt::FuzzedThreadInterrupt(FuzzedDataProvider& fuzzed_data_provider)
: m_fuzzed_data_provider{fuzzed_data_provider}
{
}
bool FuzzedThreadInterrupt::interrupted() const
{
return m_fuzzed_data_provider.ConsumeBool();
}
bool FuzzedThreadInterrupt::sleep_for(Clock::duration)
{
SetMockTime(ConsumeTime(m_fuzzed_data_provider)); // Time could go backwards.
return m_fuzzed_data_provider.ConsumeBool();
}

View File

@@ -0,0 +1,33 @@
// Copyright (c) 2024-present The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_TEST_FUZZ_UTIL_THREADINTERRUPT_H
#define BITCOIN_TEST_FUZZ_UTIL_THREADINTERRUPT_H
#include <test/fuzz/FuzzedDataProvider.h>
#include <util/threadinterrupt.h>
#include <memory>
/**
* Mocked CThreadInterrupt that returns "randomly" whether it is interrupted and never sleeps.
*/
class FuzzedThreadInterrupt : public CThreadInterrupt
{
public:
explicit FuzzedThreadInterrupt(FuzzedDataProvider& fuzzed_data_provider);
virtual bool interrupted() const override;
virtual bool sleep_for(Clock::duration) override;
private:
FuzzedDataProvider& m_fuzzed_data_provider;
};
[[nodiscard]] inline std::shared_ptr<CThreadInterrupt> ConsumeThreadInterrupt(FuzzedDataProvider& fuzzed_data_provider)
{
return std::make_shared<FuzzedThreadInterrupt>(fuzzed_data_provider);
}
#endif // BITCOIN_TEST_FUZZ_UTIL_THREADINTERRUPT_H