fix: guard BuildWitnessCache against an off-active-chain pindex (heap overflow)

BuildWitnessCache sizes its blockCms buffer from pindex->GetHeight() but the
Phase-1 loop walks the active chain (chainActive.Next), terminating only on
pbi==pindex. If a reorg moved pindex off the active chain while the notify
thread lagged (cs_main is released between per-block ChainTip calls) and the new
active tip is taller, the loop never reaches pindex and, once past pindex's
height, writes blockCms[h-startHeight] out of bounds -- a heap overflow.
Rebuilding witnesses for an abandoned block is meaningless anyway, so bail early
when pindex is not on the active chain; cs_main is held for the whole function,
so the check cannot race the loop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-08 05:48:39 +02:00
parent 3bb4eb3a5a
commit 762e25294f

View File

@@ -1229,6 +1229,20 @@ void CWallet::BuildWitnessCache(const CBlockIndex* pindex, bool witnessOnly)
LOCK2(cs_main, cs_wallet); LOCK2(cs_main, cs_wallet);
// The Phase-1 loop below walks the ACTIVE chain (chainActive.Next) and sizes blockCms from
// pindex->GetHeight(), terminating only on pbi==pindex. If pindex was reorged OFF the active
// chain (a reorg landed while ThreadNotifyWallets drained its connect backlog with cs_main
// released), the loop never reaches pindex and, once the active tip passes pindex's height, it
// writes blockCms[h-startHeight] out of bounds -> heap overflow. Rebuilding witnesses for an
// abandoned block is meaningless; the ChainTip for the new active tip re-drives this. cs_main is
// held for the whole function, so this check cannot race the loop below.
if (pindex != chainActive[pindex->GetHeight()]) {
if (fZdebug)
LogPrintf("%s: pindex height=%d not on active chain (reorg); skipping witness rebuild\n",
__func__, pindex->GetHeight());
return;
}
int startHeight = VerifyAndSetInitialWitness(pindex, witnessOnly) + 1; int startHeight = VerifyAndSetInitialWitness(pindex, witnessOnly) + 1;
if (startHeight > pindex->GetHeight() || witnessOnly) { if (startHeight > pindex->GetHeight() || witnessOnly) {