COMMON VULNERABILITIES: REENTRANCY PART — II
Tags: web3, soliditysecurity, blockchain, security
Published: 2024-03-22 05:37:32

### Types of reentrancy:
The reentrancy vulnerability is divided into four different types. They are,

- Single function reentrancy
- Cross-function reentrancy
- Cross-contract Reentrancy
- Read-only Reentrancy

### Single function reentrancy: 
   - In single-function reentrancy, the vulnerability occurs within the same function of a contract. This typically happens when the contract performs external calls to other contracts or accounts before completing its internal state changes.
   - An attacker exploits this vulnerability by recursively calling the same function of the contract before the previous call completes, allowing them to manipulate the contract's state unpredictably.
   - Single function reentrancy vulnerability is often the result of improper sequencing of state changes and external calls within a function.
   - This type of attack is the simplest and easiest to prevent. It occurs when the vulnerable function is the same function the attacker is trying to recursively call. For example:

```javascript
contract Vulnerable {
    mapping (address => uint) private balances;

    function withdraw() public {
      // Effects
      uint amount = balances[msg.sender]; 
      // Interactions   
      (bool success, ) = msg.sender.call.value(amount)("");
      require(success);
      // Additional Effects (this line is vulnerable to reentrancy)
      balances[msg.sender] = 0;
    }
}
```

In the above code, the `withdraw()` function is vulnerable to a single function reentrancy attack. An attacker can repeatedly call the `withdraw()` function before updating the `balance`, resulting in multiple withdrawals.

### Cross-function reentrancy: 
   - Cross-function reentrancy involves multiple functions within the same contract. The vulnerability arises when one function makes an external call to another function before completing its own state changes.
   - An attacker can exploit this vulnerability by calling into the same contract from within the externally called function, potentially reentering the original function or other functions within the contract.
   - Cross-function reentrancy vulnerability can be challenging to detect as it involves interactions between different functions within the same contract.
   - This type of attack is more complex and harder to prevent. It occurs when the vulnerable function calls another function that is also vulnerable to reentrancy. For example:


```javascript
contract Vulnerable {
    mapping (address => uint) private balances;

    function transfer(address to, uint amount) public {

        // Checks
        if (balances[msg.sender] >= amount) {

        // Effects
        balances[to] += amount;
        balances[msg.sender] -= amount;
        }
    }

    function withdraw() public {

        // Checks
        uint amount = balances[msg.sender];

        // Interactions
        (bool success, ) = msg.sender.call.value(amount)("");
        require(success);

        // Effects
        balances[msg.sender] = 0;
    }
}
```

- The `withdraw` function initiates a `transfer` of funds to the caller using a low-level `call`.
- Before the function completes and sets the `balance` to zero, it does not prevent other functions (such as `transfer`) from being called.
- An attacker can exploit this by calling the `withdraw` function and, during the `call`, reentering the contract through another function like `transfer`, taking advantage of the fact that the `balance` has not yet been set to zero.

### Explanation to above Vulnerable contract:

The `Vulnerable` contract does not strictly adhere to the Checks-Effects-Interactions pattern.

1. **Transfer Function**:
   - **Checks**: The `transfer` function checks if the sender (`msg.sender`) has a sufficient `balance` to perform the `transfer`. This check ensures that the `transfer` is only executed when the sender has enough funds.
   - **Effects**: If the sender has a sufficient `balance`, the function updates the balances of the sender and the recipient accordingly.
   - **Interactions**: There is no interactions in `transfer` function.

2. **Withdraw Function**:
   - **Checks**: The `withdraw` function retrieves the balance of the caller (`msg.sender`) and uses it for the withdrawal. However, it does not explicitly check if the `balance` is greater than zero before initiating the withdrawal, which could lead to unnecessary gas expenditure if the balance is zero.(i.e, `amount = 0`)
   - **Effects**: After retrieving the `balance`, the function initiates an `external call` using `msg.sender.call.value(amount)("")` to transfer funds to the caller. Then, it immediately sets the caller's `balance` to zero.
   - **Interactions**: The external call to transfer funds is performed before the state change of setting the balance to zero, violating the sequencing principle of the Checks-Effects-Interactions pattern.


