Skip to content

Commit 29ae1c1

Browse files
committed
fuzz: split FuzzedSock interface and implementation
Move the `FuzzedSock`'s implementation from `src/test/fuzz/util.h` to `src/test/fuzz/util.cpp`. A separate interface and implementation make the code more readable for consumers who don't need to (better not) know the implementation details.
1 parent 9668e43 commit 29ae1c1

File tree

2 files changed

+182
-163
lines changed

2 files changed

+182
-163
lines changed

src/test/fuzz/util.cpp

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,170 @@
77
#include <util/rbf.h>
88
#include <version.h>
99

10+
FuzzedSock::FuzzedSock(FuzzedDataProvider& fuzzed_data_provider)
11+
: m_fuzzed_data_provider{fuzzed_data_provider}
12+
{
13+
m_socket = fuzzed_data_provider.ConsumeIntegralInRange<SOCKET>(INVALID_SOCKET - 1, INVALID_SOCKET);
14+
}
15+
16+
FuzzedSock::~FuzzedSock()
17+
{
18+
// Sock::~Sock() will be called after FuzzedSock::~FuzzedSock() and it will call
19+
// Sock::Reset() (not FuzzedSock::Reset()!) which will call CloseSocket(m_socket).
20+
// Avoid closing an arbitrary file descriptor (m_socket is just a random very high number which
21+
// theoretically may concide with a real opened file descriptor).
22+
Reset();
23+
}
24+
25+
FuzzedSock& FuzzedSock::operator=(Sock&& other)
26+
{
27+
assert(false && "Move of Sock into FuzzedSock not allowed.");
28+
return *this;
29+
}
30+
31+
void FuzzedSock::Reset()
32+
{
33+
m_socket = INVALID_SOCKET;
34+
}
35+
36+
ssize_t FuzzedSock::Send(const void* data, size_t len, int flags) const
37+
{
38+
constexpr std::array send_errnos{
39+
EACCES,
40+
EAGAIN,
41+
EALREADY,
42+
EBADF,
43+
ECONNRESET,
44+
EDESTADDRREQ,
45+
EFAULT,
46+
EINTR,
47+
EINVAL,
48+
EISCONN,
49+
EMSGSIZE,
50+
ENOBUFS,
51+
ENOMEM,
52+
ENOTCONN,
53+
ENOTSOCK,
54+
EOPNOTSUPP,
55+
EPIPE,
56+
EWOULDBLOCK,
57+
};
58+
if (m_fuzzed_data_provider.ConsumeBool()) {
59+
return len;
60+
}
61+
const ssize_t r = m_fuzzed_data_provider.ConsumeIntegralInRange<ssize_t>(-1, len);
62+
if (r == -1) {
63+
SetFuzzedErrNo(m_fuzzed_data_provider, send_errnos);
64+
}
65+
return r;
66+
}
67+
68+
ssize_t FuzzedSock::Recv(void* buf, size_t len, int flags) const
69+
{
70+
// Have a permanent error at recv_errnos[0] because when the fuzzed data is exhausted
71+
// SetFuzzedErrNo() will always return the first element and we want to avoid Recv()
72+
// returning -1 and setting errno to EAGAIN repeatedly.
73+
constexpr std::array recv_errnos{
74+
ECONNREFUSED,
75+
EAGAIN,
76+
EBADF,
77+
EFAULT,
78+
EINTR,
79+
EINVAL,
80+
ENOMEM,
81+
ENOTCONN,
82+
ENOTSOCK,
83+
EWOULDBLOCK,
84+
};
85+
assert(buf != nullptr || len == 0);
86+
if (len == 0 || m_fuzzed_data_provider.ConsumeBool()) {
87+
const ssize_t r = m_fuzzed_data_provider.ConsumeBool() ? 0 : -1;
88+
if (r == -1) {
89+
SetFuzzedErrNo(m_fuzzed_data_provider, recv_errnos);
90+
}
91+
return r;
92+
}
93+
std::vector<uint8_t> random_bytes;
94+
bool pad_to_len_bytes{m_fuzzed_data_provider.ConsumeBool()};
95+
if (m_peek_data.has_value()) {
96+
// `MSG_PEEK` was used in the preceding `Recv()` call, return `m_peek_data`.
97+
random_bytes.assign({m_peek_data.value()});
98+
if ((flags & MSG_PEEK) == 0) {
99+
m_peek_data.reset();
100+
}
101+
pad_to_len_bytes = false;
102+
} else if ((flags & MSG_PEEK) != 0) {
103+
// New call with `MSG_PEEK`.
104+
random_bytes = m_fuzzed_data_provider.ConsumeBytes<uint8_t>(1);
105+
if (!random_bytes.empty()) {
106+
m_peek_data = random_bytes[0];
107+
pad_to_len_bytes = false;
108+
}
109+
} else {
110+
random_bytes = m_fuzzed_data_provider.ConsumeBytes<uint8_t>(
111+
m_fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, len));
112+
}
113+
if (random_bytes.empty()) {
114+
const ssize_t r = m_fuzzed_data_provider.ConsumeBool() ? 0 : -1;
115+
if (r == -1) {
116+
SetFuzzedErrNo(m_fuzzed_data_provider, recv_errnos);
117+
}
118+
return r;
119+
}
120+
std::memcpy(buf, random_bytes.data(), random_bytes.size());
121+
if (pad_to_len_bytes) {
122+
if (len > random_bytes.size()) {
123+
std::memset((char*)buf + random_bytes.size(), 0, len - random_bytes.size());
124+
}
125+
return len;
126+
}
127+
if (m_fuzzed_data_provider.ConsumeBool() && std::getenv("FUZZED_SOCKET_FAKE_LATENCY") != nullptr) {
128+
std::this_thread::sleep_for(std::chrono::milliseconds{2});
129+
}
130+
return random_bytes.size();
131+
}
132+
133+
int FuzzedSock::Connect(const sockaddr*, socklen_t) const
134+
{
135+
// Have a permanent error at connect_errnos[0] because when the fuzzed data is exhausted
136+
// SetFuzzedErrNo() will always return the first element and we want to avoid Connect()
137+
// returning -1 and setting errno to EAGAIN repeatedly.
138+
constexpr std::array connect_errnos{
139+
ECONNREFUSED,
140+
EAGAIN,
141+
ECONNRESET,
142+
EHOSTUNREACH,
143+
EINPROGRESS,
144+
EINTR,
145+
ENETUNREACH,
146+
ETIMEDOUT,
147+
};
148+
if (m_fuzzed_data_provider.ConsumeBool()) {
149+
SetFuzzedErrNo(m_fuzzed_data_provider, connect_errnos);
150+
return -1;
151+
}
152+
return 0;
153+
}
154+
155+
int FuzzedSock::GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const
156+
{
157+
constexpr std::array getsockopt_errnos{
158+
ENOMEM,
159+
ENOBUFS,
160+
};
161+
if (m_fuzzed_data_provider.ConsumeBool()) {
162+
SetFuzzedErrNo(m_fuzzed_data_provider, getsockopt_errnos);
163+
return -1;
164+
}
165+
if (opt_val == nullptr) {
166+
return 0;
167+
}
168+
std::memcpy(opt_val,
169+
ConsumeFixedLengthByteVector(m_fuzzed_data_provider, *opt_len).data(),
170+
*opt_len);
171+
return 0;
172+
}
173+
10174
bool FuzzedSock::Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred) const
11175
{
12176
constexpr std::array wait_errnos{
@@ -24,6 +188,15 @@ bool FuzzedSock::Wait(std::chrono::milliseconds timeout, Event requested, Event*
24188
return true;
25189
}
26190

191+
bool FuzzedSock::IsConnected(std::string& errmsg) const
192+
{
193+
if (m_fuzzed_data_provider.ConsumeBool()) {
194+
return true;
195+
}
196+
errmsg = "disconnected at random by the fuzzer";
197+
return false;
198+
}
199+
27200
void FillNode(FuzzedDataProvider& fuzzed_data_provider, CNode& node, bool init_version) noexcept
28201
{
29202
const ServiceFlags remote_services = ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS);

src/test/fuzz/util.h

Lines changed: 9 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -575,179 +575,25 @@ class FuzzedSock : public Sock
575575
mutable std::optional<uint8_t> m_peek_data;
576576

577577
public:
578-
explicit FuzzedSock(FuzzedDataProvider& fuzzed_data_provider) : m_fuzzed_data_provider{fuzzed_data_provider}
579-
{
580-
m_socket = fuzzed_data_provider.ConsumeIntegralInRange<SOCKET>(INVALID_SOCKET - 1, INVALID_SOCKET);
581-
}
578+
explicit FuzzedSock(FuzzedDataProvider& fuzzed_data_provider);
582579

583-
~FuzzedSock() override
584-
{
585-
// Sock::~Sock() will be called after FuzzedSock::~FuzzedSock() and it will call
586-
// Sock::Reset() (not FuzzedSock::Reset()!) which will call CloseSocket(m_socket).
587-
// Avoid closing an arbitrary file descriptor (m_socket is just a random very high number which
588-
// theoretically may concide with a real opened file descriptor).
589-
Reset();
590-
}
580+
~FuzzedSock() override;
591581

592-
FuzzedSock& operator=(Sock&& other) override
593-
{
594-
assert(false && "Move of Sock into FuzzedSock not allowed.");
595-
return *this;
596-
}
582+
FuzzedSock& operator=(Sock&& other) override;
597583

598-
void Reset() override
599-
{
600-
m_socket = INVALID_SOCKET;
601-
}
584+
void Reset() override;
602585

603-
ssize_t Send(const void* data, size_t len, int flags) const override
604-
{
605-
constexpr std::array send_errnos{
606-
EACCES,
607-
EAGAIN,
608-
EALREADY,
609-
EBADF,
610-
ECONNRESET,
611-
EDESTADDRREQ,
612-
EFAULT,
613-
EINTR,
614-
EINVAL,
615-
EISCONN,
616-
EMSGSIZE,
617-
ENOBUFS,
618-
ENOMEM,
619-
ENOTCONN,
620-
ENOTSOCK,
621-
EOPNOTSUPP,
622-
EPIPE,
623-
EWOULDBLOCK,
624-
};
625-
if (m_fuzzed_data_provider.ConsumeBool()) {
626-
return len;
627-
}
628-
const ssize_t r = m_fuzzed_data_provider.ConsumeIntegralInRange<ssize_t>(-1, len);
629-
if (r == -1) {
630-
SetFuzzedErrNo(m_fuzzed_data_provider, send_errnos);
631-
}
632-
return r;
633-
}
586+
ssize_t Send(const void* data, size_t len, int flags) const override;
634587

635-
ssize_t Recv(void* buf, size_t len, int flags) const override
636-
{
637-
// Have a permanent error at recv_errnos[0] because when the fuzzed data is exhausted
638-
// SetFuzzedErrNo() will always return the first element and we want to avoid Recv()
639-
// returning -1 and setting errno to EAGAIN repeatedly.
640-
constexpr std::array recv_errnos{
641-
ECONNREFUSED,
642-
EAGAIN,
643-
EBADF,
644-
EFAULT,
645-
EINTR,
646-
EINVAL,
647-
ENOMEM,
648-
ENOTCONN,
649-
ENOTSOCK,
650-
EWOULDBLOCK,
651-
};
652-
assert(buf != nullptr || len == 0);
653-
if (len == 0 || m_fuzzed_data_provider.ConsumeBool()) {
654-
const ssize_t r = m_fuzzed_data_provider.ConsumeBool() ? 0 : -1;
655-
if (r == -1) {
656-
SetFuzzedErrNo(m_fuzzed_data_provider, recv_errnos);
657-
}
658-
return r;
659-
}
660-
std::vector<uint8_t> random_bytes;
661-
bool pad_to_len_bytes{m_fuzzed_data_provider.ConsumeBool()};
662-
if (m_peek_data.has_value()) {
663-
// `MSG_PEEK` was used in the preceding `Recv()` call, return `m_peek_data`.
664-
random_bytes.assign({m_peek_data.value()});
665-
if ((flags & MSG_PEEK) == 0) {
666-
m_peek_data.reset();
667-
}
668-
pad_to_len_bytes = false;
669-
} else if ((flags & MSG_PEEK) != 0) {
670-
// New call with `MSG_PEEK`.
671-
random_bytes = m_fuzzed_data_provider.ConsumeBytes<uint8_t>(1);
672-
if (!random_bytes.empty()) {
673-
m_peek_data = random_bytes[0];
674-
pad_to_len_bytes = false;
675-
}
676-
} else {
677-
random_bytes = m_fuzzed_data_provider.ConsumeBytes<uint8_t>(
678-
m_fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, len));
679-
}
680-
if (random_bytes.empty()) {
681-
const ssize_t r = m_fuzzed_data_provider.ConsumeBool() ? 0 : -1;
682-
if (r == -1) {
683-
SetFuzzedErrNo(m_fuzzed_data_provider, recv_errnos);
684-
}
685-
return r;
686-
}
687-
std::memcpy(buf, random_bytes.data(), random_bytes.size());
688-
if (pad_to_len_bytes) {
689-
if (len > random_bytes.size()) {
690-
std::memset((char*)buf + random_bytes.size(), 0, len - random_bytes.size());
691-
}
692-
return len;
693-
}
694-
if (m_fuzzed_data_provider.ConsumeBool() && std::getenv("FUZZED_SOCKET_FAKE_LATENCY") != nullptr) {
695-
std::this_thread::sleep_for(std::chrono::milliseconds{2});
696-
}
697-
return random_bytes.size();
698-
}
588+
ssize_t Recv(void* buf, size_t len, int flags) const override;
699589

700-
int Connect(const sockaddr*, socklen_t) const override
701-
{
702-
// Have a permanent error at connect_errnos[0] because when the fuzzed data is exhausted
703-
// SetFuzzedErrNo() will always return the first element and we want to avoid Connect()
704-
// returning -1 and setting errno to EAGAIN repeatedly.
705-
constexpr std::array connect_errnos{
706-
ECONNREFUSED,
707-
EAGAIN,
708-
ECONNRESET,
709-
EHOSTUNREACH,
710-
EINPROGRESS,
711-
EINTR,
712-
ENETUNREACH,
713-
ETIMEDOUT,
714-
};
715-
if (m_fuzzed_data_provider.ConsumeBool()) {
716-
SetFuzzedErrNo(m_fuzzed_data_provider, connect_errnos);
717-
return -1;
718-
}
719-
return 0;
720-
}
590+
int Connect(const sockaddr*, socklen_t) const override;
721591

722-
int GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const override
723-
{
724-
constexpr std::array getsockopt_errnos{
725-
ENOMEM,
726-
ENOBUFS,
727-
};
728-
if (m_fuzzed_data_provider.ConsumeBool()) {
729-
SetFuzzedErrNo(m_fuzzed_data_provider, getsockopt_errnos);
730-
return -1;
731-
}
732-
if (opt_val == nullptr) {
733-
return 0;
734-
}
735-
std::memcpy(opt_val,
736-
ConsumeFixedLengthByteVector(m_fuzzed_data_provider, *opt_len).data(),
737-
*opt_len);
738-
return 0;
739-
}
592+
int GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const override;
740593

741594
bool Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred = nullptr) const override;
742595

743-
bool IsConnected(std::string& errmsg) const override
744-
{
745-
if (m_fuzzed_data_provider.ConsumeBool()) {
746-
return true;
747-
}
748-
errmsg = "disconnected at random by the fuzzer";
749-
return false;
750-
}
596+
bool IsConnected(std::string& errmsg) const override;
751597
};
752598

753599
[[nodiscard]] inline FuzzedSock ConsumeSock(FuzzedDataProvider& fuzzed_data_provider)

0 commit comments

Comments
 (0)