Skip to content

Commit ebd8d66

Browse files
author
MarcoFalke
committed
Merge #19203: net: Add regression fuzz harness for CVE-2017-18350. Add FuzzedSocket.
366e3e1 fuzz: Add FUZZED_SOCKET_FAKE_LATENCY mode to FuzzedSock to allow for fuzzing timeout logic (practicalswift) b22d4c1 fuzz: Add fuzzing harness for Socks5(...) (practicalswift) Pull request description: Add [regression fuzz harness](https://twitter.com/kayseesee/status/1205287895923212289) for CVE-2017-18350. This fuzzing harness would have found CVE-2017-18350 within a minute of fuzzing :) 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: vasild: ACK 366e3e1 Tree-SHA512: 5d8e1863b635efd10ccb11678b71472ba1523c3ef16affa7f9cd638635c1a9c307e28f432d5b87eb0c9cd1c3c1aeafbb24fa7ae86fe4e5090fda2e20d542b6ca
2 parents 97a35f3 + 366e3e1 commit ebd8d66

File tree

5 files changed

+188
-17
lines changed

5 files changed

+188
-17
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ test_fuzz_fuzz_SOURCES = \
285285
test/fuzz/secp256k1_ecdsa_signature_parse_der_lax.cpp \
286286
test/fuzz/signature_checker.cpp \
287287
test/fuzz/signet.cpp \
288+
test/fuzz/socks5.cpp \
288289
test/fuzz/span.cpp \
289290
test/fuzz/spanparsing.cpp \
290291
test/fuzz/string.cpp \

src/netbase.cpp

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ int nConnectTimeout = DEFAULT_CONNECT_TIMEOUT;
3939
bool fNameLookup = DEFAULT_NAME_LOOKUP;
4040

4141
// Need ample time for negotiation for very slow proxies such as Tor (milliseconds)
42-
static const int SOCKS5_RECV_TIMEOUT = 20 * 1000;
42+
int g_socks5_recv_timeout = 20 * 1000;
4343
static std::atomic<bool> interruptSocks5Recv(false);
4444

4545
enum Network ParseNetwork(const std::string& net_in) {
@@ -389,13 +389,6 @@ static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, c
389389
return len == 0 ? IntrRecvError::OK : IntrRecvError::Timeout;
390390
}
391391

392-
/** Credentials for proxy authentication */
393-
struct ProxyCredentials
394-
{
395-
std::string username;
396-
std::string password;
397-
};
398-
399392
/** Convert SOCKS5 reply to an error message */
400393
static std::string Socks5ErrorString(uint8_t err)
401394
{
@@ -439,7 +432,7 @@ static std::string Socks5ErrorString(uint8_t err)
439432
* @see <a href="https://www.ietf.org/rfc/rfc1928.txt">RFC1928: SOCKS Protocol
440433
* Version 5</a>
441434
*/
442-
static bool Socks5(const std::string& strDest, int port, const ProxyCredentials* auth, const Sock& sock)
435+
bool Socks5(const std::string& strDest, int port, const ProxyCredentials* auth, const Sock& sock)
443436
{
444437
IntrRecvError recvr;
445438
LogPrint(BCLog::NET, "SOCKS5 connecting %s\n", strDest);
@@ -462,7 +455,7 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials*
462455
return error("Error sending to proxy");
463456
}
464457
uint8_t pchRet1[2];
465-
if ((recvr = InterruptibleRecv(pchRet1, 2, SOCKS5_RECV_TIMEOUT, sock)) != IntrRecvError::OK) {
458+
if ((recvr = InterruptibleRecv(pchRet1, 2, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) {
466459
LogPrintf("Socks5() connect to %s:%d failed: InterruptibleRecv() timeout or other failure\n", strDest, port);
467460
return false;
468461
}
@@ -485,7 +478,7 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials*
485478
}
486479
LogPrint(BCLog::PROXY, "SOCKS5 sending proxy authentication %s:%s\n", auth->username, auth->password);
487480
uint8_t pchRetA[2];
488-
if ((recvr = InterruptibleRecv(pchRetA, 2, SOCKS5_RECV_TIMEOUT, sock)) != IntrRecvError::OK) {
481+
if ((recvr = InterruptibleRecv(pchRetA, 2, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) {
489482
return error("Error reading proxy authentication response");
490483
}
491484
if (pchRetA[0] != 0x01 || pchRetA[1] != 0x00) {
@@ -510,7 +503,7 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials*
510503
return error("Error sending to proxy");
511504
}
512505
uint8_t pchRet2[4];
513-
if ((recvr = InterruptibleRecv(pchRet2, 4, SOCKS5_RECV_TIMEOUT, sock)) != IntrRecvError::OK) {
506+
if ((recvr = InterruptibleRecv(pchRet2, 4, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) {
514507
if (recvr == IntrRecvError::Timeout) {
515508
/* If a timeout happens here, this effectively means we timed out while connecting
516509
* to the remote node. This is very common for Tor, so do not print an
@@ -534,24 +527,24 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials*
534527
uint8_t pchRet3[256];
535528
switch (pchRet2[3])
536529
{
537-
case SOCKS5Atyp::IPV4: recvr = InterruptibleRecv(pchRet3, 4, SOCKS5_RECV_TIMEOUT, sock); break;
538-
case SOCKS5Atyp::IPV6: recvr = InterruptibleRecv(pchRet3, 16, SOCKS5_RECV_TIMEOUT, sock); break;
530+
case SOCKS5Atyp::IPV4: recvr = InterruptibleRecv(pchRet3, 4, g_socks5_recv_timeout, sock); break;
531+
case SOCKS5Atyp::IPV6: recvr = InterruptibleRecv(pchRet3, 16, g_socks5_recv_timeout, sock); break;
539532
case SOCKS5Atyp::DOMAINNAME:
540533
{
541-
recvr = InterruptibleRecv(pchRet3, 1, SOCKS5_RECV_TIMEOUT, sock);
534+
recvr = InterruptibleRecv(pchRet3, 1, g_socks5_recv_timeout, sock);
542535
if (recvr != IntrRecvError::OK) {
543536
return error("Error reading from proxy");
544537
}
545538
int nRecv = pchRet3[0];
546-
recvr = InterruptibleRecv(pchRet3, nRecv, SOCKS5_RECV_TIMEOUT, sock);
539+
recvr = InterruptibleRecv(pchRet3, nRecv, g_socks5_recv_timeout, sock);
547540
break;
548541
}
549542
default: return error("Error: malformed proxy response");
550543
}
551544
if (recvr != IntrRecvError::OK) {
552545
return error("Error reading from proxy");
553546
}
554-
if ((recvr = InterruptibleRecv(pchRet3, 2, SOCKS5_RECV_TIMEOUT, sock)) != IntrRecvError::OK) {
547+
if ((recvr = InterruptibleRecv(pchRet3, 2, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) {
555548
return error("Error reading from proxy");
556549
}
557550
LogPrint(BCLog::NET, "SOCKS5 connected %s\n", strDest);

src/netbase.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ class proxyType
4040
bool randomize_credentials;
4141
};
4242

43+
/** Credentials for proxy authentication */
44+
struct ProxyCredentials
45+
{
46+
std::string username;
47+
std::string password;
48+
};
49+
4350
enum Network ParseNetwork(const std::string& net);
4451
std::string GetNetworkName(enum Network net);
4552
/** Return a vector of publicly routable Network names; optionally append NET_UNROUTABLE. */
@@ -77,4 +84,6 @@ bool SetSocketNonBlocking(const SOCKET& hSocket, bool fNonBlocking);
7784
bool SetSocketNoDelay(const SOCKET& hSocket);
7885
void InterruptSocks5(bool interrupt);
7986

87+
bool Socks5(const std::string& strDest, int port, const ProxyCredentials* auth, const Sock& socket);
88+
8089
#endif // BITCOIN_NETBASE_H

src/test/fuzz/socks5.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) 2020 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 <netbase.h>
6+
#include <test/fuzz/FuzzedDataProvider.h>
7+
#include <test/fuzz/fuzz.h>
8+
#include <test/fuzz/util.h>
9+
10+
#include <cstdint>
11+
#include <string>
12+
#include <vector>
13+
14+
namespace {
15+
int default_socks5_recv_timeout;
16+
};
17+
18+
extern int g_socks5_recv_timeout;
19+
20+
void initialize_socks5()
21+
{
22+
static const auto testing_setup = MakeNoLogFileContext<const BasicTestingSetup>();
23+
default_socks5_recv_timeout = g_socks5_recv_timeout;
24+
}
25+
26+
FUZZ_TARGET_INIT(socks5, initialize_socks5)
27+
{
28+
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
29+
ProxyCredentials proxy_credentials;
30+
proxy_credentials.username = fuzzed_data_provider.ConsumeRandomLengthString(512);
31+
proxy_credentials.password = fuzzed_data_provider.ConsumeRandomLengthString(512);
32+
InterruptSocks5(fuzzed_data_provider.ConsumeBool());
33+
// Set FUZZED_SOCKET_FAKE_LATENCY=1 to exercise recv timeout code paths. This
34+
// will slow down fuzzing.
35+
g_socks5_recv_timeout = (fuzzed_data_provider.ConsumeBool() && std::getenv("FUZZED_SOCKET_FAKE_LATENCY") != nullptr) ? 1 : default_socks5_recv_timeout;
36+
FuzzedSock fuzzed_sock = ConsumeSock(fuzzed_data_provider);
37+
// This Socks5(...) fuzzing harness would have caught CVE-2017-18350 within
38+
// a few seconds of fuzzing.
39+
(void)Socks5(fuzzed_data_provider.ConsumeRandomLengthString(512),
40+
fuzzed_data_provider.ConsumeIntegral<int>(),
41+
fuzzed_data_provider.ConsumeBool() ? &proxy_credentials : nullptr,
42+
fuzzed_sock);
43+
}

src/test/fuzz/util.h

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include <version.h>
3232

3333
#include <algorithm>
34+
#include <array>
3435
#include <cstdint>
3536
#include <cstdio>
3637
#include <optional>
@@ -250,6 +251,15 @@ template <class T>
250251
return false;
251252
}
252253

254+
/**
255+
* Sets errno to a value selected from the given std::array `errnos`.
256+
*/
257+
template <typename T, size_t size>
258+
void SetFuzzedErrNo(FuzzedDataProvider& fuzzed_data_provider, const std::array<T, size>& errnos)
259+
{
260+
errno = fuzzed_data_provider.PickValueInArray(errnos);
261+
}
262+
253263
/**
254264
* Returns a byte vector of specified size regardless of the number of remaining bytes available
255265
* from the fuzzer. Pads with zero value bytes if needed to achieve the specified size.
@@ -534,4 +544,119 @@ void ReadFromStream(FuzzedDataProvider& fuzzed_data_provider, Stream& stream) no
534544
}
535545
}
536546

547+
class FuzzedSock : public Sock
548+
{
549+
FuzzedDataProvider& m_fuzzed_data_provider;
550+
551+
public:
552+
explicit FuzzedSock(FuzzedDataProvider& fuzzed_data_provider) : m_fuzzed_data_provider{fuzzed_data_provider}
553+
{
554+
}
555+
556+
~FuzzedSock() override
557+
{
558+
}
559+
560+
SOCKET Get() const override
561+
{
562+
assert(false && "Not implemented yet.");
563+
}
564+
565+
SOCKET Release() override
566+
{
567+
assert(false && "Not implemented yet.");
568+
}
569+
570+
void Reset() override
571+
{
572+
assert(false && "Not implemented yet.");
573+
}
574+
575+
ssize_t Send(const void* data, size_t len, int flags) const override
576+
{
577+
constexpr std::array send_errnos{
578+
EACCES,
579+
EAGAIN,
580+
EALREADY,
581+
EBADF,
582+
ECONNRESET,
583+
EDESTADDRREQ,
584+
EFAULT,
585+
EINTR,
586+
EINVAL,
587+
EISCONN,
588+
EMSGSIZE,
589+
ENOBUFS,
590+
ENOMEM,
591+
ENOTCONN,
592+
ENOTSOCK,
593+
EOPNOTSUPP,
594+
EPIPE,
595+
EWOULDBLOCK,
596+
};
597+
if (m_fuzzed_data_provider.ConsumeBool()) {
598+
return len;
599+
}
600+
const ssize_t r = m_fuzzed_data_provider.ConsumeIntegralInRange<ssize_t>(-1, len);
601+
if (r == -1) {
602+
SetFuzzedErrNo(m_fuzzed_data_provider, send_errnos);
603+
}
604+
return r;
605+
}
606+
607+
ssize_t Recv(void* buf, size_t len, int flags) const override
608+
{
609+
constexpr std::array recv_errnos{
610+
EAGAIN,
611+
EBADF,
612+
ECONNREFUSED,
613+
EFAULT,
614+
EINTR,
615+
EINVAL,
616+
ENOMEM,
617+
ENOTCONN,
618+
ENOTSOCK,
619+
EWOULDBLOCK,
620+
};
621+
assert(buf != nullptr || len == 0);
622+
if (len == 0 || m_fuzzed_data_provider.ConsumeBool()) {
623+
const ssize_t r = m_fuzzed_data_provider.ConsumeBool() ? 0 : -1;
624+
if (r == -1) {
625+
SetFuzzedErrNo(m_fuzzed_data_provider, recv_errnos);
626+
}
627+
return r;
628+
}
629+
const std::vector<uint8_t> random_bytes = m_fuzzed_data_provider.ConsumeBytes<uint8_t>(
630+
m_fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, len));
631+
if (random_bytes.empty()) {
632+
const ssize_t r = m_fuzzed_data_provider.ConsumeBool() ? 0 : -1;
633+
if (r == -1) {
634+
SetFuzzedErrNo(m_fuzzed_data_provider, recv_errnos);
635+
}
636+
return r;
637+
}
638+
std::memcpy(buf, random_bytes.data(), random_bytes.size());
639+
if (m_fuzzed_data_provider.ConsumeBool()) {
640+
if (len > random_bytes.size()) {
641+
std::memset((char*)buf + random_bytes.size(), 0, len - random_bytes.size());
642+
}
643+
return len;
644+
}
645+
if (m_fuzzed_data_provider.ConsumeBool() && std::getenv("FUZZED_SOCKET_FAKE_LATENCY") != nullptr) {
646+
std::this_thread::sleep_for(std::chrono::milliseconds{2});
647+
}
648+
return random_bytes.size();
649+
}
650+
651+
bool Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred = nullptr) const override
652+
{
653+
return m_fuzzed_data_provider.ConsumeBool();
654+
}
655+
};
656+
657+
[[nodiscard]] inline FuzzedSock ConsumeSock(FuzzedDataProvider& fuzzed_data_provider)
658+
{
659+
return FuzzedSock{fuzzed_data_provider};
660+
}
661+
537662
#endif // BITCOIN_TEST_FUZZ_UTIL_H

0 commit comments

Comments
 (0)