fuzz: add IPC round-trip target

Add an ipc fuzz target behind ENABLE_IPC.

Set up an in-process two-way pipe and use a small reflected interface
to exercise libmultiprocess client/server calls.

Round-trip COutPoint, CScript, std::vector<uint8_t>, UniValue, and
transactions.

Add bitcoin_ipc_fuzz static library and link it to the fuzz target
via a new src/ipc/test/fuzz/CMakeLists.txt.
This commit is contained in:
Enoch Azariah
2026-06-17 15:01:39 +01:00
parent 8a739a5510
commit 037ad77071
9 changed files with 233 additions and 0 deletions

View File

@@ -47,4 +47,19 @@ if(BUILD_TESTS)
)
endif()
if (BUILD_FUZZ_BINARY)
add_library(bitcoin_ipc_fuzz STATIC EXCLUDE_FROM_ALL)
target_capnp_sources(bitcoin_ipc_fuzz ${CMAKE_CURRENT_SOURCE_DIR}
test/fuzz/ipc_fuzz.capnp
)
add_dependencies(bitcoin_ipc_fuzz bitcoin_ipc_headers)
target_link_libraries(bitcoin_ipc_fuzz
PRIVATE
core_interface
univalue
Boost::headers
)
endif()
configure_file(.clang-tidy.in .clang-tidy USE_SOURCE_PERMISSIONS COPYONLY)

View File

@@ -0,0 +1,6 @@
# Copyright (c) The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://opensource.org/license/mit/.
target_sources(fuzz PRIVATE ${PROJECT_SOURCE_DIR}/src/ipc/test/fuzz/ipc.cpp)
target_link_libraries(fuzz bitcoin_ipc_fuzz multiprocess)

137
src/ipc/test/fuzz/ipc.cpp Normal file
View File

@@ -0,0 +1,137 @@
// 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 <primitives/transaction.h>
#include <capnp/capability.h>
#include <capnp/rpc.h>
#include <kj/memory.h>
#include <mp/proxy-io.h>
#include <mp/proxy.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <ipc/test/fuzz/ipc_fuzz.capnp.h>
#include <ipc/test/fuzz/ipc_fuzz.capnp.proxy.h>
#include <ipc/test/fuzz/ipc_fuzz.h>
#include <test/fuzz/util.h>
#include <test/util/setup_common.h>
#include <future>
#include <memory>
#include <stdexcept>
#include <thread>
namespace {
class IpcFuzzSetup
{
public:
IpcFuzzSetup()
{
std::promise<std::unique_ptr<mp::ProxyClient<test::fuzz::messages::IpcFuzzInterface>>> client_promise;
auto client_future{client_promise.get_future()};
m_loop_thread = std::thread([&client_promise] {
mp::EventLoop loop("ipc-fuzz", [](mp::LogMessage message) {
if (message.level == mp::Log::Raise) throw std::runtime_error(message.message);
});
auto pipe = loop.m_io_context.provider->newTwoWayPipe();
auto server_connection = std::make_unique<mp::Connection>(
loop,
kj::mv(pipe.ends[0]),
[&](mp::Connection& connection) {
auto server_proxy = kj::heap<mp::ProxyServer<test::fuzz::messages::IpcFuzzInterface>>(
std::make_shared<IpcFuzzImplementation>(), connection);
return capnp::Capability::Client(kj::mv(server_proxy));
});
server_connection->onDisconnect([&] { server_connection.reset(); });
auto client_connection = std::make_unique<mp::Connection>(loop, kj::mv(pipe.ends[1]));
auto client_proxy = std::make_unique<mp::ProxyClient<test::fuzz::messages::IpcFuzzInterface>>(
client_connection->m_rpc_system->bootstrap(mp::ServerVatId().vat_id)
.castAs<test::fuzz::messages::IpcFuzzInterface>(),
client_connection.get(),
/* destroy_connection= */ true);
(void)client_connection.release();
client_promise.set_value(std::move(client_proxy));
loop.loop();
});
m_client = client_future.get();
}
~IpcFuzzSetup()
{
m_client.reset();
if (m_loop_thread.joinable()) m_loop_thread.join();
}
std::unique_ptr<mp::ProxyClient<test::fuzz::messages::IpcFuzzInterface>> m_client;
private:
std::thread m_loop_thread;
};
static IpcFuzzSetup* g_ipc;
static void initialize_ipc()
{
static const auto testing_setup = MakeNoLogFileContext<>();
(void)testing_setup;
// Ensure g_thread_context is destroyed after the IPC setup, since C++
// destroys thread_local objects in reverse construction order.
mp::ThreadContext& thread_context{mp::g_thread_context};
(void)thread_context;
thread_local static IpcFuzzSetup ipc; // NOLINT(bitcoin-nontrivial-threadlocal)
g_ipc = &ipc;
}
FUZZ_TARGET(ipc, .init = initialize_ipc)
{
auto& ipc = *g_ipc;
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
const size_t iterations = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 64);
for (size_t i = 0; i < iterations; ++i) {
CallOneOf(
fuzzed_data_provider,
[&] {
static constexpr int MIN_ADD{-1'000'000};
static constexpr int MAX_ADD{1'000'000};
const int a = fuzzed_data_provider.ConsumeIntegralInRange<int>(MIN_ADD, MAX_ADD);
const int b = fuzzed_data_provider.ConsumeIntegralInRange<int>(MIN_ADD, MAX_ADD);
assert(ipc.m_client->add(a, b) == a + b);
},
[&] {
COutPoint outpoint{Txid::FromUint256(ConsumeUInt256(fuzzed_data_provider)),
fuzzed_data_provider.ConsumeIntegral<uint32_t>()};
COutPoint expected{outpoint.hash, outpoint.n ^ 0xFFFFFFFFu};
assert(ipc.m_client->passOutPoint(outpoint) == expected);
},
[&] {
std::vector<uint8_t> value = ConsumeRandomLengthByteVector<uint8_t>(fuzzed_data_provider, 512);
// Empty Data currently trips UBSan in the libmultiprocess byte-span serializer.
if (value.empty()) value.push_back(0);
std::vector<uint8_t> expected{value.rbegin(), value.rend()};
assert(ipc.m_client->passVectorUint8(value) == expected);
},
[&] {
CScript script{ConsumeScript(fuzzed_data_provider)};
CScript expected{script};
expected << OP_NOP;
assert(ipc.m_client->passScript(script) == expected);
},
[&] {
UniValue value = ConsumeUniValue(fuzzed_data_provider);
assert(ipc.m_client->passUniValue(value).write() == value.write());
},
[&] {
const CMutableTransaction mutable_tx = ConsumeTransaction(fuzzed_data_provider, std::nullopt);
if (mutable_tx.vin.empty()) return;
const CTransactionRef tx = MakeTransactionRef(mutable_tx);
assert(*ipc.m_client->passTransaction(tx) == *tx);
});
}
}
} // namespace

