There is a general issue with the casting of virtualRewardsToAdd in StakingRewards:_increaseUserShare to the lower precision uint128, as this makes it likely that an overflow can happen. Using this, an attacker can deposit a large amount of LP while having their virtualRewards not increase at all. This then means that they can steal whatever amount is currently cached in totalRewards[poolID].
Proof of Concept
As I stated in another submitted issue, there is a general problem in which attackers can snipe newly whitelisted pools to get up to 1% of the emissions rewards (2_000e18 SALT by default). In this attack they will deposit 101 wei of both tokens into the pool. Therefore, we will consider this our starting state - in particular lets say stETH was newly added and this snipe has already occurred. This means theres 101 stETH, 101 WETH, totalShares[poolID]=202, and totalRewards[poolID]=2_000e18.
Let's now assume userA is depositing WETH & stETH into this pool to steal rewards. Ultimately Liquidity:_depositLiquidityAndIncreaseShare is run which has the following logic:
The addedLiquidity is calculated by Pools:addLiquidity, which ultimately calls Pools:_addLiquidity, which has the following logic for calculating the amount of LP issued to the user:
Now that we've seen all the relevant equations, let's calculate what deposit of WETH and stETH will allow the attacker to overflow the virtualRewardsToAdd calculation to get user.virtualRewards=0. We have the following set of equations (added WETH and stETH will be the same):
Therefore, the attacker can add ~17.18e18 of WETH and stETH to this pool to cause uint128(virtualRewardsToAdd) to overflow and equal 0. When this occurs (and since the user effectively LPed the entire pool, they will be able to withdraw ~2_000e18 SALT, or whatever the current totalRewards[poolID] amount is). You can see this in the StakingRewards:userRewardForPool logic:
Lines of code
https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/StakingRewards.sol#L57-L92 https://github.com/code-423n4/2024-01-salty/blob/main/src/pools/Pools.sol#L131-L135 https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/Liquidity.sol#L102-L107
Vulnerability details
Impact
There is a general issue with the casting of
virtualRewardsToAdd
inStakingRewards:_increaseUserShare
to the lower precisionuint128
, as this makes it likely that an overflow can happen. Using this, an attacker can deposit a large amount of LP while having theirvirtualRewards
not increase at all. This then means that they can steal whatever amount is currently cached intotalRewards[poolID]
.Proof of Concept
As I stated in another submitted issue, there is a general problem in which attackers can snipe newly whitelisted pools to get up to 1% of the emissions rewards (2_000e18 SALT by default). In this attack they will deposit 101 wei of both tokens into the pool. Therefore, we will consider this our starting state - in particular lets say stETH was newly added and this snipe has already occurred. This means theres 101 stETH, 101 WETH,
totalShares[poolID]
=202, andtotalRewards[poolID]
=2_000e18.Let's now assume userA is depositing WETH & stETH into this pool to steal rewards. Ultimately
Liquidity:_depositLiquidityAndIncreaseShare
is run which has the following logic:The
addedLiquidity
is calculated byPools:addLiquidity
, which ultimately callsPools:_addLiquidity
, which has the following logic for calculating the amount of LP issued to the user:After this,
StakingRewards:_increaseUserShare
is used to calculate thevirtualRewards
changes:Now that we've seen all the relevant equations, let's calculate what deposit of WETH and stETH will allow the attacker to overflow the
virtualRewardsToAdd
calculation to getuser.virtualRewards
=0. We have the following set of equations (added WETH and stETH will be the same):Therefore, the attacker can add ~17.18e18 of WETH and stETH to this pool to cause
uint128(virtualRewardsToAdd)
to overflow and equal 0. When this occurs (and since the user effectively LPed the entire pool, they will be able to withdraw ~2_000e18 SALT, or whatever the currenttotalRewards[poolID]
amount is). You can see this in theStakingRewards:userRewardForPool
logic:Assessed type
Math