From 5951ee118a39a9d9073ef4e8d0fc3247c67603ed Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 14 Jul 2026 18:54:35 -0500 Subject: [PATCH] fix(net): cap per-peer headers during IBD (header-flood DoS) Audit #8. The HEADERS handler accepted unbounded headers per peer with no cumulative cap; during IBD (fCheckPOW=0) a peer could flood cost-free PoW-less headers into mapBlockIndex/leveldb (never selected -- nMinimumChainWork gates that -- but still memory/disk growth). Add a per-peer nHeadersProcessed counter in CNodeState; while IsInitialBlockDownload(), if one peer exceeds 2*max(pindexBestHeader height, checkpoint height) + 200000 headers, Misbehaving(100) and drop it. The cap is ~2x the chain length, so honest sync never approaches it; inert post-IBD (the RandomX header check handles forged headers there). Co-Authored-By: Claude Opus 4.8 --- src/main.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 834746ad9..e6d45bc88 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -328,6 +328,8 @@ namespace { bool fBulkHeaderSeen; //! (server side) time (us) we last served a bulk stream to this peer, for flood throttling. int64_t nLastBulkServeTime; + //! (#8 IBD header-flood cap) cumulative headers this peer made us process while in IBD. + int64_t nHeadersProcessed; CNodeState() { fCurrentlyConnected = false; @@ -348,6 +350,7 @@ namespace { nBulkHashStart.SetNull(); fBulkHeaderSeen = false; nLastBulkServeTime = 0; + nHeadersProcessed = 0; } }; @@ -7888,6 +7891,27 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, } } + // SECURITY (#8: IBD header-flood cap): bound how many headers a single peer can make us + // store while in IBD. Honest headers-first sync needs at most ~chain-length headers from a + // peer; one that floods far past 2x the known chain length is only trying to bloat + // mapBlockIndex/leveldb (such headers are never selected -- nMinimumChainWork gates that -- + // but they still cost memory/disk). Cap per-peer and drop the peer. IBD-only: post-IBD the + // RandomX check in AcceptBlockHeader already makes forged headers fail RandomX and ban. + if (IsInitialBlockDownload()) { + CNodeState *hstate = State(pfrom->GetId()); + if (hstate != NULL) { + hstate->nHeadersProcessed += (int64_t)nCount; + int knownH = std::max(pindexBestHeader ? (int)pindexBestHeader->GetHeight() : 0, + Checkpoints::GetTotalBlocksEstimate(chainparams.Checkpoints())); + int64_t headerCap = 2 * (int64_t)knownH + 200000; + if (hstate->nHeadersProcessed > headerCap) { + Misbehaving(pfrom->GetId(), 100); + return error("%s: peer=%d flooded %lld headers during IBD (cap %lld)", __func__, + pfrom->id, (long long)hstate->nHeadersProcessed, (long long)headerCap); + } + } + } + if (pindexLast) UpdateBlockAvailability(pfrom->GetId(), pindexLast->GetBlockHash());