Skip to content

Commit 3ffa5fb

Browse files
committed
net: make V2Transport send uniformly random number garbage bytes
1 parent 0be752d commit 3ffa5fb

File tree

3 files changed

+47
-15
lines changed

3 files changed

+47
-15
lines changed

src/net.cpp

+19-8
Original file line numberDiff line numberDiff line change
@@ -987,20 +987,26 @@ V2Transport::V2Transport(NodeId nodeid, bool initiating, int type_in, int versio
987987
m_recv_state{initiating ? RecvState::KEY : RecvState::KEY_MAYBE_V1},
988988
m_send_state{initiating ? SendState::AWAITING_KEY : SendState::MAYBE_V1}
989989
{
990-
// Initialize the send buffer with ellswift pubkey.
991-
m_send_buffer.resize(EllSwiftPubKey::size());
990+
// Construct garbage (including its length) using a FastRandomContext.
991+
FastRandomContext rng;
992+
size_t garbage_len = rng.randrange(MAX_GARBAGE_LEN + 1);
993+
// Initialize the send buffer with ellswift pubkey + garbage.
994+
m_send_buffer.resize(EllSwiftPubKey::size() + garbage_len);
992995
std::copy(std::begin(m_cipher.GetOurPubKey()), std::end(m_cipher.GetOurPubKey()), MakeWritableByteSpan(m_send_buffer).begin());
996+
rng.fillrand(MakeWritableByteSpan(m_send_buffer).subspan(EllSwiftPubKey::size()));
993997
}
994998

995-
V2Transport::V2Transport(NodeId nodeid, bool initiating, int type_in, int version_in, const CKey& key, Span<const std::byte> ent32) noexcept :
999+
V2Transport::V2Transport(NodeId nodeid, bool initiating, int type_in, int version_in, const CKey& key, Span<const std::byte> ent32, Span<const uint8_t> garbage) noexcept :
9961000
m_cipher{key, ent32}, m_initiating{initiating}, m_nodeid{nodeid},
9971001
m_v1_fallback{nodeid, type_in, version_in}, m_recv_type{type_in}, m_recv_version{version_in},
9981002
m_recv_state{initiating ? RecvState::KEY : RecvState::KEY_MAYBE_V1},
9991003
m_send_state{initiating ? SendState::AWAITING_KEY : SendState::MAYBE_V1}
10001004
{
1001-
// Initialize the send buffer with ellswift pubkey.
1002-
m_send_buffer.resize(EllSwiftPubKey::size());
1005+
assert(garbage.size() <= MAX_GARBAGE_LEN);
1006+
// Initialize the send buffer with ellswift pubkey + provided garbage.
1007+
m_send_buffer.resize(EllSwiftPubKey::size() + garbage.size());
10031008
std::copy(std::begin(m_cipher.GetOurPubKey()), std::end(m_cipher.GetOurPubKey()), MakeWritableByteSpan(m_send_buffer).begin());
1009+
std::copy(garbage.begin(), garbage.end(), m_send_buffer.begin() + EllSwiftPubKey::size());
10041010
}
10051011

10061012
void V2Transport::SetReceiveState(RecvState recv_state) noexcept
@@ -1126,16 +1132,18 @@ void V2Transport::ProcessReceivedKeyBytes() noexcept
11261132
SetSendState(SendState::READY);
11271133

11281134
// Append the garbage terminator to the send buffer.
1135+
size_t garbage_len = m_send_buffer.size() - EllSwiftPubKey::size();
11291136
m_send_buffer.resize(m_send_buffer.size() + BIP324Cipher::GARBAGE_TERMINATOR_LEN);
11301137
std::copy(m_cipher.GetSendGarbageTerminator().begin(),
11311138
m_cipher.GetSendGarbageTerminator().end(),
11321139
MakeWritableByteSpan(m_send_buffer).last(BIP324Cipher::GARBAGE_TERMINATOR_LEN).begin());
11331140

1134-
// Construct garbage authentication packet in the send buffer.
1141+
// Construct garbage authentication packet in the send buffer (using the garbage data which
1142+
// is still there).
11351143
m_send_buffer.resize(m_send_buffer.size() + BIP324Cipher::EXPANSION);
11361144
m_cipher.Encrypt(
11371145
/*contents=*/{},
1138-
/*aad=*/{}, /* empty garbage for now */
1146+
/*aad=*/MakeByteSpan(m_send_buffer).subspan(EllSwiftPubKey::size(), garbage_len),
11391147
/*ignore=*/false,
11401148
/*output=*/MakeWritableByteSpan(m_send_buffer).last(BIP324Cipher::EXPANSION));
11411149

@@ -1490,7 +1498,10 @@ void V2Transport::MarkBytesSent(size_t bytes_sent) noexcept
14901498

14911499
m_send_pos += bytes_sent;
14921500
Assume(m_send_pos <= m_send_buffer.size());
1493-
if (m_send_pos == m_send_buffer.size()) {
1501+
// Only wipe the buffer when everything is sent in the READY state. In the AWAITING_KEY state
1502+
// we still need the garbage that's in the send buffer to construct the garbage authentication
1503+
// packet.
1504+
if (m_send_state == SendState::READY && m_send_pos == m_send_buffer.size()) {
14941505
m_send_pos = 0;
14951506
m_send_buffer = {};
14961507
}

src/net.h

+5-5
Original file line numberDiff line numberDiff line change
@@ -556,9 +556,9 @@ class V2Transport final : public Transport
556556
/** Normal sending state.
557557
*
558558
* In this state, the ciphers are initialized, so packets can be sent. When this state is
559-
* entered, the garbage terminator, garbage authentication packet, and version packet are
560-
* appended to the send buffer (in addition to the key which may still be there). In this
561-
* state a message can be provided if the send buffer is empty. */
559+
* entered, the garbage, garbage terminator, garbage authentication packet, and version
560+
* packet are appended to the send buffer (in addition to the key which may still be
561+
* there). In this state a message can be provided if the send buffer is empty. */
562562
READY,
563563

564564
/** This transport is using v1 fallback.
@@ -635,8 +635,8 @@ class V2Transport final : public Transport
635635
*/
636636
V2Transport(NodeId nodeid, bool initiating, int type_in, int version_in) noexcept;
637637

638-
/** Construct a V2 transport with specified keys (test use only). */
639-
V2Transport(NodeId nodeid, bool initiating, int type_in, int version_in, const CKey& key, Span<const std::byte> ent32) noexcept;
638+
/** Construct a V2 transport with specified keys and garbage (test use only). */
639+
V2Transport(NodeId nodeid, bool initiating, int type_in, int version_in, const CKey& key, Span<const std::byte> ent32, Span<const uint8_t> garbage) noexcept;
640640

641641
// Receive side functions.
642642
bool ReceivedMessageComplete() const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex);

src/test/fuzz/p2p_transport_serialization.cpp

+23-2
Original file line numberDiff line numberDiff line change
@@ -341,11 +341,32 @@ std::unique_ptr<Transport> MakeV2Transport(NodeId nodeid, bool initiator, RNG& r
341341
// Retrieve key
342342
auto key = ConsumePrivateKey(provider);
343343
if (!key.IsValid()) return {};
344+
// Construct garbage
345+
size_t garb_len = provider.ConsumeIntegralInRange<size_t>(0, V2Transport::MAX_GARBAGE_LEN);
346+
std::vector<uint8_t> garb;
347+
if (garb_len <= 64) {
348+
// When the garbage length is up to 64 bytes, read it directly from the fuzzer input.
349+
garb = provider.ConsumeBytes<uint8_t>(garb_len);
350+
garb.resize(garb_len);
351+
} 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+
for (auto& v : garb) v = uint8_t(rng());
355+
}
344356
// Retrieve entropy
345357
auto ent = provider.ConsumeBytes<std::byte>(32);
346358
ent.resize(32);
347-
348-
return std::make_unique<V2Transport>(nodeid, initiator, SER_NETWORK, INIT_PROTO_VERSION, key, ent);
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+
CSHA256().Write(UCharCast(ent.data()), ent.size())
366+
.Write(garb.data(), garb.size())
367+
.Finalize(UCharCast(ent.data()));
368+
369+
return std::make_unique<V2Transport>(nodeid, initiator, SER_NETWORK, INIT_PROTO_VERSION, key, ent, garb);
349370
}
350371

351372
} // namespace

0 commit comments

Comments
 (0)