p2p_transport_serialization.cpp raw

   1  // Copyright (c) 2019-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 <chainparams.h>
   6  #include <hash.h>
   7  #include <net.h>
   8  #include <netmessagemaker.h>
   9  #include <protocol.h>
  10  #include <test/fuzz/FuzzedDataProvider.h>
  11  #include <test/fuzz/fuzz.h>
  12  #include <test/fuzz/util.h>
  13  #include <util/chaintype.h>
  14  
  15  #include <algorithm>
  16  #include <cassert>
  17  #include <cstdint>
  18  #include <limits>
  19  #include <optional>
  20  #include <vector>
  21  
  22  namespace {
  23  
  24  auto g_all_messages = ALL_NET_MESSAGE_TYPES;
  25  
  26  void initialize_p2p_transport_serialization()
  27  {
  28      static ECC_Context ecc_context{};
  29      SelectParams(ChainType::REGTEST);
  30      std::sort(g_all_messages.begin(), g_all_messages.end());
  31  }
  32  
  33  } // namespace
  34  
  35  FUZZ_TARGET(p2p_transport_serialization, .init = initialize_p2p_transport_serialization)
  36  {
  37      // Construct transports for both sides, with dummy NodeIds.
  38      V1Transport recv_transport{NodeId{0}};
  39      V1Transport send_transport{NodeId{1}};
  40  
  41      FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
  42  
  43      auto checksum_assist = fuzzed_data_provider.ConsumeBool();
  44      auto magic_bytes_assist = fuzzed_data_provider.ConsumeBool();
  45      std::vector<uint8_t> mutable_msg_bytes;
  46  
  47      auto header_bytes_remaining = CMessageHeader::HEADER_SIZE;
  48      if (magic_bytes_assist) {
  49          auto msg_start = Params().MessageStart();
  50          for (size_t i = 0; i < CMessageHeader::MESSAGE_SIZE_SIZE; ++i) {
  51              mutable_msg_bytes.push_back(msg_start[i]);
  52          }
  53          header_bytes_remaining -= CMessageHeader::MESSAGE_SIZE_SIZE;
  54      }
  55  
  56      if (checksum_assist) {
  57          header_bytes_remaining -= CMessageHeader::CHECKSUM_SIZE;
  58      }
  59  
  60      auto header_random_bytes = fuzzed_data_provider.ConsumeBytes<uint8_t>(header_bytes_remaining);
  61      mutable_msg_bytes.insert(mutable_msg_bytes.end(), header_random_bytes.begin(), header_random_bytes.end());
  62      auto payload_bytes = fuzzed_data_provider.ConsumeRemainingBytes<uint8_t>();
  63  
  64      if (checksum_assist && mutable_msg_bytes.size() == CMessageHeader::CHECKSUM_OFFSET) {
  65          CHash256 hasher;
  66          unsigned char hsh[32];
  67          hasher.Write(payload_bytes);
  68          hasher.Finalize(hsh);
  69          for (size_t i = 0; i < CMessageHeader::CHECKSUM_SIZE; ++i) {
  70             mutable_msg_bytes.push_back(hsh[i]);
  71          }
  72      }
  73  
  74      mutable_msg_bytes.insert(mutable_msg_bytes.end(), payload_bytes.begin(), payload_bytes.end());
  75      std::span<const uint8_t> msg_bytes{mutable_msg_bytes};
  76      while (msg_bytes.size() > 0) {
  77          if (!recv_transport.ReceivedBytes(msg_bytes)) {
  78              break;
  79          }
  80          if (recv_transport.ReceivedMessageComplete()) {
  81              const auto time{NodeClock::time_point::max()};
  82              bool reject_message{false};
  83              CNetMessage msg = recv_transport.GetReceivedMessage(time, reject_message);
  84              assert(msg.m_type.size() <= CMessageHeader::MESSAGE_TYPE_SIZE);
  85              assert(msg.m_raw_message_size <= mutable_msg_bytes.size());
  86              assert(msg.m_raw_message_size == CMessageHeader::HEADER_SIZE + msg.m_message_size);
  87              assert(msg.m_time == time);
  88  
  89              std::vector<unsigned char> header;
  90              auto msg2 = NetMsg::Make(msg.m_type, std::span{msg.m_recv});
  91              bool queued = send_transport.SetMessageToSend(msg2);
  92              assert(queued);
  93              std::optional<bool> known_more;
  94              while (true) {
  95                  const auto& [to_send, more, _msg_type] = send_transport.GetBytesToSend(false);
  96                  if (known_more) assert(!to_send.empty() == *known_more);
  97                  if (to_send.empty()) break;
  98                  send_transport.MarkBytesSent(to_send.size());
  99                  known_more = more;
 100              }
 101          }
 102      }
 103  }
 104  
 105  namespace {
 106  
 107  template<RandomNumberGenerator R>
 108  void SimulationTest(Transport& initiator, Transport& responder, R& rng, FuzzedDataProvider& provider)
 109  {
 110      // Simulation test with two Transport objects, which send messages to each other, with
 111      // sending and receiving fragmented into multiple pieces that may be interleaved. It primarily
 112      // verifies that the sending and receiving side are compatible with each other, plus a few
 113      // sanity checks. It does not attempt to introduce errors in the communicated data.
 114  
 115      // Put the transports in an array for by-index access.
 116      const std::array<Transport*, 2> transports = {&initiator, &responder};
 117  
 118      // Two vectors representing in-flight bytes. inflight[i] is from transport[i] to transport[!i].
 119      std::array<std::vector<uint8_t>, 2> in_flight;
 120  
 121      // Two queues with expected messages. expected[i] is expected to arrive in transport[!i].
 122      std::array<std::deque<CSerializedNetMsg>, 2> expected;
 123  
 124      // Vectors with bytes last returned by GetBytesToSend() on transport[i].
 125      std::array<std::vector<uint8_t>, 2> to_send;
 126  
 127      // Last returned 'more' values (if still relevant) by transport[i]->GetBytesToSend(), for
 128      // both have_next_message false and true.
 129      std::array<std::optional<bool>, 2> last_more, last_more_next;
 130  
 131      // Whether more bytes to be sent are expected on transport[i], before and after
 132      // SetMessageToSend().
 133      std::array<std::optional<bool>, 2> expect_more, expect_more_next;
 134  
 135      // Function to consume a message type.
 136      auto msg_type_fn = [&]() {
 137          uint8_t v = provider.ConsumeIntegral<uint8_t>();
 138          if (v == 0xFF) {
 139              // If v is 0xFF, construct a valid (but possibly unknown) message type from the fuzz
 140              // data.
 141              std::string ret;
 142              while (ret.size() < CMessageHeader::MESSAGE_TYPE_SIZE) {
 143                  char c = provider.ConsumeIntegral<char>();
 144                  // Match the allowed characters in CMessageHeader::IsMessageTypeValid(). Any other
 145                  // character is interpreted as end.
 146                  if (c < ' ' || c > 0x7E) break;
 147                  ret += c;
 148              }
 149              return ret;
 150          } else {
 151              // Otherwise, use it as index into the list of known messages.
 152              return g_all_messages[v % g_all_messages.size()];
 153          }
 154      };
 155  
 156      // Function to construct a CSerializedNetMsg to send.
 157      auto make_msg_fn = [&](bool first) {
 158          CSerializedNetMsg msg;
 159          if (first) {
 160              // Always send a "version" message as first one.
 161              msg.m_type = "version";
 162          } else {
 163              msg.m_type = msg_type_fn();
 164          }
 165          // Determine size of message to send (limited to 75 kB for performance reasons).
 166          size_t size = provider.ConsumeIntegralInRange<uint32_t>(0, 75000);
 167          // Get payload of message from RNG.
 168          msg.data = rng.randbytes(size);
 169          // Return.
 170          return msg;
 171      };
 172  
 173      // The next message to be sent (initially version messages, but will be replaced once sent).
 174      std::array<CSerializedNetMsg, 2> next_msg = {
 175          make_msg_fn(/*first=*/true),
 176          make_msg_fn(/*first=*/true)
 177      };
 178  
 179      // Wrapper around transport[i]->GetBytesToSend() that performs sanity checks.
 180      auto bytes_to_send_fn = [&](int side) -> Transport::BytesToSend {
 181          // Invoke GetBytesToSend twice (for have_next_message = {false, true}). This function does
 182          // not modify state (it's const), and only the "more" return value should differ between
 183          // the calls.
 184          const auto& [bytes, more_nonext, msg_type] = transports[side]->GetBytesToSend(false);
 185          const auto& [bytes_next, more_next, msg_type_next] = transports[side]->GetBytesToSend(true);
 186          // Compare with expected more.
 187          if (expect_more[side].has_value()) assert(!bytes.empty() == *expect_more[side]);
 188          // Verify consistency between the two results.
 189          assert(std::ranges::equal(bytes, bytes_next));
 190          assert(msg_type == msg_type_next);
 191          if (more_nonext) assert(more_next);
 192          // Compare with previously reported output.
 193          assert(to_send[side].size() <= bytes.size());
 194          assert(std::ranges::equal(to_send[side], std::span{bytes}.first(to_send[side].size())));
 195          to_send[side].resize(bytes.size());
 196          std::copy(bytes.begin(), bytes.end(), to_send[side].begin());
 197          // Remember 'more' results.
 198          last_more[side] = {more_nonext};
 199          last_more_next[side] = {more_next};
 200          // Return.
 201          return {bytes, more_nonext, msg_type};
 202      };
 203  
 204      // Function to make side send a new message.
 205      auto new_msg_fn = [&](int side) {
 206          // Don't do anything if there are too many unreceived messages already.
 207          if (expected[side].size() >= 16) return;
 208          // Try to send (a copy of) the message in next_msg[side].
 209          CSerializedNetMsg msg = next_msg[side].Copy();
 210          bool queued = transports[side]->SetMessageToSend(msg);
 211          // Update expected more data.
 212          expect_more[side] = expect_more_next[side];
 213          expect_more_next[side] = std::nullopt;
 214          // Verify consistency of GetBytesToSend after SetMessageToSend
 215          bytes_to_send_fn(/*side=*/side);
 216          if (queued) {
 217              // Remember that this message is now expected by the receiver.
 218              expected[side].emplace_back(std::move(next_msg[side]));
 219              // Construct a new next message to send.
 220              next_msg[side] = make_msg_fn(/*first=*/false);
 221          }
 222      };
 223  
 224      // Function to make side send out bytes (if any).
 225      auto send_fn = [&](int side, bool everything = false) {
 226          const auto& [bytes, more, msg_type] = bytes_to_send_fn(/*side=*/side);
 227          // Don't do anything if no bytes to send.
 228          if (bytes.empty()) return false;
 229          size_t send_now = everything ? bytes.size() : provider.ConsumeIntegralInRange<size_t>(0, bytes.size());
 230          if (send_now == 0) return false;
 231          // Add bytes to the in-flight queue, and mark those bytes as consumed.
 232          in_flight[side].insert(in_flight[side].end(), bytes.begin(), bytes.begin() + send_now);
 233          transports[side]->MarkBytesSent(send_now);
 234          // If all to-be-sent bytes were sent, move last_more data to expect_more data.
 235          if (send_now == bytes.size()) {
 236              expect_more[side] = last_more[side];
 237              expect_more_next[side] = last_more_next[side];
 238          }
 239          // Remove the bytes from the last reported to-be-sent vector.
 240          assert(to_send[side].size() >= send_now);
 241          to_send[side].erase(to_send[side].begin(), to_send[side].begin() + send_now);
 242          // Verify that GetBytesToSend gives a result consistent with earlier.
 243          bytes_to_send_fn(/*side=*/side);
 244          // Return whether anything was sent.
 245          return send_now > 0;
 246      };
 247  
 248      // Function to make !side receive bytes (if any).
 249      auto recv_fn = [&](int side, bool everything = false) {
 250          // Don't do anything if no bytes in flight.
 251          if (in_flight[side].empty()) return false;
 252          // Decide span to receive
 253          size_t to_recv_len = in_flight[side].size();
 254          if (!everything) to_recv_len = provider.ConsumeIntegralInRange<size_t>(0, to_recv_len);
 255          std::span<const uint8_t> to_recv = std::span{in_flight[side]}.first(to_recv_len);
 256          // Process those bytes
 257          while (!to_recv.empty()) {
 258              size_t old_len = to_recv.size();
 259              bool ret = transports[!side]->ReceivedBytes(to_recv);
 260              // Bytes must always be accepted, as this test does not introduce any errors in
 261              // communication.
 262              assert(ret);
 263              // Clear cached expected 'more' information: if certainly no more data was to be sent
 264              // before, receiving bytes makes this uncertain.
 265              if (expect_more[!side] == false) expect_more[!side] = std::nullopt;
 266              if (expect_more_next[!side] == false) expect_more_next[!side] = std::nullopt;
 267              // Verify consistency of GetBytesToSend after ReceivedBytes
 268              bytes_to_send_fn(/*side=*/!side);
 269              bool progress = to_recv.size() < old_len;
 270              if (transports[!side]->ReceivedMessageComplete()) {
 271                  bool reject{false};
 272                  auto received = transports[!side]->GetReceivedMessage({}, reject);
 273                  // Receiving must succeed.
 274                  assert(!reject);
 275                  // There must be a corresponding expected message.
 276                  assert(!expected[side].empty());
 277                  // The m_message_size field must be correct.
 278                  assert(received.m_message_size == received.m_recv.size());
 279                  // The m_type must match what is expected.
 280                  assert(received.m_type == expected[side].front().m_type);
 281                  // The data must match what is expected.
 282                  assert(std::ranges::equal(received.m_recv, MakeByteSpan(expected[side].front().data)));
 283                  expected[side].pop_front();
 284                  progress = true;
 285              }
 286              // Progress must be made (by processing incoming bytes and/or returning complete
 287              // messages) until all received bytes are processed.
 288              assert(progress);
 289          }
 290          // Remove the processed bytes from the in_flight buffer.
 291          in_flight[side].erase(in_flight[side].begin(), in_flight[side].begin() + to_recv_len);
 292          // Return whether anything was received.
 293          return to_recv_len > 0;
 294      };
 295  
 296      // Main loop, interleaving new messages, sends, and receives.
 297      LIMITED_WHILE(provider.remaining_bytes(), 1000) {
 298          CallOneOf(provider,
 299              // (Try to) give the next message to the transport.
 300              [&] { new_msg_fn(/*side=*/0); },
 301              [&] { new_msg_fn(/*side=*/1); },
 302              // (Try to) send some bytes from the transport to the network.
 303              [&] { send_fn(/*side=*/0); },
 304              [&] { send_fn(/*side=*/1); },
 305              // (Try to) receive bytes from the network, converting to messages.
 306              [&] { recv_fn(/*side=*/0); },
 307              [&] { recv_fn(/*side=*/1); }
 308          );
 309      }
 310  
 311      // When we're done, perform sends and receives of existing messages to flush anything already
 312      // in flight.
 313      while (true) {
 314          bool any = false;
 315          if (send_fn(/*side=*/0, /*everything=*/true)) any = true;
 316          if (send_fn(/*side=*/1, /*everything=*/true)) any = true;
 317          if (recv_fn(/*side=*/0, /*everything=*/true)) any = true;
 318          if (recv_fn(/*side=*/1, /*everything=*/true)) any = true;
 319          if (!any) break;
 320      }
 321  
 322      // Make sure nothing is left in flight.
 323      assert(in_flight[0].empty());
 324      assert(in_flight[1].empty());
 325  
 326      // Make sure all expected messages were received.
 327      assert(expected[0].empty());
 328      assert(expected[1].empty());
 329  
 330      // Compare session IDs.
 331      assert(transports[0]->GetInfo().session_id == transports[1]->GetInfo().session_id);
 332  }
 333  
 334  std::unique_ptr<Transport> MakeV1Transport(NodeId nodeid) noexcept
 335  {
 336      return std::make_unique<V1Transport>(nodeid);
 337  }
 338  
 339  template<RandomNumberGenerator RNG>
 340  std::unique_ptr<Transport> MakeV2Transport(NodeId nodeid, bool initiator, RNG& rng, FuzzedDataProvider& provider)
 341  {
 342      // Retrieve key
 343      auto key = ConsumePrivateKey(provider);
 344      if (!key.IsValid()) return {};
 345      // Construct garbage
 346      size_t garb_len = provider.ConsumeIntegralInRange<size_t>(0, V2Transport::MAX_GARBAGE_LEN);
 347      std::vector<uint8_t> garb;
 348      if (garb_len <= 64) {
 349          // When the garbage length is up to 64 bytes, read it directly from the fuzzer input.
 350          garb = provider.ConsumeBytes<uint8_t>(garb_len);
 351          garb.resize(garb_len);
 352      } else {
 353          // If it's longer, generate it from the RNG. This avoids having large amounts of
 354          // (hopefully) irrelevant data needing to be stored in the fuzzer data.
 355          garb = rng.randbytes(garb_len);
 356      }
 357      // Retrieve entropy
 358      auto ent = provider.ConsumeBytes<std::byte>(32);
 359      ent.resize(32);
 360      // Use as entropy SHA256(ent || garbage). This prevents a situation where the fuzzer manages to
 361      // include the garbage terminator (which is a function of both ellswift keys) in the garbage.
 362      // This is extremely unlikely (~2^-116) with random keys/garbage, but the fuzzer can choose
 363      // both non-randomly and dependently. Since the entropy is hashed anyway inside the ellswift
 364      // computation, no coverage should be lost by using a hash as entropy, and it removes the
 365      // possibility of garbage that happens to contain what is effectively a hash of the keys.
 366      CSHA256().Write(UCharCast(ent.data()), ent.size())
 367               .Write(garb.data(), garb.size())
 368               .Finalize(UCharCast(ent.data()));
 369  
 370      return std::make_unique<V2Transport>(nodeid, initiator, key, ent, std::move(garb));
 371  }
 372  
 373  } // namespace
 374  
 375  FUZZ_TARGET(p2p_transport_bidirectional, .init = initialize_p2p_transport_serialization)
 376  {
 377      // Test with two V1 transports talking to each other.
 378      FuzzedDataProvider provider{buffer.data(), buffer.size()};
 379      InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>());
 380      auto t1 = MakeV1Transport(NodeId{0});
 381      auto t2 = MakeV1Transport(NodeId{1});
 382      if (!t1 || !t2) return;
 383      SimulationTest(*t1, *t2, rng, provider);
 384  }
 385  
 386  FUZZ_TARGET(p2p_transport_bidirectional_v2, .init = initialize_p2p_transport_serialization)
 387  {
 388      // Test with two V2 transports talking to each other.
 389      FuzzedDataProvider provider{buffer.data(), buffer.size()};
 390      InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>());
 391      auto t1 = MakeV2Transport(NodeId{0}, true, rng, provider);
 392      auto t2 = MakeV2Transport(NodeId{1}, false, rng, provider);
 393      if (!t1 || !t2) return;
 394      SimulationTest(*t1, *t2, rng, provider);
 395  }
 396  
 397  FUZZ_TARGET(p2p_transport_bidirectional_v1v2, .init = initialize_p2p_transport_serialization)
 398  {
 399      // Test with a V1 initiator talking to a V2 responder.
 400      FuzzedDataProvider provider{buffer.data(), buffer.size()};
 401      InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>());
 402      auto t1 = MakeV1Transport(NodeId{0});
 403      auto t2 = MakeV2Transport(NodeId{1}, false, rng, provider);
 404      if (!t1 || !t2) return;
 405      SimulationTest(*t1, *t2, rng, provider);
 406  }
 407