What changed, and why it matters
This commit fixes a configuration bug in btcd, a Bitcoin node implementation. Previously, setting maxpeers=0 caused the node to enter a tight loop: it would repeatedly try to connect to outbound peers and immediately reject them. The change now rejects zero or negative maxpeers values at startup with a clear error, preventing the node from starting in a broken state.
Treat as a low-severity hardening fix. Users should upgrade or ensure maxpeers is set to a positive integer. Operators running btcd with maxpeers=0 should be aware the prior behavior caused high CPU/network churn.
Security signals we found
Denial-of-service-like resource exhaustion via tight reconnect loop triggered by a configuration value
Input validation added for a previously unbounded configuration parameter
No memory corruption, authentication bypass, or remote code execution signals present
Evidence from the diff
The patch adds validateMaxPeers() in config.go and calls it during loadConfig(). It rejects maxPeers <= 0 before the value reaches connmgr, where TargetOutbound=0 was interpreted as the default of 8, leading the server to accept then immediately drop every completed peer and create a replacement request. Tests and sample config documentation are updated accordingly.
Changed components
btcd config loading (config.go)peer connection manager (connmgr) interaction via MaxPeers/TargetOutboundsample-btcd.conf documentationInspect captured patch +42 / −2
diff --git a/config.go b/config.go
index a15579b..c33a533 100644
--- a/config.go
+++ b/config.go
@@ -95,6 +95,15 @@ func minUint32(a, b uint32) uint32 {
return b
}
+// validateMaxPeers ensures btcd has a positive total peer budget.
+func validateMaxPeers(maxPeers int) error {
+ if maxPeers <= 0 {
+ return fmt.Errorf("maxpeers must be greater than zero: %d", maxPeers)
+ }
+
+ return nil
+}
+
// config defines the configuration options for btcd.
//
// See loadConfig for details on the configuration load process.
@@ -129,7 +138,7 @@ type config struct {
Listeners []string `long:"listen" description:"Add an interface/port to listen for connections (default all interfaces port: 8333, testnet: 18333)"`
LogDir string `long:"logdir" description:"Directory to log output."`
MaxOrphanTxs int `long:"maxorphantx" description:"Max number of orphan transactions to keep in memory"`
- MaxPeers int `long:"maxpeers" description:"Max number of inbound and outbound peers. Outbound slots for the configured peer mode are reserved before inbound capacity is calculated"`
+ MaxPeers int `long:"maxpeers" description:"Max number of inbound and outbound peers. Must be greater than zero. Outbound slots for the configured peer mode are reserved before inbound capacity is calculated"`
MiningAddrs []string `long:"miningaddr" description:"Add the specified payment address to the list of addresses to use for generated blocks -- At least one address is required if the generate option is set"`
MinRelayTxFee float64 `long:"minrelaytxfee" description:"The minimum transaction fee in BTC/kB to be considered a non-zero fee."`
DisableBanning bool `long:"nobanning" description:"Disable banning of misbehaving peers"`
@@ -615,6 +624,13 @@ func loadConfig() (*config, []string, error) {
return nil, nil, err
}
+ if err := validateMaxPeers(cfg.MaxPeers); err != nil {
+ err := fmt.Errorf("%s: %w", funcName, err)
+ fmt.Fprintln(os.Stderr, err)
+ fmt.Fprintln(os.Stderr, usageMessage)
+ return nil, nil, err
+ }
+
// If mainnet is active, then we won't allow the stall handler to be
// disabled.
if activeNetParams.Params.Net == wire.MainNet && cfg.DisableStallHandler {
diff --git a/config_test.go b/config_test.go
index b149943..e4b2a95 100644
--- a/config_test.go
+++ b/config_test.go
@@ -8,6 +8,30 @@ import (
"testing"
)
+func TestValidateMaxPeers(t *testing.T) {
+ tests := []struct {
+ name string
+ maxPeers int
+ wantErr bool
+ }{
+ {name: "negative", maxPeers: -1, wantErr: true},
+ {name: "zero", wantErr: true},
+ {name: "positive", maxPeers: 1},
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ err := validateMaxPeers(test.maxPeers)
+ if test.wantErr && err == nil {
+ t.Fatal("expected validation error")
+ }
+ if !test.wantErr && err != nil {
+ t.Fatalf("unexpected validation error: %v", err)
+ }
+ })
+ }
+}
+
var (
rpcuserRegexp = regexp.MustCompile("(?m)^rpcuser=.+$")
rpcpassRegexp = regexp.MustCompile("(?m)^rpcpass=.+$")
diff --git a/sample-btcd.conf b/sample-btcd.conf
index 103f75b..06521d0 100644
--- a/sample-btcd.conf
+++ b/sample-btcd.conf
@@ -106,7 +106,7 @@
; connect=fe80::1
; connect=[fe80::2]:8333
-; Maximum number of inbound and outbound peers.
+; Maximum number of inbound and outbound peers. Must be greater than zero.
; maxpeers=125
; Disable banning of misbehaving peers.
Why this scored 26/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.