Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: high
Invalid

Attacker can avoid users repayment by front-running (Force liquidating a user)

Summary

If attacker front-runs a user repayment tx with small amount of repay on behalf for him, it makes user repayment tx revert.

Vulnerability Details

for understanding issue lets check _repay() function first:

function _repay(uint256 amount, address onBehalfOf) internal {
if (amount == 0) revert InvalidAmount();
if (onBehalfOf == address(0)) revert AddressCannotBeZero();
UserData storage user = userData[onBehalfOf];
// Update reserve state before repayment
ReserveLibrary.updateReserveState(reserve, rateData);
// Calculate the user's debt (for the onBehalfOf address)
uint256 userDebt = IDebtToken(reserve.reserveDebtTokenAddress).balanceOf(onBehalfOf);
uint256 userScaledDebt = userDebt.rayDiv(reserve.usageIndex);
// If amount is greater than userDebt, cap it at userDebt
uint256 actualRepayAmount = amount > userScaledDebt ? userScaledDebt : amount;
uint256 scaledAmount = actualRepayAmount.rayDiv(reserve.usageIndex);
// Burn DebtTokens from the user whose debt is being repaid (onBehalfOf)
// is not actualRepayAmount because we want to allow paying extra dust and we will then cap there
(uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) =
IDebtToken(reserve.reserveDebtTokenAddress).burn(onBehalfOf, amount, reserve.usageIndex);
// Transfer reserve assets from the caller (msg.sender) to the reserve
IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amountScaled);
reserve.totalUsage = newTotalSupply;
user.scaledDebtBalance -= amountBurned;
// Update liquidity and interest rates
ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, amountScaled, 0);
emit Repay(msg.sender, onBehalfOf, actualRepayAmount);
}

as we can see if amount>debt the function replaces the debt value for actualRepayAmount variable. means if user has 100e18 debt and gives 120e18 amount in input of repayment it only goes to repay just 100e18, the actual debt.

// If amount is greater than userDebt, cap it at userDebt
uint256 actualRepayAmount = amount > userScaledDebt ? userScaledDebt : amount;

the issue is when function burns the debt token, passes the amount instead of actualRepayAmount. this can lead to revert of repayment because if user has 100e18 debt token, the amount to burn cant be 120e18 because it exceeds the balance.

IDebtToken(reserve.reserveDebtTokenAddress).burn(onBehalfOf, amount, reserve.usageIndex);

Attack Scenario:

  • Alice has 100e18 debt tokens.

  • Alice calls the repay() function with 100e18 to repay her entire debt.

  • The attacker front-runs Alice's transaction by calling the repayOnBehalf() function with Alice's address and 1e18 amount.
    Now, Alice's debt is 99e18, but she has provided 100e18 as input amount.
    Alice's transaction will revert because _repay() tries to burn 100e18 debt tokens while her balance is only 99e18.

Attacker can do this to force user to be get liquidated.

Impact

Force liquidating a user.

Tools Used

Manual Review

Recommendations

- IDebtToken(reserve.reserveDebtTokenAddress).burn(onBehalfOf, amount, reserve.usageIndex);
+ IDebtToken(reserve.reserveDebtTokenAddress).burn(onBehalfOf, actualRepayAmount, reserve.usageIndex);
Updates

Lead Judging Commences

inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.