LCOV - code coverage report
Current view: top level - src/test/fuzz - p2p_transport_serialization.cpp (source / functions) Hit Total Coverage
Test: fuzz_coverage.info Lines: 9 232 3.9 %
Date: 2023-09-26 12:08:55 Functions: 13 33 39.4 %

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

Generated by: LCOV version 1.14