Excellent description of the nitty-gritty of the DAO hack over here on Peter Vessenes’ blog. I wanted to jump on this opportunity to point out that another reason this hack was possible was because the creators of the DAO contract were using the wrong paradigm for thinking about money. We shouldn’t blame them for this particular mistake (they have much more grave mistakes to answer for) because it’s the paradigm we all use, but that doesn’t make it any less wrong, and understanding the problem may help guide future development.
When you think of money, what’s the first thing that comes to mind? For anyone born before the 21st century, it’s probably coins and bills, which is the first kind of money that most people are exposed to. The fact that these are physical tokens colors the way we think about money from our childhood onwards, and leads to the belief that money is the fundamental unit of finance. But this is not true!
The fundamental unit of finance is not money, but the exchange of money.
Our human brains are much more adept at thinking about nouns than about verbs, so we’ve focused our attention on the technology that records the exchange of money rather than the underlying exchange itself.
But think about it, coins and bills (and to go back further, gold and seashells) are no more than a technology that helps us keep track of the past history of economic transactions. Their primary advantage is that they’re not easily counterfeited, so when someone hands us a wad of cash we believe that there was a history of economic transactions that led that person to earn that wad of cash.
Thinking about it this way, cash and bills are a rather poor technology in our digital age. Paper bank ledgers are better, digital bank ledgers are much better, and decentralized ledgers are (might be?) even better still, because they contain much richer information about the history of exchanges. Indeed Blockchain ledgers are organized this way: there is a public record of all transactions and account balances can be deduced form them. (The fact that Ethereum explicitly tracks balances of accounts is in fact unnecessary and possibly useful only for optimization reasons.)
Returning to the incorrect paradigm, as humans with our proclivity to focus on nouns, we think much more readily about account balances, the digital analogue of tokens, than we think about histories of transactions. But an account balance is nothing more than a summary of a history of transactions, and so an account balance is just the output of running a function on a history of transactions.
This function seems simple: subtract outgoing transfers and add incoming ones. If we had a ledger that didn’t change this would be fine. But this ignores the dynamic nature of ledgers, where new transactions are constantly being added and those transactions require some integrity guarantee (e.g. no new transaction should be added that makes the net total transfer go negative) and violating transactions should be rejected.
How did this lead to the DAO hack?
I’ll again defer to this excellent recap of the hack for the details, but I’ll try to summarize the hack using the above perspective on account balances vs. transaction histories. The DAO contract checks whether certain transactions are legal by comparing them to a stored record of account balances (red flag!). If the transaction does not cause the account balance to go negative, then the transaction is permitted. After the transaction is created the account balance is updated to reflect the outgoing transfer.
The hacker was able to exploit two bugs where the transaction history and account balance fell out of sync. The first bug is subtle: the code allowed for a recursive call where two or more transactions are recursively created. Both transactions attempt to move out the attacker’s entire balance out of the DAO. When it succeeds, the inner transaction deducts the attacker’s previous balance from the DAO’s balance and sets the attacker’s remaining funds balance to zero. The outer transaction also completes, and deducts the attacker’s previous balance from the DAO’s balance. But the attacker’s previous balance was just updated to zero by the inner transaction! So after the outer transaction completes the DAO has not only transferred twice the amount it should have to the attacker, it also thinks it has more funds than it actually does.
The second bug is a glaring error: before the withdrawal happens there is a function call that looks like it’s supposed to reduce the attacker’s balance even before the transfer is created. This call is unfortunately mistyped to call a simple logging function instead. Had it been called correctly, the attack would have been prevented. (Although, looking closely at the code, calling the function that reduces the attacker’s code balance would have caused a different though less severe bug. It may have been a sloppy fix to this less severe bug that introduced the vulnerability.)
Suggestion 1: stop using account balances
The first and most radical suggestion is that developers stop using account balances in their code. Instead, checking whether or not a transaction is valid (e.g. there is enough Ether to transfer out) should always be done by looking at the history of transactions and recomputing the net total transfer into/out of an account.
This is admittedly computationally expensive, and in a resource-constrained environment like the EVM may be a non-starter. Nevertheless perhaps this philosophy can help guide future development of the EVM or successor technologies.
Suggestion 2: create SQL-like commit/rollback functionality
This error would also have been prevented if transaction creation and account balance update were atomic: both succeed or fail together and at the same time. This is a basic standard notion in applications like databases and surprisingly absent from the Ethereum spec, given the importance of data integrity in financial settings.
In this particular case, wrapping everything in a transaction would have resulted in a final commit that tried to create multiple transactions and deduct the total amount of all the requested transactions from the account balance, which would have been rejected since the account did not have a sufficient balance.
I see no reason why the EVM couldn’t be modified to support such functionality. Maybe it’s even possible to support this functionality at a higher level without modifying the EVM spec. The protection it provides is orthogonal to some of the other tools that people are proposing (strong type safety, formal verification, etc.) so they should all be pursued, but I would guess that this is both simpler and more user-friendly since most people with some dev experience have used a database commit/rollback before.
Update: Thanks to edmundedgar on the reddit channel for helping me see that the above suggestion about commit/rollback isn’t really appropriate, since the EVM execution environment is single-threaded. The problem is not contesting threads, its re-entrant calls on a single thread.
I think a related solution would still help though: create a standard library for managing transfers of balances (could be both for tokens or Ether). An example transfer function might do the following: keep track of a “transfer active” flag per account, and when a transfer into/out of an account is attempted, first check the flag. If it’s on then throw an error, otherwise perform the transfer, update the account balance, and clear the flag. In this case, if the receiving contract attempts a re-entrant call, the flag will be on and an error will be thrown, thwarting the attack.
Final notes on terminology
I’ve avoided using the word “database transaction” (which is what this kind of commit/rollback mechanism is doing) since the word transaction is overloaded here. Any suggestions on how to refer to this idea without using the word transaction?
I also think it’d be nice if we had a simple and precise word to capture the notion of “exchange of money”. I don’t have the best words, but someone who has better words may be able to come up with a fitting and memorable term that we can use to help put the exchange of money at the center of our thinking about financial transactions rather than the money itself.