Skip to content
#19 largest Reentrancy 2016

The The DAO hack — ~$60M lost

Loss~$60M
Date17 Jun 2016
ChainEthereum
Failure classReentrancy
In assets3.6M ETH · 5% of all ETH then
Targetthe one that split the chain
1

What happened

The original. splitDAO paid the caller out and called withdrawRewardFor — both of which hand control to the caller's contract — and only then zeroed the caller's balance. The attacker's fallback simply called splitDAO again, and again, each pass paid against a balance that was still on the books. 3.6 million ETH left in a few hours. The response was the hard fork that split the network into Ethereum and Ethereum Classic — the single most consequential bug in the industry's history. Ten years later, reentrancy is still in the top three causes of loss.

2

How the attack ran

  1. Call splitDAO()The ether payout leaves the contract first
  2. withdrawRewardFor calls out againYour fallback runs while the books are unchanged
  3. The balance is zeroed lastEvery recursive pass is paid against the full balance
  4. Loop until empty3.6M ETH — and the Ethereum / Classic hard fork
3

The code

DAO.sol — splitDAO
function splitDAO(uint _proposalID, address _newCurator) noEther onlyTokenholders
    returns (bool _success) {
    ...
    // ❶ move the ether out — control leaves the contract here
    uint fundsToBeMoved = (balances[msg.sender] * p.splitData[0].splitBalance)
                          / p.splitData[0].totalSupply;
    if (p.splitData[0].newDAO.createTokenProxy.value(fundsToBeMoved)(msg.sender) == false)
        throw;
    ...
    // ❷ burn the tokens — this is where the balance finally goes to zero
    Transfer(msg.sender, 0, balances[msg.sender]);
    withdrawRewardFor(msg.sender);          // ← another external call, still before ❸
    totalSupply       -= balances[msg.sender];
    balances[msg.sender] = 0;                // ❸ too late
    paidOut[msg.sender]  = 0;
    return true;
}

// attacker's fallback: call splitDAO() again.
// balances[msg.sender] is still the original number, every single pass.
→ result: 3,600,000 ETH · the Ethereum / Ethereum Classic hard fork
4

What would have caught it

What an audit looks for: checks-effects-interactions, on every function, without exception — write state before you call out, and guard the ones that cannot. The DAO's reviewers missed it because they read functions one at a time; the bug lived in the interaction between splitDAO and withdrawRewardFor. Auditing functions in isolation is how this gets missed.
6

Sources

Every figure on this page comes from the post-mortems above, not from us. Losses are US dollars at the time of the incident.

Check your own contract for this

Reentrancy is one of the 203 classes the SaferICO scanner checks for. It will not review your signing process — but it will read your Solidity.

Run the scanner See how it is attacked Read the docs