89055 sc high missing period check in rescuewithdrawfromblocklisted marks a still open period as claimed causing permanent freezing of funds for any withdrawal later requested into that period
Submitted on Aug 16th 2026 at 19:41:33 UTC by @AlexScherbatyuk for Audit Comp | Firelight
Report ID: #89055
Report Type: Smart Contract
Report severity: High
Target: https://github.com/immunefi-team/audit-comp-firelight/blob/v1_audit_ready/contracts/core/FirelightVault.sol
Impacts:
Permanent freezing of funds
Description
Brief/Intro
FirelightVault batches withdrawals into periods where a request credits withdrawSharesOf[P][account] while currentPeriod() == P - 1, and the resulting assets are claimed once the period has ended. However, a user could be added to the blocklist that opens a new branch where a rescuer can call rescueWithdrawFromBlocklisted to move blocked user funds to a safe address. This mechanism has a flow that can cause a user's funds to be permanently frozen.
The implemented blocklist mechanics assume that a user could be blocklisted by calling addToBlocklist and removed from blocklist via removeFromBlocklist (e.g. user proved the case for blocklisting is false positive), allowing the user to proceed with deposit/withdraw operations.
However, in case rescueWithdrawFromBlocklisted has been called during the blocklisting period and removal from blocklist has happened during the same period, it creates a window when any further withdrawal requests (incease there is more funds to withdraw) would be scheduled for P, which has already been marked as claimed, locking user funds permanently.
This requires no trusted role to misbehave — blocklist → rescue → un-blocklist is exactly the intended handling of a false-positive blocklist, and the final trigger is a normal, permissionless withdrawal request.
According to docs current period is 1 day, but it is going to be up to 30 days period which increases the likelihood.
Vulnerability Details
The withdrawal flow relies on a temporal invariant: pool P only accepts credits while currentPeriod() == P - 1, and isWithdrawClaimed[P][account] can normally only become true once currentPeriod() >= P + 1 (claims are restricted to past periods). A true claim flag can therefore never precede a credit.
rescueWithdrawFromBlocklisted breaks this invariant. It has no period check, so it can set the claim flag for a future period that is still accepting requests:
_requestWithdraw credits the receiver without ever checking the claim flag:
And claimWithdraw hard-reverts on the flag:
Atack Path
Impact Details
The assets of the new request in step 4 are permanently unrecoverable:
claimWithdraw(P)reverts withAlreadyClaimedPeriodRecovery via rescue is also impossible, even if the user is re-blocklisted: the rescue path reverts on the same flag.
The assets remain in
pendingWithdrawAssetsforever and are permanently excluded fromtotalAssets(). they are not even socialized back to remaining share holders. The user's burned vault shares are a total loss.
This is permanent, unrecoverable loss of user funds with no recovery path for any role, mapping to in-scope impact #Permanent freezing of funds. It is not "temporary freezing" (no state transition ever unlocks the funds) and not mere "griefing" (the damage is permanent loss of principal).
No trusted role needs to misbehave (blocklist → rescue → un-blocklist) is the intended resolution of a false-positive blocklist, and the final trigger is a normal, permissionless withdrawal request in the same period.
Recommendation
Consider removing the isWithdrawClaimed[periods[i]][from] = true line in rescueWithdrawFromBlocklisted:
References
https://docs.firelight.finance/for-stakers/deployments-and-withdrawals
Proof of Concept
Was this helpful?