Skip to content

Commit 246f3ae

Browse files
committed
fuzz: Add fuzz test for v2 transport {de}serialization
1 parent 748a232 commit 246f3ae

File tree

4 files changed

+119
-1
lines changed

4 files changed

+119
-1
lines changed

src/Makefile.test.include

+1
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ test_fuzz_fuzz_SOURCES = \
292292
test/fuzz/netbase_dns_lookup.cpp \
293293
test/fuzz/node_eviction.cpp \
294294
test/fuzz/p2p_transport_serialization.cpp \
295+
test/fuzz/p2p_v2_transport_serialization.cpp \
295296
test/fuzz/parse_hd_keypath.cpp \
296297
test/fuzz/parse_numbers.cpp \
297298
test/fuzz/parse_script.cpp \

src/crypto/rfc8439.cpp

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
#include <crypto/rfc8439.h>
66

7-
#include <crypto/chacha20.h>
87
#include <crypto/common.h>
98

109
#include <cstring>

src/crypto/rfc8439.h

+8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#ifndef BITCOIN_CRYPTO_RFC8439_H
66
#define BITCOIN_CRYPTO_RFC8439_H
77

8+
#include <crypto/chacha20.h>
89
#include <crypto/poly1305.h>
910
#include <span.h>
1011

@@ -19,4 +20,11 @@ void RFC8439Encrypt(const Span<const std::byte> aad, const Span<const std::byte>
1920

2021
// returns false if authentication fails
2122
bool RFC8439Decrypt(const Span<const std::byte> aad, const Span<const std::byte> key, const std::array<std::byte, 12>& nonce, const Span<const std::byte> input, Span<std::byte> plaintext);
23+
24+
void ComputeRFC8439Tag(const std::array<std::byte, POLY1305_KEYLEN>& polykey,
25+
Span<const std::byte> aad, Span<const std::byte> ciphertext,
26+
Span<std::byte> tag_out);
27+
28+
std::array<std::byte, POLY1305_KEYLEN> GetPoly1305Key(ChaCha20& c20);
29+
2230
#endif // BITCOIN_CRYPTO_RFC8439_H
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright (c) 2019-2021 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 <compat/endian.h>
6+
#include <crypto/bip324_suite.h>
7+
#include <crypto/rfc8439.h>
8+
#include <key.h>
9+
#include <net.h>
10+
#include <netmessagemaker.h>
11+
#include <test/fuzz/FuzzedDataProvider.h>
12+
#include <test/fuzz/fuzz.h>
13+
14+
#include <array>
15+
#include <cassert>
16+
#include <cstddef>
17+
18+
FUZZ_TARGET(p2p_v2_transport_serialization)
19+
{
20+
FuzzedDataProvider fdp{buffer.data(), buffer.size()};
21+
22+
// Picking constant keys seems to give us higher fuzz test coverage
23+
// The BIP324 Cipher suite is separately fuzzed, so we don't have to
24+
// pick fuzzed keys here.
25+
BIP324Key key_L, key_P;
26+
memset(key_L.data(), 1, BIP324_KEY_LEN);
27+
memset(key_P.data(), 2, BIP324_KEY_LEN);
28+
29+
// Construct deserializer, with a dummy NodeId
30+
V2TransportDeserializer deserializer{(NodeId)0, key_L, key_P};
31+
V2TransportSerializer serializer{key_L, key_P};
32+
FSChaCha20 fsc20{key_L, REKEY_INTERVAL};
33+
ChaCha20 c20{reinterpret_cast<unsigned char*>(key_P.data())};
34+
35+
std::array<std::byte, 12> nonce;
36+
memset(nonce.data(), 0, 12);
37+
c20.SetRFC8439Nonce(nonce);
38+
39+
bool length_assist = fdp.ConsumeBool();
40+
41+
// There is no sense in providing a mac assist if the length is incorrect.
42+
bool mac_assist = length_assist && fdp.ConsumeBool();
43+
auto encrypted_packet = fdp.ConsumeRemainingBytes<uint8_t>();
44+
bool is_decoy_packet{false};
45+
46+
if (encrypted_packet.size() >= V2_MIN_PACKET_LENGTH) {
47+
if (length_assist) {
48+
uint32_t contents_len = encrypted_packet.size() - BIP324_LENGTH_FIELD_LEN - BIP324_HEADER_LEN - RFC8439_EXPANSION;
49+
contents_len = htole32(contents_len);
50+
fsc20.Crypt({reinterpret_cast<std::byte*>(&contents_len), BIP324_LENGTH_FIELD_LEN},
51+
{reinterpret_cast<std::byte*>(encrypted_packet.data()), BIP324_LENGTH_FIELD_LEN});
52+
}
53+
54+
if (mac_assist) {
55+
std::array<std::byte, RFC8439_EXPANSION> tag;
56+
ComputeRFC8439Tag(GetPoly1305Key(c20), {},
57+
{reinterpret_cast<std::byte*>(encrypted_packet.data()) + BIP324_LENGTH_FIELD_LEN,
58+
encrypted_packet.size() - BIP324_LENGTH_FIELD_LEN - RFC8439_EXPANSION},
59+
tag);
60+
memcpy(encrypted_packet.data() + encrypted_packet.size() - RFC8439_EXPANSION, tag.data(), RFC8439_EXPANSION);
61+
62+
std::vector<std::byte> dec_header_and_contents(
63+
encrypted_packet.size() - BIP324_LENGTH_FIELD_LEN - RFC8439_EXPANSION);
64+
RFC8439Decrypt({}, key_P, nonce,
65+
{reinterpret_cast<std::byte*>(encrypted_packet.data() + BIP324_LENGTH_FIELD_LEN),
66+
encrypted_packet.size() - BIP324_LENGTH_FIELD_LEN},
67+
dec_header_and_contents);
68+
if (BIP324HeaderFlags((uint8_t)dec_header_and_contents.at(0) & BIP324_IGNORE) != BIP324_NONE) {
69+
is_decoy_packet = true;
70+
}
71+
}
72+
}
73+
74+
Span<const uint8_t> pkt_bytes{encrypted_packet};
75+
while (pkt_bytes.size() > 0) {
76+
const int handled = deserializer.Read(pkt_bytes);
77+
if (handled < 0) {
78+
break;
79+
}
80+
if (deserializer.Complete()) {
81+
const std::chrono::microseconds m_time{std::numeric_limits<int64_t>::max()};
82+
bool reject_message{true};
83+
bool disconnect{true};
84+
CNetMessage result{deserializer.GetMessage(m_time, reject_message, disconnect)};
85+
86+
if (mac_assist) {
87+
assert(!disconnect);
88+
}
89+
90+
if (is_decoy_packet) {
91+
assert(reject_message);
92+
}
93+
94+
if (!reject_message) {
95+
assert(result.m_type.size() <= CMessageHeader::COMMAND_SIZE);
96+
assert(result.m_raw_message_size <= buffer.size());
97+
98+
auto message_type_size = result.m_raw_message_size - V2_MIN_PACKET_LENGTH - result.m_message_size;
99+
assert(message_type_size <= 13);
100+
assert(message_type_size >= 1);
101+
assert(result.m_time == m_time);
102+
103+
std::vector<unsigned char> header;
104+
auto msg = CNetMsgMaker{result.m_recv.GetVersion()}.Make(result.m_type, MakeUCharSpan(result.m_recv));
105+
// if decryption succeeds, encryption must succeed
106+
assert(serializer.prepareForTransport(msg, header));
107+
}
108+
}
109+
}
110+
}

0 commit comments

Comments
 (0)