Submitted by crypticdefense, also found by 0xhacksmithh, mrMorningstar, Bigsam, Chinmay, and pkqs90
CDPVault allows users to borrow underlying from PoolV3 by depositing collateral into the vault, such that the (collateral value of their position / liquidationRatio) >= their current total debt.
Users must repay their debt fully via CDPVault::repay, and the amount must cover their entire current total debt, which also includes various interest factors. If the value of their collateral divided by liquidationRatio is less than the debt of their position, then their position is considered unsafe and anyone can liquidate the position by buying the collateral at a discount. The amount spent by the caller is used to cover for the debt.
To ensure that users cannot profit from self liquidations, the liquidatePosition function incorporates a penalty mechanism, that is intended to deduct fees from the payment amount, which subsequently goes to the protocol as profit.
The problem is that when the liquidatePosition function calculates the collateral to give to the caller, it utilizes the the repay amount without the penalty, essentially functioning as if there is no penalty mechanism at all. The caller can specify any repay amount, and the collateral they receive will correspond directly to repay amount / discount, with no penalty.
This allows malicious users to profit by deposit collateral -> borrow WETH -> have their position become unsafe -> buy collateral with WETH at a discount. Malicious users can profit and steal funds from lenders and the protocol.
However, we will see in the PoC how this has no impact against profitable self liquidations
The following block is executed when users repay their debt:
CDPVault.sol#L402-L426
For users to completely repay their loan, they must pay maxRepayment amount, which is calculated via a call to calcTotalDebt.
If the position is unsafe (collateral value / liquidation ratio < total debt), then anyone can liquidate it for a discount:
CDPVault.sol#L521-L532
There is also a penalty that the liquidator must pay (deducted from repayAmount). This is to mitigate profits from self-liquidation, as stated by the natspec of this function:
CDPVault.sol#L503-L504
So the actual amount of debt repaid by the liquidator is repayAmount - penalty:
CDPVault.sol#L538-L539
In the same call, the penalty is also transferred to the pool, taken as a profit for the protocol.
CDPVault.sol#L567-L569
However, there is a critical problem here. We can see that the intention here is that the caller pays repayAmount - penalty for the debt, and that the penalty goes towards profit.
This can be confirmed by observing the amount of debt that is covered via repayment:
CDPVault.sol#L530
Note that repayAmount * liqConfig_.liquidationPenalty is equivalent to repayAmount - penalty. So the debt reduced is repayAmount - penalty. The problem is that the collateral sent to the caller does not incorporate the penalty for liquidation.
Essentially, this makes the penalty redundant, because the caller still receives the full repayAmount of collateral specified, including a discount.
A malicious user can perform the following attack scenario:
Note: The value of the discount and penalty were chosen by observing the values currently set in scripts/config.js, they were not chosen arbitrarily.
Add the following to test/unit/CDPVault.t.sol and run forge test --mt testSelfLiquidateProfit -vv:
As displayed in the coded PoC, since the user receives the full amount of collateral without the penalty applied to the amount they receive, the user profits 1.6e18 WETH with the attack scenario described above.
Foundry
Apply the penalty to repayAmount when calculating the amount of collateral to give to the caller. In addition, ensure that the protocol applies a high enough penalty such that self-liquidators cannot profit from this attack.
Error
0xtj24 (LoopFi) disputed via duplicate issue #58: 
crypticdefense (warden) commented:
Koolex (judge) commented:
crypticdefense (warden) commented:
Koolex (judge) commented:
