Skip to content

Commit eceb3f7

Browse files
author
MarcoFalke
committed
Merge #19415: net: Make DNS lookup mockable, add fuzzing harness
e528075 tests: Add fuzzing harness for Lookup(...)/LookupHost(...)/LookupNumeric(...)/LookupSubNet(...) (practicalswift) c6b4bfb net: Make DNS lookup code testable (practicalswift) Pull request description: Make DNS lookup mockable/testable/fuzzable. Add fuzzing harness for `Lookup(…)`/`LookupHost(…)`/`LookupNumeric(…)`/`LookupSubNet(…)`. See [`doc/fuzzing.md`](https://github.com/bitcoin/bitcoin/blob/master/doc/fuzzing.md) for information on how to fuzz Bitcoin Core. Don't forget to contribute any coverage increasing inputs you find to the [Bitcoin Core fuzzing corpus repo](https://github.com/bitcoin-core/qa-assets). Happy fuzzing :) ACKs for top commit: Crypt-iQ: cr ACK e528075 vasild: ACK e528075 Tree-SHA512: 9984c2e2fedc3c1e1c3dbd701bb739ebd2f01766e6e83543dae5ae43eb8646c452bba0e101dd2f06079e5258bd5846c7d27a60ed5d77c1682b54c9544ffad443
2 parents c479797 + e528075 commit eceb3f7

File tree

4 files changed

+154
-61
lines changed

4 files changed

+154
-61
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ test_fuzz_fuzz_SOURCES = \
252252
test/fuzz/net.cpp \
253253
test/fuzz/net_permissions.cpp \
254254
test/fuzz/netaddress.cpp \
255+
test/fuzz/netbase_dns_lookup.cpp \
255256
test/fuzz/node_eviction.cpp \
256257
test/fuzz/p2p_transport_deserializer.cpp \
257258
test/fuzz/parse_hd_keypath.cpp \

src/netbase.cpp

Lines changed: 61 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,50 @@ bool fNameLookup = DEFAULT_NAME_LOOKUP;
4242
int g_socks5_recv_timeout = 20 * 1000;
4343
static std::atomic<bool> interruptSocks5Recv(false);
4444

45+
std::vector<CNetAddr> WrappedGetAddrInfo(const std::string& name, bool allow_lookup)
46+
{
47+
addrinfo ai_hint{};
48+
// We want a TCP port, which is a streaming socket type
49+
ai_hint.ai_socktype = SOCK_STREAM;
50+
ai_hint.ai_protocol = IPPROTO_TCP;
51+
// We don't care which address family (IPv4 or IPv6) is returned
52+
ai_hint.ai_family = AF_UNSPEC;
53+
// If we allow lookups of hostnames, use the AI_ADDRCONFIG flag to only
54+
// return addresses whose family we have an address configured for.
55+
//
56+
// If we don't allow lookups, then use the AI_NUMERICHOST flag for
57+
// getaddrinfo to only decode numerical network addresses and suppress
58+
// hostname lookups.
59+
ai_hint.ai_flags = allow_lookup ? AI_ADDRCONFIG : AI_NUMERICHOST;
60+
61+
addrinfo* ai_res{nullptr};
62+
const int n_err{getaddrinfo(name.c_str(), nullptr, &ai_hint, &ai_res)};
63+
if (n_err != 0) {
64+
return {};
65+
}
66+
67+
// Traverse the linked list starting with ai_trav.
68+
addrinfo* ai_trav{ai_res};
69+
std::vector<CNetAddr> resolved_addresses;
70+
while (ai_trav != nullptr) {
71+
if (ai_trav->ai_family == AF_INET) {
72+
assert(ai_trav->ai_addrlen >= sizeof(sockaddr_in));
73+
resolved_addresses.emplace_back(reinterpret_cast<sockaddr_in*>(ai_trav->ai_addr)->sin_addr);
74+
}
75+
if (ai_trav->ai_family == AF_INET6) {
76+
assert(ai_trav->ai_addrlen >= sizeof(sockaddr_in6));
77+
const sockaddr_in6* s6{reinterpret_cast<sockaddr_in6*>(ai_trav->ai_addr)};
78+
resolved_addresses.emplace_back(s6->sin6_addr, s6->sin6_scope_id);
79+
}
80+
ai_trav = ai_trav->ai_next;
81+
}
82+
freeaddrinfo(ai_res);
83+
84+
return resolved_addresses;
85+
}
86+
87+
DNSLookupFn g_dns_lookup{WrappedGetAddrInfo};
88+
4589
enum Network ParseNetwork(const std::string& net_in) {
4690
std::string net = ToLower(net_in);
4791
if (net == "ipv4") return NET_IPV4;
@@ -87,7 +131,7 @@ std::vector<std::string> GetNetworkNames(bool append_unroutable)
87131
return names;
88132
}
89133

90-
bool static LookupIntern(const std::string& name, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup)
134+
static bool LookupIntern(const std::string& name, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup, DNSLookupFn dns_lookup_function)
91135
{
92136
vIP.clear();
93137

@@ -109,54 +153,16 @@ bool static LookupIntern(const std::string& name, std::vector<CNetAddr>& vIP, un
109153
}
110154
}
111155

112-
struct addrinfo aiHint;
113-
memset(&aiHint, 0, sizeof(struct addrinfo));
114-
115-
// We want a TCP port, which is a streaming socket type
116-
aiHint.ai_socktype = SOCK_STREAM;
117-
aiHint.ai_protocol = IPPROTO_TCP;
118-
// We don't care which address family (IPv4 or IPv6) is returned
119-
aiHint.ai_family = AF_UNSPEC;
120-
// If we allow lookups of hostnames, use the AI_ADDRCONFIG flag to only
121-
// return addresses whose family we have an address configured for.
122-
//
123-
// If we don't allow lookups, then use the AI_NUMERICHOST flag for
124-
// getaddrinfo to only decode numerical network addresses and suppress
125-
// hostname lookups.
126-
aiHint.ai_flags = fAllowLookup ? AI_ADDRCONFIG : AI_NUMERICHOST;
127-
struct addrinfo *aiRes = nullptr;
128-
int nErr = getaddrinfo(name.c_str(), nullptr, &aiHint, &aiRes);
129-
if (nErr)
130-
return false;
131-
132-
// Traverse the linked list starting with aiTrav, add all non-internal
133-
// IPv4,v6 addresses to vIP while respecting nMaxSolutions.
134-
struct addrinfo *aiTrav = aiRes;
135-
while (aiTrav != nullptr && (nMaxSolutions == 0 || vIP.size() < nMaxSolutions))
136-
{
137-
CNetAddr resolved;
138-
if (aiTrav->ai_family == AF_INET)
139-
{
140-
assert(aiTrav->ai_addrlen >= sizeof(sockaddr_in));
141-
resolved = CNetAddr(((struct sockaddr_in*)(aiTrav->ai_addr))->sin_addr);
142-
}
143-
144-
if (aiTrav->ai_family == AF_INET6)
145-
{
146-
assert(aiTrav->ai_addrlen >= sizeof(sockaddr_in6));
147-
struct sockaddr_in6* s6 = (struct sockaddr_in6*) aiTrav->ai_addr;
148-
resolved = CNetAddr(s6->sin6_addr, s6->sin6_scope_id);
156+
for (const CNetAddr& resolved : dns_lookup_function(name, fAllowLookup)) {
157+
if (nMaxSolutions > 0 && vIP.size() >= nMaxSolutions) {
158+
break;
149159
}
150160
/* Never allow resolving to an internal address. Consider any such result invalid */
151161
if (!resolved.IsInternal()) {
152162
vIP.push_back(resolved);
153163
}
154-
155-
aiTrav = aiTrav->ai_next;
156164
}
157165

158-
freeaddrinfo(aiRes);
159-
160166
return (vIP.size() > 0);
161167
}
162168

@@ -175,7 +181,7 @@ bool static LookupIntern(const std::string& name, std::vector<CNetAddr>& vIP, un
175181
* @see Lookup(const char *, std::vector<CService>&, int, bool, unsigned int)
176182
* for additional parameter descriptions.
177183
*/
178-
bool LookupHost(const std::string& name, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup)
184+
bool LookupHost(const std::string& name, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup, DNSLookupFn dns_lookup_function)
179185
{
180186
if (!ValidAsCString(name)) {
181187
return false;
@@ -187,7 +193,7 @@ bool LookupHost(const std::string& name, std::vector<CNetAddr>& vIP, unsigned in
187193
strHost = strHost.substr(1, strHost.size() - 2);
188194
}
189195

190-
return LookupIntern(strHost, vIP, nMaxSolutions, fAllowLookup);
196+
return LookupIntern(strHost, vIP, nMaxSolutions, fAllowLookup, dns_lookup_function);
191197
}
192198

193199
/**
@@ -196,13 +202,13 @@ bool LookupHost(const std::string& name, std::vector<CNetAddr>& vIP, unsigned in
196202
* @see LookupHost(const std::string&, std::vector<CNetAddr>&, unsigned int, bool) for
197203
* additional parameter descriptions.
198204
*/
199-
bool LookupHost(const std::string& name, CNetAddr& addr, bool fAllowLookup)
205+
bool LookupHost(const std::string& name, CNetAddr& addr, bool fAllowLookup, DNSLookupFn dns_lookup_function)
200206
{
201207
if (!ValidAsCString(name)) {
202208
return false;
203209
}
204210
std::vector<CNetAddr> vIP;
205-
LookupHost(name, vIP, 1, fAllowLookup);
211+
LookupHost(name, vIP, 1, fAllowLookup, dns_lookup_function);
206212
if(vIP.empty())
207213
return false;
208214
addr = vIP.front();
@@ -229,7 +235,7 @@ bool LookupHost(const std::string& name, CNetAddr& addr, bool fAllowLookup)
229235
* @returns Whether or not the service string successfully resolved to any
230236
* resulting services.
231237
*/
232-
bool Lookup(const std::string& name, std::vector<CService>& vAddr, int portDefault, bool fAllowLookup, unsigned int nMaxSolutions)
238+
bool Lookup(const std::string& name, std::vector<CService>& vAddr, int portDefault, bool fAllowLookup, unsigned int nMaxSolutions, DNSLookupFn dns_lookup_function)
233239
{
234240
if (name.empty() || !ValidAsCString(name)) {
235241
return false;
@@ -239,7 +245,7 @@ bool Lookup(const std::string& name, std::vector<CService>& vAddr, int portDefau
239245
SplitHostPort(name, port, hostname);
240246

241247
std::vector<CNetAddr> vIP;
242-
bool fRet = LookupIntern(hostname, vIP, nMaxSolutions, fAllowLookup);
248+
bool fRet = LookupIntern(hostname, vIP, nMaxSolutions, fAllowLookup, dns_lookup_function);
243249
if (!fRet)
244250
return false;
245251
vAddr.resize(vIP.size());
@@ -254,13 +260,13 @@ bool Lookup(const std::string& name, std::vector<CService>& vAddr, int portDefau
254260
* @see Lookup(const char *, std::vector<CService>&, int, bool, unsigned int)
255261
* for additional parameter descriptions.
256262
*/
257-
bool Lookup(const std::string& name, CService& addr, int portDefault, bool fAllowLookup)
263+
bool Lookup(const std::string& name, CService& addr, int portDefault, bool fAllowLookup, DNSLookupFn dns_lookup_function)
258264
{
259265
if (!ValidAsCString(name)) {
260266
return false;
261267
}
262268
std::vector<CService> vService;
263-
bool fRet = Lookup(name, vService, portDefault, fAllowLookup, 1);
269+
bool fRet = Lookup(name, vService, portDefault, fAllowLookup, 1, dns_lookup_function);
264270
if (!fRet)
265271
return false;
266272
addr = vService[0];
@@ -277,15 +283,15 @@ bool Lookup(const std::string& name, CService& addr, int portDefault, bool fAllo
277283
* @see Lookup(const char *, CService&, int, bool) for additional parameter
278284
* descriptions.
279285
*/
280-
CService LookupNumeric(const std::string& name, int portDefault)
286+
CService LookupNumeric(const std::string& name, int portDefault, DNSLookupFn dns_lookup_function)
281287
{
282288
if (!ValidAsCString(name)) {
283289
return {};
284290
}
285291
CService addr;
286292
// "1.2:345" will fail to resolve the ip, but will still set the port.
287293
// If the ip fails to resolve, re-init the result.
288-
if(!Lookup(name, addr, portDefault, false))
294+
if(!Lookup(name, addr, portDefault, false, dns_lookup_function))
289295
addr = CService();
290296
return addr;
291297
}
@@ -811,7 +817,7 @@ bool ConnectThroughProxy(const proxyType& proxy, const std::string& strDest, int
811817
*
812818
* @returns Whether the operation succeeded or not.
813819
*/
814-
bool LookupSubNet(const std::string& strSubnet, CSubNet& ret)
820+
bool LookupSubNet(const std::string& strSubnet, CSubNet& ret, DNSLookupFn dns_lookup_function)
815821
{
816822
if (!ValidAsCString(strSubnet)) {
817823
return false;
@@ -822,7 +828,7 @@ bool LookupSubNet(const std::string& strSubnet, CSubNet& ret)
822828
std::string strAddress = strSubnet.substr(0, slash);
823829
// TODO: Use LookupHost(const std::string&, CNetAddr&, bool) instead to just get
824830
// one CNetAddr.
825-
if (LookupHost(strAddress, vIP, 1, false))
831+
if (LookupHost(strAddress, vIP, 1, false, dns_lookup_function))
826832
{
827833
CNetAddr network = vIP[0];
828834
if (slash != strSubnet.npos)
@@ -837,7 +843,7 @@ bool LookupSubNet(const std::string& strSubnet, CSubNet& ret)
837843
else // If not a valid number, try full netmask syntax
838844
{
839845
// Never allow lookup for netmask
840-
if (LookupHost(strNetmask, vIP, 1, false)) {
846+
if (LookupHost(strNetmask, vIP, 1, false, dns_lookup_function)) {
841847
ret = CSubNet(network, vIP[0]);
842848
return ret.IsValid();
843849
}

src/netbase.h

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ struct ProxyCredentials
6464
std::string password;
6565
};
6666

67+
/**
68+
* Wrapper for getaddrinfo(3). Do not use directly: call Lookup/LookupHost/LookupNumeric/LookupSubNet.
69+
*/
70+
std::vector<CNetAddr> WrappedGetAddrInfo(const std::string& name, bool allow_lookup);
71+
6772
enum Network ParseNetwork(const std::string& net);
6873
std::string GetNetworkName(enum Network net);
6974
/** Return a vector of publicly routable Network names; optionally append NET_UNROUTABLE. */
@@ -74,12 +79,16 @@ bool IsProxy(const CNetAddr &addr);
7479
bool SetNameProxy(const proxyType &addrProxy);
7580
bool HaveNameProxy();
7681
bool GetNameProxy(proxyType &nameProxyOut);
77-
bool LookupHost(const std::string& name, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup);
78-
bool LookupHost(const std::string& name, CNetAddr& addr, bool fAllowLookup);
79-
bool Lookup(const std::string& name, CService& addr, int portDefault, bool fAllowLookup);
80-
bool Lookup(const std::string& name, std::vector<CService>& vAddr, int portDefault, bool fAllowLookup, unsigned int nMaxSolutions);
81-
CService LookupNumeric(const std::string& name, int portDefault = 0);
82-
bool LookupSubNet(const std::string& strSubnet, CSubNet& subnet);
82+
83+
using DNSLookupFn = std::function<std::vector<CNetAddr>(const std::string&, bool)>;
84+
extern DNSLookupFn g_dns_lookup;
85+
86+
bool LookupHost(const std::string& name, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup, DNSLookupFn dns_lookup_function = g_dns_lookup);
87+
bool LookupHost(const std::string& name, CNetAddr& addr, bool fAllowLookup, DNSLookupFn dns_lookup_function = g_dns_lookup);
88+
bool Lookup(const std::string& name, CService& addr, int portDefault, bool fAllowLookup, DNSLookupFn dns_lookup_function = g_dns_lookup);
89+
bool Lookup(const std::string& name, std::vector<CService>& vAddr, int portDefault, bool fAllowLookup, unsigned int nMaxSolutions, DNSLookupFn dns_lookup_function = g_dns_lookup);
90+
CService LookupNumeric(const std::string& name, int portDefault = 0, DNSLookupFn dns_lookup_function = g_dns_lookup);
91+
bool LookupSubNet(const std::string& strSubnet, CSubNet& subnet, DNSLookupFn dns_lookup_function = g_dns_lookup);
8392

8493
/**
8594
* Create a TCP socket in the given address family.

src/test/fuzz/netbase_dns_lookup.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright (c) 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 <netaddress.h>
6+
#include <netbase.h>
7+
#include <test/fuzz/FuzzedDataProvider.h>
8+
#include <test/fuzz/fuzz.h>
9+
#include <test/fuzz/util.h>
10+
11+
#include <cstdint>
12+
#include <string>
13+
#include <vector>
14+
15+
namespace {
16+
FuzzedDataProvider* fuzzed_data_provider_ptr = nullptr;
17+
18+
std::vector<CNetAddr> fuzzed_dns_lookup_function(const std::string& name, bool allow_lookup)
19+
{
20+
std::vector<CNetAddr> resolved_addresses;
21+
while (fuzzed_data_provider_ptr->ConsumeBool()) {
22+
resolved_addresses.push_back(ConsumeNetAddr(*fuzzed_data_provider_ptr));
23+
}
24+
return resolved_addresses;
25+
}
26+
} // namespace
27+
28+
FUZZ_TARGET(netbase_dns_lookup)
29+
{
30+
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
31+
fuzzed_data_provider_ptr = &fuzzed_data_provider;
32+
const std::string name = fuzzed_data_provider.ConsumeRandomLengthString(512);
33+
const unsigned int max_results = fuzzed_data_provider.ConsumeIntegral<unsigned int>();
34+
const bool allow_lookup = fuzzed_data_provider.ConsumeBool();
35+
const int default_port = fuzzed_data_provider.ConsumeIntegral<int>();
36+
{
37+
std::vector<CNetAddr> resolved_addresses;
38+
if (LookupHost(name, resolved_addresses, max_results, allow_lookup, fuzzed_dns_lookup_function)) {
39+
for (const CNetAddr& resolved_address : resolved_addresses) {
40+
assert(!resolved_address.IsInternal());
41+
}
42+
}
43+
assert(resolved_addresses.size() <= max_results || max_results == 0);
44+
}
45+
{
46+
CNetAddr resolved_address;
47+
if (LookupHost(name, resolved_address, allow_lookup, fuzzed_dns_lookup_function)) {
48+
assert(!resolved_address.IsInternal());
49+
}
50+
}
51+
{
52+
std::vector<CService> resolved_services;
53+
if (Lookup(name, resolved_services, default_port, allow_lookup, max_results, fuzzed_dns_lookup_function)) {
54+
for (const CNetAddr& resolved_service : resolved_services) {
55+
assert(!resolved_service.IsInternal());
56+
}
57+
}
58+
assert(resolved_services.size() <= max_results || max_results == 0);
59+
}
60+
{
61+
CService resolved_service;
62+
if (Lookup(name, resolved_service, default_port, allow_lookup, fuzzed_dns_lookup_function)) {
63+
assert(!resolved_service.IsInternal());
64+
}
65+
}
66+
{
67+
CService resolved_service = LookupNumeric(name, default_port, fuzzed_dns_lookup_function);
68+
assert(!resolved_service.IsInternal());
69+
}
70+
{
71+
CSubNet resolved_subnet;
72+
if (LookupSubNet(name, resolved_subnet, fuzzed_dns_lookup_function)) {
73+
assert(resolved_subnet.IsValid());
74+
}
75+
}
76+
fuzzed_data_provider_ptr = nullptr;
77+
}

0 commit comments

Comments
 (0)