Skip to content

Commit ed6cddd

Browse files
committed
Require callers of AcceptBlockHeader() to perform anti-dos checks
In order to prevent memory DoS, we must ensure that we don't accept a new header into memory until we've performed anti-DoS checks, such as verifying that the header is part of a sufficiently high work chain. This commit adds a new argument to AcceptBlockHeader() so that we can ensure that all call-sites which might cause a new header to be accepted into memory have to grapple with the question of whether the header is safe to accept, or needs further validation. This patch also fixes two places where low-difficulty-headers could have been processed without such validation (processing an unrequested block from the network, and processing a compact block). Credit to Niklas Gögge for noticing this issue, and thanks to Sjors Provoost for test code.
1 parent 551a8d9 commit ed6cddd

18 files changed

+120
-48
lines changed

src/bitcoin-chainstate.cpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ int main(int argc, char* argv[])
195195
bool new_block;
196196
auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash());
197197
RegisterSharedValidationInterface(sc);
198-
bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*new_block=*/&new_block);
198+
bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&new_block);
199199
UnregisterSharedValidationInterface(sc);
200200
if (!new_block && accepted) {
201201
std::cerr << "duplicate" << std::endl;
@@ -210,6 +210,9 @@ int main(int argc, char* argv[])
210210
case BlockValidationResult::BLOCK_RESULT_UNSET:
211211
std::cerr << "initial value. Block has not yet been rejected" << std::endl;
212212
break;
213+
case BlockValidationResult::BLOCK_HEADER_LOW_WORK:
214+
std::cerr << "the block header may be on a too-little-work chain" << std::endl;
215+
break;
213216
case BlockValidationResult::BLOCK_CONSENSUS:
214217
std::cerr << "invalid by consensus rules (excluding any below reasons)" << std::endl;
215218
break;

src/consensus/validation.h

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ enum class BlockValidationResult {
7979
BLOCK_INVALID_PREV, //!< A block this one builds on is invalid
8080
BLOCK_TIME_FUTURE, //!< block timestamp was > 2 hours in the future (or our clock is bad)
8181
BLOCK_CHECKPOINT, //!< the block failed to meet one of our checkpoints
82+
BLOCK_HEADER_LOW_WORK //!< the block header may be on a too-little-work chain
8283
};
8384

8485

src/net_processing.cpp

+25-9
Original file line numberDiff line numberDiff line change
@@ -875,7 +875,7 @@ class PeerManagerImpl final : public PeerManager
875875
EXCLUSIVE_LOCKS_REQUIRED(!m_most_recent_block_mutex, peer.m_getdata_requests_mutex) LOCKS_EXCLUDED(::cs_main);
876876

877877
/** Process a new block. Perform any post-processing housekeeping */
878-
void ProcessBlock(CNode& node, const std::shared_ptr<const CBlock>& block, bool force_processing);
878+
void ProcessBlock(CNode& node, const std::shared_ptr<const CBlock>& block, bool force_processing, bool min_pow_checked);
879879

880880
/** Relay map (txid or wtxid -> CTransactionRef) */
881881
typedef std::map<uint256, CTransactionRef> MapRelay;
@@ -1603,6 +1603,10 @@ bool PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidati
16031603
switch (state.GetResult()) {
16041604
case BlockValidationResult::BLOCK_RESULT_UNSET:
16051605
break;
1606+
case BlockValidationResult::BLOCK_HEADER_LOW_WORK:
1607+
// We didn't try to process the block because the header chain may have
1608+
// too little work.
1609+
break;
16061610
// The node is providing invalid data:
16071611
case BlockValidationResult::BLOCK_CONSENSUS:
16081612
case BlockValidationResult::BLOCK_MUTATED:
@@ -2758,7 +2762,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer,
27582762

27592763
// Now process all the headers.
27602764
BlockValidationState state;
2761-
if (!m_chainman.ProcessNewBlockHeaders(headers, state, &pindexLast)) {
2765+
if (!m_chainman.ProcessNewBlockHeaders(headers, /*min_pow_checked=*/true, state, &pindexLast)) {
27622766
if (state.IsInvalid()) {
27632767
MaybePunishNodeForBlock(pfrom.GetId(), state, via_compact_block, "invalid header received");
27642768
return;
@@ -3030,10 +3034,10 @@ void PeerManagerImpl::ProcessGetCFCheckPt(CNode& node, Peer& peer, CDataStream&
30303034
m_connman.PushMessage(&node, std::move(msg));
30313035
}
30323036

3033-
void PeerManagerImpl::ProcessBlock(CNode& node, const std::shared_ptr<const CBlock>& block, bool force_processing)
3037+
void PeerManagerImpl::ProcessBlock(CNode& node, const std::shared_ptr<const CBlock>& block, bool force_processing, bool min_pow_checked)
30343038
{
30353039
bool new_block{false};
3036-
m_chainman.ProcessNewBlock(block, force_processing, &new_block);
3040+
m_chainman.ProcessNewBlock(block, force_processing, min_pow_checked, &new_block);
30373041
if (new_block) {
30383042
node.m_last_block_time = GetTime<std::chrono::seconds>();
30393043
} else {
@@ -4008,12 +4012,17 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
40084012
{
40094013
LOCK(cs_main);
40104014

4011-
if (!m_chainman.m_blockman.LookupBlockIndex(cmpctblock.header.hashPrevBlock)) {
4015+
const CBlockIndex* prev_block = m_chainman.m_blockman.LookupBlockIndex(cmpctblock.header.hashPrevBlock);
4016+
if (!prev_block) {
40124017
// Doesn't connect (or is genesis), instead of DoSing in AcceptBlockHeader, request deeper headers
40134018
if (!m_chainman.ActiveChainstate().IsInitialBlockDownload()) {
40144019
MaybeSendGetHeaders(pfrom, GetLocator(m_chainman.m_best_header), *peer);
40154020
}
40164021
return;
4022+
} else if (prev_block->nChainWork + CalculateHeadersWork({cmpctblock.header}) < GetAntiDoSWorkThreshold()) {
4023+
// If we get a low-work header in a compact block, we can ignore it.
4024+
LogPrint(BCLog::NET, "Ignoring low-work compact block from peer %d\n", pfrom.GetId());
4025+
return;
40174026
}
40184027

40194028
if (!m_chainman.m_blockman.LookupBlockIndex(cmpctblock.header.GetHash())) {
@@ -4023,7 +4032,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
40234032

40244033
const CBlockIndex *pindex = nullptr;
40254034
BlockValidationState state;
4026-
if (!m_chainman.ProcessNewBlockHeaders({cmpctblock.header}, state, &pindex)) {
4035+
if (!m_chainman.ProcessNewBlockHeaders({cmpctblock.header}, /*min_pow_checked=*/true, state, &pindex)) {
40274036
if (state.IsInvalid()) {
40284037
MaybePunishNodeForBlock(pfrom.GetId(), state, /*via_compact_block=*/true, "invalid header via cmpctblock");
40294038
return;
@@ -4190,7 +4199,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
41904199
// we have a chain with at least nMinimumChainWork), and we ignore
41914200
// compact blocks with less work than our tip, it is safe to treat
41924201
// reconstructed compact blocks as having been requested.
4193-
ProcessBlock(pfrom, pblock, /*force_processing=*/true);
4202+
ProcessBlock(pfrom, pblock, /*force_processing=*/true, /*min_pow_checked=*/true);
41944203
LOCK(cs_main); // hold cs_main for CBlockIndex::IsValid()
41954204
if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS)) {
41964205
// Clear download state for this block, which is in
@@ -4273,7 +4282,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
42734282
// disk-space attacks), but this should be safe due to the
42744283
// protections in the compact block handler -- see related comment
42754284
// in compact block optimistic reconstruction handling.
4276-
ProcessBlock(pfrom, pblock, /*force_processing=*/true);
4285+
ProcessBlock(pfrom, pblock, /*force_processing=*/true, /*min_pow_checked=*/true);
42774286
}
42784287
return;
42794288
}
@@ -4322,6 +4331,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
43224331

43234332
bool forceProcessing = false;
43244333
const uint256 hash(pblock->GetHash());
4334+
bool min_pow_checked = false;
43254335
{
43264336
LOCK(cs_main);
43274337
// Always process the block if we requested it, since we may
@@ -4332,8 +4342,14 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
43324342
// which peers send us compact blocks, so the race between here and
43334343
// cs_main in ProcessNewBlock is fine.
43344344
mapBlockSource.emplace(hash, std::make_pair(pfrom.GetId(), true));
4345+
4346+
// Check work on this block against our anti-dos thresholds.
4347+
const CBlockIndex* prev_block = m_chainman.m_blockman.LookupBlockIndex(pblock->hashPrevBlock);
4348+
if (prev_block && prev_block->nChainWork + CalculateHeadersWork({pblock->GetBlockHeader()}) >= GetAntiDoSWorkThreshold()) {
4349+
min_pow_checked = true;
4350+
}
43354351
}
4336-
ProcessBlock(pfrom, pblock, forceProcessing);
4352+
ProcessBlock(pfrom, pblock, forceProcessing, min_pow_checked);
43374353
return;
43384354
}
43394355

src/rpc/mining.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t&
132132
}
133133

134134
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block);
135-
if (!chainman.ProcessNewBlock(shared_pblock, true, nullptr)) {
135+
if (!chainman.ProcessNewBlock(shared_pblock, /*force_processing=*/true, /*min_pow_checked=*/true, nullptr)) {
136136
throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted");
137137
}
138138

@@ -981,7 +981,7 @@ static RPCHelpMan submitblock()
981981
bool new_block;
982982
auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash());
983983
RegisterSharedValidationInterface(sc);
984-
bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*new_block=*/&new_block);
984+
bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&new_block);
985985
UnregisterSharedValidationInterface(sc);
986986
if (!new_block && accepted) {
987987
return "duplicate";
@@ -1023,7 +1023,7 @@ static RPCHelpMan submitheader()
10231023
}
10241024

10251025
BlockValidationState state;
1026-
chainman.ProcessNewBlockHeaders({h}, state);
1026+
chainman.ProcessNewBlockHeaders({h}, /*min_pow_checked=*/true, state);
10271027
if (state.IsValid()) return UniValue::VNULL;
10281028
if (state.IsError()) {
10291029
throw JSONRPCError(RPC_VERIFY_ERROR, state.ToString());

src/test/blockfilter_index_tests.cpp

+4-4
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ bool BuildChainTestingSetup::BuildChain(const CBlockIndex* pindex,
101101
CBlockHeader header = block->GetBlockHeader();
102102

103103
BlockValidationState state;
104-
if (!Assert(m_node.chainman)->ProcessNewBlockHeaders({header}, state, &pindex)) {
104+
if (!Assert(m_node.chainman)->ProcessNewBlockHeaders({header}, true, state, &pindex)) {
105105
return false;
106106
}
107107
}
@@ -178,7 +178,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup)
178178
uint256 chainA_last_header = last_header;
179179
for (size_t i = 0; i < 2; i++) {
180180
const auto& block = chainA[i];
181-
BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, nullptr));
181+
BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr));
182182
}
183183
for (size_t i = 0; i < 2; i++) {
184184
const auto& block = chainA[i];
@@ -196,7 +196,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup)
196196
uint256 chainB_last_header = last_header;
197197
for (size_t i = 0; i < 3; i++) {
198198
const auto& block = chainB[i];
199-
BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, nullptr));
199+
BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr));
200200
}
201201
for (size_t i = 0; i < 3; i++) {
202202
const auto& block = chainB[i];
@@ -227,7 +227,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup)
227227
// Reorg back to chain A.
228228
for (size_t i = 2; i < 4; i++) {
229229
const auto& block = chainA[i];
230-
BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, nullptr));
230+
BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr));
231231
}
232232

