Skip to content

Commit 37886d5

Browse files
committed
Disconnect outbound peers relaying invalid headers
1 parent 4637f18 commit 37886d5

File tree

3 files changed

+56
-12
lines changed

3 files changed

+56
-12
lines changed

src/net_processing.cpp

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,7 +1205,7 @@ inline void static SendBlockTransactions(const CBlock& block, const BlockTransac
12051205
connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCKTXN, resp));
12061206
}
12071207

1208-
bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::vector<CBlockHeader>& headers, const CChainParams& chainparams)
1208+
bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::vector<CBlockHeader>& headers, const CChainParams& chainparams, bool punish_duplicate_invalid)
12091209
{
12101210
const CNetMsgMaker msgMaker(pfrom->GetSendVersion());
12111211
size_t nCount = headers.size();
@@ -1258,13 +1258,48 @@ bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::ve
12581258
}
12591259

12601260
CValidationState state;
1261-
if (!ProcessNewBlockHeaders(headers, state, chainparams, &pindexLast)) {
1261+
CBlockHeader first_invalid_header;
1262+
if (!ProcessNewBlockHeaders(headers, state, chainparams, &pindexLast, &first_invalid_header)) {
12621263
int nDoS;
12631264
if (state.IsInvalid(nDoS)) {
12641265
if (nDoS > 0) {
12651266
LOCK(cs_main);
12661267
Misbehaving(pfrom->GetId(), nDoS);
12671268
}
1269+
if (punish_duplicate_invalid && mapBlockIndex.find(first_invalid_header.GetHash()) != mapBlockIndex.end()) {
1270+
// Goal: don't allow outbound peers to use up our outbound
1271+
// connection slots if they are on incompatible chains.
1272+
//
1273+
// We ask the caller to set punish_invalid appropriately based
1274+
// on the peer and the method of header delivery (compact
1275+
// blocks are allowed to be invalid in some circumstances,
1276+
// under BIP 152).
1277+
// Here, we try to detect the narrow situation that we have a
1278+
// valid block header (ie it was valid at the time the header
1279+
// was received, and hence stored in mapBlockIndex) but know the
1280+
// block is invalid, and that a peer has announced that same
1281+
// block as being on its active chain.
1282+
// Disconnect the peer in such a situation.
1283+
//
1284+
// Note: if the header that is invalid was not accepted to our
1285+
// mapBlockIndex at all, that may also be grounds for
1286+
// disconnecting the peer, as the chain they are on is likely
1287+
// to be incompatible. However, there is a circumstance where
1288+
// that does not hold: if the header's timestamp is more than
1289+
// 2 hours ahead of our current time. In that case, the header
1290+
// may become valid in the future, and we don't want to
1291+
// disconnect a peer merely for serving us one too-far-ahead
1292+
// block header, to prevent an attacker from splitting the
1293+
// network by mining a block right at the 2 hour boundary.
1294+
//
1295+
// TODO: update the DoS logic (or, rather, rewrite the
1296+
// DoS-interface between validation and net_processing) so that
1297+
// the interface is cleaner, and so that we disconnect on all the
1298+
// reasons that a peer's headers chain is incompatible
1299+
// with ours (eg block->nVersion softforks, MTP violations,
1300+
// etc), and not just the duplicate-invalid case.
1301+
pfrom->fDisconnect = true;
1302+
}
12681303
return error("invalid header received");
12691304
}
12701305
}
@@ -2219,7 +2254,6 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
22192254
// If we end up treating this as a plain headers message, call that as well
22202255
// without cs_main.
22212256
bool fRevertToHeaderProcessing = false;
2222-
CDataStream vHeadersMsg(SER_NETWORK, PROTOCOL_VERSION);
22232257

22242258
// Keep a CBlock for "optimistic" compactblock reconstructions (see
22252259
// below)
@@ -2336,10 +2370,6 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
23362370
return true;
23372371
} else {
23382372
// If this was an announce-cmpctblock, we want the same treatment as a header message
2339-
// Dirty hack to process as if it were just a headers message (TODO: move message handling into their own functions)
2340-
std::vector<CBlock> headers;
2341-
headers.push_back(cmpctblock.header);
2342-
vHeadersMsg << headers;
23432373
fRevertToHeaderProcessing = true;
23442374
}
23452375
}
@@ -2348,8 +2378,14 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
23482378
if (fProcessBLOCKTXN)
23492379
return ProcessMessage(pfrom, NetMsgType::BLOCKTXN, blockTxnMsg, nTimeReceived, chainparams, connman, interruptMsgProc);
23502380

2351-
if (fRevertToHeaderProcessing)
2352-
return ProcessMessage(pfrom, NetMsgType::HEADERS, vHeadersMsg, nTimeReceived, chainparams, connman, interruptMsgProc);
2381+
if (fRevertToHeaderProcessing) {
2382+
// Headers received from HB compact block peers are permitted to be
2383+
// relayed before full validation (see BIP 152), so we don't want to disconnect
2384+
// the peer if the header turns out to be for an invalid block.
2385+
// Note that if a peer tries to build on an invalid chain, that
2386+
// will be detected and the peer will be banned.
2387+
return ProcessHeadersMessage(pfrom, connman, {cmpctblock.header}, chainparams, /*punish_duplicate_invalid=*/false);
2388+
}
23532389

23542390
if (fBlockReconstructed) {
23552391
// If we got here, we were able to optimistically reconstruct a
@@ -2480,7 +2516,12 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
24802516
ReadCompactSize(vRecv); // ignore tx count; assume it is 0.
24812517
}
24822518

2483-
return ProcessHeadersMessage(pfrom, connman, headers, chainparams);
2519+
// Headers received via a HEADERS message should be valid, and reflect
2520+
// the chain the peer is on. If we receive a known-invalid header,
2521+
// disconnect the peer if it is using one of our outbound connection
2522+
// slots.
2523+
bool should_punish = !pfrom->fInbound && !pfrom->m_manual_connection;
2524+
return ProcessHeadersMessage(pfrom, connman, headers, chainparams, should_punish);
24842525
}
24852526

24862527
else if (strCommand == NetMsgType::BLOCK && !fImporting && !fReindex) // Ignore blocks received while importing

src/validation.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3079,13 +3079,15 @@ static bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state
30793079
}
30803080

30813081
// Exposed wrapper for AcceptBlockHeader
3082-
bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, CValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex)
3082+
bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, CValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex, CBlockHeader *first_invalid)
30833083
{
3084+
if (first_invalid != nullptr) first_invalid->SetNull();
30843085
{
30853086
LOCK(cs_main);
30863087
for (const CBlockHeader& header : headers) {
30873088
CBlockIndex *pindex = nullptr; // Use a temp pindex instead of ppindex to avoid a const_cast
30883089
if (!AcceptBlockHeader(header, state, chainparams, &pindex)) {
3090+
if (first_invalid) *first_invalid = header;
30893091
return false;
30903092
}
30913093
if (ppindex) {

src/validation.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,9 @@ bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<cons
247247
* @param[out] state This may be set to an Error state if any error occurred processing them
248248
* @param[in] chainparams The params for the chain we want to connect to
249249
* @param[out] ppindex If set, the pointer will be set to point to the last new block index object for the given headers
250+
* @param[out] first_invalid First header that fails validation, if one exists
250251
*/
251-
bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, CValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex=nullptr);
252+
bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, CValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex=nullptr, CBlockHeader *first_invalid=nullptr);
252253

253254
/** Check whether enough disk space is available for an incoming block */
254255
bool CheckDiskSpace(uint64_t nAdditionalBytes = 0);

0 commit comments

Comments
 (0)