diff --git a/src/ipc/CMakeLists.txt b/src/ipc/CMakeLists.txt index 8326423d8bc..e9bdf0b39b1 100644 --- a/src/ipc/CMakeLists.txt +++ b/src/ipc/CMakeLists.txt @@ -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) diff --git a/src/ipc/test/fuzz/CMakeLists.txt b/src/ipc/test/fuzz/CMakeLists.txt new file mode 100644 index 00000000000..7af1477a37b --- /dev/null +++ b/src/ipc/test/fuzz/CMakeLists.txt @@ -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) diff --git a/src/ipc/test/fuzz/ipc.cpp b/src/ipc/test/fuzz/ipc.cpp new file mode 100644 index 00000000000..76374c43bb9 --- /dev/null +++ b/src/ipc/test/fuzz/ipc.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace { +class IpcFuzzSetup +{ +public: + IpcFuzzSetup() + { + std::promise>> 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( + loop, + kj::mv(pipe.ends[0]), + [&](mp::Connection& connection) { + auto server_proxy = kj::heap>( + std::make_shared(), connection); + return capnp::Capability::Client(kj::mv(server_proxy)); + }); + server_connection->onDisconnect([&] { server_connection.reset(); }); + + auto client_connection = std::make_unique(loop, kj::mv(pipe.ends[1])); + auto client_proxy = std::make_unique>( + client_connection->m_rpc_system->bootstrap(mp::ServerVatId().vat_id) + .castAs(), + 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> 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(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(MIN_ADD, MAX_ADD); + const int b = fuzzed_data_provider.ConsumeIntegralInRange(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()}; + COutPoint expected{outpoint.hash, outpoint.n ^ 0xFFFFFFFFu}; + assert(ipc.m_client->passOutPoint(outpoint) == expected); + }, + [&] { + std::vector value = ConsumeRandomLengthByteVector(fuzzed_data_provider, 512); + // Empty Data currently trips UBSan in the libmultiprocess byte-span serializer. + if (value.empty()) value.push_back(0); + std::vector 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 diff --git a/src/ipc/test/fuzz/ipc_fuzz.capnp b/src/ipc/test/fuzz/ipc_fuzz.capnp new file mode 100644 index 00000000000..3e7ec8b3c4f --- /dev/null +++ b/src/ipc/test/fuzz/ipc_fuzz.capnp @@ -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); +} diff --git a/src/ipc/test/fuzz/ipc_fuzz.h b/src/ipc/test/fuzz/ipc_fuzz.h new file mode 100644 index 00000000000..b3c89908bda --- /dev/null +++ b/src/ipc/test/fuzz/ipc_fuzz.h @@ -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 +#include