What changed, and why it matters
This change fixes a minor startup bug in Bitcoin Core where an empty -addnode setting (for manually adding network peers) would create a useless peer record that the software tried to connect to over and over. Now empty or whitespace-only values are skipped, and a warning is logged. It is a hardening/cleanup fix rather than a serious security vulnerability.
No urgent action needed. Treat as routine hardening/cleanup. Users can ignore the change; operators using -addnode should ensure they supply valid peer addresses.
Security signals we found
Denial-of-service hardening: prevents indefinite retry loop against an invalid added-node target
Input validation added for command-line/config argument
Logging added for ignored invalid input
Evidence from the diff
The patch filters the -addnode argument list in AppInitMain, dropping values that are empty or whitespace-only after trimming. Previously, an empty string was passed into connOptions.m_added_nodes and treated as a valid added-node target, leading to repeated unresolvable connection attempts. A functional test verifies that empty/whitespace values are ignored and that an empty -addnode does not delay fallback to fixed seeds when DNS seeding is disabled.
Changed components
src/init.cpp: AppInitMain -addnode argument handlingtest/functional/feature_config_args.py: new test_empty_addnode testInspect captured patch +29 / −2
diff --git a/src/init.cpp b/src/init.cpp
index bc457e78..54e02d44 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -169,6 +169,7 @@ using node::VerifyLoadedChainstate;
using util::Join;
using util::ReplaceAll;
using util::ToString;
+using util::TrimStringView;
static constexpr bool DEFAULT_PROXYRANDOMIZE{true};
static constexpr bool DEFAULT_REST_ENABLE{false};
@@ -2133,7 +2134,15 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
connOptions.m_msgproc = node.peerman.get();
connOptions.nSendBufferMaxSize = 1000 * args.GetIntArg("-maxsendbuffer", DEFAULT_MAXSENDBUFFER);
connOptions.nReceiveFloodSize = 1000 * args.GetIntArg("-maxreceivebuffer", DEFAULT_MAXRECEIVEBUFFER);
- connOptions.m_added_nodes = args.GetArgs("-addnode");
+ for (const std::string& added_node : args.GetArgs("-addnode")) {
+ // Such a value is not a valid connection target, but would otherwise be
+ // treated as one and retried indefinitely.
+ if (TrimStringView(added_node).empty()) {
+ LogWarning("Ignoring empty -addnode value");
+ continue;
+ }
+ connOptions.m_added_nodes.push_back(added_node);
+ }
connOptions.nMaxOutboundLimit = *opt_max_upload;
connOptions.m_peer_connect_timeout = peer_connect_timeout;
connOptions.whitelist_forcerelay = args.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY);
diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py
index feca0f16..62590266 100755
--- a/test/functional/feature_config_args.py
+++ b/test/functional/feature_config_args.py
@@ -265,6 +265,21 @@ class ConfArgsTest(BitcoinTestFramework):
])
self.stop_node(0)
+ def test_empty_addnode(self):
+ self.log.info("Test empty addnode configuration values are ignored")
+ node = self.nodes[0]
+ util.append_config(node.datadir_path, ["addnode="])
+
+ with node.assert_debug_log(expected_msgs=["Ignoring empty -addnode value"]):
+ # Values consisting of whitespace only are trimmed away by the
+ # config file parser, so they can only be passed on the command
+ # line. Non-empty values are unaffected.
+ self.start_node(0, extra_args=["-addnode= ", "-addnode=some.node"])
+ util.assert_equal([added["addednode"] for added in node.getaddednodeinfo()], ["some.node"])
+ self.stop_node(0)
+
+ node.replace_in_config([("addnode=\n", "")])
+
def test_networkactive(self):
self.log.info('Test -networkactive option')
with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: true\n']):
@@ -325,13 +340,15 @@ class ConfArgsTest(BitcoinTestFramework):
# No peers.dat exists and -dnsseed=0
# We expect the node will fallback immediately to fixed seeds
+ # An empty -addnode value is ignored, so it must not delay the fallback
+ # either.
assert not peer_dat.exists()
with self.nodes[0].assert_debug_log(expected_msgs=[
"Loaded 0 addresses from peers.dat",
"DNS seeding disabled",
"Adding fixed seeds as -dnsseed=0 (or IPv4/IPv6 connections are disabled via -onlynet) and neither -addnode nor -seednode are provided\n",
], timeout=2):
- self.start_node(0, extra_args=['-dnsseed=0', '-fixedseeds=1'])
+ self.start_node(0, extra_args=['-dnsseed=0', '-fixedseeds=1', '-addnode='])
self.stop_node(0)
self.nodes[0].assert_start_raises_init_error(['-dnsseed=1', '-onlynet=i2p', '-i2psam=127.0.0.1:7656'], "Error: Incompatible options: -dnsseed=1 was explicitly specified, but -onlynet forbids connections to IPv4/IPv6")
@@ -516,6 +533,7 @@ class ConfArgsTest(BitcoinTestFramework):
def run_test(self):
self.test_log_buffer()
self.test_args_log()
+ self.test_empty_addnode()
self.test_seed_peers()
self.test_networkactive()
self.test_connect_with_seednode()
Why this scored 22/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.