Rari-Capital Re-entrancy Vulnerability Analysis

Shashank
SolidityScan
Published in
3 min readNov 14, 2022

--

Overview:

On 30 April 2022, a Re-entrancy attack occurred on Rari Capital, which resulted in draining a total of ~$80M from a variety of Rari’s Fuse Pool.

An attacker was able to exploit a callback function (responsible for Re-entrancy) that lacked a check-effect-interaction pattern in the borrow function leading to transfers of ETH to the attacker’s contract before updating the attacker’s actual borrow records.

Attack Overview

Decoding the Smart Contract Vulnerability:

Rari Re-entrancy attack diagram

From the above diagram, here’s how the attack worked:

  • The attacker deposited a flash loan asset into the lending pool, which was further used as collateral to borrow 1977 ETH.
  • The attacker reentrant calls the doTransferOut () function, which allows him to borrow ETH before the borrow () function updates its state (debt not updated yet).
  • As soon as the exitMarket() function is called, the asset is no longer collateral & can be withdrawn as there is no record of borrowed ETH.
  • The attacker receives free ETH borrowed.
  • The attacker continues in this manner until all borrowable funds have been depleted from a variety of other lending pools.
  • The attacker then paid back the flash loan and transferred the remaining funds as profit.

Vulnerability 1:
The doTranferOut() function was invoked before the borrow records were updated. The attacker called doTranferOut() function before the attacker’s account borrow () record state was updated.

Pull Request: https://github.com/Rari-Capital/compound-protocol/pull/9.

Etherscan code: https://etherscan.io/address/0xe16db319d9da7ce40b666dd2e365a4b8b3c18217#code

doTransferOut(borrower,borrowAmount);

// write the previously calculated values into storage

accountBorrows[borrower].principal = vars.accountBorrowsNew;
accountBorrows[borrower].interestIndex = borrowIndex;
totalBorrows = vars.totalBorrowsNew;

//emit a Borrow Event
emit Borrow(borrower.borrowAmount,vars.accountBorrowsNew,vars.totalBorrowsNew);

return uint(Error.NO_ERROR);
}

From the above code, the doTransferOut() function can be invoked before the borrow records accountBorrows[] & totalBorrows are updated, thereby allowing an attacker to transfer funds to his account. The Contract could not record the borrowed amount for the attacker’s account, which allowed the contract to transfer the ETH requested to the account directly.

Vulnerability 2:
The attacker contract had a fallback() function to exitmarket(), i.e., doTransferOut() Function, which transferred the ETH to the receiver via a low-level reentrant call & exiting the lending pool.

function doTransferOut(address payable to, unit amount) internal {
//sends the ETH and reverts on failure
(bool success , ) = to.call.value(amount)("");
Require (success,"doTransferOut failed");
}

The attacker used his contract fallback () function, which the reentrant called doTransferOut() function, and transferred all ETHs to the contract address at 0x39F. This was possible as a low-level call.value()("") was invoked to transfer the funds from the contract, which is an old way & vulnerable to a Re-entrancy attack.

Mitigation & best practices recommendation:

  • Our product, SolidityScan, can detect Re-entrancy vulnerabilities along with 130+ other vulnerability patterns.
Solidity Scan Smart Contract Vulnerability Scanner
  • Use the secure transfer() function in place of call.value()("") function to avoid a reentrancy attack. The transfer() uses 2300 gas which is not adjustable if the receiving contract needs more gas, it will throw an exception back to the sender’s contract and will update the state to prevent Re-entrancy while that’s not the case for call.value()() and send() function which returns false to the sender’s contract without updating the state variables.
  • Always ensure that every state change happens before calling external contracts, i.e., update balances or code internally before calling external code.
  • Use function modifiers that prevent reentrancy, like Open Zepplin’s Re-entrancy Guard.
  • Limit gas on the call functions.

Conclusion
A successful reentrancy attack is disastrous and may empty all the funds from the origin contract. It is essential to be cognizant of the potential threats and deploy safe solutions.

SolidityScan is an advanced smart-contract scanning tool that discovers vulnerabilities and reduces risks in code. Request a security audit with us, and we will help you secure your smart contracts. Signup for a free trial at https://solidityscan.com/signup

--

--