-
Notifications
You must be signed in to change notification settings - Fork 4
10. Reentrance
Steal all funds from the contract.**
- Sending funds to arbitrary contract that can implement a malicious
receive
orfallback
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.
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.
- Deploy an attacker contract
- Implement a payable fallback that "reenter" in the victim contract: the fallback calls
reentrance.withdraw()
- Donate an amount
donation
- "Reenter" by withdrawing
donation
: callreentrance.withdraw(donation)
from attacker contract - Read
remaining
balance of victim contract:remaining = reentrance.balance
- Withdraw
remaining
: callreentrance.withdraw(remaining)
from attacker contract
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.
- Perform checks
- who called?
msg.sender == ?
- how much is send?
msg.value == ?
- Are arguments in range?
- Other conditions...
- If checks are passed, perform effects to state variables
- 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