aboutsummaryrefslogtreecommitdiff
path: root/src/test/fuzz/util.h
diff options
context:
space:
mode:
authorWladimir J. van der Laan <laanwj@protonmail.com>2021-03-30 17:27:07 +0200
committerWladimir J. van der Laan <laanwj@protonmail.com>2021-03-30 17:41:13 +0200
commitf9e86d89660002d3dd17211d8b030a47b95d2fa2 (patch)
treecb02dd5555388535322436bb12e6b33f52076214 /src/test/fuzz/util.h
parentdede9eb9247363aacc3816c7352494a3609fa28b (diff)
parent40316a37cb02cf8a9a8b2cbd4d7153ffa57e7ec5 (diff)
downloadbitcoin-f9e86d89660002d3dd17211d8b030a47b95d2fa2.tar.xz
Merge #21387: p2p: Refactor sock to add I2P fuzz and unit tests
40316a37cb02cf8a9a8b2cbd4d7153ffa57e7ec5 test: add I2P test for a runaway SAM proxy (Vasil Dimov) 2d8ac779708322e1235e823edfc9c8f6e2dd65e4 fuzz: add tests for the I2P Session public interface (Vasil Dimov) 9947e44de0cbd79e99d883443a9ac441d8c69713 i2p: use pointers to Sock to accommodate mocking (Vasil Dimov) 82d360b5a88d9057b6c09b61cd69e426c7a2412d net: change ConnectSocketDirectly() to take a Sock argument (Vasil Dimov) b5861100f85fef77b00f55dcdf01ffb4a2a112d8 net: add connect() and getsockopt() wrappers to Sock (Vasil Dimov) 5a887d49b2b39e59d7cce8e9d5b89c21ad694f8b fuzz: avoid FuzzedSock::Recv() repeated errors with EAGAIN (Vasil Dimov) 3088f83d016e7ebb6e6aa559e6326fa0ef0d6282 fuzz: extend FuzzedSock::Recv() to support MSG_PEEK (Vasil Dimov) 9b05c49ade729311a0f4388a109530ff8d0ed1f9 fuzz: implement unimplemented FuzzedSock methods (Vasil Dimov) Pull request description: Change the networking code and the I2P code to be fully mockable and use `FuzzedSocket` to fuzz the I2P methods `Listen()`, `Accept()` and `Connect()`. Add a mocked `Sock` implementation that returns a predefined data on reads and use it for a regression unit test for the bug fixed in https://github.com/bitcoin/bitcoin/pull/21407. ACKs for top commit: practicalswift: Tested ACK 40316a37cb02cf8a9a8b2cbd4d7153ffa57e7ec5 MarcoFalke: Concept ACK 40316a37cb jonatack: re-ACK 40316a37cb02cf8a9a8b2cbd4d7153ffa57e7ec5 reviewed `git range-diff 01bb3afb 23c861d 40316a3` and the new unit test commit, debug built, ran unit tests, ran bitcoind with an I2P service and network operation with seven I2P peers (2 in, 5 out) is looking nominal laanwj: Code review ACK 40316a37cb02cf8a9a8b2cbd4d7153ffa57e7ec5 Tree-SHA512: 7fc4f129849e16e0c7e16662d9f4d35dfcc369bb31450ee369a2b97bdca95285533bee7787983e881e5a3d248f912afb42b4a2299d5860ace7129b0b19623cc8
Diffstat (limited to 'src/test/fuzz/util.h')
-rw-r--r--src/test/fuzz/util.h107
1 files changed, 89 insertions, 18 deletions
diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h
index d5c54911c5..50d3ac66e5 100644
--- a/src/test/fuzz/util.h
+++ b/src/test/fuzz/util.h
@@ -567,36 +567,37 @@ class FuzzedSock : public Sock
{
FuzzedDataProvider& m_fuzzed_data_provider;
+ /**
+ * Data to return when `MSG_PEEK` is used as a `Recv()` flag.
+ * If `MSG_PEEK` is used, then our `Recv()` returns some random data as usual, but on the next
+ * `Recv()` call we must return the same data, thus we remember it here.
+ */
+ mutable std::optional<uint8_t> m_peek_data;
+
public:
explicit FuzzedSock(FuzzedDataProvider& fuzzed_data_provider) : m_fuzzed_data_provider{fuzzed_data_provider}
{
+ m_socket = fuzzed_data_provider.ConsumeIntegral<SOCKET>();
}
~FuzzedSock() override
{
+ // Sock::~Sock() will be called after FuzzedSock::~FuzzedSock() and it will call
+ // Sock::Reset() (not FuzzedSock::Reset()!) which will call CloseSocket(m_socket).
+ // Avoid closing an arbitrary file descriptor (m_socket is just a random number which
+ // may concide with a real opened file descriptor).
+ Reset();
}
FuzzedSock& operator=(Sock&& other) override
{
- assert(false && "Not implemented yet.");
+ assert(false && "Move of Sock into FuzzedSock not allowed.");
return *this;
}
- SOCKET Get() const override
- {
- assert(false && "Not implemented yet.");
- return INVALID_SOCKET;
- }
-
- SOCKET Release() override
- {
- assert(false && "Not implemented yet.");
- return INVALID_SOCKET;
- }
-
void Reset() override
{
- assert(false && "Not implemented yet.");
+ m_socket = INVALID_SOCKET;
}
ssize_t Send(const void* data, size_t len, int flags) const override
@@ -633,10 +634,13 @@ public:
ssize_t Recv(void* buf, size_t len, int flags) const override
{
+ // Have a permanent error at recv_errnos[0] because when the fuzzed data is exhausted
+ // SetFuzzedErrNo() will always return the first element and we want to avoid Recv()
+ // returning -1 and setting errno to EAGAIN repeatedly.
constexpr std::array recv_errnos{
+ ECONNREFUSED,
EAGAIN,
EBADF,
- ECONNREFUSED,
EFAULT,
EINTR,
EINVAL,
@@ -653,8 +657,26 @@ public:
}
return r;
}
- const std::vector<uint8_t> random_bytes = m_fuzzed_data_provider.ConsumeBytes<uint8_t>(
- m_fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, len));
+ std::vector<uint8_t> random_bytes;
+ bool pad_to_len_bytes{m_fuzzed_data_provider.ConsumeBool()};
+ if (m_peek_data.has_value()) {
+ // `MSG_PEEK` was used in the preceding `Recv()` call, return `m_peek_data`.
+ random_bytes.assign({m_peek_data.value()});
+ if ((flags & MSG_PEEK) == 0) {
+ m_peek_data.reset();
+ }
+ pad_to_len_bytes = false;
+ } else if ((flags & MSG_PEEK) != 0) {
+ // New call with `MSG_PEEK`.
+ random_bytes = m_fuzzed_data_provider.ConsumeBytes<uint8_t>(1);
+ if (!random_bytes.empty()) {
+ m_peek_data = random_bytes[0];
+ pad_to_len_bytes = false;
+ }
+ } else {
+ random_bytes = m_fuzzed_data_provider.ConsumeBytes<uint8_t>(
+ m_fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, len));
+ }
if (random_bytes.empty()) {
const ssize_t r = m_fuzzed_data_provider.ConsumeBool() ? 0 : -1;
if (r == -1) {
@@ -663,7 +685,7 @@ public:
return r;
}
std::memcpy(buf, random_bytes.data(), random_bytes.size());
- if (m_fuzzed_data_provider.ConsumeBool()) {
+ if (pad_to_len_bytes) {
if (len > random_bytes.size()) {
std::memset((char*)buf + random_bytes.size(), 0, len - random_bytes.size());
}
@@ -675,10 +697,59 @@ public:
return random_bytes.size();
}
+ int Connect(const sockaddr*, socklen_t) const override
+ {
+ // Have a permanent error at connect_errnos[0] because when the fuzzed data is exhausted
+ // SetFuzzedErrNo() will always return the first element and we want to avoid Connect()
+ // returning -1 and setting errno to EAGAIN repeatedly.
+ constexpr std::array connect_errnos{
+ ECONNREFUSED,
+ EAGAIN,
+ ECONNRESET,
+ EHOSTUNREACH,
+ EINPROGRESS,
+ EINTR,
+ ENETUNREACH,
+ ETIMEDOUT,
+ };
+ if (m_fuzzed_data_provider.ConsumeBool()) {
+ SetFuzzedErrNo(m_fuzzed_data_provider, connect_errnos);
+ return -1;
+ }
+ return 0;
+ }
+
+ int GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const override
+ {
+ constexpr std::array getsockopt_errnos{
+ ENOMEM,
+ ENOBUFS,
+ };
+ if (m_fuzzed_data_provider.ConsumeBool()) {
+ SetFuzzedErrNo(m_fuzzed_data_provider, getsockopt_errnos);
+ return -1;
+ }
+ if (opt_val == nullptr) {
+ return 0;
+ }
+ std::memcpy(opt_val,
+ ConsumeFixedLengthByteVector(m_fuzzed_data_provider, *opt_len).data(),
+ *opt_len);
+ return 0;
+ }
+
bool Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred = nullptr) const override
{
return m_fuzzed_data_provider.ConsumeBool();
}
+
+ bool IsConnected(std::string& errmsg) const override {
+ if (m_fuzzed_data_provider.ConsumeBool()) {
+ return true;
+ }
+ errmsg = "disconnected at random by the fuzzer";
+ return false;
+ }
};
[[nodiscard]] inline FuzzedSock ConsumeSock(FuzzedDataProvider& fuzzed_data_provider)