Skip to content

Commit e364e6b

Browse files
committed
Merge bitcoin/bitcoin#32176: net: Prevent accidental circuit sharing when using Tor stream isolation
ec81a72 net: Add randomized prefix to Tor stream isolation credentials (laanwj) c47f81e net: Rename `_randomize_credentials` Proxy parameter to `tor_stream_isolation` (laanwj) Pull request description: Add a class TorsStreamIsolationCredentialsGenerator that generates unique credentials based on a randomly generated session prefix and an atomic counter. Use this in `ConnectThroughProxy` instead of a simple atomic int counter. This makes sure that different launches of the application won't share the same credentials, and thus circuits, even in edge cases. Example with `-debug=proxy`: ``` 2025-03-31T16:30:27Z [proxy] SOCKS5 sending proxy authentication 0afb2da441f5c105-0:0afb2da441f5c105-0 2025-03-31T16:30:31Z [proxy] SOCKS5 sending proxy authentication 0afb2da441f5c105-1:0afb2da441f5c105-1 ``` Thanks to hodlinator in bitcoin/bitcoin#32166 (comment) for the idea. ACKs for top commit: hodlinator: re-ACK ec81a72 jonatack: ACK ec81a72 danielabrozzoni: tACK ec81a72 Tree-SHA512: 195f5885fade77545977b91bdc41394234ae575679cb61631341df443fd8482cd74650104e323c7dbfff7826b10ad61692cca1284d6810f84500a3488f46597a
2 parents c58ae19 + ec81a72 commit e364e6b

File tree

8 files changed

+55
-19
lines changed

8 files changed

+55
-19
lines changed

src/init.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,14 +1578,14 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
15781578
if (proxyArg != "" && proxyArg != "0") {
15791579
Proxy addrProxy;
15801580
if (IsUnixSocketPath(proxyArg)) {
1581-
addrProxy = Proxy(proxyArg, proxyRandomize);
1581+
addrProxy = Proxy(proxyArg, /*tor_stream_isolation=*/proxyRandomize);
15821582
} else {
15831583
const std::optional<CService> proxyAddr{Lookup(proxyArg, 9050, fNameLookup)};
15841584
if (!proxyAddr.has_value()) {
15851585
return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg));
15861586
}
15871587

1588-
addrProxy = Proxy(proxyAddr.value(), proxyRandomize);
1588+
addrProxy = Proxy(proxyAddr.value(), /*tor_stream_isolation=*/proxyRandomize);
15891589
}
15901590

15911591
if (!addrProxy.IsValid())
@@ -1614,14 +1614,14 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
16141614
}
16151615
} else {
16161616
if (IsUnixSocketPath(onionArg)) {
1617-
onion_proxy = Proxy(onionArg, proxyRandomize);
1617+
onion_proxy = Proxy(onionArg, /*tor_stream_isolation=*/proxyRandomize);
16181618
} else {
16191619
const std::optional<CService> addr{Lookup(onionArg, 9050, fNameLookup)};
16201620
if (!addr.has_value() || !addr->IsValid()) {
16211621
return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg));
16221622
}
16231623

1624-
onion_proxy = Proxy(addr.value(), proxyRandomize);
1624+
onion_proxy = Proxy(addr.value(), /*tor_stream_isolation=*/proxyRandomize);
16251625
}
16261626
}
16271627
}

src/netbase.cpp

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,43 @@ bool IsProxy(const CNetAddr &addr) {
749749
return false;
750750
}
751751

