Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/test/fuzz/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ target_sources(fuzz
PRIVATE
fuzz.cpp
check_globals.cpp
sv2_messages.cpp
sv2_noise.cpp
../sv2_test_setup.cpp
)
Expand Down
2 changes: 2 additions & 0 deletions src/test/fuzz/fuzz.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <test/fuzz/fuzz.h>

#include <test/fuzz/check_globals.h>
#include <netaddress.h>
#include <netbase.h>
#include <test/util/coverage.h>
Expand Down Expand Up @@ -93,6 +94,7 @@ static const TypeTestOneInput* g_test_one_input{nullptr};

static void test_one_input(FuzzBufferType buffer)
{
CheckGlobals check{};
(*Assert(g_test_one_input))(buffer);
}

Expand Down
31 changes: 31 additions & 0 deletions src/test/fuzz/sv2_fuzz_util.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) 2026-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_SV2_FUZZ_UTIL_H
#define BITCOIN_TEST_FUZZ_SV2_FUZZ_UTIL_H

#include <test/fuzz/FuzzedDataProvider.h>
#include <test/sv2_test_setup.h>
#include <uint256.h>

#include <cstdint>
#include <memory>
#include <vector>

/** Shared one-time initialization for SV2 fuzz targets. */
inline void Sv2FuzzInitialize()
{
static const auto testing_setup = std::make_unique<const Sv2BasicTestingSetup>();
}

/** Consume 32 bytes from the fuzzer to produce a uint256.
* Returns a zero uint256 if fewer than 32 bytes remain. */
[[nodiscard]] inline uint256 ConsumeUint256(FuzzedDataProvider& provider) noexcept
{
auto v = provider.ConsumeBytes<uint8_t>(32);
if (v.size() != 32) return {};
return uint256{std::span<const unsigned char>(v)};
}

#endif // BITCOIN_TEST_FUZZ_SV2_FUZZ_UTIL_H
117 changes: 117 additions & 0 deletions src/test/fuzz/sv2_messages.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright (c) 2026-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 <sv2/messages.h>
#include <streams.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/sv2_fuzz_util.h>

#include <cstdint>
#include <vector>

using node::Sv2MsgType;
using node::Sv2NetHeader;
using node::Sv2NetMsg;
using node::Sv2SetupConnectionMsg;
using node::Sv2CoinbaseOutputConstraintsMsg;
using node::Sv2RequestTransactionDataMsg;
using node::Sv2SubmitSolutionMsg;

// Feed arbitrary bytes into a deserializer.
// Sanitizers (ASan/UBSan/MSan) catch memory errors along the way.
template <typename T>
static void FuzzDeserialize(FuzzBufferType buffer)
{
DataStream ds{buffer};
try {
T msg;
ds >> msg;
} catch (const std::ios_base::failure&) {
}
}

// Deserialize, then verify the roundtrip invariant: serialize the
// result and deserialize again. Only for types with both Serialize
// and Unserialize.
template <typename T>
static void FuzzDeserializeRoundtrip(FuzzBufferType buffer)
{
DataStream ds{buffer};
try {
T msg;
ds >> msg;

DataStream rt{};
rt << msg;
T msg2;
rt >> msg2;
} catch (const std::ios_base::failure&) {
}
}

// Client -> TP messages: these arrive over the network from untrusted
// peers and are the primary deserialization attack surface.
// These types have Unserialize only (no Serialize).

FUZZ_TARGET(sv2_setup_connection_raw, .init = Sv2FuzzInitialize)
{
FuzzDeserialize<Sv2SetupConnectionMsg>(buffer);
}

FUZZ_TARGET(sv2_request_transaction_data_raw, .init = Sv2FuzzInitialize)
{
FuzzDeserialize<Sv2RequestTransactionDataMsg>(buffer);
}

FUZZ_TARGET(sv2_submit_solution_raw, .init = Sv2FuzzInitialize)
{
FuzzDeserialize<Sv2SubmitSolutionMsg>(buffer);
}

// CoinbaseOutputConstraints has both Serialize and Unserialize,
// and uses catch(...) for the optional sigops field -- roundtrip it.
FUZZ_TARGET(sv2_coinbase_output_constraints_raw, .init = Sv2FuzzInitialize)
{
FuzzDeserializeRoundtrip<Sv2CoinbaseOutputConstraintsMsg>(buffer);
}

