Skip to content

Commit 44bd315

Browse files
committed
Merge bitcoin/bitcoin#31676: fuzz: add targets for PCP and NAT-PMP port mapping requests
c73b59d fuzz: implement targets for PCP and NAT-PMP port mapping requests (Antoine Poinsot) 1695c8a fuzz: in FuzzedSock::GetSockName(), return a random-length name (Antoine Poinsot) 0d472c1 fuzz: never return an uninitialized sockaddr in FuzzedSock::GetSockName (Antoine Poinsot) 39b7e2b fuzz: add steady clock mocking to FuzzedSock (Antoine Poinsot) 6fe1c35 pcp: make NAT-PMP error codes uint16_t (Antoine Poinsot) 01906ce pcp: make the ToString method const (Antoine Poinsot) Pull request description: Based on bitcoin/bitcoin#31022, this introduces a fuzz target for `PCPRequestPortMap` and `NATPMPRequestPortMap`. Like in #31022 we set `CreateSock` to return a `Sock` which mocks the responses from the server and uses a mocked steady clock for the `Wait`s. Except here we simply respond with fuzzer-provided data until the client stop sending requests. We also sometimes inject errors and connection failures based on fuzzer-provided data. We reuse the existing `FuzzedSock`, so a preparatory commit is included that adds steady clock mocking to it. This may be useful for other harnesses as well. ACKs for top commit: laanwj: re-ACK c73b59d marcofleon: ACK c73b59d dergoegge: utACK c73b59d Tree-SHA512: 24cd4d958a0999946a0c3d164a242fc3f0a0b66770630252b881423ad0065d29fdaab765014d193b705d3eff397f201d51a88a3ca80c63fd3867745e6f21bb2b
2 parents 5b8fd7c + c73b59d commit 44bd315

File tree

6 files changed

+111
-6
lines changed

6 files changed

+111
-6
lines changed

src/common/pcp.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ constexpr uint8_t NATPMP_RESULT_UNSUPP_VERSION = 1;
8484
constexpr uint8_t NATPMP_RESULT_NO_RESOURCES = 4;
8585

