Skip to content

Commit d67330d

Browse files
author
MarcoFalke
committed
Merge bitcoin/bitcoin#21129: fuzz: check that ser+unser produces the same AddrMan
8765179 fuzz: check that ser+unser produces the same AddrMan (Vasil Dimov) 6408b24 fuzz: move init code to the CAddrManDeterministic constructor (Vasil Dimov) Pull request description: Add a fuzz test that fills addrman with a pile of randomly generated addresses, serializes it to a stream, unserializes the stream to another addrman object and compares the two. Some discussion of this already happened at jnewbery/bitcoin#18. ACKs for top commit: practicalswift: cr ACK 8765179 jonatack: ACK 8765179 rebased to current master, reviewed, fuzz build, ran `FUZZ=addrman_serdeser src/test/fuzz/fuzz` Tree-SHA512: 7eda79279f14f2649840bf752e575d7b02cbaad541f74f7254855ebd4a32da988f042d78aa9228983350283bb74dd0c71f51f04c0846889c3ba2f19f01a0c303
2 parents f4328eb + 8765179 commit d67330d

File tree

2 files changed

+215
-11
lines changed

2 files changed

+215
-11
lines changed

src/addrman.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class CAddrInfo : public CAddress
5858
mutable int nRandomPos{-1};
5959

6060
friend class CAddrMan;
61+
friend class CAddrManDeterministic;
6162

6263
public:
6364

@@ -778,6 +779,7 @@ class CAddrMan
778779
void SetServices_(const CService &addr, ServiceFlags nServices) EXCLUSIVE_LOCKS_REQUIRED(cs);
779780

780781
friend class CAddrManTest;
782+
friend class CAddrManDeterministic;
781783
};
782784

783785
#endif // BITCOIN_ADDRMAN_H

src/test/fuzz/addrman.cpp

Lines changed: 213 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <time.h>
1313
#include <util/asmap.h>
1414

