Open sherlock-admin opened 1 year ago
@T-Woodward / @weitianjie2000 we should discuss how to remediate this issue. I think the auditor has a good point about enter / exits within the same block that we should take a look at.
At the same time, I believe this attack is more pronounced when the attacker can get much higher leverage than the entire vault value (as in this example), so in practice it might be difficult.
Note to self: it looks like a more strict enforcement of the minAccountBorrowSize would be sufficient to reduce the profitability of these attacks by forcing the account to borrow, will have to investigate how to do that without hampering other UX.
Yeah I think this is a legitimate issue. We are implementing the following changes:
Together, these changes will make the attack uneconomical because of the fees involved, it will require substantial capital, and you wouldn't know when we are going to call reinvestReward so you would have to basically always have your capital in to make sure you caught the reward reinvestment which would defeat the purpose of the whole thing.
Minimum Leverage Ratio here: https://github.com/notional-finance/contracts-v2/pull/104/commits/0dbaac7bc559d04e1006cfbe12d5e6d73e82e306 5 block minimum hold time here: https://github.com/notional-finance/contracts-v2/pull/104/commits/bf98affbf49e2b15f6cd0714dade9f1c138df7db
The issue is fixed.
reinvestReward
is not permissionless now.
https://github.com/notional-finance/leveraged-vaults/commit/cc0401752f5c8b5be9a7dd5e48c249cb16018d87
And this fix(https://github.com/notional-finance/contracts-v2/commit/0dbaac7bc559d04e1006cfbe12d5e6d73e82e306) makes the flash-loan attack uneconomical.
xiaoming90
high
Gain From Balancer Vaults Can Be Stolen
Summary
The BPT gain (rewards) of the vault can be stolen by an attacker.
Vulnerability Detail
At T0 (Time 0), assume that the state of the WETH/wstETH MetaPool Vault is as follows:
Assume that if the
reinvestReward
is called, it will reinvest 1000 BPT back into the vault. Thus, if thereinvestReward
is called, thetotalBPTHeld
of the vault will become 2000 BPT.Following is the description of the attack:
The attacker notice that if the
reinvestReward
is called, it will result in a large increase in the total BPT held by the vaultThe attacker flash-loan a large amount of WETH (e.g. 1,000,000) from a lending protocol (e.g. dydx)
Enter the vault by depositing 1,000,000 WETH by calling the
VaultAccountAction.enterVault
function. However, do not borrow any cash from Notional by setting thefCash
parameter of theVaultAccountAction.enterVault
function to0
.There is no need to borrow from Notional as the attacker could already flash-loan a large amount of WETH with a non-existence fee rate (e.g. 1 Wei in dydx). Most importantly, the vault fee will only be charged if the user borrows from Notional. The fee is assessed within the
VaultAccount._borrowIntoVault
, which will be skipped if users are not borrowing. By not borrowing from Notional, the attacker does not need to pay any fee when entering the vault and this will make the attacker more profitable.The vault will deposit 1,000,000 WETH to the Balancer pool and receive a large amount of BPT in return. For simplicity's sake, assume that the vault receives 1,000,000 BPT in return.
Based on the
StrategyUtils._convertBPTClaimToStrategyTokens
function, the attacker will receive 100,000 strategy tokens. The state of the vault will be as follows after the attacker deposits:totalBPTHeld = 1,001,000 BPT
totalStrategyTokenGlobal = 1,001,000
1 Strategy Token can claim 1 BPT
Alice holds 1000 Strategy Tokens
Attacker holds 1,000,000 Strategy Tokens
The attacker calls the
reinvestReward
function, and reward tokens will be reinvested. Assume that the vault receives 1000 BPT. The state of the vault will be as follows after the reinvest:totalBPTHeld = 1,002,000 BPT
totalStrategyTokenGlobal = 1,001,000
1 Strategy Token can claim ~1.0009 BPT
Alice holds 1000 Strategy Tokens
Attacker holds 1,000,000 Strategy Tokens
The attacker exits the vault with all his strategy tokens by calling the
VaultAccountAction.exitVault
function. This will cause the vault the redeem all the 100,000 Strategy Tokens owned by the attacker. Based on theStrategyUtils._convertStrategyTokensToBPTClaim
function, the attacker will receive 1,000,999 BPT in return. Note that there is no fee for exiting the vault and there is no need for repaying the debt as the attacker did not borrow any assets from Notional at the beginning.reinvestReward
function, and effectively gain around 999 BPT.reinvestReward
function was triggered managed to obtain almost all of her allocated shares of rewards (999 BPT) and left only 1 BPT for Alice.Note: A flash-loan is not required if the attacker has sufficient liquidity to carry out the attack or the vault does not have much liquidity.
Following are the two functions for converting between BPT and Strategy Token for reference.
https://github.com/sherlock-audit/2022-09-notional/blob/main/leveraged-vaults/contracts/vaults/balancer/internal/strategy/StrategyUtils.sol#L27
https://github.com/sherlock-audit/2022-09-notional/blob/main/leveraged-vaults/contracts/vaults/balancer/internal/strategy/StrategyUtils.sol#L18
Impact
Loss of assets for the users as their BPT gain (rewards) can be stolen. This issue affects all balancer-related vaults that contain the permissionless
reinvestReward
function.Code Snippet
https://github.com/sherlock-audit/2022-09-notional/blob/main/leveraged-vaults/contracts/vaults/balancer/internal/strategy/StrategyUtils.sol#L27 https://github.com/sherlock-audit/2022-09-notional/blob/main/leveraged-vaults/contracts/vaults/balancer/internal/strategy/StrategyUtils.sol#L18
Tool used
Manual Review
Recommendation
Following are the list of root causes of the issue and some recommendation to mitigate them.
reinvestReward
function is permissionless and can be called by anyone. It is recommended to implement access control to ensure that this function can only be triggered by Notional. Do note that even if the attacker cannot trigger thereinvestReward
function, it is still possible for the attacker to front-run and back-end thereinvestReward
transaction to carry out the attack if they see this transaction in the public mempool. Thus, consider sending thereinvestReward
transaction as a private transaction via Flashbot so that the attacker cannot sandwich the transaction.reinvestReward
function is triggered and exit the vault afterward and reap most of the gains. Consider implementing snapshotting within the vault.