View File

@@ -0,0 +1,21 @@
# 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.
@0xf918ff05f5bf04d1;
using Cxx = import "/capnp/c++.capnp";
$Cxx.namespace("test::fuzz::messages");
using Proxy = import "/mp/proxy.capnp";
$Proxy.include("ipc/test/fuzz/ipc_fuzz.h");
$Proxy.includeTypes("ipc/test/fuzz/ipc_fuzz_types.h");
interface IpcFuzzInterface $Proxy.wrap("IpcFuzzImplementation") {
add @0 (a :Int32, b :Int32) -> (result :Int32);
passOutPoint @1 (arg :Data) -> (result :Data);
passVectorUint8 @2 (arg :Data) -> (result :Data);
passScript @3 (arg :Data) -> (result :Data);
passUniValue @4 (arg :Text) -> (result :Text);
passTransaction @5 (arg :Data) -> (result :Data);
}

View File

@@ -0,0 +1,26 @@
// 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_IPC_TEST_FUZZ_IPC_FUZZ_H
#define BITCOIN_IPC_TEST_FUZZ_IPC_FUZZ_H
#include <primitives/transaction.h>
#include <script/script.h>
#include <univalue.h>
#include <algorithm>
#include <vector>
class IpcFuzzImplementation
{
public:
int add(int a, int b) { return a + b; }
COutPoint passOutPoint(COutPoint o) { return COutPoint{o.hash, o.n ^ 0xFFFFFFFFu}; }
std::vector<uint8_t> passVectorUint8(std::vector<uint8_t> v) { std::reverse(v.begin(), v.end()); return v; }
CScript passScript(CScript s) { s << OP_NOP; return s; }
UniValue passUniValue(UniValue v) { return v; }
CTransactionRef passTransaction(CTransactionRef t) { return t; }
};
#endif // BITCOIN_IPC_TEST_FUZZ_IPC_FUZZ_H

View File

@@ -0,0 +1,11 @@
// 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_IPC_TEST_FUZZ_IPC_FUZZ_TYPES_H
#define BITCOIN_IPC_TEST_FUZZ_IPC_FUZZ_TYPES_H
#include <ipc/capnp/common-types.h>
#include <ipc/test/fuzz/ipc_fuzz.capnp.h>
#endif // BITCOIN_IPC_TEST_FUZZ_IPC_FUZZ_TYPES_H

View File

@@ -138,6 +138,10 @@ add_executable(fuzz
versionbits.cpp
)
if(ENABLE_IPC)
add_subdirectory(${PROJECT_SOURCE_DIR}/src/ipc/test/fuzz ipc)
endif()
add_windows_application_manifest(fuzz)
target_link_libraries(fuzz

View File

@@ -237,6 +237,16 @@ CKey ConsumePrivateKey(FuzzedDataProvider& fuzzed_data_provider, std::optional<b
return key;
}
UniValue ConsumeUniValue(FuzzedDataProvider& fuzzed_data_provider) noexcept
{
UniValue value{UniValue::VOBJ};
value.pushKV("bool", fuzzed_data_provider.ConsumeBool());
value.pushKV("number", fuzzed_data_provider.ConsumeIntegralInRange<int>(-1'000'000, 1'000'000));
value.pushKV("string", "ipc fuzz");
return value;
}
bool ContainsSpentInput(const CTransaction& tx, const CCoinsViewCache& inputs) noexcept
{
for (const CTxIn& tx_in : tx.vin) {

View File

@@ -5,6 +5,7 @@
#ifndef BITCOIN_TEST_FUZZ_UTIL_H
#define BITCOIN_TEST_FUZZ_UTIL_H
#include <univalue.h>
#include <addresstype.h>
#include <arith_uint256.h>
#include <coins.h>
@@ -209,6 +210,8 @@ template <class Dur>
[[nodiscard]] CKey ConsumePrivateKey(FuzzedDataProvider& fuzzed_data_provider, std::optional<bool> compressed = std::nullopt) noexcept;
[[nodiscard]] UniValue ConsumeUniValue(FuzzedDataProvider& fuzzed_data_provider) noexcept;
template <typename T>
[[nodiscard]] bool MultiplicationOverflow(const T i, const T j) noexcept
{