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 <noreply@anthropic.com>
This commit is contained in:
24
src/main.cpp
24
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());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user