In summary, while the `transfer` function in the `Vulnerable` contract follows the Checks-Effects-Interactions pattern by performing state changes before interactions, the `withdraw` function violates this pattern by initiating an external call before completing its internal state changes. This violation exposes the contract to reentrancy vulnerabilities. To mitigate this vulnerability, developers should ensure that all state changes are completed before interacting with external contracts or accounts, following the Checks-Effects-Interactions pattern consistently across all contract functions.

### Cross-contract Reentrancy:
   - Cross-contract reentrancy occurs when a contract interacts with external contracts, and the vulnerability lies in the interaction between different contracts.
   - An attacker exploits this vulnerability by recursively calling back into the vulnerable contract from within an external contract before the original call completes, potentially causing unexpected behavior or manipulating the state of the contracts involved.
   - This type of reentrancy vulnerability often arises when contracts trust external contracts to perform certain actions without properly validating or handling the consequences of the interactions.

```javascript
contract Vulnerable {
    mapping(address => uint256) public balances;

    function withdraw(uint256 amount) public {

        // Checks: Whether the `user` has neccessary amount
        require(balances[msg.sender] >= amount, "Insufficient balance");

        // Effects: Update sender's balance
        balances[msg.sender] -= amount;

        // Interactions: External contract call
        ExternalContract externalContract = ExternalContract(msg.sender);
        externalContract.withdraw(amount);

        // Additional effects (this line is vulnerable to reentrancy)
        balances[msg.sender] += amount;
    }
}

contract ExternalContract {
    function withdraw(uint256 amount) external {
        // Some logic
        // Vulnerable to reentrancy attack
        msg.sender.call("");
    }
}
```
In this example, the `withdraw` function of the `ExternalContract` is vulnerable to reentrancy. When called by the `Vulnerable` contract, an attacker can exploit this vulnerability to reenter the `Vulnerable` contract and manipulate the sender's `balance`.


### Read-only Reentrancy:
   - Read-only reentrancy is a specific type of reentrancy vulnerability where the attacker is limited to reading contract state during the reentrant call, without the ability to modify state directly.
   - While read-only reentrancy may seem less severe than other types, it can still lead to information leakage or manipulation of contract behavior based on the observed state.
   - This type of vulnerability may occur when contracts allow external calls to view certain state variables without adequate protection against reentrancy attacks.

In the example below, a banking contract is presented that permits users to deposit and withdraw:

```javascript
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract Bank is ReentrancyGuard {
    mapping (address => uint) public balances;

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

    function withdraw() public nonReentrant {

        // Checks
        uint amount = balances[msg.sender];

        // Interactions
        (bool success, ) = payable(msg.sender).call{value: amount}("");
        require(success);

        // Effects
        balances[msg.sender] = 0;
    }
}
```

The `Bank` contract protects itself against reentrancy issues by using OpenZeppelin's `ReentrancyGuard`. Additionally, a third-party smart contract exists that uses the bank's publicly available `balances` mapping for its own business logic, e.g. managing shares based on a user's investment in the bank:

```javascript
contract BankConsumer {
    Bank private bank;

    constructor(address _bank) {
        bank = Bank(_bank);
    }
    function getBalance(address account) public view returns (uint256) {
        return bank.balances(account);
    }
}
```
The bank's `withdraw` function is safeguarded against reentrancy. Still, this guard only applies to the contract itself and does not extend to other systems. The execution flow of the external call in `withdraw` can be taken over by an attacker, who can then craft a smart contract that presents a misleading balance while interacting with other projects.

