A malicios user can call distributeAssets()
with carefully selected input, backrunning a transaction that liquidates native tokens, and steal all native token rewards. The input would consist of the _assets
array only having a native token entry with address(0)
, a large amount
and high _collateralRate
and _hundredPC
, causing rewards to be added with little cost to the user position. The malicios user will then call claimRewards()
and steal the native token rewards from the LiquidationPool
and thus other stakers.
The distributeAssets()
function does not have any validation on msg.sender
, meaning anybody can call it. Normally this function would only be called by LiquidationPoolManager
in runLiquidation()
(see code below) during vault liquidations and would revert if any ERC20s
are passed during the safeTransferFrom()
in distributeAssets()
(see code below) call, pulling the ERC20
funds from the LiquidationPoolManager
:
https://github.com/Cyfrin/2023-12-the-standard/blob/main/contracts/LiquidationPool.sol#L229
However, there are no checks that make the transaction revert if the input array only cosists of the native token.
This is a consequence of the design of asset transfer within the protocol. When ERC20s
are liquidated they are first approved by the LiquidationPoolManager
and then pulled by the LiquidationPool
making the transaction revert if there is no approval, however when native tokens are liquidated, they are directly sent to the LiquidationPool
, without any checks on the LiquidationPool
side.
https://github.com/Cyfrin/2023-12-the-standard/blob/main/contracts/LiquidationPoolManager.sol#L59
Let's now take a more detailed look at how amount
and costInEuros
can be manipulated inside distributeAssets()
(see code below).
The _assets
array is composed of ILiquidationPoolManager.Asset
and ITokenManager.Token
, which look like this:
https://github.com/Cyfrin/2023-12-the-standard/blob/main/contracts/interfaces/ILiquidationPoolManager.sol#L7
https://github.com/Cyfrin/2023-12-the-standard/blob/main/contracts/interfaces/ITokenManager.sol#L5
A malicios user can select amount such that it is large enough that his _portion
covers all native token rewards in the LiquidationPool
.
In distributeAssets()
we can see that _portion
is calculated as follows:
Suppose there is 100
liquidated ETH
available in the LiquidationPool
for claiming and the malicios user has 1%
of the total stake.
After a normal transaction, the user would have 1%
of that ETH
, so 1 ETH
available in rewards to claim. The malicios user can call the function again with amount= 100*100 = 10000
and have _portion=100
, making 100 ETH
available as rewards for himself.
Under normal circuimstances, these are 'rewards' subtracted from the current EUROs
stake of the user, however this can too be manipulated with the input in four ways:
very high _collateralrate
very high _hundredPC
higher token decimals(for ERC20s
with less than 18)
oracle address returning lower price
In distributeAssets()
costInEuros
is calculated as follows:
https://github.com/Cyfrin/2023-12-the-standard/blob/main/contracts/LiquidationPool.sol#L205
As we can see rewards are increased by _portion
. They can be later claimed in the claimRewards()
function.
https://github.com/Cyfrin/2023-12-the-standard/blob/main/contracts/LiquidationPool.sol#L164
After the malicios user completes the transaction having increased their rewards by a large amount, and having close to 0, or even 0 costInEuros
, they can just call claimRewards()
and steal native tokens from the LiquidationPool
and thus other stakers.
The malicios user is only required to have a small stake in the LiquidationPool
before the transaction.
A side effect of this attack would be all stakers having inflated native token rewards.
This attack vector can be further used to DoS the distributeAssets()
function by bringing the highest stakers rewards close to the maximum uint256
, causing the transactions to revert on overflow on any attempt to liquidate a vault that has native tokens because their rewards would not be able to be increased any further.
Native tokens can be stolen from the vault and all stakers will have permanently inflated native token rewards.
Manual Review
The dev team can handle this attack vector by for example requiring distributeAssets()
to only be called by the intended actors. If the function is only inteded to be called by the LiquidationPoolManager
, add the onlyManager
modifier. This will prevent the function from being called by any random users with manipulated input.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.