15+
#include <cassert>
1516
#include <cstdint>
1617
#include <optional>
1718
#include <string>
@@ -25,25 +26,208 @@ void initialize_addrman()
2526
class CAddrManDeterministic : public CAddrMan
2627
{
2728
public:
28-
void MakeDeterministic(const uint256& random_seed)
29+
FuzzedDataProvider& m_fuzzed_data_provider;
30+
31+
explicit CAddrManDeterministic(FuzzedDataProvider& fuzzed_data_provider)
32+
: m_fuzzed_data_provider(fuzzed_data_provider)
33+
{
34+
WITH_LOCK(cs, insecure_rand = FastRandomContext{ConsumeUInt256(fuzzed_data_provider)});
35+
if (fuzzed_data_provider.ConsumeBool()) {
36+
m_asmap = ConsumeRandomLengthBitVector(fuzzed_data_provider);
37+
if (!SanityCheckASMap(m_asmap)) {
38+
m_asmap.clear();
39+
}
40+
}
41+
}
42+
43+
/**
44+
* Generate a random address. Always returns a valid address.
45+
*/
46+
CNetAddr RandAddr() EXCLUSIVE_LOCKS_REQUIRED(cs)
47+
{
48+
CNetAddr addr;
49+
if (m_fuzzed_data_provider.remaining_bytes() > 1 && m_fuzzed_data_provider.ConsumeBool()) {
50+
addr = ConsumeNetAddr(m_fuzzed_data_provider);
51+
} else {
52+
// The networks [1..6] correspond to CNetAddr::BIP155Network (private).
53+
static const std::map<uint8_t, uint8_t> net_len_map = {{1, ADDR_IPV4_SIZE},
54+
{2, ADDR_IPV6_SIZE},
55+
{4, ADDR_TORV3_SIZE},
56+
{5, ADDR_I2P_SIZE},
57+
{6, ADDR_CJDNS_SIZE}};
58+
uint8_t net = insecure_rand.randrange(5) + 1; // [1..5]
59+
if (net == 3) {
60+
net = 6;
61+
}
62+
63+
CDataStream s(SER_NETWORK, PROTOCOL_VERSION | ADDRV2_FORMAT);
64+
65+
s << net;
66+
s << insecure_rand.randbytes(net_len_map.at(net));
67+
68+
s >> addr;
69+
}
70+
71+
// Return a dummy IPv4 5.5.5.5 if we generated an invalid address.
72+
if (!addr.IsValid()) {
73+
in_addr v4_addr = {};
74+
v4_addr.s_addr = 0x05050505;
75+
addr = CNetAddr{v4_addr};
76+
}
77+
78+
return addr;
79+
}
80+
81+
/**
82+
* Fill this addrman with lots of addresses from lots of sources.
83+
*/
84+
void Fill()
2985
{
30-
WITH_LOCK(cs, insecure_rand = FastRandomContext{random_seed});
31-
Clear();
86+
LOCK(cs);
87+
88+
// Add some of the addresses directly to the "tried" table.
89+
90+
// 0, 1, 2, 3 corresponding to 0%, 100%, 50%, 33%
91+
const size_t n = m_fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 3);
92+
93+
const size_t num_sources = m_fuzzed_data_provider.ConsumeIntegralInRange<size_t>(10, 50);
94+
CNetAddr prev_source;
95+
// Use insecure_rand inside the loops instead of m_fuzzed_data_provider because when
96+
// the latter is exhausted it just returns 0.
97+
for (size_t i = 0; i < num_sources; ++i) {
98+
const auto source = RandAddr();
99+
const size_t num_addresses = insecure_rand.randrange(500) + 1; // [1..500]
100+
101+
for (size_t j = 0; j < num_addresses; ++j) {
102+
const auto addr = CAddress{CService{RandAddr(), 8333}, NODE_NETWORK};
103+
const auto time_penalty = insecure_rand.randrange(100000001);
104+
#if 1
105+
// 2.83 sec to fill.
106+
if (n > 0 && mapInfo.size() % n == 0 && mapAddr.find(addr) == mapAddr.end()) {
107+
// Add to the "tried" table (if the bucket slot is free).
108+
const CAddrInfo dummy{addr, source};
109+
const int bucket = dummy.GetTriedBucket(nKey, m_asmap);
110+
const int bucket_pos = dummy.GetBucketPosition(nKey, false, bucket);
111+
if (vvTried[bucket][bucket_pos] == -1) {
112+
int id;
113+
CAddrInfo* addr_info = Create(addr, source, &id);
114+
vvTried[bucket][bucket_pos] = id;
115+
addr_info->fInTried = true;
116+
++nTried;
117+
}
118+
} else {
119+
// Add to the "new" table.
120+
Add_(addr, source, time_penalty);
121+
}
122+
#else
123+
// 261.91 sec to fill.
124+
Add_(addr, source, time_penalty);
125+
if (n > 0 && mapInfo.size() % n == 0) {
126+
Good_(addr, false, GetTime());
127+
}
128+
#endif
129+
// Add 10% of the addresses from more than one source.
130+
if (insecure_rand.randrange(10) == 0 && prev_source.IsValid()) {
131+
Add_(addr, prev_source, time_penalty);
132+
}
133+
}
134+
prev_source = source;
135+
}
136+
}
137+
138+
/**
139+
* Compare with another AddrMan.
140+
* This compares:
141+
* - the values in `mapInfo` (the keys aka ids are ignored)
142+
* - vvNew entries refer to the same addresses
143+
* - vvTried entries refer to the same addresses
144+
*/
145+
bool operator==(const CAddrManDeterministic& other)
146+
{
147+
LOCK2(cs, other.cs);
148+
149+
if (mapInfo.size() != other.mapInfo.size() || nNew != other.nNew ||
150+
nTried != other.nTried) {
151+
return false;
152+
}
153+
154+
// Check that all values in `mapInfo` are equal to all values in `other.mapInfo`.
155+
// Keys may be different.
156+
157+
using CAddrInfoHasher = std::function<size_t(const CAddrInfo&)>;
158+
using CAddrInfoEq = std::function<bool(const CAddrInfo&, const CAddrInfo&)>;
159+
160+
CNetAddrHash netaddr_hasher;
161+
162+
CAddrInfoHasher addrinfo_hasher = [&netaddr_hasher](const CAddrInfo& a) {
163+
return netaddr_hasher(static_cast<CNetAddr>(a)) ^ netaddr_hasher(a.source) ^
164+
a.nLastSuccess ^ a.nAttempts ^ a.nRefCount ^ a.fInTried;
165+
};
166+
167+
CAddrInfoEq addrinfo_eq = [](const CAddrInfo& lhs, const CAddrInfo& rhs) {
168+
return static_cast<CNetAddr>(lhs) == static_cast<CNetAddr>(rhs) &&
169+
lhs.source == rhs.source && lhs.nLastSuccess == rhs.nLastSuccess &&
170+
lhs.nAttempts == rhs.nAttempts && lhs.nRefCount == rhs.nRefCount &&
171+
lhs.fInTried == rhs.fInTried;
172+
};
173+
174+
using Addresses = std::unordered_set<CAddrInfo, CAddrInfoHasher, CAddrInfoEq>;
175+
176+
const size_t num_addresses{mapInfo.size()};
177+
178+
Addresses addresses{num_addresses, addrinfo_hasher, addrinfo_eq};
179+
for (const auto& [id, addr] : mapInfo) {
180+
addresses.insert(addr);
181+
}
182+
183+
Addresses other_addresses{num_addresses, addrinfo_hasher, addrinfo_eq};
184+
for (const auto& [id, addr] : other.mapInfo) {
185+
other_addresses.insert(addr);
186+
}
187+
188+
if (addresses != other_addresses) {
189+
return false;
190+
}
191+
192+
auto IdsReferToSameAddress = [&](int id, int other_id) EXCLUSIVE_LOCKS_REQUIRED(cs, other.cs) {
193+
if (id == -1 && other_id == -1) {
194+
return true;
195+
}
196+
if ((id == -1 && other_id != -1) || (id != -1 && other_id == -1)) {
197+
return false;
198+
}
199+
return mapInfo.at(id) == other.mapInfo.at(other_id);
200+
};
201+
202+
// Check that `vvNew` contains the same addresses as `other.vvNew`. Notice - `vvNew[i][j]`
203+
// contains just an id and the address is to be found in `mapInfo.at(id)`. The ids
204+
// themselves may differ between `vvNew` and `other.vvNew`.
205+
for (size_t i = 0; i < ADDRMAN_NEW_BUCKET_COUNT; ++i) {
206+
for (size_t j = 0; j < ADDRMAN_BUCKET_SIZE; ++j) {
207+
if (!IdsReferToSameAddress(vvNew[i][j], other.vvNew[i][j])) {
208+
return false;
209+
}
210+
}
211+
}
212+
213+
// Same for `vvTried`.
214+
for (size_t i = 0; i < ADDRMAN_TRIED_BUCKET_COUNT; ++i) {
215+
for (size_t j = 0; j < ADDRMAN_BUCKET_SIZE; ++j) {
216+
if (!IdsReferToSameAddress(vvTried[i][j], other.vvTried[i][j])) {
217+
return false;
218+
}
219+
}
220+
}
221+
222+
return true;
32223
}
33224
};
34225

