tor: enable PoW defenses for automatically created hidden services
What changed, and why it matters
This change makes Bitcoin Core nodes that advertise themselves as hidden services on the Tor network ask Tor to enable a built-in anti-spam feature called 'Proof-of-Work defenses' when creating those hidden services. If the installed Tor version is too old to understand that option, the code falls back to creating the hidden service without it. It is a hardening improvement, not a fix for an active vulnerability in Bitcoin Core itself.
Treat as a routine hardening improvement. Users running Bitcoin Core with Tor hidden services should upgrade Tor to 0.4.9.2-alpha or later to benefit from the new PoW defenses. No urgent patch or incident response is indicated.
Security signals we found
Defense-in-depth hardening for onion-service DoS resistance
Graceful fallback on older Tor versions via syntax-error retry
No memory-safety, authentication, or consensus bugs evident in diff
No explicit CVE or security advisory referenced in commit
Evidence from the diff
The patch adds PoWDefensesEnabled=1 to the Tor ADD_ONION command generated in src/torcontrol.cpp. A new helper MakeAddOnionCmd() builds the command, and add_onion_cb() now accepts a pow_was_enabled flag. If Tor returns a 512 syntax error and PoW was enabled, the code retries with PoW disabled. This requires Tor 0.4.9.2-alpha or newer for the feature to actually activate; older Tors silently degrade to the previous behavior. Fuzz coverage is updated to exercise the new reply code and callback path.
Changed components
src/torcontrol.cppsrc/torcontrol.hsrc/test/fuzz/torcontrol.cppInspect captured patch +31 / −7
diff --git a/src/test/fuzz/torcontrol.cpp b/src/test/fuzz/torcontrol.cpp
index 114bc524..1ea30690 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<int>();
});
@@ -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 04092639..b948de3e 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<std::string,std::string> m = ParseTorReplyMapping(s);
std::map<std::string,std::string>::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 f331e76a..b8a1d654 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 */
Why this scored 23/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.