8686
//! Mapping of NATPMP result code to string (RFC6886 3.5). Result codes <=2 match PCP.
87-
const std::map<uint8_t, std::string> NATPMP_RESULT_STR{
87+
const std::map<uint16_t, std::string> NATPMP_RESULT_STR{
8888
{0, "SUCCESS"},
8989
{1, "UNSUPP_VERSION"},
9090
{2, "NOT_AUTHORIZED"},
@@ -165,7 +165,7 @@ const std::map<uint8_t, std::string> PCP_RESULT_STR{
165165
};
166166

167167
//! Return human-readable string from NATPMP result code.
168-
std::string NATPMPResultString(uint8_t result_code)
168+
std::string NATPMPResultString(uint16_t result_code)
169169
{
170170
auto result_i = NATPMP_RESULT_STR.find(result_code);
171171
return strprintf("%s (code %d)", result_i == NATPMP_RESULT_STR.end() ? "(unknown)" : result_i->second, result_code);
@@ -512,7 +512,7 @@ std::variant<MappingResult, MappingError> PCPRequestPortMap(const PCPMappingNonc
512512
return MappingResult(PCP_VERSION, CService(internal, port), CService(external_addr, external_port), lifetime_ret);
513513
}
514514

515-
std::string MappingResult::ToString()
515+
std::string MappingResult::ToString() const
516516
{
517517
Assume(version == NATPMP_VERSION || version == PCP_VERSION);
518518
return strprintf("%s:%s -> %s (for %ds)",

src/common/pcp.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ struct MappingResult {
4040
uint32_t lifetime;
4141

4242
//! Format mapping as string for logging.
43-
std::string ToString();
43+
std::string ToString() const;
4444
};
4545

4646
//! Try to open a port using RFC 6886 NAT-PMP. IPv4 only.

src/test/fuzz/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ add_executable(fuzz
7575
p2p_handshake.cpp
7676
p2p_headers_presync.cpp
7777
p2p_transport_serialization.cpp
78+
pcp.cpp
7879
package_eval.cpp
7980
parse_hd_keypath.cpp
8081
parse_iso8601.cpp

src/test/fuzz/pcp.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright (c) 2024 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 <test/fuzz/FuzzedDataProvider.h>
6+
#include <test/fuzz/fuzz.h>
7+
#include <test/fuzz/util.h>
8+
#include <test/fuzz/util/net.h>
9+
10+
#include <common/pcp.h>
11+
#include <util/check.h>
12+
13+
using namespace std::literals;
14+
15+
//! Fixed nonce to use in PCP port mapping requests.
16+
constexpr PCPMappingNonce PCP_NONCE{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc};
17+
18+
//! Number of attempts to request a NAT-PMP or PCP port mapping to the gateway.
19+
constexpr int NUM_TRIES{5};
20+
21+
//! Timeout for each attempt to request a port mapping.
22+
constexpr std::chrono::duration TIMEOUT{100ms};
23+
24+
void port_map_target_init()
25+
{
26+
LogInstance().DisableLogging();
27+
}
28+
29+
FUZZ_TARGET(pcp_request_port_map, .init = port_map_target_init)
30+
{
31+
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
32+
33+
// Create a mocked socket between random (and potentially invalid) client and gateway addresses.
34+
CreateSock = [&](int domain, int type, int protocol) {
35+
if ((domain == AF_INET || domain == AF_INET6) && type == SOCK_DGRAM && protocol == IPPROTO_UDP) {
36+
return std::make_unique<FuzzedSock>(fuzzed_data_provider);
37+
}
38+
return std::unique_ptr<FuzzedSock>();
39+
};
40+
41+
// Perform the port mapping request. The mocked socket will return fuzzer-provided data.
42+
const auto gateway_addr{ConsumeNetAddr(fuzzed_data_provider)};
43+
const auto local_addr{ConsumeNetAddr(fuzzed_data_provider)};
44+
const auto port{fuzzed_data_provider.ConsumeIntegral<uint16_t>()};
45+
const auto lifetime{fuzzed_data_provider.ConsumeIntegral<uint32_t>()};
46+
const auto res{PCPRequestPortMap(PCP_NONCE, gateway_addr, local_addr, port, lifetime, NUM_TRIES, TIMEOUT)};
47+
48+
// In case of success the mapping must be consistent with the request.
49+
if (const MappingResult* mapping = std::get_if<MappingResult>(&res)) {
50+
Assert(mapping);
51+
Assert(mapping->internal.GetPort() == port);
52+
mapping->ToString();
53+
}
54+
}
55+
56+
FUZZ_TARGET(natpmp_request_port_map, .init = port_map_target_init)
57+
{
58+
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
59+
60+
// Create a mocked socket between random (and potentially invalid) client and gateway addresses.
61+
CreateSock = [&](int domain, int type, int protocol) {
62+
if (domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP) {
63+
return std::make_unique<FuzzedSock>(fuzzed_data_provider);
64+
}
65+
return std::unique_ptr<FuzzedSock>();
66+
};
67+
68+
// Perform the port mapping request. The mocked socket will return fuzzer-provided data.
69+
const auto gateway_addr{ConsumeNetAddr(fuzzed_data_provider)};
70+
const auto port{fuzzed_data_provider.ConsumeIntegral<uint16_t>()};
71+
const auto lifetime{fuzzed_data_provider.ConsumeIntegral<uint32_t>()};
72+
const auto res{NATPMPRequestPortMap(gateway_addr, port, lifetime, NUM_TRIES, TIMEOUT)};
73+
74+
// In case of success the mapping must be consistent with the request.
75+
if (const MappingResult* mapping = std::get_if<MappingResult>(&res)) {
76+
Assert(mapping);
77+
Assert(mapping->internal.GetPort() == port);
78+
mapping->ToString();
79+
}
80+
}

src/test/fuzz/util/net.cpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,10 @@ template CAddress::SerParams ConsumeDeserializationParams(FuzzedDataProvider&) n
113113
FuzzedSock::FuzzedSock(FuzzedDataProvider& fuzzed_data_provider)
114114
: Sock{fuzzed_data_provider.ConsumeIntegralInRange<SOCKET>(INVALID_SOCKET - 1, INVALID_SOCKET)},
115115
m_fuzzed_data_provider{fuzzed_data_provider},
116-
m_selectable{fuzzed_data_provider.ConsumeBool()}
116+
m_selectable{fuzzed_data_provider.ConsumeBool()},
117+
m_time{MockableSteadyClock::INITIAL_MOCK_TIME}
117118
{
119+
ElapseTime(std::chrono::seconds(0)); // start mocking the steady clock.
118120
}
119121

120122
FuzzedSock::~FuzzedSock()
@@ -126,6 +128,12 @@ FuzzedSock::~FuzzedSock()
126128
m_socket = INVALID_SOCKET;
127129
}
128130

131+
void FuzzedSock::ElapseTime(std::chrono::milliseconds duration) const
132+
{
133+
m_time += duration;
134+
MockableSteadyClock::SetMockTime(m_time);
135+
}
136+
129137
FuzzedSock& FuzzedSock::operator=(Sock&& other)
130138
{
131139
assert(false && "Move of Sock into FuzzedSock not allowed.");
@@ -349,7 +357,11 @@ int FuzzedSock::GetSockName(sockaddr* name, socklen_t* name_len) const
349357
SetFuzzedErrNo(m_fuzzed_data_provider, getsockname_errnos);
350358
return -1;
351359
}
352-
*name_len = m_fuzzed_data_provider.ConsumeData(name, *name_len);
360+
assert(name_len);
361+
const auto bytes{ConsumeRandomLengthByteVector(m_fuzzed_data_provider, *name_len)};
362+
if (bytes.size() < (int)sizeof(sockaddr)) return -1;
363+
std::memcpy(name, bytes.data(), bytes.size());
364+
*name_len = bytes.size();
353365
return 0;
354366
}
355367

@@ -388,6 +400,7 @@ bool FuzzedSock::Wait(std::chrono::milliseconds timeout, Event requested, Event*
388400
// FuzzedDataProvider runs out of data.
389401
*occurred = m_fuzzed_data_provider.ConsumeBool() ? 0 : requested;
390402
}
403+
ElapseTime(timeout);
391404
return true;
392405
}
393406

@@ -400,6 +413,7 @@ bool FuzzedSock::WaitMany(std::chrono::milliseconds timeout, EventsPerSock& even
400413
// FuzzedDataProvider runs out of data.
401414
events.occurred = m_fuzzed_data_provider.ConsumeBool() ? 0 : events.requested;
402415
}
416+
ElapseTime(timeout);
403417
return true;
404418
}
405419

src/test/fuzz/util/net.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,16 @@ class FuzzedSock : public Sock
157157
*/
158158
const bool m_selectable;
159159

160+
/**
161+
* Used to mock the steady clock in methods waiting for a given duration.
162+
*/
163+
mutable std::chrono::milliseconds m_time;
164+
165+
/**
166+
* Set the value of the mocked steady clock such as that many ms have passed.
167+
*/
168+
void ElapseTime(std::chrono::milliseconds duration) const;
169+
160170
public:
161171
explicit FuzzedSock(FuzzedDataProvider& fuzzed_data_provider);
162172

0 commit comments

Comments
 (0)