The most well-known example of a reentrancy attack happened in 2016 when Ethereum’s DAO (decentralized autonomous organization) was hacked for $60 million in Ether!
Do you think it could have been prevented with proper measures? To prevent reentrancy, the best method is to use a reentrancy guard. What is it? How does it work?
This blog will answer all the questions that are looming large in your mind right now. But first, let us get a little insight into reentrancy attacks. When you know what it is, you will understand better how to prevent them. Briefly discussing the attack, we will proceed with how to prevent such attacks.
Without any further ado, let us get started with the blog.
Table of Contents
Why Are Reentrancy Attacks Considered To Be The Most Devastating Ones?
While developing smart contracts with Solidity, you will encounter many attacks. However, among them, the most devastating must be reentrancy attacks.
The following are the two main reasons why they are so harmful.
- They will entirely deplete the Ether in your smart contract.
- They can find their way into your code if you’re not careful.
When a cybercriminal creates a function that, in turn, calls an untrusted contract, it is called a reentrancy attack. The attacker can easily make a recursive call to the original function if he/she controls the contract.
For example, a balance variable is used for internal accounting, and a withdraw function is exposed. If the susceptible contract transfers funds before setting the balance to zero, the attacker can repeatedly execute the withdrawal method and drain the whole contract. This is why this type of attack is the most devastating.
The below example will clear this for you.
An attacker must only transfer some balance to their smart contract address to exploit this function and construct a backup function that calls withdraw.
What is a Reentrancy guard?
At any point during execution, there can be times when we are unsure whether our contract’s invariants hold or not; at that time, it is suggested that we must not call other untrusted contracts. This is because it will be possible for them to re-enter again. However, if we are left with no choice, but to do it, then we have an option to use the reentrancy guard to prevent reentrancy.
A reentrancy guard or mutex (mutually exclusive flag) can be built as a function or function modification. It is nothing but a piece of code responsible for causing the execution to fail whenever the act of reentrancy is found. Still, the idea is simple: a boolean lock is placed around the susceptible function call. The original state of “locked” is false (unlocked). Still, it is set to proper (locked) shortly before the vulnerable function execution begins and then reset to false (unlocked) when it ends.
The image below will explain this better.
Now, the question here is, what happens if the contracts have multiple functions? Each modifier is applied for each function. If you want to eliminate reentrancy, then modifiers must be applied to all functions.
Otherwise, they can re-enter some other function and change it into a reentrancy attack, in case it is sensitive to broken invariants.
However, if we wish to make every function non-reentrant, we must keep Solidity’s public variables in mind. A contract variable declared public will construct a getter function to read its value, and that function cannot be modified.
In most circumstances, this will not cause reentrancy issues. However, it is still worth being concerned about since it may result in other contracts witnessing contradictory states owing to violated invariants that they will assume to hold.
With all of its drawbacks, reentrancy guards can be useful in some situations.
However, completely removing reentrancy has drawbacks: reentrancy can be safe in some instances, and as Ethereum smart contracts get more complicated, composable, and networked, we may find genuine applications in the wild.
The mutex or lock function is implemented by OpenZeppelin using Reentrancy Guard. Mutex secures the contract against cross-function reentrancy, preventing repetitive calls from exploiting the withdrawal function.
Reentrancy guard is a modification offered by the openzeppelin library that applies to the reentrant function, which guards the function using a mutex.
Due to fluctuating opcode pricing, we can no longer rely on transfer, so reentrancy is increasingly necessary for today’s reality. We must program our contracts to resist reentrancy by following the checks-effects-interactions pattern or employing tools such as reentrancy guards. When state invariants are violated, attackers can exploit it against a contract that alerts untrusted accounts.