From 4c6798a3d386c2c1a4bcc4a8694281a8f0bef92d Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Mon, 15 Sep 2025 15:44:03 +0200 Subject: [PATCH] tor: enable PoW defenses for automatically created hidden services Enable PoW defenses [1] for hidden services that we create via Tor Control using the `ADD_ONION` command [2]. The ability to do that has been added in tor-0.4.9.2-alpha [3]. Previous versions return a syntax error to the `ADD_ONION` command with `PoWDefensesEnabled=1`, so the approach here is to try with PoW and if we get syntax error, then retry without PoW. [1] https://tpo.pages.torproject.net/onion-services/ecosystem/technology/security/pow/ [2] https://spec.torproject.org/control-spec/commands.html#add_onion [3] https://gitlab.torproject.org/tpo/core/tor/-/commit/02c18044464bfe45f168b55297a785244094cfd5 --- src/test/fuzz/torcontrol.cpp | 8 +++++++- src/torcontrol.cpp | 27 ++++++++++++++++++++++----- src/torcontrol.h | 3 ++- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/test/fuzz/torcontrol.cpp b/src/test/fuzz/torcontrol.cpp index 114bc524c20..1ea3069000a 100644 --- a/src/test/fuzz/torcontrol.cpp +++ b/src/test/fuzz/torcontrol.cpp @@ -54,6 +54,9 @@ FUZZ_TARGET(torcontrol, .init = initialize_torcontrol) [&] { 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 04092639e8f..b948de3e5bc 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -423,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; @@ -453,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); } @@ -474,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 f331e76a20f..b8a1d6540ba 100644 --- a/src/torcontrol.h +++ b/src/torcontrol.h @@ -27,6 +27,7 @@ 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(); @@ -142,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 */