ipc.cpp raw
1 // Copyright (c) 2026-present The Bitcoin Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5 #include <primitives/transaction.h>
6 #include <capnp/capability.h>
7 #include <capnp/rpc.h>
8 #include <kj/memory.h>
9 #include <mp/proxy-io.h>
10 #include <mp/proxy.h>
11 #include <test/fuzz/FuzzedDataProvider.h>
12 #include <test/fuzz/fuzz.h>
13 #include <ipc/test/fuzz/ipc_fuzz.capnp.h>
14 #include <ipc/test/fuzz/ipc_fuzz.capnp.proxy.h>
15 #include <ipc/test/fuzz/ipc_fuzz.h>
16 #include <test/fuzz/util.h>
17 #include <test/util/setup_common.h>
18
19 #include <future>
20 #include <memory>
21 #include <stdexcept>
22 #include <thread>
23
24 namespace {
25 class IpcFuzzSetup
26 {
27 public:
28 IpcFuzzSetup()
29 {
30 std::promise<std::unique_ptr<mp::ProxyClient<test::fuzz::messages::IpcFuzzInterface>>> client_promise;
31 auto client_future{client_promise.get_future()};
32 m_loop_thread = std::thread([&client_promise] {
33 mp::EventLoop loop("ipc-fuzz", [](mp::LogMessage message) {
34 if (message.level == mp::Log::Raise) throw std::runtime_error(message.message);
35 });
36 auto pipe = loop.m_io_context.provider->newTwoWayPipe();
37
38 auto server_connection = std::make_unique<mp::Connection>(
39 loop,
40 kj::mv(pipe.ends[0]),
41 [&](mp::Connection& connection) {
42 auto server_proxy = kj::heap<mp::ProxyServer<test::fuzz::messages::IpcFuzzInterface>>(
43 std::make_shared<IpcFuzzImplementation>(), connection);
44 return capnp::Capability::Client(kj::mv(server_proxy));
45 });
46 server_connection->onDisconnect([&] { server_connection.reset(); });
47
48 auto client_connection = std::make_unique<mp::Connection>(loop, kj::mv(pipe.ends[1]));
49 auto client_proxy = std::make_unique<mp::ProxyClient<test::fuzz::messages::IpcFuzzInterface>>(
50 client_connection->m_rpc_system->bootstrap(mp::ServerVatId().vat_id)
51 .castAs<test::fuzz::messages::IpcFuzzInterface>(),
52 client_connection.get(),
53 /* destroy_connection= */ true);
54 (void)client_connection.release();
55
56 client_promise.set_value(std::move(client_proxy));
57 loop.loop();
58 });
59 m_client = client_future.get();
60 }
61
62 ~IpcFuzzSetup()
63 {
64 m_client.reset();
65 if (m_loop_thread.joinable()) m_loop_thread.join();
66 }
67
68 std::unique_ptr<mp::ProxyClient<test::fuzz::messages::IpcFuzzInterface>> m_client;
69
70 private:
71 std::thread m_loop_thread;
72 };
73
74 static IpcFuzzSetup* g_ipc;
75
76 static void initialize_ipc()
77 {
78 static const auto testing_setup = MakeNoLogFileContext<>();
79 (void)testing_setup;
80
81 // Ensure g_thread_context is destroyed after the IPC setup, since C++
82 // destroys thread_local objects in reverse construction order.
83 mp::ThreadContext& thread_context{mp::g_thread_context};
84 (void)thread_context;
85
86 thread_local static IpcFuzzSetup ipc; // NOLINT(bitcoin-nontrivial-threadlocal)
87 g_ipc = &ipc;
88 }
89
90 FUZZ_TARGET(ipc, .init = initialize_ipc)
91 {
92 auto& ipc = *g_ipc;
93 FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
94 const size_t iterations = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 64);
95
96 for (size_t i = 0; i < iterations; ++i) {
97 CallOneOf(
98 fuzzed_data_provider,
99 [&] {
100 static constexpr int MIN_ADD{-1'000'000};
101 static constexpr int MAX_ADD{1'000'000};
102 const int a = fuzzed_data_provider.ConsumeIntegralInRange<int>(MIN_ADD, MAX_ADD);
103 const int b = fuzzed_data_provider.ConsumeIntegralInRange<int>(MIN_ADD, MAX_ADD);
104 assert(ipc.m_client->add(a, b) == a + b);
105 },
106 [&] {
107 COutPoint outpoint{Txid::FromUint256(ConsumeUInt256(fuzzed_data_provider)),
108 fuzzed_data_provider.ConsumeIntegral<uint32_t>()};
109 COutPoint expected{outpoint.hash, outpoint.n ^ 0xFFFFFFFFu};
110 assert(ipc.m_client->passOutPoint(outpoint) == expected);
111 },
112 [&] {
113 std::vector<uint8_t> value = ConsumeRandomLengthByteVector<uint8_t>(fuzzed_data_provider, 512);
114 // Empty Data currently trips UBSan in the libmultiprocess byte-span serializer.
115 if (value.empty()) value.push_back(0);
116 std::vector<uint8_t> expected{value.rbegin(), value.rend()};
117 assert(ipc.m_client->passVectorUint8(value) == expected);
118 },
119 [&] {
120 CScript script{ConsumeScript(fuzzed_data_provider)};
121 CScript expected{script};
122 expected << OP_NOP;
123 assert(ipc.m_client->passScript(script) == expected);
124 },
125 [&] {
126 UniValue value;
127 if (!value.read(fuzzed_data_provider.ConsumeRandomLengthString(512))) return;
128 assert(ipc.m_client->passUniValue(value).write() == value.write());
129 },
130 [&] {
131 const CMutableTransaction mutable_tx = ConsumeTransaction(fuzzed_data_provider, std::nullopt);
132 if (mutable_tx.vin.empty()) return;
133 const CTransactionRef tx = MakeTransactionRef(mutable_tx);
134 assert(*ipc.m_client->passTransaction(tx) == *tx);
135 });
136 }
137 }
138 } // namespace
139