In this case, `BankConsumer` is such a project, merely exposing a `view` function for illustration purposes. Such a `view` function could be internally utilized elsewhere, e.g. for assigning shares and a check preventing users to liquidate more shares than they own. An outdated `balance` exposed by the `Bank` contract can be exploited by designing a malicious receive function:

```javascript
contract Attacker {
    event Checkpoint(uint256 balance);

    Bank private bank;
    BankConsumer private consumer;

    constructor(address _bank, address _consumer) payable {
        bank = Bank(_bank);
        consumer = BankConsumer(_consumer);
    }

    function attack() public {
        emit Checkpoint(consumer.getBalance(address(this)));
        bank.deposit{value: 1 ether}();
        bank.withdraw();
        emit Checkpoint(consumer.getBalance(address(this)));
    }

    receive() external payable  {
        emit Checkpoint(consumer.getBalance(address(this)));
        // more malicious code here
    }
}
```
The Attacker contract records Checkpoint events, illustrating that the balances visible to the `BankConsumer` contract are outdated. When the system is set up in `Remix`, and the attack function is executed, the following events are logged by the Attacker contract:

```javascript
[
    {
        "from": "0xE3Ca443c9fd7AF40A2B5a95d43207E763e56005F",
        "topic": "0xde5ae8a37da230f7df39b8ea385fa1ab48e7caa55f1c25eaaef1ed8690f36998",
        "event": "Checkpoint",
        "args": {
            "0": "0",
            "balance": "0"
        }
    },
    {
        "from": "0xE3Ca443c9fd7AF40A2B5a95d43207E763e56005F",
        "topic": "0xde5ae8a37da230f7df39b8ea385fa1ab48e7caa55f1c25eaaef1ed8690f36998",
        "event": "Checkpoint",
        "args": {
            "0": "1000000000000000000",
            "balance": "1000000000000000000"
        }
    },
    {
        "from": "0xE3Ca443c9fd7AF40A2B5a95d43207E763e56005F",
        "topic": "0xde5ae8a37da230f7df39b8ea385fa1ab48e7caa55f1c25eaaef1ed8690f36998",
        "event": "Checkpoint",
        "args": {
            "0": "0",
            "balance": "0"
        }
    }
]
```
The second Checkpoint event indicates that, during the `receive` function, the `Bank` contract still presents a `balance` of 1 ETH for the Attacker contract address despite the funds having been transferred to the attacker. This outdated information can be manipulated to mislead third-party infrastructure building on the Bank contract, such as `BankConsumer`.


**Another Example**:

```javascript
contract Vulnerable {
    mapping(address => uint256) public balances;

    function viewBalance(address account) external view returns (uint256) {
        // Vulnerable to read-only reentrancy
        return balances[account];
    }
}

contract Attacker {
    Vulnerable vulnerableContract;

    constructor(address vulnerableAddress) public {
        vulnerableContract = Vulnerable(vulnerableAddress);
    }

    function viewAndManipulateBalance(address account) external {
        // Read the balance
        uint256 balance = vulnerableContract.viewBalance(account);

        // Manipulate contract behavior based on the observed balance
        if (balance > 100) {
            // Do something
        }
    }
}
```
In this example, the `viewBalance` function of the Vulnerable contract is vulnerable to read-only reentrancy. An attacker can call this function recursively, observing the `balance` of multiple accounts, and manipulate the behavior of another contract (Attacker) based on the observed balances.


### Conclusion :
Each type of reentrancy vulnerability presents unique challenges and risks, but they all share the common characteristic of allowing attackers to exploit the sequential execution of contract code to their advantage. Solidity developers should be aware of these vulnerabilities and follow best practices, such as using mutex patterns or reentrancy guards, to prevent and mitigate their impact.

To prevent reentrancy attacks, you should avoid making external calls in critical sections of your code, and use the nonReentrant modifier provided by the OpenZeppelin library or implement the Checks-Effects-Interactions pattern.

For more visit my Github: https://github.com/logesh21n/Solidity-Common-Vulnerabilities