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