3.6 Reentrancy
The reentrancy security pitfall is perhaps unique to smart contracts where external calls made to contracts can result in what can be thought of as callbacks to the called contract itself.
So for example, if there is a contract C1
, that makes a call to an external contract C2
, where C2
could potentially be untrusted could be malicious because it is not developed by the same team or within the same project as C1
, then that external contract C2
could call back into C1
to the same function that called it or to any other function of C1
that allows such a call.
This could be exploited to do malicious things, such as multiple withdrawals or something less harmful, such as out of order emission of events. There have been multiple exploits that have taken advantage of this class of reentrancy attacks, some of them are historical in nature such as the DAO hack on Ethereum.
So this class of security vulnerabilities, that is specific to smart contracts needs to be paid attention to, the best practice to prevent such reentrancy vulnerabilities from being exploited is to follow what is known as the Checks-Effects-Interactions pattern (the CEI pattern for short), where the interactions with external potentially untrusted contracts is only made after performing all the checks and all the effects where effects are nothing, but changes to the state of the calling contract, so that any anticipated side-effects of interactions with the external contracts are already reflected in the state of the calling contract.
So this CEI pattern is recommended as a best practice to be followed in all functions that are making external contract calls specifically to contact calls that could be malicious because they're untrusted. The other best practice is to use what are known as reentrancy guards, we talked about this in the context of the reentrancy guard library from OpenZeppelin where a a nonReentrant
modifier is provided. This modifier when applied to specific functions prevents them from being called within a callback, so it avoids any reentrances to that function itself.
Reentrancy via ERC777
This security pitfall is related to the use of ERC777
standard, the potential for re-entrancy vulnerabilities due to the callbacks it supports.
Remember that ERC777
standard is considered as an extension to the ERC20
standard it's considered as making improvements to it. One improvement is the notion of hooks that it supports during token transfers, if such an ERC777
token contract is potentially malicious, then it could use these hooks to cause reentrancy into the calling contract.
So for example, if there's a contract C1
that calls an ERC777
token contract that is malicious, then that contract could use the hook functionality to cause a reentrancy into the calling contract C1
and take advantage of it as we just mentioned.
The best practice again is to follow the Checks Effects Interaction (CEI) pattern in the calling contract and also to consider the use of reentrancy guards.
OOG in transfer()
& send()
transfer()
& send()
This security pitfall is related to the use of the transfer
and send
primitives in Solidity
.
These primitives were introduced as reentrancy mitigations, because they only forward 2300 Gas to the called contract, which is typically sufficient only for basic processing such as emitting a few logs or something even simpler, this Gas is not enough to make a real currency call back to the calling contract which requires more than 2300 gas.
So this has been recommended for a long time as a security best practice for preventing reentrancy attacks however over time some of the opcodes have been reprised when it comes to their Gas usage, so their Gas Cost has increased in some of the recent hard forks on Ethereum and because of that the use of these primitives that enforce the Gas subsidiary 2300 Gas could break the contract because it might not allow the called contract to even do the basic processing that we just talked about.
So the latest security best practice recommendation is to not rely on transfer()
and send()
as reentrancy mitigations, but instead to use the low-level call()
directly that does not have those hard-coded Gas Limits and couple that with a CEI pattern or reentrancy guard or both for re-entrance mitigation.
Last updated