mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-03-25 23:16:16 +01:00
Merge bitcoin/bitcoin#29412: p2p: Don't process mutated blocks
d8087adc7e[test] IsBlockMutated unit tests (dergoegge)1ed2c98297Add transaction_identifier::size to allow Span conversion (dergoegge)1ec6bbeb8d[validation] Cache merkle root and witness commitment checks (dergoegge)5bf4f5ba32[test] Add regression test for #27608 (dergoegge)49257c0304[net processing] Don't process mutated blocks (dergoegge)2d8495e080[validation] Merkle root malleation should be caught by IsBlockMutated (dergoegge)66abce1d98[validation] Introduce IsBlockMutated (dergoegge)e7669e1343[refactor] Cleanup merkle root checks (dergoegge)95bddb930a[validation] Isolate merkle root checks (dergoegge) Pull request description: This PR proposes to check for mutated blocks early as a defense-in-depth mitigation against attacks leveraging mutated blocks. We introduce `IsBlockMutated` which catches all known forms of block malleation and use it to do an early mutation check whenever we receive a `block` message. We have observed attacks that abused mutated blocks in the past, which could have been prevented by simply not processing mutated blocks (e.g. https://github.com/bitcoin/bitcoin/pull/27608 for which a regression test is included in this PR). ACKs for top commit: achow101: ACKd8087adc7emaflcko: ACKd8087adc7e🏄 fjahr: Code review ACKd8087adc7esr-gi: Code review ACKd8087adc7eTree-SHA512: 618ff4ea7f168e10f07504d3651290efbb1bb2ab3b838ffff3527c028caf6c52dedad18d04d3dbc627977479710930e200f2dfae18a08f627efe7e64a57e535f
This commit is contained in:
@@ -3662,6 +3662,87 @@ static bool CheckBlockHeader(const CBlockHeader& block, BlockValidationState& st
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool CheckMerkleRoot(const CBlock& block, BlockValidationState& state)
|
||||
{
|
||||
if (block.m_checked_merkle_root) return true;
|
||||
|
||||
bool mutated;
|
||||
uint256 merkle_root = BlockMerkleRoot(block, &mutated);
|
||||
if (block.hashMerkleRoot != merkle_root) {
|
||||
return state.Invalid(
|
||||
/*result=*/BlockValidationResult::BLOCK_MUTATED,
|
||||
/*reject_reason=*/"bad-txnmrklroot",
|
||||
/*debug_message=*/"hashMerkleRoot mismatch");
|
||||
}
|
||||
|
||||
// Check for merkle tree malleability (CVE-2012-2459): repeating sequences
|
||||
// of transactions in a block without affecting the merkle root of a block,
|
||||
// while still invalidating it.
|
||||
if (mutated) {
|
||||
return state.Invalid(
|
||||
/*result=*/BlockValidationResult::BLOCK_MUTATED,
|
||||
/*reject_reason=*/"bad-txns-duplicate",
|
||||
/*debug_message=*/"duplicate transaction");
|
||||
}
|
||||
|
||||
block.m_checked_merkle_root = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** CheckWitnessMalleation performs checks for block malleation with regard to
|
||||
* its witnesses.
|
||||
*
|
||||
* Note: If the witness commitment is expected (i.e. `expect_witness_commitment
|
||||
* = true`), then the block is required to have at least one transaction and the
|
||||
* first transaction needs to have at least one input. */
|
||||
static bool CheckWitnessMalleation(const CBlock& block, bool expect_witness_commitment, BlockValidationState& state)
|
||||
{
|
||||
if (expect_witness_commitment) {
|
||||
if (block.m_checked_witness_commitment) return true;
|
||||
|
||||
int commitpos = GetWitnessCommitmentIndex(block);
|
||||
if (commitpos != NO_WITNESS_COMMITMENT) {
|
||||
assert(!block.vtx.empty() && !block.vtx[0]->vin.empty());
|
||||
const auto& witness_stack{block.vtx[0]->vin[0].scriptWitness.stack};
|
||||
|
||||
if (witness_stack.size() != 1 || witness_stack[0].size() != 32) {
|
||||
return state.Invalid(
|
||||
/*result=*/BlockValidationResult::BLOCK_MUTATED,
|
||||
/*reject_reason=*/"bad-witness-nonce-size",
|
||||
/*debug_message=*/strprintf("%s : invalid witness reserved value size", __func__));
|
||||
}
|
||||
|
||||
// The malleation check is ignored; as the transaction tree itself
|
||||
// already does not permit it, it is impossible to trigger in the
|
||||
// witness tree.
|
||||
uint256 hash_witness = BlockWitnessMerkleRoot(block, /*mutated=*/nullptr);
|
||||
|
||||
CHash256().Write(hash_witness).Write(witness_stack[0]).Finalize(hash_witness);
|
||||
if (memcmp(hash_witness.begin(), &block.vtx[0]->vout[commitpos].scriptPubKey[6], 32)) {
|
||||
return state.Invalid(
|
||||
/*result=*/BlockValidationResult::BLOCK_MUTATED,
|
||||
/*reject_reason=*/"bad-witness-merkle-match",
|
||||
/*debug_message=*/strprintf("%s : witness merkle commitment mismatch", __func__));
|
||||
}
|
||||
|
||||
block.m_checked_witness_commitment = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// No witness data is allowed in blocks that don't commit to witness data, as this would otherwise leave room for spam
|
||||
for (const auto& tx : block.vtx) {
|
||||
if (tx->HasWitness()) {
|
||||
return state.Invalid(
|
||||
/*result=*/BlockValidationResult::BLOCK_MUTATED,
|
||||
/*reject_reason=*/"unexpected-witness",
|
||||
/*debug_message=*/strprintf("%s : unexpected witness data found", __func__));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW, bool fCheckMerkleRoot)
|
||||
{
|
||||
// These are checks that are independent of context.
|
||||
@@ -3680,17 +3761,8 @@ bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensu
|
||||
}
|
||||
|
||||
// Check the merkle root.
|
||||
if (fCheckMerkleRoot) {
|
||||
bool mutated;
|
||||
uint256 hashMerkleRoot2 = BlockMerkleRoot(block, &mutated);
|
||||
if (block.hashMerkleRoot != hashMerkleRoot2)
|
||||
return state.Invalid(BlockValidationResult::BLOCK_MUTATED, "bad-txnmrklroot", "hashMerkleRoot mismatch");
|
||||
|
||||
// Check for merkle tree malleability (CVE-2012-2459): repeating sequences
|
||||
// of transactions in a block without affecting the merkle root of a block,
|
||||
// while still invalidating it.
|
||||
if (mutated)
|
||||
return state.Invalid(BlockValidationResult::BLOCK_MUTATED, "bad-txns-duplicate", "duplicate transaction");
|
||||
if (fCheckMerkleRoot && !CheckMerkleRoot(block, state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// All potential-corruption validation must be done before we do any
|
||||
@@ -3781,6 +3853,37 @@ bool HasValidProofOfWork(const std::vector<CBlockHeader>& headers, const Consens
|
||||
[&](const auto& header) { return CheckProofOfWork(header.GetHash(), header.nBits, consensusParams);});
|
||||
}
|
||||
|
||||
bool IsBlockMutated(const CBlock& block, bool check_witness_root)
|
||||
{
|
||||
BlockValidationState state;
|
||||
if (!CheckMerkleRoot(block, state)) {
|
||||
LogDebug(BCLog::VALIDATION, "Block mutated: %s\n", state.ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (block.vtx.empty() || !block.vtx[0]->IsCoinBase()) {
|
||||
// Consider the block mutated if any transaction is 64 bytes in size (see 3.1
|
||||
// in "Weaknesses in Bitcoin’s Merkle Root Construction":
|
||||
// https://lists.linuxfoundation.org/pipermail/bitcoin-dev/attachments/20190225/a27d8837/attachment-0001.pdf).
|
||||
//
|
||||
// Note: This is not a consensus change as this only applies to blocks that
|
||||
// don't have a coinbase transaction and would therefore already be invalid.
|
||||
return std::any_of(block.vtx.begin(), block.vtx.end(),
|
||||
[](auto& tx) { return GetSerializeSize(TX_NO_WITNESS(tx)) == 64; });
|
||||
} else {
|
||||
// Theoretically it is still possible for a block with a 64 byte
|
||||
// coinbase transaction to be mutated but we neglect that possibility
|
||||
// here as it requires at least 224 bits of work.
|
||||
}
|
||||
|
||||
if (!CheckWitnessMalleation(block, check_witness_root, state)) {
|
||||
LogDebug(BCLog::VALIDATION, "Block mutated: %s\n", state.ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
arith_uint256 CalculateHeadersWork(const std::vector<CBlockHeader>& headers)
|
||||
{
|
||||
arith_uint256 total_work{0};
|
||||
@@ -3889,33 +3992,8 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat
|
||||
// * There must be at least one output whose scriptPubKey is a single 36-byte push, the first 4 bytes of which are
|
||||
// {0xaa, 0x21, 0xa9, 0xed}, and the following 32 bytes are SHA256^2(witness root, witness reserved value). In case there are
|
||||
// multiple, the last one is used.
|
||||
bool fHaveWitness = false;
|
||||
if (DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_SEGWIT)) {
|
||||
int commitpos = GetWitnessCommitmentIndex(block);
|
||||
if (commitpos != NO_WITNESS_COMMITMENT) {
|
||||
bool malleated = false;
|
||||
uint256 hashWitness = BlockWitnessMerkleRoot(block, &malleated);
|
||||
// The malleation check is ignored; as the transaction tree itself
|
||||
// already does not permit it, it is impossible to trigger in the
|
||||
// witness tree.
|
||||
if (block.vtx[0]->vin[0].scriptWitness.stack.size() != 1 || block.vtx[0]->vin[0].scriptWitness.stack[0].size() != 32) {
|
||||
return state.Invalid(BlockValidationResult::BLOCK_MUTATED, "bad-witness-nonce-size", strprintf("%s : invalid witness reserved value size", __func__));
|
||||
}
|
||||
CHash256().Write(hashWitness).Write(block.vtx[0]->vin[0].scriptWitness.stack[0]).Finalize(hashWitness);
|
||||
if (memcmp(hashWitness.begin(), &block.vtx[0]->vout[commitpos].scriptPubKey[6], 32)) {
|
||||
return state.Invalid(BlockValidationResult::BLOCK_MUTATED, "bad-witness-merkle-match", strprintf("%s : witness merkle commitment mismatch", __func__));
|
||||
}
|
||||
fHaveWitness = true;
|
||||
}
|
||||
}
|
||||
|
||||
// No witness data is allowed in blocks that don't commit to witness data, as this would otherwise leave room for spam
|
||||
if (!fHaveWitness) {
|
||||
for (const auto& tx : block.vtx) {
|
||||
if (tx->HasWitness()) {
|
||||
return state.Invalid(BlockValidationResult::BLOCK_MUTATED, "unexpected-witness", strprintf("%s : unexpected witness data found", __func__));
|
||||
}
|
||||
}
|
||||
if (!CheckWitnessMalleation(block, DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_SEGWIT), state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// After the coinbase witness reserved value and commitment are verified,
|
||||
|
||||
Reference in New Issue
Block a user