Fix crowdloan reentrancy bug#2786
Conversation
🛡️ AI Review — Skeptic (security review)VERDICT: SAFE BASELINE scrutiny: established 2018 account, repo write permission, substantial prior subtensor PR history, no Gittensor allowlist match; branch fix-crowdloan-reentrancy -> devnet-ready. No FindingsNo findings. Prior-comment reconciliation
ConclusionNo malicious behavior or security vulnerability found in the current diff. The prior rollback concern does not apply here because pallet dispatchables execute inside FRAME storage layers, and the added reentrancy guards fail before funds move while failed finalization rolls back storage changes. 📜 Previous run (superseded)
🔍 AI Review — Auditor (domain review)VERDICT: 👎 Gittensor: UNKNOWN; author is an established repo contributor with write permission. No overlapping PRs found. PR body is substantive and matches the intended reentrancy fix. I did not auto-fill the PR description. Duplicate-work check: no overlapping PRs in the pre-fetched context. Validation note: targeted Findings
ConclusionBlocking because the new write-before-dispatch ordering makes failed finalization paths non-atomic and can leave crowdloans finalized or |
|
🔄 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 |
|
🔄 AI review updated — Skeptic: SAFE Auditor: 👎 |
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