// Sv2NetHeader uses a 24-bit little-endian length encoding and ignores
// a 2-byte extension type prefix -- unusual parsing worth fuzzing.
FUZZ_TARGET(sv2_net_header_raw, .init = Sv2FuzzInitialize)
{
DataStream ds{buffer};
try {
Sv2NetHeader hdr;
ds >> hdr;

// Roundtrip
DataStream rt{};
rt << hdr;
Sv2NetHeader hdr2;
rt >> hdr2;
assert(hdr.m_msg_type == hdr2.m_msg_type);
assert(hdr.m_msg_len == hdr2.m_msg_len);
} catch (const std::ios_base::failure&) {
}
}

// Sv2NetMsg::Unserialize calls m_msg.resize(s.size()) which allocates
// based on remaining stream size -- test with arbitrary input lengths.
FUZZ_TARGET(sv2_net_msg_raw, .init = Sv2FuzzInitialize)
{
DataStream ds{buffer};
try {
Sv2NetMsg msg(Sv2MsgType::SETUP_CONNECTION, {});
ds >> msg;

// Roundtrip
DataStream rt{};
rt << msg;
Sv2NetMsg msg2(Sv2MsgType::SETUP_CONNECTION, {});
rt >> msg2;
assert(msg.m_msg_type == msg2.m_msg_type);
assert(msg.m_msg == msg2.m_msg);
} catch (const std::ios_base::failure&) {
}
}
54 changes: 4 additions & 50 deletions src/test/fuzz/sv2_noise.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,59 +7,14 @@
#include <random.h>
#include <span.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/check_globals.h>
#include <test/fuzz/fuzz.h>
#include <test/sv2_test_setup.h>
#include <functional>
#include <string_view>
#include <cstddef>
#include <test/fuzz/sv2_fuzz_util.h>

#include <cstdlib>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <util/vector.h>

// Exposed by the fuzz harness to pass through double-dash arguments.
extern const std::function<std::vector<const char*>()> G_TEST_COMMAND_LINE_ARGUMENTS;

namespace {

void Initialize()
{
// Add test context for debugging. Usage:
// --debug=sv2 --loglevel=sv2:trace
static const auto testing_setup = std::make_unique<const Sv2BasicTestingSetup>();

// Optional: enable console logging when requested via double-dash args.
// Recognized flags: --printtoconsole=1, --debug=sv2, --loglevel=sv2:trace
// These flags are passed through the fuzz harness and exposed via G_TEST_COMMAND_LINE_ARGUMENTS.
bool want_console{false};
bool want_sv2_debug{false};
bool want_sv2_trace{false};
if (G_TEST_COMMAND_LINE_ARGUMENTS) {
for (const char* arg : G_TEST_COMMAND_LINE_ARGUMENTS()) {
if (!arg) continue;
std::string_view s{arg};
// Accept both forms in case a caller wants to force console logging explicitly.
if (s == "--printtoconsole" || s == "--printtoconsole=1") want_console = true;
if (s == "--debug=sv2" || s == "--debug=1" || s == "--debug=all") want_sv2_debug = true;
if (s == "--loglevel=sv2:trace" || s == "--loglevel=trace") want_sv2_trace = true;
}
}
if (want_console || std::getenv("SV2_FUZZ_LOG")) {
// Turn on console logging and ensure SV2 category is enabled at the desired level.
LogInstance().m_print_to_console = true;
LogInstance().EnableCategory(BCLog::SV2);
if (want_sv2_trace) {
LogInstance().SetCategoryLogLevel({{BCLog::SV2, BCLog::Level::Trace}});
} else if (want_sv2_debug || std::getenv("SV2_FUZZ_LOG_DEBUG")) {
LogInstance().SetCategoryLogLevel({{BCLog::SV2, BCLog::Level::Debug}});
}
// Start logging to flush any buffered messages.
LogInstance().StartLogging();
}
}
} // namespace

bool MaybeDamage(FuzzedDataProvider& provider, std::vector<std::byte>& transport)
{
if (transport.size() == 0) return false;
Expand All @@ -77,9 +32,8 @@ bool MaybeDamage(FuzzedDataProvider& provider, std::vector<std::byte>& transport
return damage;
}

FUZZ_TARGET(sv2_noise_cipher_roundtrip, .init = Initialize)
FUZZ_TARGET(sv2_noise_cipher_roundtrip, .init = Sv2FuzzInitialize)
{
const CheckGlobals check_globals{};
SeedRandomStateForTest(SeedRand::ZEROS);
// Test that Sv2Noise's encryption and decryption agree.

Expand Down
Loading