sherlock-audit / 2022-09-notional-judging

4 stars 2 forks source link

xiaoming90 - Malicious Users Can Force An Emergency Settlement On Any Vault #103

Closed sherlock-admin closed 1 year ago

sherlock-admin commented 1 year ago

xiaoming90

high

Malicious Users Can Force An Emergency Settlement On Any Vault

Summary

Malicious users can force an emergency settlement on any vault causing a denial of service.

Vulnerability Detail

The following function shows that an emergency settlement can only be triggered if the total number of BPT held by the vault exceeds the BPT threshold.

https://github.com/sherlock-audit/2022-09-notional/blob/main/leveraged-vaults/contracts/vaults/balancer/internal/settlement/SettlementUtils.sol#L86

File: SettlementUtils.sol
86:     function _getEmergencySettlementParams(
87:         StrategyContext memory strategyContext,
88:         PoolContext memory poolContext,
89:         uint256 maturity,
90:         uint256 totalBPTSupply
91:     )  internal view returns(uint256 bptToSettle) {
92:         StrategyVaultSettings memory settings = strategyContext.vaultSettings;
93:         StrategyVaultState memory state = strategyContext.vaultState;
94: 
95:         // Not in settlement window, check if BPT held is greater than maxBalancerPoolShare * total BPT supply
96:         uint256 emergencyBPTWithdrawThreshold = settings._bptThreshold(totalBPTSupply);
97: 
98:         if (strategyContext.totalBPTHeld <= emergencyBPTWithdrawThreshold)
99:             revert Errors.InvalidEmergencySettlement();

Another point to note is that a major side-effect of an emergency settlement is that the vault will be locked up after the emergency settlement. No one is allowed to enter the vault and users are only allowed to exit from the vault by taking their proportional share of cash and strategy tokens. Thus, if anyone can force an emergency settlement on any vault, it would cause a widespread denial of service.

However, it is possible for anyone to force an emergency settlement on any vault.

Notice that in Line 65 below, the totalBPTSupply used for computing the BPT threshold is the spot/current total supply of BPT of a pool.

https://github.com/sherlock-audit/2022-09-notional/blob/main/leveraged-vaults/contracts/vaults/balancer/external/MetaStable2TokenAuraHelper.sol#L52

File: MetaStable2TokenAuraHelper.sol
52:     function settleVaultEmergency(
53:         MetaStable2TokenAuraStrategyContext calldata context, 
54:         uint256 maturity, 
55:         bytes calldata data
56:     ) external {
57:         RedeemParams memory params = SettlementUtils._decodeParamsAndValidate(
58:             context.baseStrategy.vaultSettings.emergencySettlementSlippageLimitPercent,
59:             data
60:         );
61: 
62:         uint256 bptToSettle = context.baseStrategy._getEmergencySettlementParams({
63:             poolContext: context.poolContext.basePool, 
64:             maturity: maturity, 
65:             totalBPTSupply: IERC20(context.poolContext.basePool.pool).totalSupply()
66:         });
67: 
68:         uint256 redeemStrategyTokenAmount = 
69:             context.baseStrategy._convertBPTClaimToStrategyTokens(bptToSettle);
70: 
71:         _executeSettlement({
72:             strategyContext: context.baseStrategy,
73:             oracleContext: context.oracleContext,
74:             poolContext: context.poolContext,
75:             maturity: maturity,
76:             bptToSettle: bptToSettle,
77:             redeemStrategyTokenAmount: redeemStrategyTokenAmount,
78:             params: params
79:         });
80: 
81:         emit BalancerEvents.EmergencyVaultSettlement(maturity, bptToSettle, redeemStrategyTokenAmount);
82:     }

Assume the following:

At this point in time, there is no way for anyone to trigger an emergency settlement on the vault because the total number of BTP held by the vault has not exceeded to BPT threshold yet.

However, since the BPT threshold is calculated on the spot/current total supply of BPT, it can be manipulated within a single block/transaction. BPT can also be obtained from the open market.

A malicious user could obtain a flash-loan DAI and obtain 90 BPT from the open market. He then exits the Balancer pool with all the 90 BPT and redeems the underlying assets, and this will cause the total supply of BPT to fall from 100 to 10.

At this point, the total supply of BPT is 10, so the BPT threshold of the vault will become 2 BPT (10 * 0.2). The malicious user can proceed to trigger the emergency settlement on the vault and cause the vault to be locked up.

After the attack, the attacker proceeds to swap the redeemed underlying assets to the DAI and repay back the loan. Flash-loan cost is 1 wei in dydx, so the cost of attack is limited to slippage during swaps. If swaps involve only stablecoin, then the cost of attack is further reduced since stable swap usually has minimum slippage.

Impact

Malicious users can lock up all the leverage vaults offered by Notional causing denial-of-service. This results in a loss of funds for the protocol as the vault is no longer generating profit for the protocol, and also a loss of funds for vault users as they cannot realize the profits because they are forced to exit the vault prematurely.

The following are various reasons why someone would want to perform a DOS on Notional vaults:

Code Snippet

https://github.com/sherlock-audit/2022-09-notional/blob/main/leveraged-vaults/contracts/vaults/balancer/internal/settlement/SettlementUtils.sol#L86 https://github.com/sherlock-audit/2022-09-notional/blob/main/leveraged-vaults/contracts/vaults/balancer/external/MetaStable2TokenAuraHelper.sol#L52

Tool used

Manual Review

Recommendation

Consider updating the implementation to avoid using the spot/current total supply of BPT to determine the BPT threshold as this can be manipulated within a single block/transaction to force a certain condition. A short-term quick fix would be to hardcode the threshold or have the BTP threshold manually set by Notional governance instead of computing the threshold on the fly.

In addition, the emergency settlement function is permissionless and can be called by anyone. If possible, implement access control to ensure that this function can only be triggered by Notional to reduce the attack surface.

Duplicate of #66

jeffywu commented 1 year ago

This seems like a duplicate of #66