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());