Bitcoin Core 28.99.0
P2P Digital Currency
p2p_transport_serialization.cpp
Go to the documentation of this file.
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>
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
22namespace {
23
24auto g_all_messages = ALL_NET_MESSAGE_TYPES;
25
26void initialize_p2p_transport_serialization()
27{
28 static ECC_Context ecc_context{};
30 std::sort(g_all_messages.begin(), g_all_messages.end());
31}
32
33} // namespace
34
35FUZZ_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 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 std::chrono::microseconds m_time{std::numeric_limits<int64_t>::max()};
82 bool reject_message{false};
83 CNetMessage msg = recv_transport.GetReceivedMessage(m_time, reject_message);
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 == m_time);
88
89 std::vector<unsigned char> header;
90 auto msg2 = NetMsg::Make(msg.m_type, 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
105namespace {
106
107template<RandomNumberGenerator R>
108void 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) {
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], 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 Span<const uint8_t> to_recv = 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
334std::unique_ptr<Transport> MakeV1Transport(NodeId nodeid) noexcept
335{
336 return std::make_unique<V1Transport>(nodeid);
337}
338
339template<RandomNumberGenerator RNG>
340std::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
375FUZZ_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
386FUZZ_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
397FUZZ_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}
int ret
ECC_Context ecc_context
void SelectParams(const ChainType chain)
Sets the params returned by Params() to those for the given chain type.
const CChainParams & Params()
Return the currently selected parameters.
const MessageStartChars & MessageStart() const
Definition: chainparams.h:94
A hasher class for Bitcoin's 256-bit hash (double SHA-256).
Definition: hash.h:24
void Finalize(Span< unsigned char > output)
Definition: hash.h:30
CHash256 & Write(Span< const unsigned char > input)
Definition: hash.h:37
static constexpr size_t CHECKSUM_OFFSET
Definition: protocol.h:35
static constexpr size_t MESSAGE_TYPE_SIZE
Definition: protocol.h:31
static constexpr size_t CHECKSUM_SIZE
Definition: protocol.h:33
static constexpr size_t MESSAGE_SIZE_SIZE
Definition: protocol.h:32
static constexpr size_t HEADER_SIZE
Definition: protocol.h:36
Transport protocol agnostic message container.
Definition: net.h:231
A hasher class for SHA-256.
Definition: sha256.h:14
void Finalize(unsigned char hash[OUTPUT_SIZE])
Definition: sha256.cpp:727
CSHA256 & Write(const unsigned char *data, size_t len)
Definition: sha256.cpp:701
RAII class initializing and deinitializing global state for elliptic curve support.
Definition: key.h:322
std::vector< T > ConsumeBytes(size_t num_bytes)
T ConsumeIntegralInRange(T min, T max)
xoroshiro128++ PRNG.
Definition: random.h:416
A Span is an object that can refer to a contiguous sequence of objects.
Definition: span.h:98
CONSTEXPR_IF_NOT_DEBUG Span< C > first(std::size_t count) const noexcept
Definition: span.h:205
The Transport converts one connection's sent messages to wire bytes, and received bytes back.
Definition: net.h:254
std::tuple< Span< const uint8_t >, bool, const std::string & > BytesToSend
Return type for GetBytesToSend, consisting of:
Definition: net.h:311
static constexpr uint32_t MAX_GARBAGE_LEN
Definition: net.h:634
#define LIMITED_WHILE(condition, limit)
Can be used to limit a theoretically unbounded loop.
Definition: fuzz.h:22
CSerializedNetMsg Make(std::string msg_type, Args &&... args)
int64_t NodeId
Definition: net.h:97
FUZZ_TARGET(p2p_transport_serialization,.init=initialize_p2p_transport_serialization)
const std::array ALL_NET_MESSAGE_TYPES
All known message types (see above).
Definition: protocol.h:270
Span< const std::byte > MakeByteSpan(V &&v) noexcept
Definition: span.h:270
unsigned char * UCharCast(char *c)
Definition: span.h:281
CKey ConsumePrivateKey(FuzzedDataProvider &fuzzed_data_provider, std::optional< bool > compressed) noexcept
Definition: util.cpp:230
size_t CallOneOf(FuzzedDataProvider &fuzzed_data_provider, Callables... callables)
Definition: util.h:35
static TxMempoolInfo GetInfo(CTxMemPool::indexed_transaction_set::const_iterator it)
Definition: txmempool.cpp:847
assert(!tx.IsCoinBase())