diff --git a/doc/release-notes-33414.md b/doc/release-notes-33414.md new file mode 100644 index 00000000000..977b88d7977 --- /dev/null +++ b/doc/release-notes-33414.md @@ -0,0 +1,9 @@ +Notable changes +=============== + +P2P and network changes +----------------------- + +Tor hidden services that are created automatically by Bitcoin Core will have +[PoW defenses](https://tpo.pages.torproject.net/onion-services/ecosystem/technology/security/pow/) +enabled if the Tor daemon supports that. (#33414) diff --git a/doc/tor.md b/doc/tor.md index e9db555fc98..7003fcfe300 100644 --- a/doc/tor.md +++ b/doc/tor.md @@ -181,6 +181,10 @@ Add these lines to your `/etc/tor/torrc` (or equivalent config file): HiddenServiceDir /var/lib/tor/bitcoin-service/ HiddenServicePort 8333 127.0.0.1:8334 + # If `tor --list-modules` shows "pow: yes", then enable PoW protection. + # It is available in tor-0.4.8.1-alpha and newer when configured with + # `./configure --enable-gpl`. + HiddenServicePoWDefensesEnabled 1 The directory can be different of course, but virtual port numbers should be equal to your bitcoind's P2P listen port (8333 by default), and target addresses and ports diff --git a/src/test/fuzz/torcontrol.cpp b/src/test/fuzz/torcontrol.cpp index 62c2ad57658..1ea3069000a 100644 --- a/src/test/fuzz/torcontrol.cpp +++ b/src/test/fuzz/torcontrol.cpp @@ -49,10 +49,13 @@ FUZZ_TARGET(torcontrol, .init = initialize_torcontrol) CallOneOf( fuzzed_data_provider, [&] { - tor_control_reply.code = 250; + tor_control_reply.code = TOR_REPLY_OK; }, [&] { - tor_control_reply.code = 510; + tor_control_reply.code = TOR_REPLY_UNRECOGNIZED; + }, + [&] { + tor_control_reply.code = TOR_REPLY_SYNTAX_ERROR; }, [&] { tor_control_reply.code = fuzzed_data_provider.ConsumeIntegral(); @@ -65,7 +68,10 @@ FUZZ_TARGET(torcontrol, .init = initialize_torcontrol) CallOneOf( fuzzed_data_provider, [&] { - tor_controller.add_onion_cb(dummy_tor_control_connection, tor_control_reply); + tor_controller.add_onion_cb(dummy_tor_control_connection, tor_control_reply, /*pow_was_enabled=*/true); + }, + [&] { + tor_controller.add_onion_cb(dummy_tor_control_connection, tor_control_reply, /*pow_was_enabled=*/false); }, [&] { tor_controller.auth_cb(dummy_tor_control_connection, tor_control_reply); diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index d37fc2dd4a3..b948de3e5bc 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -53,9 +53,6 @@ const std::string DEFAULT_TOR_CONTROL = "127.0.0.1:" + ToString(DEFAULT_TOR_CONT static const int TOR_COOKIE_SIZE = 32; /** Size of client/server nonce for SAFECOOKIE */ static const int TOR_NONCE_SIZE = 32; -/** Tor control reply code. Ref: https://spec.torproject.org/control-spec/replies.html */ -static const int TOR_REPLY_OK = 250; -static const int TOR_REPLY_UNRECOGNIZED = 510; /** For computing serverHash in SAFECOOKIE */ static const std::string TOR_SAFE_SERVERKEY = "Tor safe cookie authentication server-to-controller hash"; /** For computing clientHash in SAFECOOKIE */ @@ -426,10 +423,20 @@ void TorController::get_socks_cb(TorControlConnection& _conn, const TorControlRe } } -void TorController::add_onion_cb(TorControlConnection& _conn, const TorControlReply& reply) +static std::string MakeAddOnionCmd(const std::string& private_key, const std::string& target, bool enable_pow) +{ + // Note that the 'virtual' port is always the default port to avoid decloaking nodes using other ports. + return strprintf("ADD_ONION %s%s Port=%i,%s", + private_key, + enable_pow ? " PoWDefensesEnabled=1" : "", + Params().GetDefaultPort(), + target); +} + +void TorController::add_onion_cb(TorControlConnection& _conn, const TorControlReply& reply, bool pow_was_enabled) { if (reply.code == TOR_REPLY_OK) { - LogDebug(BCLog::TOR, "ADD_ONION successful\n"); + LogDebug(BCLog::TOR, "ADD_ONION successful (PoW defenses %s)", pow_was_enabled ? "enabled" : "disabled"); for (const std::string &s : reply.lines) { std::map m = ParseTorReplyMapping(s); std::map::iterator i; @@ -456,6 +463,12 @@ void TorController::add_onion_cb(TorControlConnection& _conn, const TorControlRe // ... onion requested - keep connection open } else if (reply.code == TOR_REPLY_UNRECOGNIZED) { LogWarning("tor: Add onion failed with unrecognized command (You probably need to upgrade Tor)"); + } else if (pow_was_enabled && reply.code == TOR_REPLY_SYNTAX_ERROR) { + LogDebug(BCLog::TOR, "ADD_ONION failed with PoW defenses, retrying without"); + _conn.Command(MakeAddOnionCmd(private_key, m_target.ToStringAddrPort(), /*enable_pow=*/false), + [this](TorControlConnection& conn, const TorControlReply& reply) { + add_onion_cb(conn, reply, /*pow_was_enabled=*/false); + }); } else { LogWarning("tor: Add onion failed; error code %d", reply.code); } @@ -477,9 +490,10 @@ void TorController::auth_cb(TorControlConnection& _conn, const TorControlReply& private_key = "NEW:ED25519-V3"; // Explicitly request key type - see issue #9214 } // Request onion service, redirect port. - // Note that the 'virtual' port is always the default port to avoid decloaking nodes using other ports. - _conn.Command(strprintf("ADD_ONION %s Port=%i,%s", private_key, Params().GetDefaultPort(), m_target.ToStringAddrPort()), - std::bind_front(&TorController::add_onion_cb, this)); + _conn.Command(MakeAddOnionCmd(private_key, m_target.ToStringAddrPort(), /*enable_pow=*/true), + [this](TorControlConnection& conn, const TorControlReply& reply) { + add_onion_cb(conn, reply, /*pow_was_enabled=*/true); + }); } else { LogWarning("tor: Authentication failed"); } diff --git a/src/torcontrol.h b/src/torcontrol.h index e64f9d38df0..b8a1d6540ba 100644 --- a/src/torcontrol.h +++ b/src/torcontrol.h @@ -24,6 +24,11 @@ constexpr int DEFAULT_TOR_CONTROL_PORT = 9051; extern const std::string DEFAULT_TOR_CONTROL; static const bool DEFAULT_LISTEN_ONION = true; +/** Tor control reply code. Ref: https://spec.torproject.org/control-spec/replies.html */ +constexpr int TOR_REPLY_OK{250}; +constexpr int TOR_REPLY_UNRECOGNIZED{510}; +constexpr int TOR_REPLY_SYNTAX_ERROR{512}; //!< Syntax error in command argument + void StartTorControl(CService onion_service_target); void InterruptTorControl(); void StopTorControl(); @@ -138,7 +143,7 @@ public: /** Callback for GETINFO net/listeners/socks result */ void get_socks_cb(TorControlConnection& conn, const TorControlReply& reply); /** Callback for ADD_ONION result */ - void add_onion_cb(TorControlConnection& conn, const TorControlReply& reply); + void add_onion_cb(TorControlConnection& conn, const TorControlReply& reply, bool pow_was_enabled); /** Callback for AUTHENTICATE result */ void auth_cb(TorControlConnection& conn, const TorControlReply& reply); /** Callback for AUTHCHALLENGE result */