20,000 USDC
View results
Submission Details
Severity: high

Unprotected External Calls Risking Reentrancy Attacks

Summary

In my review of the provided smart contract, I identified an unprotected external call that could expose the system to a reentrancy attack. Specifically, the contract executes external transfers using the transfer and transferFrom methods of the ERC20 token before its internal state is updated. By crafting a malicious fallback function in their contract, an attacker could potentially exploit this, re-entering and interacting with the original contract multiple times before its state is finalized. This vulnerability could lead to unintended behaviors, including potential fund losses or other unexpected state alterations.

Vulnerability Details

In each of these functions, the contract interacts with external contracts (in the form of token transfers) before updating its internal state. This pattern can be exploited by an attacker, especially if they control the external contract being interacted with.

Function: setPool()

// if new balance > current balance then transfer the difference from the lender
IERC20(p.loanToken).transferFrom(
p.lender,
address(this),
p.poolBalance - currentBalance
);
...
// if new balance < current balance then transfer the difference back to the lender
IERC20(p.loanToken).transfer(
p.lender,
currentBalance - p.poolBalance
);

In the setPool function, the contract transfers tokens before updating its internal state. An attacker could exploit this by creating a malicious ERC20 token that triggers a callback to the setPool function.

Function: 'borrow()`

// transfer fees
IERC20(loan.loanToken).transfer(feeReceiver, fees);
// transfer the loan tokens from the pool to the borrower
IERC20(loan.loanToken).transfer(msg.sender, debt - fees);
// transfer the collateral tokens from the borrower to the contract
IERC20(loan.collateralToken).transferFrom(
msg.sender,
address(this),
collateral
);

The borrow function transfers loan tokens to the borrower and then transfers collateral tokens from the borrower. This sequence can be exploited if the borrower uses a malicious token contract.

Function: repay()

// transfer the loan tokens from the borrower to the pool
IERC20(loan.loanToken).transferFrom(
msg.sender,
address(this),
loan.debt + lenderInterest
);
// transfer the protocol fee to the fee receiver
IERC20(loan.loanToken).transferFrom(
msg.sender,
feeReceiver,
protocolInterest
);
// transfer the collateral tokens from the contract to the borrower
IERC20(loan.collateralToken).transfer(
loan.borrower,
loan.collateral
);

Function: giveLoan()

// transfer the protocol fee to the governance
IERC20(loan.loanToken).transfer(feeReceiver, protocolInterest);

The giveLoan function transfers protocol interest to the fee receiver before updating the loan's internal state, which can be exploited.

Function: buyLoan()

// transfer the protocol fee to the governance
IERC20(loan.loanToken).transfer(feeReceiver, protocolInterest);

Similarly, in the buyLoan function, the contract transfers protocol interest before updating the loan's state, making it vulnerable.

Function: refinance()

// we owe more in debt so we need the borrower to give us more loan tokens
// transfer the loan tokens from the borrower to the contract
IERC20(loan.loanToken).transferFrom(
msg.sender,
address(this),
debtToPay - debt
);

The refinance function transfers loan tokens and protocol interest before updating the loan's state, making it vulnerable to reentrancy attacks.

Impact

The reentrancy vulnerability in this contract poses a significant risk to both the contract's integrity and the funds it manages. If exploited, an attacker could potentially drain funds from the contract or manipulate its state to their advantage. This could lead to financial losses for users and damage the trustworthiness of the platform. Additionally, the vulnerability could be used in conjunction with other vulnerabilities, amplifying the potential damage. The contract's operations could be disrupted, and its reputation could suffer irreparably. Addressing this vulnerability is crucial to ensure the safety and reliability of the system.

Tools Used

VSCode, Slither

Recommendations

  • Use ReentrancyGuard: Implement the ReentrancyGuard modifier provided by OpenZeppelin's contracts. This modifier prevents recursive calls by blocking external calls while a function is being executed.

  • State Changes Before External Calls:
    Always update the contract's state before making an external call. This ensures that even if a reentrant call is made, the contract's state is already updated, and the function will not execute with the previous state.

Support

FAQs

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