233233
// Check that chain A and B blocks can be retrieved.

src/test/coinstatsindex_tests.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_unclean_shutdown, TestChain100Setup)
102102
LOCK(cs_main);
103103
BlockValidationState state;
104104
BOOST_CHECK(CheckBlock(block, state, params.GetConsensus()));
105-
BOOST_CHECK(chainstate.AcceptBlock(new_block, state, &new_block_index, true, nullptr, nullptr));
105+
BOOST_CHECK(chainstate.AcceptBlock(new_block, state, &new_block_index, true, nullptr, nullptr, true));
106106
CCoinsViewCache view(&chainstate.CoinsTip());
107107
BOOST_CHECK(chainstate.ConnectBlock(block, state, new_block_index, view));
108108
}

src/test/fuzz/utxo_snapshot.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ FUZZ_TARGET_INIT(utxo_snapshot, initialize_chain)
5858
if (fuzzed_data_provider.ConsumeBool()) {
5959
for (const auto& block : *g_chain) {
6060
BlockValidationState dummy;
61-
bool processed{chainman.ProcessNewBlockHeaders({*block}, dummy)};
61+
bool processed{chainman.ProcessNewBlockHeaders({*block}, true, dummy)};
6262
Assert(processed);
6363
const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
6464
Assert(index);

src/test/miner_tests.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
588588
pblock->nNonce = bi.nonce;
589589
}
590590
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock);
591-
BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, true, nullptr));
591+
BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, true, true, nullptr));
592592
pblock->hashPrevBlock = pblock->GetHash();
593593
}
594594

