fuzz: share a single mocked steady clock across FuzzedSock instances

Each FuzzedSock used to own its mocked steady clock and call
MockableSteadyClock::SetMockTime() directly. Hold the clock by reference
to an externally provided FakeSteadyClock instead, so that several
FuzzedSock instances sharing a test case (e.g. one per peer, or one
created via Accept()) advance a single mocked clock, and the mocking goes
through the FakeSteadyClock RAII helper that resets mocktime on
destruction.

FakeSteadyClock is a LimitOne type, so each fuzz target constructs one
instance per iteration and passes it to ConsumeSock / ConsumeNode / the
FuzzedSock constructor.
This commit is contained in:
Hao Xu
2026-06-15 19:35:25 +08:00
parent 6b58eb6d51
commit 6fa4132298
11 changed files with 45 additions and 40 deletions

View File

@@ -164,6 +164,7 @@ FUZZ_TARGET(cmpctblock, .init = initialize_cmpctblock)
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
FakeNodeClock clock{1610000000s};
FakeSteadyClock steady_clock;
auto setup = g_setup;
auto& mempool = *setup->m_node.mempool;
@@ -189,7 +190,7 @@ FUZZ_TARGET(cmpctblock, .init = initialize_cmpctblock)
std::vector<CNode*> peers;
for (int i = 0; i < 4; ++i) {
peers.push_back(ConsumeNodeAsUniquePtr(fuzzed_data_provider, i).release());
peers.push_back(ConsumeNodeAsUniquePtr(fuzzed_data_provider, steady_clock, i).release());
CNode& p2p_node = *peers.back();
FillNode(fuzzed_data_provider, connman, p2p_node);
connman.AddTestNode(p2p_node);

View File

@@ -42,6 +42,7 @@ FUZZ_TARGET(connman, .init = initialize_connman)
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)};
FakeSteadyClock steady_clock;
auto netgroupman{ConsumeNetGroupManager(fuzzed_data_provider)};
auto addr_man_ptr{std::make_unique<AddrManDeterministic>(netgroupman, fuzzed_data_provider, GetCheckRatio())};
if (fuzzed_data_provider.ConsumeBool()) {
@@ -58,8 +59,8 @@ FUZZ_TARGET(connman, .init = initialize_connman)
// Mock CreateSock() to create FuzzedSock.
auto CreateSockOrig = CreateSock;
CreateSock = [&fuzzed_data_provider](int, int, int) {
return std::make_unique<FuzzedSock>(fuzzed_data_provider);
CreateSock = [&fuzzed_data_provider, &steady_clock](int, int, int) {
return std::make_unique<FuzzedSock>(fuzzed_data_provider, steady_clock);
};
// Mock g_dns_lookup() to return a fuzzed address.
@@ -96,13 +97,13 @@ FUZZ_TARGET(connman, .init = initialize_connman)
CNetAddr random_netaddr;
CAddress random_address;
CNode random_node = ConsumeNode(fuzzed_data_provider);
CNode random_node = ConsumeNode(fuzzed_data_provider, steady_clock);
CSubNet random_subnet;
std::string random_string;
std::vector<NodeId> node_ids;
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100) {
CNode& p2p_node{*ConsumeNodeAsUniquePtr(fuzzed_data_provider).release()};
CNode& p2p_node{*ConsumeNodeAsUniquePtr(fuzzed_data_provider, steady_clock).release()};
// Simulate post-handshake state.
p2p_node.fSuccessfullyConnected = true;
connman.AddTestNode(p2p_node);

View File

@@ -28,11 +28,12 @@ FUZZ_TARGET(i2p, .init = initialize_i2p)
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)};
FakeSteadyClock steady_clock;
// Mock CreateSock() to create FuzzedSock.
auto CreateSockOrig = CreateSock;
CreateSock = [&fuzzed_data_provider](int, int, int) {
return std::make_unique<FuzzedSock>(fuzzed_data_provider);
CreateSock = [&fuzzed_data_provider, &steady_clock](int, int, int) {
return std::make_unique<FuzzedSock>(fuzzed_data_provider, steady_clock);
};
const fs::path private_key_path = gArgs.GetDataDirNet() / "fuzzed_i2p_private_key";

View File

@@ -33,7 +33,8 @@ FUZZ_TARGET(net, .init = initialize_net)
{
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)};
CNode node{ConsumeNode(fuzzed_data_provider)};
FakeSteadyClock steady_clock;
CNode node{ConsumeNode(fuzzed_data_provider, steady_clock)};
node.SetCommonVersion(fuzzed_data_provider.ConsumeIntegral<int>());
if (const auto service_opt =
ConsumeDeserializable<CService>(fuzzed_data_provider, ConsumeDeserializationParams<CNetAddr::SerParams>(fuzzed_data_provider)))
@@ -82,8 +83,9 @@ FUZZ_TARGET(local_address, .init = initialize_net)
{
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)};
FakeSteadyClock steady_clock;
CService service{ConsumeService(fuzzed_data_provider)};
CNode node{ConsumeNode(fuzzed_data_provider)};
CNode node{ConsumeNode(fuzzed_data_provider, steady_clock)};
{
LOCK(g_maplocalhost_mutex);
mapLocalHost.clear();

View File

@@ -42,6 +42,7 @@ FUZZ_TARGET(p2p_handshake, .init = ::initialize)
auto& connman{static_cast<ConnmanTestMsg&>(*node.connman)};
auto& chainman{static_cast<TestChainstateManager&>(*node.chainman)};
FakeNodeClock clock{1610000000s}; // any time to successfully reset ibd
FakeSteadyClock steady_clock;
chainman.ResetIbd();
node.banman.reset();
@@ -64,7 +65,7 @@ FUZZ_TARGET(p2p_handshake, .init = ::initialize)
std::vector<CNode*> peers;
const auto num_peers_to_add = fuzzed_data_provider.ConsumeIntegralInRange(1, 3);
for (int i = 0; i < num_peers_to_add; ++i) {
peers.push_back(ConsumeNodeAsUniquePtr(fuzzed_data_provider, i).release());
peers.push_back(ConsumeNodeAsUniquePtr(fuzzed_data_provider, steady_clock, i).release());
connman.AddTestNode(*peers.back());
node.peerman->InitializeNode(
*peers.back(),

View File

@@ -6,6 +6,7 @@
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
#include <test/fuzz/util/net.h>
#include <test/util/time.h>
#include <common/pcp.h>
#include <logging.h>
@@ -31,11 +32,12 @@ void port_map_target_init()
FUZZ_TARGET(pcp_request_port_map, .init = port_map_target_init)
{
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
FakeSteadyClock steady_clock;
// Create a mocked socket between random (and potentially invalid) client and gateway addresses.
CreateSock = [&](int domain, int type, int protocol) {
if ((domain == AF_INET || domain == AF_INET6) && type == SOCK_DGRAM && protocol == IPPROTO_UDP) {
return std::make_unique<FuzzedSock>(fuzzed_data_provider);
return std::make_unique<FuzzedSock>(fuzzed_data_provider, steady_clock);
}
return std::unique_ptr<FuzzedSock>();
};
@@ -59,11 +61,12 @@ FUZZ_TARGET(pcp_request_port_map, .init = port_map_target_init)
FUZZ_TARGET(natpmp_request_port_map, .init = port_map_target_init)
{
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
FakeSteadyClock steady_clock;
// Create a mocked socket between random (and potentially invalid) client and gateway addresses.
CreateSock = [&](int domain, int type, int protocol) {
if (domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP) {
return std::make_unique<FuzzedSock>(fuzzed_data_provider);
return std::make_unique<FuzzedSock>(fuzzed_data_provider, steady_clock);
}
return std::unique_ptr<FuzzedSock>();
};

View File

@@ -84,6 +84,7 @@ FUZZ_TARGET(process_message, .init = initialize_process_message)
auto& chainman{static_cast<TestChainstateManager&>(*node.chainman)};
const auto block_index_size{WITH_LOCK(chainman.GetMutex(), return chainman.BlockIndex().size())};
FakeNodeClock clock{1610000000s}; // any time to successfully reset ibd
FakeSteadyClock steady_clock;
chainman.ResetIbd();
chainman.DisableNextWrite();
@@ -111,7 +112,7 @@ FUZZ_TARGET(process_message, .init = initialize_process_message)
node.validation_signals->RegisterValidationInterface(node.peerman.get());
CNode& p2p_node = *ConsumeNodeAsUniquePtr(fuzzed_data_provider).release();
CNode& p2p_node = *ConsumeNodeAsUniquePtr(fuzzed_data_provider, steady_clock).release();
connman.AddTestNode(p2p_node);
FillNode(fuzzed_data_provider, connman, p2p_node);

View File

@@ -73,6 +73,7 @@ FUZZ_TARGET(process_messages, .init = initialize_process_messages)
auto& chainman{static_cast<TestChainstateManager&>(*node.chainman)};
const auto block_index_size{WITH_LOCK(chainman.GetMutex(), return chainman.BlockIndex().size())};
FakeNodeClock clock{1610000000s}; // any time to successfully reset ibd
FakeSteadyClock steady_clock;
chainman.ResetIbd();
chainman.DisableNextWrite();
@@ -98,7 +99,7 @@ FUZZ_TARGET(process_messages, .init = initialize_process_messages)
std::vector<CNode*> peers;
const auto num_peers_to_add = fuzzed_data_provider.ConsumeIntegralInRange(1, 3);
for (int i = 0; i < num_peers_to_add; ++i) {
peers.push_back(ConsumeNodeAsUniquePtr(fuzzed_data_provider, i).release());
peers.push_back(ConsumeNodeAsUniquePtr(fuzzed_data_provider, steady_clock, i).release());
CNode& p2p_node = *peers.back();
FillNode(fuzzed_data_provider, connman, p2p_node);

View File

@@ -32,6 +32,7 @@ FUZZ_TARGET(socks5, .init = initialize_socks5)
{
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)};
FakeSteadyClock steady_clock;
ProxyCredentials proxy_credentials;
proxy_credentials.username = fuzzed_data_provider.ConsumeRandomLengthString(512);
proxy_credentials.password = fuzzed_data_provider.ConsumeRandomLengthString(512);
@@ -41,7 +42,7 @@ FUZZ_TARGET(socks5, .init = initialize_socks5)
// Set FUZZED_SOCKET_FAKE_LATENCY=1 to exercise recv timeout code paths. This
// will slow down fuzzing.
g_socks5_recv_timeout = (fuzzed_data_provider.ConsumeBool() && std::getenv("FUZZED_SOCKET_FAKE_LATENCY") != nullptr) ? 1ms : default_socks5_recv_timeout;
FuzzedSock fuzzed_sock = ConsumeSock(fuzzed_data_provider);
FuzzedSock fuzzed_sock = ConsumeSock(fuzzed_data_provider, steady_clock);
// This Socks5(...) fuzzing harness would have caught CVE-2017-18350 within
// a few seconds of fuzzing.
auto str_dest = fuzzed_data_provider.ConsumeRandomLengthString(512);

View File

@@ -111,13 +111,12 @@ P ConsumeDeserializationParams(FuzzedDataProvider& fuzzed_data_provider) noexcep
template CNetAddr::SerParams ConsumeDeserializationParams(FuzzedDataProvider&) noexcept;
template CAddress::SerParams ConsumeDeserializationParams(FuzzedDataProvider&) noexcept;
FuzzedSock::FuzzedSock(FuzzedDataProvider& fuzzed_data_provider)
FuzzedSock::FuzzedSock(FuzzedDataProvider& fuzzed_data_provider, FakeSteadyClock& clock)
: Sock{fuzzed_data_provider.ConsumeIntegralInRange<SOCKET>(INVALID_SOCKET - 1, INVALID_SOCKET)},
m_fuzzed_data_provider{fuzzed_data_provider},
m_selectable{fuzzed_data_provider.ConsumeBool()},
m_time{MockableSteadyClock::INITIAL_MOCK_TIME}
m_clock{clock}
{
ElapseTime(std::chrono::seconds(0)); // start mocking the steady clock.
}
FuzzedSock::~FuzzedSock()
@@ -129,12 +128,6 @@ FuzzedSock::~FuzzedSock()
m_socket = INVALID_SOCKET;
}
void FuzzedSock::ElapseTime(std::chrono::milliseconds duration) const
{
m_time += duration;
MockableSteadyClock::SetMockTime(m_time);
}
FuzzedSock& FuzzedSock::operator=(Sock&& other)
{
assert(false && "Move of Sock into FuzzedSock not allowed.");
@@ -340,7 +333,7 @@ std::unique_ptr<Sock> FuzzedSock::Accept(sockaddr* addr, socklen_t* addr_len) co
}
}
}
return std::make_unique<FuzzedSock>(m_fuzzed_data_provider);
return std::make_unique<FuzzedSock>(m_fuzzed_data_provider, m_clock);
}
int FuzzedSock::GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const
@@ -428,7 +421,7 @@ bool FuzzedSock::Wait(std::chrono::milliseconds timeout, Event requested, Event*
// FuzzedDataProvider runs out of data.
*occurred = m_fuzzed_data_provider.ConsumeBool() ? 0 : requested;
}
ElapseTime(timeout);
m_clock += timeout;
return true;
}
@@ -441,7 +434,7 @@ bool FuzzedSock::WaitMany(std::chrono::milliseconds timeout, EventsPerSock& even
// FuzzedDataProvider runs out of data.
events.occurred = m_fuzzed_data_provider.ConsumeBool() ? 0 : events.requested;
}
ElapseTime(timeout);
m_clock += timeout;
return true;
}

