Light ModeLight
Light ModeDark

One Bug Per Day

One H/M every day from top Wardens

Checkmark

Join over 460 wardens!

Checkmark

Receive the email at any hour!

Ad

malicious borrowers can follow reclaimLiquidity() then execute addPremium() to invalidate renewalCutoffTime

mediumCode4rena

Lines of code

https://github.com/code-423n4/2023-12-particle/blob/a3af40839b24aa13f5764d4f84933dbfa8bc8134/contracts/protocol/ParticlePositionManager.sol#L509

Vulnerability details

Vulnerability details

LP can set renewalCutoffTime=block.timestamp by executing reclaimLiquidity(), to force close position

solidity
function liquidatePosition( DataStruct.ClosePositionParams calldata params, address borrower ) external override nonReentrant { ... if ( !((closeCache.tokenFromPremium < liquidateCache.tokenFromOwed || closeCache.tokenToPremium < liquidateCache.tokenToOwed) || @> (lien.startTime < lps.getRenewalCutoffTime(lien.tokenId) && lien.startTime + LOAN_TERM < block.timestamp)) ) { revert Errors.LiquidationNotMet(); }

The restrictions are:

  1. Loan expires
  2. lien.startTime < lps.getRenewalCutoffTime(lien.tokenId)

Since addPremium() will modify lien.startTime = block.timestamp we need to restrict , if lps.getRenewalCutoffTime(lien.tokenId) > lien.startTime, addPremium() cannot be executed. Otherwise, addPremium() can indefinitely postpone liquidatePosition() to forcibly close position.

solidity
function addPremium(uint96 lienId, uint128 premium0, uint128 premium1) external override nonReentrant { .. @> if (lps.getRenewalCutoffTime(lien.tokenId) > lien.startTime) revert Errors.RenewalDisabled();

The problem is that this judgment condition is >, so if lps.getRenewalCutoffTime(lien.tokenId) == lien.startTime, it can be executed.

This leads to, the borrower can monitor the mempool, if LP executes reclaimLiquidity(), front-run or follow closely to execute addPremium(0). It can be executed successfully, resulting in lien.startTime == lps[tokenId].renewalCutoffTime == block.timestamp.

In this case, liquidatePosition() will fail , because the current condition for liquidatePosition() is lien.startTime < lps.getRenewalCutoffTime(lien.tokenId), liquidatePosition() will revert Errors.LiquidationNotMet();.

So by executing reclaimLiquidity() and addPremium() in the same block, it can maliciously continuously prevent LP from retrieving Liquidity.

Impact

Malicious borrowers can follow reclaimLiquidity() then execute addPremium() to invalidate renewalCutoffTime, thereby maliciously preventing the position from being forcibly closed, and LP cannot retrieve Liquidity.

Recommended Mitigation

Use >= lien.startTime to replace > lien.startTime.

In this way, the borrower has at most one chance and cannot postpone indefinitely.

diff
function addPremium(uint96 lienId, uint128 premium0, uint128 premium1) external override nonReentrant { .. - if (lps.getRenewalCutoffTime(lien.tokenId) > lien.startTime) revert Errors.RenewalDisabled(); + if (lps.getRenewalCutoffTime(lien.tokenId) >= lien.startTime) revert Errors.RenewalDisabled();

Assessed type

Other