src/test/util/mining.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ CTxIn MineBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey)
6868
assert(block->nNonce);
6969
}
7070

71-
bool processed{Assert(node.chainman)->ProcessNewBlock(block, true, nullptr)};
71+
bool processed{Assert(node.chainman)->ProcessNewBlock(block, true, true, nullptr)};
7272
assert(processed);
7373

7474
return CTxIn{block->vtx[0]->GetHash(), 0};

src/test/util/setup_common.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ CBlock TestChain100Setup::CreateAndProcessBlock(
321321

322322
const CBlock block = this->CreateBlock(txns, scriptPubKey, *chainstate);
323323
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block);
324-
Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, true, nullptr);
324+
Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, true, true, nullptr);
325325

326326
return block;
327327
}

src/test/validation_block_tests.cpp

+5-5
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ std::shared_ptr<CBlock> MinerTestingSetup::FinalizeBlock(std::shared_ptr<CBlock>
100100
// submit block header, so that miner can get the block height from the
101101
// global state and the node has the topology of the chain
102102
BlockValidationState ignored;
103-
BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlockHeaders({pblock->GetBlockHeader()}, ignored));
103+
BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlockHeaders({pblock->GetBlockHeader()}, true, ignored));
104104

105105
return pblock;
106106
}
@@ -157,7 +157,7 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
157157

