validation: fix UB in LoadChainTip

The removal of the chain tip from setBlockIndexCandidates was
happening after nSequenceId was modified. Since the set uses
nSequenceId as a sort key, modifying it while the element is in the
set is undefined behavior, which can cause the erase to fail.

With assumeutxo, a second form of UB exists: two chainstates each
have their own candidate set, but share the same CBlockIndex
objects. Calling LoadChainTip on one chainstate mutates nSequenceIds
that are also in the other chainstate's set.

Fix by populating setBlockIndexCandidates after all changes to
nSequenceId.
This commit is contained in:
marcofleon
2026-03-04 19:39:20 +00:00
parent 9249e6089e
commit 854a6d5a9a
5 changed files with 45 additions and 23 deletions

View File

@@ -126,6 +126,13 @@ static ChainstateLoadResult CompleteChainstateInitialization(
}
}
// Populate setBlockIndexCandidates in a separate loop, after all LoadChainTip()
// calls have finished modifying nSequenceId. Because nSequenceId is used in the
// set's comparator, changing it while blocks are in the set would be UB.
for (const auto& chainstate : chainman.m_chainstates) {
chainstate->PopulateBlockIndexCandidates();
}
const auto& chainstates{chainman.m_chainstates};
if (std::any_of(chainstates.begin(), chainstates.end(),
[](const auto& cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) {