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