p2p_transport_serialization.cpp
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 }