35226
FUZZ_TARGET_INIT(addrman, initialize_addrman)
36227
{
37228
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
38229
SetMockTime(ConsumeTime(fuzzed_data_provider));
39-
CAddrManDeterministic addr_man;
40-
addr_man.MakeDeterministic(ConsumeUInt256(fuzzed_data_provider));
41-
if (fuzzed_data_provider.ConsumeBool()) {
42-
addr_man.m_asmap = ConsumeRandomLengthBitVector(fuzzed_data_provider);
43-
if (!SanityCheckASMap(addr_man.m_asmap)) {
44-
addr_man.m_asmap.clear();
45-
}
46-
}
230+
CAddrManDeterministic addr_man{fuzzed_data_provider};
47231
if (fuzzed_data_provider.ConsumeBool()) {
48232
const std::vector<uint8_t> serialized_data{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
49233
CDataStream ds(serialized_data, SER_DISK, INIT_PROTO_VERSION);
@@ -123,3 +307,21 @@ FUZZ_TARGET_INIT(addrman, initialize_addrman)
123307
CDataStream data_stream(SER_NETWORK, PROTOCOL_VERSION);
124308
data_stream << const_addr_man;
125309
}
310+
311+
// Check that serialize followed by unserialize produces the same addrman.
312+
FUZZ_TARGET_INIT(addrman_serdeser, initialize_addrman)
313+
{
314+
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
315+
SetMockTime(ConsumeTime(fuzzed_data_provider));
316+
317+
CAddrManDeterministic addr_man1{fuzzed_data_provider};
318+
CAddrManDeterministic addr_man2{fuzzed_data_provider};
319+
addr_man2.m_asmap = addr_man1.m_asmap;
320+
321+
CDataStream data_stream(SER_NETWORK, PROTOCOL_VERSION);
322+
323+
addr_man1.Fill();
324+
data_stream << addr_man1;
325+
data_stream >> addr_man2;
326+
assert(addr_man1 == addr_man2);
327+
}

0 commit comments

Comments
 (0)