Reentrancy attacks and why dfinance is immune to them
Following yesterday’s unfortunate news of the attack on dForce protocol, which led to significant the loss of value, adding up to over 25 Million USD, we have analyzed the attack and would like to share our findings, as well as explain why this attack would not be possible on dfinance.
The malicious attack that occurred today is none other than the well-known reentrancy attack, mainly known following the theft of approximately 150 million worth of ETH from TheDAO contract, which has resulted in Ethereum Classic creation.
Here’s an explanation in layman terms:
Source: https://twitter.com/aliatiia_/status/1251997020769878016?s=19
Zorro goes to bank Branch #1:
1. Zorro: I'd like to withdraw my entire balance in cash.
2. Teller Alice: sure, here you go.
3. Zorro mysteriously vanishes. "What da? where did he go?" wonders Teller Alice.
4. "Ah well", Teller Alice mumbles to herself and proceeds to click "Finish transaction" on her computer in order to finalize Zorro's withdrawal transaction.
This should result in Zorro's balance updated to zero, as it should since, you know, he disappeared with the cash.
5. But a pop-up message appears on Teller Alice's computer: "Failed to finalize transaction because this account (Zorro's) is currently being used by another teller at another branch".
Turned out, after getting the cash from Teller Alice, Zorro used his superpowers to travel, at the speed of light, to Branch #2:
6. Zorro: I want to withdraw my entire balance
7. Teller Bob: sure, here you go.
8. Zorro mysteriously vanishes, "what da? where did he go?" says Bob
9. "Ah well", Bob mumbles to himself, and proceeds to click "Finish transaction" on his computer in order to finalize Zorro's withdrawal and hence updating his balance to zero.
10. A pop-up message: "Failed to finalize transaction, account (Zorro's) being used at another branch"
Turned out, in step 3, Zorro used her superpowers to travel, *YET AGAIN*, at the speed of light, to Branch #3:
11. Zorro: I want to withdraw my entire balance.
12. Teller Charlie: sure, here you go
*The cycle continues*
Each branch, the teller hands money over to Zorro because his balance never gets updated to zero by the preceding teller.
Each teller is waiting for the account to be released so they can successfully click "Finalize Transaction" and make the update.
Zorro just keeps speed-light traveling from one branch to another, taking and accumulating the cash with him, till he drains the whole bank of all its money in all branches.
What about dfinance and reentrancy attacks?
The dfinance platform integrates Libra’s Move Virtual Machine, which has made reentrancy attacks impossible by design. The Move language is based on resources and their restriction management; a resource can exist only in one place during the execution runtime, it can be moved between modules or destroyed; yet it can not exist in multiple locations simultaneously.
To dive in a bit into the technical part:
- One of the Move key features is the ability to define a custom resource type. These types are then used as an inherently safe programmatic representation of digital assets
- These resources are ordinary values in the Move language. They can be stored as data structures, passed as arguments to procedures, returned from procedures, and so on
- Move type system provides special integrity safeguards for these resources. They can never be duplicated, reused, discarded, but rather only moved by authorized subsidiary code. A resource type can only be created or destroyed by the module defining the type. These guarantees are statically enforced by the Move Virtual Machine, secured through a bytecode verification level. It’s not possible to run any bytecode that has not passed the bytecode verifier
By utilizing this custom resources system, all the asset balances have exact properties and behaviors that can be predefined. To transfer an asset between two “accounts”, the transaction initiator first needs to withdraw the exact transaction amount from the “account” balance before being able to make a deposit into another user “account”.
Back to layman’s terms and the Zorro example:
Zorro goes to bank Branch #1:
1. Zorro: I'd like to withdraw my entire balance in cash.
2. Teller Alice (accesses Zorro’s “bank resource” and makes the withdrawal): sure, here you go.
3. Zorro mysteriously vanishes. "What da? where did he go?" wonders Teller Alice.
4. "Ah well", Teller Alice mumbles to herself
In the meanwhile, Zorro used his superpowers to travel, at the speed of light, to Branch #2:
6. Zorro: I want to withdraw my entire balance
7. Teller Bob (accesses Zorro’s “bank resource” and is unable to make a withdrawal because it’s now empty): i’m sorry sir, your “bank resource” balance is 0
8. Zorro: :(
If we go back to technical terms, below there’s an Mvir assets transfer example:
script {
use 0x0::Account;
use 0x0::DFI;
fun main(recipient: address, amount: u128) {
let dfi = Account::withdraw_from_sender<DFI::T>(amount);
Account::deposit(recipient, dfi);
}
}
After initiating a withdrawal from a resource’s balance, the withdrawn portion is no longer part of the resource anymore and only then can it be deposited into another resource, as in the example (or it could even be destroyed). This feature provides complete protection against reentrancy attacks, without having to include special measures against such attacks.
If you want to learn more, you can check out “Move book”, which, while still under development, already provides good explanations and examples of how to safely work with Move. Additional examples can be found on our website and in our documentation.
As always, we invite you to subscribe and join our channels or ask any questions about dfinance or Move in our discord group or community portal.
IMPORTANT: The current dfinance testnet version includes full support of Mvir and partial support of Move, with full Move support to be added in the coming versions.