From 762e25294ff2e0b1bc84f87d7af70a927bb5c6d8 Mon Sep 17 00:00:00 2001 From: DanS Date: Wed, 8 Jul 2026 05:48:39 +0200 Subject: [PATCH] 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) --- src/wallet/wallet.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 3cca3277f..c762b0b97 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1229,6 +1229,20 @@ void CWallet::BuildWitnessCache(const CBlockIndex* pindex, bool witnessOnly) 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; if (startHeight > pindex->GetHeight() || witnessOnly) {