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

Lender can Sandwich a borrower to seize his collateral

Summary

A malicious lender can sandwich a borrow making him eligible to seize the collateral in the first block after 1 second.

Vulnerability Details

A loan auction can be seized after auctionLength seconds have passed since the start of the auction. Seizing the loan gives the lender the collateral amount which would usually be worth much more than the debt.

A very small value of auctionLength like 1 would allow the lender to seize the loan. If the block time of the network is more than 1 second, he can start an auction by calling the startAuction function in a block and then call the seizeLoan function in the next block front-running any attempt to repay from the borrower. If the block time is less than 1second, he can stuff the blocks till 1 second passes to prevent the borrower from repaying although it is very unlikely that a borrower will be able to attempt to repay within 1second.

But since the borrower has the ability to know the auctionLength before borrowing from a pool, he can avoid borrowing from such pools. But a malicious lender can perform the following attack to obtain the above result:

  1. Lender creates a pool with a reasonable auctionLength

  2. A borrower attempts to borrow from the lender's pool

  3. Lender sandwiches the borrowers transaction and sets the auctionLength of the pool to 1 by calling the setPool function before the borrowers transaction and starts the loan auction after the borrower has borrowed.

  4. He calls the seizeLoan function after 1 second passes

Impact

The borrower will loose the collateral which will usually be worth much more than the debt.

Tools Used

Manual review

Recommendations

Allow the borrower to pass the expected state of the pool when attempting to borrow and perform validation.

struct Borrow {
/// @notice the pool ID to borrow from
bytes32 poolId;
/// @notice the amount to borrow
uint256 debt;
/// @notice the amount of collateral to put up
uint256 collateral;
/// @notice the expected auction length
uint256 expectedAuctionLength;
}
function borrow(Borrow[] calldata borrows) public {
for (uint256 i = 0; i < borrows.length; i++) {
.......
bytes32 poolId = borrows[i].poolId;
Pool memory pool = pools[poolId];
// validate the auctionLength
if (pool.auctionLength != borrows[i].expectedAuctionLength) revert PoolConfig();
........

Support

FAQs

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

Give us feedback!