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