Fix crowdloan reentrancy bug#2786
Conversation
🛡️ AI Review — Skeptic (security review)VERDICT: VULNERABLE BASELINE scrutiny: established 2018 account, repo write permission, substantial prior subtensor PR history; branch fix-crowdloan-reentrancy -> devnet-ready. No Findings
Prior-comment reconciliation
ConclusionThe PR appears legitimate, but 📜 Previous run (superseded)
# 🔍 AI Review — Auditor (domain review) has not yet run on this PR. |
|
🔄 AI review updated — Skeptic: VULNERABLE |
|
🔄 AI review updated — Skeptic: VULNERABLE |
| ensure!(crowdloan.raised == crowdloan.cap, Error::<T>::CapNotRaised); | ||
| ensure!(!crowdloan.finalized, Error::<T>::AlreadyFinalized); | ||
|
|
||
| crowdloan.finalized = true; |
There was a problem hiding this comment.
[HIGH] Make finalize transactional before setting finalized early
This write now happens before several fallible operations: preimage lookup/drop, dispatched call execution, balance transfer, and invalid finalization config. Because finalize itself is not annotated #[transactional] or wrapped in an equivalent storage transaction, a later Err can leave crowdloan.finalized = true persisted without emitting Finalized or moving funds. That turns transient failures, including a re-entrant AlreadyFinalized, into a permanent lock that also blocks withdraw, refund, updates, and dissolve paths.
The new test_finalize_blocks_reentrant_withdraw wraps the call in frame_support::storage::with_storage_layer, but that only changes the test harness; production finalize still has no transaction boundary. Add use frame_support::transactional; and #[transactional] to finalize, or keep the early guard in a transactional helper, so any post-marking error rolls this storage write back.
|
🔄 AI review updated — Skeptic: VULNERABLE |
|
One question: if the withdraw and refund cann't be dispatched successfully, should we check both types of all in create extrinsic |
|
🔄 AI review updated — Skeptic: VULNERABLE |
|
🔄 AI review updated — Skeptic: VULNERABLE |
Summary
AlreadyFinalizedguard before moving funds.AlreadyFinalizingguard to reject nested finalize calls whileCurrentCrowdloanIdis set, preserving the invariant that only one crowdloan finalization context can be active at a time.AlreadyFinalizingis returned and both crowdloans remain unfinalized