Closed code423n4 closed 2 years ago
Completely disagree with the finding as that's the point of the Pause, additionally the Vault has a "pausedWithdrawals" mode
Also see Quantstamp Audit
I take your word for it, but this comment in the top-level BaseStrategy.sol made me think that the _withdrawAll() function should not be susceptible to the pause.
/// @dev This can be called even when paused, and strategist can trigger this via the vault.
function withdrawToVault() external {
_onlyVault();
_withdrawAll();
uint256 balance = IERC20Upgradeable(want).balanceOf(address(this));
_transferToVault(balance);
}
Lines of code
https://github.com/Badger-Finance/vested-aura/blob/d504684e4f9b56660a9e6c6dfb839dcebac3c174/contracts/MyStrategy.sol#L182-L190
Vulnerability details
Impact
When the strategy contract is paused, all withdrawal functionality will be paused. Based on the comments in
MyStrategy.sol
andbaseStrategy.sol
,withdrawToVault()
should not be affected by the pause functionality. This is not the case due to thewhenNotpaused
modifier all the way down the execution chain inmanualProcessExpiredLocks()
.Proof of Concept
If the strategy contract is paused, the following withdraw calls will be paused as well:
BaseStrategy.withdraw()
, intentionally due towhenNotPaused
modifierBaseStrategy.withdrawToVault()
, unintentionally.BaseStrategy.withdrawToVault()
callsMyStrategy._withdrawAll()
which doesn't do anything other than checking balances. Per the comments of_withdrawAll()
, "Make sure to call prepareWithdrawAll before _withdrawAll".prepareWithdrawAll()
callsmanualProcessExpiredLocks();
which has thewhenNotPaused
modifier.Therefore, if a vulnerability is discovered or is currently being exploited and Badger must decide to pause the contract, they will not be able to withdraw from any Aura locks. If this were not known previously, Badger would be required to unpause, process locks, and then withdraw. An exploiter could easily front run this, bypassing the protection granted by the pausing functionality.
Tools Used
Manual review.
Recommended Mitigation Steps
Either remove the
whenNotPaused
modifier frommanualProcessExpiredLocks()
or callLOCKER.processExpiredLocks(false);
within_withdrawAll()
, similar to how it is done in_withdrawSome()
.