Skip to content

10. Reentrance

r1oga edited this page Oct 28, 2022 · 1 revision

Target

Steal all funds from the contract.**

Weaknesses

  • Sending funds to arbitrary contract that can implement a malicious receive or fallback function.
    Similarly to the attack in the level 7, when sending directly funds to an address, one does not now if it is an EOA or a contract, and how the contract will handle the funds. The fallback could "reenter" in the function that triggered it. If the check effect interaction pattern is not followed, one could withdraw all the funds of a contract: e.g if a mapping that lists the users' balance is updated only at the end at the function!
  • Unchecked arithmetic The operation that decreases the sender's balances can underflow. And it will when reentering the function.

Solidity Concepts

"Reenter"

It means calling back the contract that initiated the transaction and execute the same function again. Remember the differences between call, send and transfer seen in level 7. Especially by using call(), gas is forwarded, so the effect would be to reenter multiple times until the gas is exhausted.

Hack

  1. Deploy an attacker contract
  2. Implement a payable fallback that "reenter" in the victim contract: the fallback calls reentrance.withdraw()
  3. Donate an amount donation
  4. "Reenter" by withdrawing donation: call reentrance.withdraw(donation) from attacker contract
  5. Read remaining balance of victim contract: remaining = reentrance.balance
  6. Withdraw remaining: call reentrance.withdraw(remaining) from attacker contract img

Takeaways

To protect smart contracts against re-entrancy attacks, it used to be recommended to use transfer() instead of send or call as it limits the gas forwarded. However gas costs are subject to change. Especially with EIP 1884 gas price changed.
So smart contracts logic should not depend on gas costs as it can potentially break contracts.
transfer does depend on gas costs (forwards 2300 gas stipend, not adjustable), therefore it is no longer recommended: Source 1 Source 2
Use call instead. As it forwards all the gas, execution of smart contracts won't break. But if we use call and don't limit gas anymore to prevent ourselves from errors caused by running out of gas, we are then exposed to re-entrancy attacks, aren't we?! This is why one must:

  • Respect the check-effect-interaction pattern.
    1. Perform checks
    • who called? msg.sender == ?
    • how much is send? msg.value == ?
    • Are arguments in range?
    • Other conditions...
    1. If checks are passed, perform effects to state variables
    2. Interact with other contracts or addresses
    • external contract function calls
    • send ETH
  • Or use a use a re-entrancy guard: a modifier that checks for the value of a locked bool
Clone this wiki locally