752+
/**
753+
* Generate unique credentials for Tor stream isolation. Tor will create
754+
* separate circuits for SOCKS5 proxy connections with different credentials, which
755+
* makes it harder to correlate the connections.
756+
*/
757+
class TorStreamIsolationCredentialsGenerator
758+
{
759+
public:
760+
TorStreamIsolationCredentialsGenerator():
761+
m_prefix(GenerateUniquePrefix()) {
762+
}
763+
764+
/** Return the next unique proxy credentials. */
765+
ProxyCredentials Generate() {
766+
ProxyCredentials auth;
767+
auth.username = auth.password = strprintf("%s%i", m_prefix, m_counter);
768+
++m_counter;
769+
return auth;
770+
}
771+
772+
/** Size of session prefix in bytes. */
773+
static constexpr size_t PREFIX_BYTE_LENGTH = 8;
774+
private:
775+
const std::string m_prefix;
776+
std::atomic<uint64_t> m_counter;
777+
778+
/** Generate a random prefix for each of the credentials returned by this generator.
779+
* This makes sure that different launches of the application (either successively or in parallel)
780+
* will not share the same circuits, as would be the case with a bare counter.
781+
*/
782+
static std::string GenerateUniquePrefix() {
783+
std::array<uint8_t, PREFIX_BYTE_LENGTH> prefix_bytes;
784+
GetRandBytes(prefix_bytes);
785+
return HexStr(prefix_bytes) + "-";
786+
}
787+
};
788+
752789
std::unique_ptr<Sock> ConnectThroughProxy(const Proxy& proxy,
753790
const std::string& dest,
754791
uint16_t port,
@@ -762,10 +799,9 @@ std::unique_ptr<Sock> ConnectThroughProxy(const Proxy& proxy,
762799
}
763800

764801
// do socks negotiation
765-
if (proxy.m_randomize_credentials) {
766-
ProxyCredentials random_auth;
767-
static std::atomic_int counter(0);
768-
random_auth.username = random_auth.password = strprintf("%i", counter++);
802+
if (proxy.m_tor_stream_isolation) {
803+
static TorStreamIsolationCredentialsGenerator generator;
804+
ProxyCredentials random_auth{generator.Generate()};
769805
if (!Socks5(dest, port, &random_auth, *sock)) {
770806
return {};
771807
}

src/netbase.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,14 @@ bool IsUnixSocketPath(const std::string& name);
5858
class Proxy
5959
{
6060
public:
61-
Proxy() : m_is_unix_socket(false), m_randomize_credentials(false) {}
62-
explicit Proxy(const CService& _proxy, bool _randomize_credentials = false) : proxy(_proxy), m_is_unix_socket(false), m_randomize_credentials(_randomize_credentials) {}
63-
explicit Proxy(const std::string path, bool _randomize_credentials = false) : m_unix_socket_path(path), m_is_unix_socket(true), m_randomize_credentials(_randomize_credentials) {}
61+
Proxy() : m_is_unix_socket(false), m_tor_stream_isolation(false) {}
62+
explicit Proxy(const CService& _proxy, bool tor_stream_isolation = false) : proxy(_proxy), m_is_unix_socket(false), m_tor_stream_isolation(tor_stream_isolation) {}
63+
explicit Proxy(const std::string path, bool tor_stream_isolation = false) : m_unix_socket_path(path), m_is_unix_socket(true), m_tor_stream_isolation(tor_stream_isolation) {}
6464

6565
CService proxy;
6666
std::string m_unix_socket_path;
6767
bool m_is_unix_socket;
68-
bool m_randomize_credentials;
68+
bool m_tor_stream_isolation;
6969

7070
bool IsValid() const
7171
{

src/qt/optionsdialog.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ QValidator::State ProxyAddressValidator::validate(QString &input, int &pos) cons
482482
if (!SplitHostPort(input.toStdString(), port, hostname) || port != 0) return QValidator::Invalid;
483483

484484
CService serv(LookupNumeric(input.toStdString(), DEFAULT_GUI_PROXY_PORT));
485-
Proxy addrProxy = Proxy(serv, true);
485+
Proxy addrProxy = Proxy(serv, /*tor_stream_isolation=*/true);
486486
if (addrProxy.IsValid())
487487
return QValidator::Acceptable;
488488

src/rpc/net.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,7 @@ static UniValue GetNetworksInfo()
609609
obj.pushKV("limited", !g_reachable_nets.Contains(network));
610610
obj.pushKV("reachable", g_reachable_nets.Contains(network));
611611
obj.pushKV("proxy", proxy.IsValid() ? proxy.ToString() : std::string());
612-
obj.pushKV("proxy_randomize_credentials", proxy.m_randomize_credentials);
612+
obj.pushKV("proxy_randomize_credentials", proxy.m_tor_stream_isolation);
613613
networks.push_back(std::move(obj));
614614
}
615615
return networks;

src/test/fuzz/i2p.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ FUZZ_TARGET(i2p, .init = initialize_i2p)
3434

3535
const fs::path private_key_path = gArgs.GetDataDirNet() / "fuzzed_i2p_private_key";
3636
const CService addr{in6_addr(IN6ADDR_LOOPBACK_INIT), 7656};
37-
const Proxy sam_proxy{addr, false};
37+
const Proxy sam_proxy{addr, /*tor_stream_isolation=*/false};
3838
CThreadInterrupt interrupt;
3939

4040
i2p::sam::Session session{private_key_path, sam_proxy, &interrupt};

src/test/i2p_tests.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ BOOST_AUTO_TEST_CASE(unlimited_recv)
5252

5353
CThreadInterrupt interrupt;
5454
const std::optional<CService> addr{Lookup("127.0.0.1", 9000, false)};
55-
const Proxy sam_proxy(addr.value(), false);
55+
const Proxy sam_proxy(addr.value(), /*tor_stream_isolation=*/false);
5656
i2p::sam::Session session(gArgs.GetDataDirNet() / "test_i2p_private_key", sam_proxy, &interrupt);
5757

5858
{
@@ -114,7 +114,7 @@ BOOST_AUTO_TEST_CASE(listen_ok_accept_fail)
114114

115115
CThreadInterrupt interrupt;
116116
const CService addr{in6_addr(IN6ADDR_LOOPBACK_INIT), /*port=*/7656};
117-
const Proxy sam_proxy(addr, false);
117+
const Proxy sam_proxy(addr, /*tor_stream_isolation=*/false);
118118
i2p::sam::Session session(gArgs.GetDataDirNet() / "test_i2p_private_key",
119119
sam_proxy,
120120
&interrupt);
@@ -157,7 +157,7 @@ BOOST_AUTO_TEST_CASE(damaged_private_key)
157157

158158
CThreadInterrupt interrupt;
159159
const CService addr{in6_addr(IN6ADDR_LOOPBACK_INIT), /*port=*/7656};
160-
const Proxy sam_proxy{addr, false};
160+
const Proxy sam_proxy{addr, /*tor_stream_isolation=*/false};
161161
i2p::sam::Session session(i2p_private_key_file, sam_proxy, &interrupt);
162162

163163
{

src/torcontrol.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ void TorController::get_socks_cb(TorControlConnection& _conn, const TorControlRe
407407
// With m_randomize_credentials = true, generates unique SOCKS credentials per proxy connection (e.g., Tor).
408408
// Prevents connection correlation and enhances privacy by forcing different Tor circuits.
409409
// Requires Tor's IsolateSOCKSAuth (default enabled) for effective isolation (see IsolateSOCKSAuth section in https://2019.www.torproject.org/docs/tor-manual.html.en).
410-
Proxy addrOnion = Proxy(resolved, /*_randomize_credentials=*/ true);
410+
Proxy addrOnion = Proxy(resolved, /*tor_stream_isolation=*/ true);
411411
SetProxy(NET_ONION, addrOnion);
412412

413413
const auto onlynets = gArgs.GetArgs("-onlynet");

0 commit comments

Comments
 (0)