mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-09-11 21:20:39 +02:00
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:
@@ -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)
|
||||
|
||||
6
src/ipc/test/fuzz/CMakeLists.txt
Normal file
6
src/ipc/test/fuzz/CMakeLists.txt
Normal 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
137
src/ipc/test/fuzz/ipc.cpp
Normal 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
|
||||
21
src/ipc/test/fuzz/ipc_fuzz.capnp
Normal file
21
src/ipc/test/fuzz/ipc_fuzz.capnp
Normal 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);
|
||||
}
|
||||
26
src/ipc/test/fuzz/ipc_fuzz.h
Normal file
26
src/ipc/test/fuzz/ipc_fuzz.h
Normal 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
|
||||
11
src/ipc/test/fuzz/ipc_fuzz_types.h
Normal file
11
src/ipc/test/fuzz/ipc_fuzz_types.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user