158158
bool ignored;
159159
// Connect the genesis block and drain any outstanding events
160-
BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(std::make_shared<CBlock>(Params().GenesisBlock()), true, &ignored));
160+
BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(std::make_shared<CBlock>(Params().GenesisBlock()), true, true, &ignored));
161161
SyncWithValidationInterfaceQueue();
162162

163163
// subscribe to events (this subscriber will validate event ordering)
@@ -179,13 +179,13 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
179179
FastRandomContext insecure;
180180
for (int i = 0; i < 1000; i++) {
181181
auto block = blocks[insecure.randrange(blocks.size() - 1)];
182-
Assert(m_node.chainman)->ProcessNewBlock(block, true, &ignored);
182+
Assert(m_node.chainman)->ProcessNewBlock(block, true, true, &ignored);
183183
}
184184

185185
// to make sure that eventually we process the full chain - do it here
186186
for (const auto& block : blocks) {
187187
if (block->vtx.size() == 1) {
188-
bool processed = Assert(m_node.chainman)->ProcessNewBlock(block, true, &ignored);
188+
bool processed = Assert(m_node.chainman)->ProcessNewBlock(block, true, true, &ignored);
189189
assert(processed);
190190
}
191191
}
@@ -224,7 +224,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg)
224224
{
225225
bool ignored;
226226
auto ProcessBlock = [&](std::shared_ptr<const CBlock> block) -> bool {
227-
return Assert(m_node.chainman)->ProcessNewBlock(block, /*force_processing=*/true, /*new_block=*/&ignored);
227+
return Assert(m_node.chainman)->ProcessNewBlock(block, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&ignored);
228228
};
229229

230230
// Process all mined blocks

src/test/validation_chainstate_tests.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup)
132132
bool checked = CheckBlock(*pblock, state, chainparams.GetConsensus());
133133
BOOST_CHECK(checked);
134134
bool accepted = background_cs.AcceptBlock(
135-
pblock, state, &pindex, true, nullptr, &newblock);
135+
pblock, state, &pindex, true, nullptr, &newblock, true);
136136
BOOST_CHECK(accepted);
137137
}
138138
// UpdateTip is called here

0 commit comments

Comments
 (0)