View File

@@ -17,6 +17,7 @@
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/util.h>
#include <test/util/net.h>
#include <test/util/time.h>
#include <util/asmap.h>
#include <util/sock.h>
@@ -178,17 +179,16 @@ class FuzzedSock : public Sock
const bool m_selectable;
/**
* Used to mock the steady clock in methods waiting for a given duration.
* Externally-provided context used to mock the steady clock in methods
* waiting for a given duration. It is a reference (rather than an owned
* member) so that several FuzzedSock instances sharing a test case (e.g.
* one per peer, or one created from Accept()) advance a single mocked
* clock.
*/
mutable std::chrono::milliseconds m_time;
/**
* Set the value of the mocked steady clock such as that many ms have passed.
*/
void ElapseTime(std::chrono::milliseconds duration) const;
FakeSteadyClock& m_clock;
public:
explicit FuzzedSock(FuzzedDataProvider& fuzzed_data_provider);
explicit FuzzedSock(FuzzedDataProvider& fuzzed_data_provider, FakeSteadyClock& clock);
~FuzzedSock() override;
@@ -228,9 +228,9 @@ public:
return FuzzedNetEvents{fdp};
}
[[nodiscard]] inline FuzzedSock ConsumeSock(FuzzedDataProvider& fuzzed_data_provider)
[[nodiscard]] inline FuzzedSock ConsumeSock(FuzzedDataProvider& fuzzed_data_provider, FakeSteadyClock& clock)
{
return FuzzedSock{fuzzed_data_provider};
return FuzzedSock{fuzzed_data_provider, clock};
}
[[nodiscard]] inline NetGroupManager ConsumeNetGroupManager(FuzzedDataProvider& fuzzed_data_provider) noexcept
@@ -267,10 +267,10 @@ inline std::vector<CService> ConsumeServiceVector(FuzzedDataProvider& fuzzed_dat
CAddress ConsumeAddress(FuzzedDataProvider& fuzzed_data_provider) noexcept;
template <bool ReturnUniquePtr = false>
auto ConsumeNode(FuzzedDataProvider& fuzzed_data_provider, const std::optional<NodeId>& node_id_in = std::nullopt) noexcept
auto ConsumeNode(FuzzedDataProvider& fuzzed_data_provider, FakeSteadyClock& clock, const std::optional<NodeId>& node_id_in = std::nullopt) noexcept
{
const NodeId node_id = node_id_in.value_or(fuzzed_data_provider.ConsumeIntegralInRange<NodeId>(0, std::numeric_limits<NodeId>::max()));
const auto sock = std::make_shared<FuzzedSock>(fuzzed_data_provider);
const auto sock = std::make_shared<FuzzedSock>(fuzzed_data_provider, clock);
const CAddress address = ConsumeAddress(fuzzed_data_provider);
const uint64_t keyed_net_group = fuzzed_data_provider.ConsumeIntegral<uint64_t>();
const uint64_t local_host_nonce = fuzzed_data_provider.ConsumeIntegral<uint64_t>();
@@ -307,7 +307,7 @@ auto ConsumeNode(FuzzedDataProvider& fuzzed_data_provider, const std::optional<N
CNodeOptions{ .permission_flags = permission_flags }};
}
}
inline std::unique_ptr<CNode> ConsumeNodeAsUniquePtr(FuzzedDataProvider& fdp, const std::optional<NodeId>& node_id_in = std::nullopt) { return ConsumeNode<true>(fdp, node_id_in); }
inline std::unique_ptr<CNode> ConsumeNodeAsUniquePtr(FuzzedDataProvider& fdp, FakeSteadyClock& clock, const std::optional<NodeId>& node_id_in = std::nullopt) { return ConsumeNode<true>(fdp, clock, node_id_in); }
void FillNode(FuzzedDataProvider& fuzzed_data_provider, ConnmanTestMsg& connman, CNode& node) noexcept EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex);