This will leave the socket in a blocking mode. I think the rest of the P2P code expects and assumes non-blocking sockets. Better set this also to non-blocking and make the Socks5()
more robust:
0commit 4c8d08ca2b495da609d5586247dbd2b306e90e61 (HEAD -> tor-unix-domain-socket)
1Parent: af4eb52ff611342dbd285f218474649f16badbaf
2Author: Vasil Dimov <vd@FreeBSD.org>
3AuthorDate: Thu Jul 13 14:17:30 2023 +0200
4Commit: Vasil Dimov <vd@FreeBSD.org>
5CommitDate: Thu Jul 13 14:17:30 2023 +0200
6gpg: Signature made Thu Jul 13 14:23:03 2023 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 netbase: use reliable send() during SOCKS5 handshake
14
15 `send(2)` can be interrupted or for another reason it may not fully
16 complete sending all the bytes. We should be ready to retry the send
17 with the remaining bytes. This is what `Sock::SendComplete()` does,
18 thus use it in `Socks5()`.
19
20 Since `Sock::SendComplete()` takes a `CThreadInterrupt` argument,
21 change also the recv part of `Socks5()` to use `CThreadInterrupt`
22 instead of a boolean.
23
24 Easier reviewed with `git show -b` (ignore white-space changes).
25
26diff --git a/src/net.cpp b/src/net.cpp
27index a46cd25e90..4eb96f950d 100644
28--- a/src/net.cpp
29+++ b/src/net.cpp
30@@ -2336,13 +2336,12 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
31 }
32
33 //
34 // Start threads
35 //
36 assert(m_msgproc);
37- InterruptSocks5(false);
38 interruptNet.reset();
39 flagInterruptMsgProc = false;
40
41 {
42 LOCK(mutexMsgProc);
43 fMsgProcWake = false;
44@@ -2408,13 +2407,13 @@ void CConnman::Interrupt()
45 LOCK(mutexMsgProc);
46 flagInterruptMsgProc = true;
47 }
48 condMsgProc.notify_all();
49
50 interruptNet();
51- InterruptSocks5(true);
52+ g_socks5_interrupt();
53
54 if (semOutbound) {
55 for (int i=0; i<m_max_outbound; i++) {
56 semOutbound->post();
57 }
58 }
59diff --git a/src/netbase.cpp b/src/netbase.cpp
60index 548e1483b8..cee50e67bb 100644
61--- a/src/netbase.cpp
62+++ b/src/netbase.cpp
63@@ -32,13 +32,13 @@ static Proxy proxyInfo[NET_MAX] GUARDED_BY(g_proxyinfo_mutex);
64 static Proxy nameProxy GUARDED_BY(g_proxyinfo_mutex);
65 int nConnectTimeout = DEFAULT_CONNECT_TIMEOUT;
66 bool fNameLookup = DEFAULT_NAME_LOOKUP;
67
68 // Need ample time for negotiation for very slow proxies such as Tor
69 std::chrono::milliseconds g_socks5_recv_timeout = 20s;
70-static std::atomic<bool> interruptSocks5Recv(false);
71+CThreadInterrupt g_socks5_interrupt;
72
73 std::vector<CNetAddr> WrappedGetAddrInfo(const std::string& name, bool allow_lookup)
74 {
75 addrinfo ai_hint{};
76 // We want a TCP port, which is a streaming socket type
77 ai_hint.ai_socktype = SOCK_STREAM;
78@@ -289,13 +289,13 @@ enum class IntrRecvError {
79 * [@param](/bitcoin-bitcoin/contributor/param/) sock The socket (has to be in non-blocking mode) from which to read bytes.
80 *
81 * [@returns](/bitcoin-bitcoin/contributor/returns/) An IntrRecvError indicating the resulting status of this read.
82 * IntrRecvError::OK only if all of the specified number of bytes were
83 * read.
84 *
85- * [@see](/bitcoin-bitcoin/contributor/see/) This function can be interrupted by calling InterruptSocks5(bool).
86+ * [@see](/bitcoin-bitcoin/contributor/see/) This function can be interrupted by calling g_socks5_interrupt().
87 * Sockets can be made non-blocking with Sock::SetNonBlocking().
88 */
89 static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, std::chrono::milliseconds timeout, const Sock& sock)
90 {
91 auto curTime{Now<SteadyMilliseconds>()};
92 const auto endTime{curTime + timeout};
93@@ -317,14 +317,15 @@ static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, std::chrono::m
94 return IntrRecvError::NetworkError;
95 }
96 } else {
97 return IntrRecvError::NetworkError;
98 }
99 }
100- if (interruptSocks5Recv)
101+ if (g_socks5_interrupt) {
102 return IntrRecvError::Interrupted;
103+ }
104 curTime = Now<SteadyMilliseconds>();
105 }
106 return len == 0 ? IntrRecvError::OK : IntrRecvError::Timeout;
107 }
108
109 /** Convert SOCKS5 reply to an error message */
110@@ -351,127 +352,120 @@ static std::string Socks5ErrorString(uint8_t err)
111 return "unknown";
112 }
113 }
114
115 bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* auth, const Sock& sock)
116 {
117- IntrRecvError recvr;
118- LogPrint(BCLog::NET, "SOCKS5 connecting %s\n", strDest);
119- if (strDest.size() > 255) {
120- return error("Hostname too long");
121- }
122- // Construct the version identifier/method selection message
123- std::vector<uint8_t> vSocks5Init;
124- vSocks5Init.push_back(SOCKSVersion::SOCKS5); // We want the SOCK5 protocol
125- if (auth) {
126- vSocks5Init.push_back(0x02); // 2 method identifiers follow...
127- vSocks5Init.push_back(SOCKS5Method::NOAUTH);
128- vSocks5Init.push_back(SOCKS5Method::USER_PASS);
129- } else {
130- vSocks5Init.push_back(0x01); // 1 method identifier follows...
131- vSocks5Init.push_back(SOCKS5Method::NOAUTH);
132- }
133- ssize_t ret = sock.Send(vSocks5Init.data(), vSocks5Init.size(), MSG_NOSIGNAL);
134- if (ret != (ssize_t)vSocks5Init.size()) {
135- return error("Error sending to proxy");
136- }
137- uint8_t pchRet1[2];
138- if (InterruptibleRecv(pchRet1, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {
139- LogPrintf("Socks5() connect to %s:%d failed: InterruptibleRecv() timeout or other failure\n", strDest, port);
140- return false;
141- }
142- if (pchRet1[0] != SOCKSVersion::SOCKS5) {
143- return error("Proxy failed to initialize");
144- }
145- if (pchRet1[1] == SOCKS5Method::USER_PASS && auth) {
146- // Perform username/password authentication (as described in RFC1929)
147- std::vector<uint8_t> vAuth;
148- vAuth.push_back(0x01); // Current (and only) version of user/pass subnegotiation
149- if (auth->username.size() > 255 || auth->password.size() > 255)
150- return error("Proxy username or password too long");
151- vAuth.push_back(auth->username.size());
152- vAuth.insert(vAuth.end(), auth->username.begin(), auth->username.end());
153- vAuth.push_back(auth->password.size());
154- vAuth.insert(vAuth.end(), auth->password.begin(), auth->password.end());
155- ret = sock.Send(vAuth.data(), vAuth.size(), MSG_NOSIGNAL);
156- if (ret != (ssize_t)vAuth.size()) {
157- return error("Error sending authentication to proxy");
158- }
159- LogPrint(BCLog::PROXY, "SOCKS5 sending proxy authentication %s:%s\n", auth->username, auth->password);
160- uint8_t pchRetA[2];
161- if (InterruptibleRecv(pchRetA, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {
162- return error("Error reading proxy authentication response");
163+ try {
164+ IntrRecvError recvr;
165+ LogPrint(BCLog::NET, "SOCKS5 connecting %s\n", strDest);
166+ if (strDest.size() > 255) {
167+ return error("Hostname too long");
168 }
169- if (pchRetA[0] != 0x01 || pchRetA[1] != 0x00) {
170- return error("Proxy authentication unsuccessful");
171+ // Construct the version identifier/method selection message
172+ std::string vSocks5Init;
173+ vSocks5Init.push_back(SOCKSVersion::SOCKS5); // We want the SOCK5 protocol
174+ if (auth) {
175+ vSocks5Init.push_back(0x02); // 2 method identifiers follow...
176+ vSocks5Init.push_back(SOCKS5Method::NOAUTH);
177+ vSocks5Init.push_back(SOCKS5Method::USER_PASS);
178+ } else {
179+ vSocks5Init.push_back(0x01); // 1 method identifier follows...
180+ vSocks5Init.push_back(SOCKS5Method::NOAUTH);
181 }
182- } else if (pchRet1[1] == SOCKS5Method::NOAUTH) {
183- // Perform no authentication
184- } else {
185- return error("Proxy requested wrong authentication method %02x", pchRet1[1]);
186- }
187- std::vector<uint8_t> vSocks5;
188- vSocks5.push_back(SOCKSVersion::SOCKS5); // VER protocol version
189- vSocks5.push_back(SOCKS5Command::CONNECT); // CMD CONNECT
190- vSocks5.push_back(0x00); // RSV Reserved must be 0
191- vSocks5.push_back(SOCKS5Atyp::DOMAINNAME); // ATYP DOMAINNAME
192- vSocks5.push_back(strDest.size()); // Length<=255 is checked at beginning of function
193- vSocks5.insert(vSocks5.end(), strDest.begin(), strDest.end());
194- vSocks5.push_back((port >> 8) & 0xFF);
195- vSocks5.push_back((port >> 0) & 0xFF);
196- ret = sock.Send(vSocks5.data(), vSocks5.size(), MSG_NOSIGNAL);
197- if (ret != (ssize_t)vSocks5.size()) {
198- return error("Error sending to proxy");
199- }
200- uint8_t pchRet2[4];
201- if ((recvr = InterruptibleRecv(pchRet2, 4, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) {
202- if (recvr == IntrRecvError::Timeout) {
203- /* If a timeout happens here, this effectively means we timed out while connecting
204- * to the remote node. This is very common for Tor, so do not print an
205- * error message. */
206+ sock.SendComplete(vSocks5Init, g_socks5_recv_timeout, g_socks5_interrupt);
207+ uint8_t pchRet1[2];
208+ if (InterruptibleRecv(pchRet1, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {
209+ LogPrintf("Socks5() connect to %s:%d failed: InterruptibleRecv() timeout or other failure\n", strDest, port);
210 return false;
211+ }
212+ if (pchRet1[0] != SOCKSVersion::SOCKS5) {
213+ return error("Proxy failed to initialize");
214+ }
215+ if (pchRet1[1] == SOCKS5Method::USER_PASS && auth) {
216+ // Perform username/password authentication (as described in RFC1929)
217+ std::string vAuth;
218+ vAuth.push_back(0x01); // Current (and only) version of user/pass subnegotiation
219+ if (auth->username.size() > 255 || auth->password.size() > 255)
220+ return error("Proxy username or password too long");
221+ vAuth.push_back(auth->username.size());
222+ vAuth.insert(vAuth.end(), auth->username.begin(), auth->username.end());
223+ vAuth.push_back(auth->password.size());
224+ vAuth.insert(vAuth.end(), auth->password.begin(), auth->password.end());
225+ sock.SendComplete(vAuth, g_socks5_recv_timeout, g_socks5_interrupt);
226+ LogPrint(BCLog::PROXY, "SOCKS5 sending proxy authentication %s:%s\n", auth->username, auth->password);
227+ uint8_t pchRetA[2];
228+ if (InterruptibleRecv(pchRetA, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {
229+ return error("Error reading proxy authentication response");
230+ }
231+ if (pchRetA[0] != 0x01 || pchRetA[1] != 0x00) {
232+ return error("Proxy authentication unsuccessful");
233+ }
234+ } else if (pchRet1[1] == SOCKS5Method::NOAUTH) {
235+ // Perform no authentication
236 } else {
237- return error("Error while reading proxy response");
238+ return error("Proxy requested wrong authentication method %02x", pchRet1[1]);
239 }
240- }
241- if (pchRet2[0] != SOCKSVersion::SOCKS5) {
242- return error("Proxy failed to accept request");
243- }
244- if (pchRet2[1] != SOCKS5Reply::SUCCEEDED) {
245- // Failures to connect to a peer that are not proxy errors
246- LogPrintf("Socks5() connect to %s:%d failed: %s\n", strDest, port, Socks5ErrorString(pchRet2[1]));
247- return false;
248- }
249- if (pchRet2[2] != 0x00) { // Reserved field must be 0
250- return error("Error: malformed proxy response");
251- }
252- uint8_t pchRet3[256];
253- switch (pchRet2[3])
254- {
255+ std::string vSocks5;
256+ vSocks5.push_back(SOCKSVersion::SOCKS5); // VER protocol version
257+ vSocks5.push_back(SOCKS5Command::CONNECT); // CMD CONNECT
258+ vSocks5.push_back(0x00); // RSV Reserved must be 0
259+ vSocks5.push_back(SOCKS5Atyp::DOMAINNAME); // ATYP DOMAINNAME
260+ vSocks5.push_back(strDest.size()); // Length<=255 is checked at beginning of function
261+ vSocks5.insert(vSocks5.end(), strDest.begin(), strDest.end());
262+ vSocks5.push_back((port >> 8) & 0xFF);
263+ vSocks5.push_back((port >> 0) & 0xFF);
264+ sock.SendComplete(vSocks5, g_socks5_recv_timeout, g_socks5_interrupt);
265+ uint8_t pchRet2[4];
266+ if ((recvr = InterruptibleRecv(pchRet2, 4, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) {
267+ if (recvr == IntrRecvError::Timeout) {
268+ /* If a timeout happens here, this effectively means we timed out while connecting
269+ * to the remote node. This is very common for Tor, so do not print an
270+ * error message. */
271+ return false;
272+ } else {
273+ return error("Error while reading proxy response");
274+ }
275+ }
276+ if (pchRet2[0] != SOCKSVersion::SOCKS5) {
277+ return error("Proxy failed to accept request");
278+ }
279+ if (pchRet2[1] != SOCKS5Reply::SUCCEEDED) {
280+ // Failures to connect to a peer that are not proxy errors
281+ LogPrintf("Socks5() connect to %s:%d failed: %s\n", strDest, port, Socks5ErrorString(pchRet2[1]));
282+ return false;
283+ }
284+ if (pchRet2[2] != 0x00) { // Reserved field must be 0
285+ return error("Error: malformed proxy response");
286+ }
287+ uint8_t pchRet3[256];
288+ switch (pchRet2[3]) {
289 case SOCKS5Atyp::IPV4: recvr = InterruptibleRecv(pchRet3, 4, g_socks5_recv_timeout, sock); break;
290 case SOCKS5Atyp::IPV6: recvr = InterruptibleRecv(pchRet3, 16, g_socks5_recv_timeout, sock); break;
291- case SOCKS5Atyp::DOMAINNAME:
292- {
293+ case SOCKS5Atyp::DOMAINNAME: {
294 recvr = InterruptibleRecv(pchRet3, 1, g_socks5_recv_timeout, sock);
295 if (recvr != IntrRecvError::OK) {
296 return error("Error reading from proxy");
297 }
298 int nRecv = pchRet3[0];
299 recvr = InterruptibleRecv(pchRet3, nRecv, g_socks5_recv_timeout, sock);
300 break;
301 }
302 default: return error("Error: malformed proxy response");
303+ }
304+ if (recvr != IntrRecvError::OK) {
305+ return error("Error reading from proxy");
306+ }
307+ if (InterruptibleRecv(pchRet3, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {
308+ return error("Error reading from proxy");
309+ }
310+ LogPrint(BCLog::NET, "SOCKS5 connected %s\n", strDest);
311+ return true;
312+ } catch (const std::runtime_error& e) {
313+ return error("Error during SOCKS5 proxy handshake: %s", e.what());
314 }
315- if (recvr != IntrRecvError::OK) {
316- return error("Error reading from proxy");
317- }
318- if (InterruptibleRecv(pchRet3, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) {
319- return error("Error reading from proxy");
320- }
321- LogPrint(BCLog::NET, "SOCKS5 connected %s\n", strDest);
322- return true;
323 }
324
325 std::unique_ptr<Sock> CreateSockOS(sa_family_t address_family)
326 {
327 // Not IPv4, IPv6 or UNIX
328 if (address_family == AF_UNSPEC) return nullptr;
329@@ -743,17 +737,12 @@ bool LookupSubNet(const std::string& subnet_str, CSubNet& subnet_out)
330 return subnet_out.IsValid();
331 }
332 }
333 return false;
334 }
335
336-void InterruptSocks5(bool interrupt)
337-{
338- interruptSocks5Recv = interrupt;
339-}
340-
341 bool IsBadPort(uint16_t port)
342 {
343 /* Don't forget to update doc/p2p-bad-ports.md if you change this list. */
344
345 switch (port) {
346 case 1: // tcpmux
347diff --git a/src/netbase.h b/src/netbase.h
348index 997bccf30d..3c41ba3834 100644
349--- a/src/netbase.h
350+++ b/src/netbase.h
351@@ -10,12 +10,13 @@
352 #endif
353
354 #include <compat/compat.h>
355 #include <netaddress.h>
356 #include <serialize.h>
357 #include <util/sock.h>
358+#include <util/threadinterrupt.h>
359
360 #include <functional>
361 #include <memory>
362 #include <stdint.h>
363 #include <string>
364 #include <type_traits>
365@@ -266,13 +267,16 @@ std::unique_ptr<Sock> ConnectDirectly(const CService& dest, bool manual_connecti
366 */
367 std::unique_ptr<Sock> ConnectThroughProxy(const Proxy& proxy,
368 const std::string& dest,
369 uint16_t port,
370 bool& proxy_connection_failed);
371
372-void InterruptSocks5(bool interrupt);
373+/**
374+ * Interrupt SOCKS5 reads or writes.
375+ */
376+extern CThreadInterrupt g_socks5_interrupt;
377
378 /**
379 * Connect to a specified destination service through an already connected
380 * SOCKS5 proxy.
381 *
382 * [@param](/bitcoin-bitcoin/contributor/param/) strDest The destination fully-qualified domain name.
383diff --git a/src/test/fuzz/socks5.cpp b/src/test/fuzz/socks5.cpp
384index 73235b7ced..7378cec7b8 100644
385--- a/src/test/fuzz/socks5.cpp
386+++ b/src/test/fuzz/socks5.cpp
387@@ -29,13 +29,15 @@ void initialize_socks5()
388 FUZZ_TARGET_INIT(socks5, initialize_socks5)
389 {
390 FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
391 ProxyCredentials proxy_credentials;
392 proxy_credentials.username = fuzzed_data_provider.ConsumeRandomLengthString(512);
393 proxy_credentials.password = fuzzed_data_provider.ConsumeRandomLengthString(512);
394- InterruptSocks5(fuzzed_data_provider.ConsumeBool());
395+ if (fuzzed_data_provider.ConsumeBool()) {
396+ g_socks5_interrupt();
397+ }
398 // Set FUZZED_SOCKET_FAKE_LATENCY=1 to exercise recv timeout code paths. This
399 // will slow down fuzzing.
400 g_socks5_recv_timeout = (fuzzed_data_provider.ConsumeBool() && std::getenv("FUZZED_SOCKET_FAKE_LATENCY") != nullptr) ? 1ms : default_socks5_recv_timeout;
401 FuzzedSock fuzzed_sock = ConsumeSock(fuzzed_data_provider);
402 // This Socks5(...) fuzzing harness would have caught CVE-2017-18350 within
403 // a few seconds of fuzzing.