I like separating the interface from the implementation - it makes it more readable for consumers who want to use it without being bothered with implementation details (actually it is better to not see the implementation so that one does not accidentally start relying on undocumented implementation details).
0commit 33c203d6878d9b6adee046f36311c58a13e79941 (HEAD -> pull/21617_1617705813_33333755f__2104-fuzzValgrind, vasild/2104-fuzzValgrind)
1Parent: 33333755f2edcbe88fcd136f6fef81f94819002e
2Author: Vasil Dimov <vd@FreeBSD.org>
3AuthorDate: Wed Apr 7 10:18:39 2021 +0200
4Commit: Vasil Dimov <vd@FreeBSD.org>
5CommitDate: Wed Apr 7 10:18:39 2021 +0200
6gpg: Signature made Wed Apr 7 10:18:59 2021 CEST
7gpg: using RSA key E64D8D45614DB07545D9CCC154DF06F64B55CBBF
8gpg: Good signature from "Vasil Dimov <vd@myforest.net>" [ultimate]
9gpg: aka "Vasil Dimov <vd@FreeBSD.org>" [ultimate]
10gpg: aka "Vasil Dimov <vasild@gmail.com>" [ultimate]
11
12
13 fuzz: split FuzzedSock interface and implementation
14
15diff --git a/src/test/fuzz/util.cpp b/src/test/fuzz/util.cpp
16index cf5244e31..2ab227e29 100644
17--- a/src/test/fuzz/util.cpp
18+++ b/src/test/fuzz/util.cpp
19@@ -4,21 +4,194 @@
20
21 #include <test/fuzz/util.h>
22 #include <test/util/script.h>
23 #include <util/rbf.h>
24 #include <version.h>
25
26+FuzzedSock::FuzzedSock(FuzzedDataProvider& fuzzed_data_provider)
27+ : m_fuzzed_data_provider{fuzzed_data_provider}
28+{
29+ m_socket = fuzzed_data_provider.ConsumeIntegral<SOCKET>();
30+}
31+
32+FuzzedSock::~FuzzedSock()
33+{
34+ // Sock::~Sock() will be called after FuzzedSock::~FuzzedSock() and it will call
35+ // Sock::Reset() (not FuzzedSock::Reset()!) which will call CloseSocket(m_socket).
36+ // Avoid closing an arbitrary file descriptor (m_socket is just a random number which
37+ // may concide with a real opened file descriptor).
38+ Reset();
39+}
40+
41+FuzzedSock& FuzzedSock::operator=(Sock&& other)
42+{
43+ assert(false && "Move of Sock into FuzzedSock not allowed.");
44+ return *this;
45+}
46+
47+void FuzzedSock::Reset()
48+{
49+ m_socket = INVALID_SOCKET;
50+}
51+
52+ssize_t FuzzedSock::Send(const void* data, size_t len, int flags) const
53+{
54+ constexpr std::array send_errnos{
55+ EACCES,
56+ EAGAIN,
57+ EALREADY,
58+ EBADF,
59+ ECONNRESET,
60+ EDESTADDRREQ,
61+ EFAULT,
62+ EINTR,
63+ EINVAL,
64+ EISCONN,
65+ EMSGSIZE,
66+ ENOBUFS,
67+ ENOMEM,
68+ ENOTCONN,
69+ ENOTSOCK,
70+ EOPNOTSUPP,
71+ EPIPE,
72+ EWOULDBLOCK,
73+ };
74+ if (m_fuzzed_data_provider.ConsumeBool()) {
75+ return len;
76+ }
77+ const ssize_t r = m_fuzzed_data_provider.ConsumeIntegralInRange<ssize_t>(-1, len);
78+ if (r == -1) {
79+ SetFuzzedErrNo(m_fuzzed_data_provider, send_errnos);
80+ }
81+ return r;
82+}
83+
84+ssize_t FuzzedSock::Recv(void* buf, size_t len, int flags) const
85+{
86+ // Have a permanent error at recv_errnos[0] because when the fuzzed data is exhausted
87+ // SetFuzzedErrNo() will always return the first element and we want to avoid Recv()
88+ // returning -1 and setting errno to EAGAIN repeatedly.
89+ constexpr std::array recv_errnos{
90+ ECONNREFUSED,
91+ EAGAIN,
92+ EBADF,
93+ EFAULT,
94+ EINTR,
95+ EINVAL,
96+ ENOMEM,
97+ ENOTCONN,
98+ ENOTSOCK,
99+ EWOULDBLOCK,
100+ };
101+ assert(buf != nullptr || len == 0);
102+ if (len == 0 || m_fuzzed_data_provider.ConsumeBool()) {
103+ const ssize_t r = m_fuzzed_data_provider.ConsumeBool() ? 0 : -1;
104+ if (r == -1) {
105+ SetFuzzedErrNo(m_fuzzed_data_provider, recv_errnos);
106+ }
107+ return r;
108+ }
109+ std::vector<uint8_t> random_bytes;
110+ bool pad_to_len_bytes{m_fuzzed_data_provider.ConsumeBool()};
111+ if (m_peek_data.has_value()) {
112+ // `MSG_PEEK` was used in the preceding `Recv()` call, return `m_peek_data`.
113+ random_bytes.assign({m_peek_data.value()});
114+ if ((flags & MSG_PEEK) == 0) {
115+ m_peek_data.reset();
116+ }
117+ pad_to_len_bytes = false;
118+ } else if ((flags & MSG_PEEK) != 0) {
119+ // New call with `MSG_PEEK`.
120+ random_bytes = m_fuzzed_data_provider.ConsumeBytes<uint8_t>(1);
121+ if (!random_bytes.empty()) {
122+ m_peek_data = random_bytes[0];
123+ pad_to_len_bytes = false;
124+ }
125+ } else {
126+ random_bytes = m_fuzzed_data_provider.ConsumeBytes<uint8_t>(
127+ m_fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, len));
128+ }
129+ if (random_bytes.empty()) {
130+ const ssize_t r = m_fuzzed_data_provider.ConsumeBool() ? 0 : -1;
131+ if (r == -1) {
132+ SetFuzzedErrNo(m_fuzzed_data_provider, recv_errnos);
133+ }
134+ return r;
135+ }
136+ std::memcpy(buf, random_bytes.data(), random_bytes.size());
137+ if (pad_to_len_bytes) {
138+ if (len > random_bytes.size()) {
139+ std::memset((char*)buf + random_bytes.size(), 0, len - random_bytes.size());
140+ }
141+ return len;
142+ }
143+ if (m_fuzzed_data_provider.ConsumeBool() && std::getenv("FUZZED_SOCKET_FAKE_LATENCY") != nullptr) {
144+ std::this_thread::sleep_for(std::chrono::milliseconds{2});
145+ }
146+ return random_bytes.size();
147+}
148+
149+int FuzzedSock::Connect(const sockaddr*, socklen_t) const
150+{
151+ // Have a permanent error at connect_errnos[0] because when the fuzzed data is exhausted
152+ // SetFuzzedErrNo() will always return the first element and we want to avoid Connect()
153+ // returning -1 and setting errno to EAGAIN repeatedly.
154+ constexpr std::array connect_errnos{
155+ ECONNREFUSED,
156+ EAGAIN,
157+ ECONNRESET,
158+ EHOSTUNREACH,
159+ EINPROGRESS,
160+ EINTR,
161+ ENETUNREACH,
162+ ETIMEDOUT,
163+ };
164+ if (m_fuzzed_data_provider.ConsumeBool()) {
165+ SetFuzzedErrNo(m_fuzzed_data_provider, connect_errnos);
166+ return -1;
167+ }
168+ return 0;
169+}
170+
171+int FuzzedSock::GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const
172+{
173+ constexpr std::array getsockopt_errnos{
174+ ENOMEM,
175+ ENOBUFS,
176+ };
177+ if (m_fuzzed_data_provider.ConsumeBool()) {
178+ SetFuzzedErrNo(m_fuzzed_data_provider, getsockopt_errnos);
179+ return -1;
180+ }
181+ if (opt_val == nullptr) {
182+ return 0;
183+ }
184+ std::memcpy(opt_val,
185+ ConsumeFixedLengthByteVector(m_fuzzed_data_provider, *opt_len).data(),
186+ *opt_len);
187+ return 0;
188+}
189+
190 bool FuzzedSock::Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred ) const
191 {
192 if (!m_fuzzed_data_provider.ConsumeBool()) {
193 return false;
194 }
195 if (occurred) *occurred = 0;
196 return true;
197 }
198
199+bool FuzzedSock::IsConnected(std::string& errmsg) const
200+{
201+ if (m_fuzzed_data_provider.ConsumeBool()) {
202+ return true;
203+ }
204+ errmsg = "disconnected at random by the fuzzer";
205+ return false;
206+}
207+
208 void FillNode(FuzzedDataProvider& fuzzed_data_provider, CNode& node, bool init_version) noexcept
209 {
210 const ServiceFlags remote_services = ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS);
211 const NetPermissionFlags permission_flags = ConsumeWeakEnum(fuzzed_data_provider, ALL_NET_PERMISSION_FLAGS);
212 const int32_t version = fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(MIN_PEER_PROTO_VERSION, std::numeric_limits<int32_t>::max());
213 const bool filter_txs = fuzzed_data_provider.ConsumeBool();
214diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h
215index adcdd7174..8f4f87fbd 100644
216--- a/src/test/fuzz/util.h
217+++ b/src/test/fuzz/util.h
218@@ -572,185 +572,31 @@ class FuzzedSock : public Sock
219 * If `MSG_PEEK` is used, then our `Recv()` returns some random data as usual, but on the next
220 * `Recv()` call we must return the same data, thus we remember it here.
221 */
222 mutable std::optional<uint8_t> m_peek_data;
223
224 public:
225- explicit FuzzedSock(FuzzedDataProvider& fuzzed_data_provider) : m_fuzzed_data_provider{fuzzed_data_provider}
226- {
227- m_socket = fuzzed_data_provider.ConsumeIntegral<SOCKET>();
228- }
229+ explicit FuzzedSock(FuzzedDataProvider& fuzzed_data_provider);
230
231- ~FuzzedSock() override
232- {
233- // Sock::~Sock() will be called after FuzzedSock::~FuzzedSock() and it will call
234- // Sock::Reset() (not FuzzedSock::Reset()!) which will call CloseSocket(m_socket).
235- // Avoid closing an arbitrary file descriptor (m_socket is just a random number which
236- // may concide with a real opened file descriptor).
237- Reset();
238- }
239+ ~FuzzedSock() override;
240
241- FuzzedSock& operator=(Sock&& other) override
242- {
243- assert(false && "Move of Sock into FuzzedSock not allowed.");
244- return *this;
245- }
246+ FuzzedSock& operator=(Sock&& other) override;
247
248- void Reset() override
249- {
250- m_socket = INVALID_SOCKET;
251- }
252+ void Reset() override;
253
254- ssize_t Send(const void* data, size_t len, int flags) const override
255- {
256- constexpr std::array send_errnos{
257- EACCES,
258- EAGAIN,
259- EALREADY,
260- EBADF,
261- ECONNRESET,
262- EDESTADDRREQ,
263- EFAULT,
264- EINTR,
265- EINVAL,
266- EISCONN,
267- EMSGSIZE,
268- ENOBUFS,
269- ENOMEM,
270- ENOTCONN,
271- ENOTSOCK,
272- EOPNOTSUPP,
273- EPIPE,
274- EWOULDBLOCK,
275- };
276- if (m_fuzzed_data_provider.ConsumeBool()) {
277- return len;
278- }
279- const ssize_t r = m_fuzzed_data_provider.ConsumeIntegralInRange<ssize_t>(-1, len);
280- if (r == -1) {
281- SetFuzzedErrNo(m_fuzzed_data_provider, send_errnos);
282- }
283- return r;
284- }
285+ ssize_t Send(const void* data, size_t len, int flags) const override;
286
287- ssize_t Recv(void* buf, size_t len, int flags) const override
288- {
289- // Have a permanent error at recv_errnos[0] because when the fuzzed data is exhausted
290- // SetFuzzedErrNo() will always return the first element and we want to avoid Recv()
291- // returning -1 and setting errno to EAGAIN repeatedly.
292- constexpr std::array recv_errnos{
293- ECONNREFUSED,
294- EAGAIN,
295- EBADF,
296- EFAULT,
297- EINTR,
298- EINVAL,
299- ENOMEM,
300- ENOTCONN,
301- ENOTSOCK,
302- EWOULDBLOCK,
303- };
304- assert(buf != nullptr || len == 0);
305- if (len == 0 || m_fuzzed_data_provider.ConsumeBool()) {
306- const ssize_t r = m_fuzzed_data_provider.ConsumeBool() ? 0 : -1;
307- if (r == -1) {
308- SetFuzzedErrNo(m_fuzzed_data_provider, recv_errnos);
309- }
310- return r;
311- }
312- std::vector<uint8_t> random_bytes;
313- bool pad_to_len_bytes{m_fuzzed_data_provider.ConsumeBool()};
314- if (m_peek_data.has_value()) {
315- // `MSG_PEEK` was used in the preceding `Recv()` call, return `m_peek_data`.
316- random_bytes.assign({m_peek_data.value()});
317- if ((flags & MSG_PEEK) == 0) {
318- m_peek_data.reset();
319- }
320- pad_to_len_bytes = false;
321- } else if ((flags & MSG_PEEK) != 0) {
322- // New call with `MSG_PEEK`.
323- random_bytes = m_fuzzed_data_provider.ConsumeBytes<uint8_t>(1);
324- if (!random_bytes.empty()) {
325- m_peek_data = random_bytes[0];
326- pad_to_len_bytes = false;
327- }
328- } else {
329- random_bytes = m_fuzzed_data_provider.ConsumeBytes<uint8_t>(
330- m_fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, len));
331- }
332- if (random_bytes.empty()) {
333- const ssize_t r = m_fuzzed_data_provider.ConsumeBool() ? 0 : -1;
334- if (r == -1) {
335- SetFuzzedErrNo(m_fuzzed_data_provider, recv_errnos);
336- }
337- return r;
338- }
339- std::memcpy(buf, random_bytes.data(), random_bytes.size());
340- if (pad_to_len_bytes) {
341- if (len > random_bytes.size()) {
342- std::memset((char*)buf + random_bytes.size(), 0, len - random_bytes.size());
343- }
344- return len;
345- }
346- if (m_fuzzed_data_provider.ConsumeBool() && std::getenv("FUZZED_SOCKET_FAKE_LATENCY") != nullptr) {
347- std::this_thread::sleep_for(std::chrono::milliseconds{2});
348- }
349- return random_bytes.size();
350- }
351+ ssize_t Recv(void* buf, size_t len, int flags) const override;
352
353- int Connect(const sockaddr*, socklen_t) const override
354- {
355- // Have a permanent error at connect_errnos[0] because when the fuzzed data is exhausted
356- // SetFuzzedErrNo() will always return the first element and we want to avoid Connect()
357- // returning -1 and setting errno to EAGAIN repeatedly.
358- constexpr std::array connect_errnos{
359- ECONNREFUSED,
360- EAGAIN,
361- ECONNRESET,
362- EHOSTUNREACH,
363- EINPROGRESS,
364- EINTR,
365- ENETUNREACH,
366- ETIMEDOUT,
367- };
368- if (m_fuzzed_data_provider.ConsumeBool()) {
369- SetFuzzedErrNo(m_fuzzed_data_provider, connect_errnos);
370- return -1;
371- }
372- return 0;
373- }
374+ int Connect(const sockaddr*, socklen_t) const override;
375
376- int GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const override
377- {
378- constexpr std::array getsockopt_errnos{
379- ENOMEM,
380- ENOBUFS,
381- };
382- if (m_fuzzed_data_provider.ConsumeBool()) {
383- SetFuzzedErrNo(m_fuzzed_data_provider, getsockopt_errnos);
384- return -1;
385- }
386- if (opt_val == nullptr) {
387- return 0;
388- }
389- std::memcpy(opt_val,
390- ConsumeFixedLengthByteVector(m_fuzzed_data_provider, *opt_len).data(),
391- *opt_len);
392- return 0;
393- }
394+ int GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const override;
395
396 bool Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred = nullptr) const override;
397
398- bool IsConnected(std::string& errmsg) const override
399- {
400- if (m_fuzzed_data_provider.ConsumeBool()) {
401- return true;
402- }
403- errmsg = "disconnected at random by the fuzzer";
404- return false;
405- }
406+ bool IsConnected(std::string& errmsg) const override;
407 };
408
409 [[nodiscard]] inline FuzzedSock ConsumeSock(FuzzedDataProvider& fuzzed_data_provider)
410 {
411 return FuzzedSock{fuzzed_data_provider};
412 }