From c99d859781ae3d61db8e8167940a8cc4333628c5 Mon Sep 17 00:00:00 2001 From: Sami Ahmed Date: Thu, 11 Jun 2026 01:33:16 -0700 Subject: [PATCH] Fix use-after-move crash in orphan block handling; add -ignoredupstake recovery flag mapOrphanBlocksByPrev.insert dereferenced pblock2 after std::move'ing it into mapOrphanBlocks - guaranteed null deref (segfault at offset 4) on every orphan block received. Capture hashPrevBlock and the raw pointer before the move. Also add -ignoredupstake (default off): bypasses the duplicate proof-of-stake rejection so canonical blocks can be imported when a fork twin staking the same outpoint was seen first. Recovery/diagnostic use only. --- src/main.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 94d2fcc..cc4b946 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3071,7 +3071,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) // triangles: check proof-of-stake // Limited duplicity on stake: prevents block flood attack // Duplicate stake allowed only when there is orphan child block - if (pblock->IsProofOfStake() && setStakeSeen.count(pblock->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash)) + if (pblock->IsProofOfStake() && !GetBoolArg("-ignoredupstake", false) && setStakeSeen.count(pblock->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash)) return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for block %s", pblock->GetProofOfStake().first.ToString().c_str(), pblock->GetProofOfStake().second, hash.ToString().c_str()); // Preliminary checks @@ -3129,13 +3129,15 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) { // Limited duplicity on stake: prevents block flood attack // Duplicate stake allowed only when there is orphan child block - if (setStakeSeenOrphan.count(pblock2->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash)) + if (!GetBoolArg("-ignoredupstake", false) && setStakeSeenOrphan.count(pblock2->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash)) return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for orphan block %s", pblock2->GetProofOfStake().first.ToString().c_str(), pblock2->GetProofOfStake().second, hash.ToString().c_str()); else setStakeSeenOrphan.insert(pblock2->GetProofOfStake()); } + uint256 hashPrevOrphan = pblock2->hashPrevBlock; + CBlock* pblock2raw = pblock2.get(); mapOrphanBlocks.insert(make_pair(hash, std::move(pblock2))); - mapOrphanBlocksByPrev.insert(make_pair(pblock2->hashPrevBlock, pblock2.get())); + mapOrphanBlocksByPrev.insert(make_pair(hashPrevOrphan, pblock2raw)); dequeOrphanOrder.push_back(hash); // track insertion order for FIFO eviction // Limit orphan blocks to prevent memory exhaustion. @@ -3147,11 +3149,11 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) // Ask this guy to fill in what we're missing if (pfrom && pindexBest) { - pfrom->PushGetBlocks(pindexBest, GetOrphanRoot(pblock2.get())); + pfrom->PushGetBlocks(pindexBest, GetOrphanRoot(pblock2raw)); // triangles: getblocks may not obtain the ancestor block rejected // earlier by duplicate-stake check so we ask for it again directly if (!IsInitialBlockDownload()) - pfrom->AskFor(CInv(MSG_BLOCK, WantedByOrphan(pblock2.get()))); + pfrom->AskFor(CInv(MSG_BLOCK, WantedByOrphan(pblock2raw))); } return true; }