Solidity Pitfalls and Hazards Part One CS 1951

  • Slides: 67
Download presentation
Solidity Pitfalls and Hazards Part One CS 1951 L Spring 2019 21 February 2019

Solidity Pitfalls and Hazards Part One CS 1951 L Spring 2019 21 February 2019 Maurice Herlihy Brown University

Today’s Pitfalls: External Contract References Race Conditions 2

Today’s Pitfalls: External Contract References Race Conditions 2

Solidity is based on Javascript Odd, complex behavior considered normal No precise notion of

Solidity is based on Javascript Odd, complex behavior considered normal No precise notion of correctness Just has to work “well enough” Must be “easy” for non-experts No actual adversary

Using a web scripting language for financial applications? small errorscan be catastrophic precise definitions

Using a web scripting language for financial applications? small errorscan be catastrophic precise definitions matter corner cases matter Language design is hard, and not for amateurs 4

Здравствуйте! I will be reviewing your Solidity code I have all day 5

Здравствуйте! I will be reviewing your Solidity code I have all day 5

On Language Choice Emin Gün Sirer Hacking, Distributed 19 July 2017 6

On Language Choice Emin Gün Sirer Hacking, Distributed 19 July 2017 6

Next Four Lectures Review 16 Vulnerabilities Some common to all blockchains Some are Solidity

Next Four Lectures Review 16 Vulnerabilities Some common to all blockchains Some are Solidity idiosyncracies … "nonsense is nonsense, but the history of nonsense is scholarship" 7

Re-Entrancy Attack Call external contract … Who unexpectedly calls you back … When you

Re-Entrancy Attack Call external contract … Who unexpectedly calls you back … When you were not expecting visitors! 8

Adapted from here 9

Adapted from here 9

An Application DAO = Decentralized Autonomous Organization Invests in other businesses: about $50 Million

An Application DAO = Decentralized Autonomous Organization Invests in other businesses: about $50 Million capital In Ether cryptocurrency No managers or board of directors Controlled by smart contacts and investor voting 10

DAO = Decentralized Autonomous Organization “code is law” Invests in other businesses: about $50

DAO = Decentralized Autonomous Organization “code is law” Invests in other businesses: about $50 Million capital In Ether cryptocurrency No managers or board of directors Controlled by smart contacts and investor voting 11

Much hyperventilation about effect on future of finance 12

Much hyperventilation about effect on future of finance 12

Schematic DAO Code function withdraw(uint amount) { client = msg. sender; if (balance[client] >=

Schematic DAO Code function withdraw(uint amount) { client = msg. sender; if (balance[client] >= amount} { if (client. call. send. Money(amount)) { balance[client] -= amount; }}} Client wants to withdraw own money 13 13

Schematic DAO Code function withdraw(uint amount) { client = msg. sender; if (balance[client] >=

Schematic DAO Code function withdraw(uint amount) { client = msg. sender; if (balance[client] >= amount} { if (client. call. send. Money(amount)) { balance[client] -= amount; }}} Which client? 14 14

Schematic DAO Code function withdraw(uint amount) { client = msg. sender; if (balance[client] >=

Schematic DAO Code function withdraw(uint amount) { client = msg. sender; if (balance[client] >= amount} { if (client. call. send. Money(amount)) { balance[client] -= amount; }}} Does client have enough money? 15 15

Schematic DAO Code function withdraw(uint amount) { client = msg. sender; if (balance[client] >=

Schematic DAO Code function withdraw(uint amount) { client = msg. sender; if (balance[client] >= amount} { if (client. call. send. Money(amount)) { balance[client] -= amount; }}} Transfer the money by calling a function in another contract … 16 16

Schematic DAO Code function withdraw(uint amount) { client = msg. sender; if (balance[client] >=

Schematic DAO Code function withdraw(uint amount) { client = msg. sender; if (balance[client] >= amount} { if (client. call. send. Money(amount)) { balance[client] -= amount; Let’s rewind … }}} 17 17

function withdraw(uint amount) { client = msg. sender; if (balance[client] >= amount} { if

function withdraw(uint amount) { client = msg. sender; if (balance[client] >= amount} { if (client. call. send. Money(amount)) { balance[client] -= amount; }}} Transfer the money by calling another contract … function send. Money(uint amount) { balance += amount msg. sender. call. withdraw(amount). . . } 18 18

function withdraw(uint amount) { client = msg. sender; if (balance[client] >= amount} { if

function withdraw(uint amount) { client = msg. sender; if (balance[client] >= amount} { if (client. call. send. Money(amount)) { balance[client] -= amount; }}} Credit account function send. Money(uint amount) { balance += amount msg. sender. call. withdraw(amount). . . } 19 19

function withdraw(uint amount) { client = msg. sender; if (balance[client] >= amount} { if

function withdraw(uint amount) { client = msg. sender; if (balance[client] >= amount} { if (client. call. send. Money(amount)) { balance[client] -= amount; }}} Wait, what? Client makes “re-entrant” withdraw request! function send. Money(uint amount) { balance += amount msg. sender. call. withdraw(amount). . . } 20 20

function withdraw(uint amount) { client = msg. sender; if (balance[client] >= amount} { if

function withdraw(uint amount) { client = msg. sender; if (balance[client] >= amount} { if (client. call. send. Money(amount)) { balance[client] -= amount; }}} function send. Money(uint amount) { balance += amount msg. sender. call. withdraw(amount). . . } 21 21

function withdraw(uint amount) { client = msg. sender; if (balance[client] >= amount} { if

function withdraw(uint amount) { client = msg. sender; if (balance[client] >= amount} { if (client. call. send. Money(amount)) { balance[client] -= amount; }}} Second time around, balance still looks OK … function send. Money(uint amount) { balance += amount msg. sender. call. withdraw(amount). . . } 22 22

function withdraw(uint amount) { client = msg. sender; if (balance[client] >= amount} { if

function withdraw(uint amount) { client = msg. sender; if (balance[client] >= amount} { if (client. call. send. Money(amount)) { balance[client] -= amount; }}} Send money again … and again … function send. Money(uint amount) { balance += amount msg. sender. call. withdraw(amount). . . } 23 23

This happened “ The attack is a recursive calling vulnerability, where an attacker called

This happened “ The attack is a recursive calling vulnerability, where an attacker called the “split” function, and then calls the split function recursively …” The actual fix? 24

25 25

25 25

Hard-Fork Ethereum and roll back … Ha-ha, just kidding about that “code is law”

Hard-Fork Ethereum and roll back … Ha-ha, just kidding about that “code is law” thing … Because language design is hard 26

Rationale for Hard Fork Client did something stupid? Client’s fault, no refund. Bug in

Rationale for Hard Fork Client did something stupid? Client’s fault, no refund. Bug in Ethereum run-time system? Ethereum’s fault, refund. No warning about re-entrancy vulterability? Ethereum’s fault, refund. 27

I don’t care what you say …

I don’t care what you say …

This talk is not about Bitcoin This is a concurrency error

This talk is not about Bitcoin This is a concurrency error

Classical “monitor lock” pitfall Calling another contract = releasing monitor lock Don’t violate invariants

Classical “monitor lock” pitfall Calling another contract = releasing monitor lock Don’t violate invariants before a call 30

Prevention Change state before any external calls Use a mutex Use transfer() function (too

Prevention Change state before any external calls Use a mutex Use transfer() function (too little gas to call back) 31

Silent Overflow Attack uint 8 x = 0; uint 8 y = x –

Silent Overflow Attack uint 8 x = 0; uint 8 y = x – 1; // y is 255! int and uint variables have fixed ranges EVM quietly rolls over on over/under flow Not exactly what you expected 32

Example contract Token { mapping(address => uint) balances; uint public total. Supply; constructor (uint

Example contract Token { mapping(address => uint) balances; uint public total. Supply; constructor (uint _initial. Supply) public { balances[msg. sender] = total. Supply = _initial. Supply; } … } 33

Example contract Token { mapping(address => uint) balances; uint public total. Supply; simple token

Example contract Token { mapping(address => uint) balances; uint public total. Supply; simple token contract constructor (uint _initial. Supply) public { balances[msg. sender] = total. Supply = _initial. Supply; } … } 34

Example contract Token { mapping(address => uint) balances; uint public total. Supply; constructor (uint

Example contract Token { mapping(address => uint) balances; uint public total. Supply; constructor (uint _initial. Supply) public { keep track of each client’s balances[msg. sender] = total. Supply = _initial. Supply; } … } 35

Example contract Token { mapping(address => uint) balances; uint public total. Supply; constructor (uint

Example contract Token { mapping(address => uint) balances; uint public total. Supply; constructor (uint _initial. Supply) public { balances[msg. sender] = total. Supply = _initial. Supply; } … } constructor 36

Example pragma solidity ^0. 5. 2; contract Token { … function transfer(address _to, uint

Example pragma solidity ^0. 5. 2; contract Token { … function transfer(address _to, uint _value) public returns (bool) { require(balances[msg. sender] - _value >= 0); balances[msg. sender] -= _value; transfer caller’s tokens to another client balances[_to] += _value; return success code return true; } 37

Example pragma solidity ^0. 5. 2; contract Token { … function transfer(address _to, uint

Example pragma solidity ^0. 5. 2; contract Token { … function transfer(address _to, uint _value) public returns (bool) { require(balances[msg. sender] - _value >= 0); balances[msg. sender] -= _value; balances[_to] += _value; Make sure caller has enough tokens return true; } 38

Example pragma solidity ^0. 5. 2; contract Token { … function transfer(address _to, uint

Example pragma solidity ^0. 5. 2; contract Token { … function transfer(address _to, uint _value) public returns (bool) { require(balances[msg. sender] - _value >= 0); balances[msg. sender] -= _value; balances[_to] += _value; return true; } do the transfer 39

You Got a Problem with That? pragma solidity ^0. 5. 2; contract Token {

You Got a Problem with That? pragma solidity ^0. 5. 2; contract Token { … function transfer(address _to, uint _value) public returns (bool) { require(balances[msg. sender] - _value >= 0); balances[msg. sender] -= _value; balances[_to] += _value; return true; } 40

You Got a Problem with That? pragma solidity ^0. 5. 2; contract Token {

You Got a Problem with That? pragma solidity ^0. 5. 2; contract Token { … function transfer(address _to, uint _value) public returns (bool) { require(balances[msg. sender] - _value >= 0); balances[msg. sender] -= _value; balances[_to] += _value; return true; if _value > 0, difference always positive, } test passes even if client has zero balance! 41

Prevention library Safe. Math { … function (sum uint 256 a, uint 256 b)

Prevention library Safe. Math { … function (sum uint 256 a, uint 256 b) public view returns (uint 256) { assert(b <= a); return a – b; Never use Solidity arithmetic directly! } } Use Safe. Math library always Ugly, but safe 42

This Happened Self-described Ponzi scheme Attacker managed to subtract 1 from 0 -balance account

This Happened Self-described Ponzi scheme Attacker managed to subtract 1 from 0 -balance account … Leaving balance of 115 quattuorvigintillion ETH Somehow only managed to escape with 866 ETH 43

Unexpected Ether Attack You can prevent others from taking your money But you can’t

Unexpected Ether Attack You can prevent others from taking your money But you can’t stop them from sending you money Surprisingly, this can be a bad thing 44

Sending Ether desination. foo. value(1000)(arg) send 1000 wei via payable named function contract destination

Sending Ether desination. foo. value(1000)(arg) send 1000 wei via payable named function contract destination { function foo(uint arg) public payable { … } } 45

Sending Ether desination. transfer(1000); send 1000 wei via payable fallback function contract destination {

Sending Ether desination. transfer(1000); send 1000 wei via payable fallback function contract destination { function (uint arg) public payable { … } } 46

Controlling Ether Receipt Ether receipt triggers … Function, named or fallback Possible to refuse

Controlling Ether Receipt Ether receipt triggers … Function, named or fallback Possible to refuse ether by reverting (throwing) not being payable 47

Ether You Cannot Refuse self. Destruct(destination); send balance without calling destination code contract destination

Ether You Cannot Refuse self. Destruct(destination); send balance without calling destination code contract destination { … } 48

Example Simple gambling game Sequence of milestones (amounts) Players send small ether amounts …

Example Simple gambling game Sequence of milestones (amounts) Players send small ether amounts … First player to reach milestone wins! 49

function play() public payable { require(msg. value == 0. 5 ether); uint bal =

function play() public payable { require(msg. value == 0. 5 ether); uint bal = this. balance + msg. value; if (bal == mile. Stone 1) { … } else if (bal == mile. Stone 2) { … } else if (bal == mile. Stone 3) { … } return; } 50

function play() public payable { require(msg. value == 0. 5 ether); uint bal =

function play() public payable { require(msg. value == 0. 5 ether); uint bal = this. balance + msg. value; if (bal == mile. Stone 1) { … each play must be 0. 5 ether } else if (bal == mile. Stone 2) { … } else if (bal == mile. Stone 3) { … } return; } 51

function play() public payable { require(msg. value == 0. 5 ether); uint bal =

function play() public payable { require(msg. value == 0. 5 ether); uint bal = this. balance + msg. value; if (bal == mile. Stone 1) { … } else if. Compute current balance (bal == mile. Stone 2) { … } else if (bal == mile. Stone 3) { … } return; } 52

function play() public payable { require(msg. value == 0. 5 ether); uint bal =

function play() public payable { require(msg. value == 0. 5 ether); uint bal = this. balance + msg. value; if (bal == mile. Stone 1) { … payout if player } hits milestone else if (bal == mile. Stone 2) { … } else if (bal == mile. Stone 3) { … } return; } 53

function play() public payable { require(msg. value == 0. 5 ether); uint bal =

function play() public payable { require(msg. value == 0. 5 ether); uint bal = this. balance + msg. value; if (bal == mile. Stone 1) { … } else if (bal == mile. Stone 2) { … If adversary calls self. Destruct() } to transfer 0. 0001 ether, else if (bal == mile. Stone 3) { … this. balance becomes slightly off, and comparions all fail! } return; } 54

Prevention Never rely on exact value of this. balance! Have payable functions track own

Prevention Never rely on exact value of this. balance! Have payable functions track own transfers 55

Delegate. Call Attack There are 2 ways to call other contracts’s functions call executes

Delegate. Call Attack There are 2 ways to call other contracts’s functions call executes in remote context delegatecall executes in caller’s context The distinction matters! 56

Delegate. Call Opcode Contract A Contract B delegatecall Copy of Contract B’s code copy

Delegate. Call Opcode Contract A Contract B delegatecall Copy of Contract B’s code copy code A delegatecalls B’s function … which runs in A’s address space 57

Composite Wallet A Wallet B Private data Wallet Library (uninitialized) Shared Code 58

Composite Wallet A Wallet B Private data Wallet Library (uninitialized) Shared Code 58

Composite Wallet A Wallet B Wallet Library (uninitialized) hardwired link address constant _wallet. Library

Composite Wallet A Wallet B Wallet Library (uninitialized) hardwired link address constant _wallet. Library = 0 x 863 df 6 b… Private data Shared Code 59

Each Call Wallet B Wallet Library (uninitialized) Copy of Wallet Library code Private data

Each Call Wallet B Wallet Library (uninitialized) Copy of Wallet Library code Private data 60 Shared Code

Attack Wallet B Private data initialize and claim ownership Wallet Library (owned by attacker)

Attack Wallet B Private data initialize and claim ownership Wallet Library (owned by attacker) 61 Shared Code

Attack order to self-destruct! Wallet Library Wallet B Private data 62 Shared Code

Attack order to self-destruct! Wallet Library Wallet B Private data 62 Shared Code

Wallet A Uh, Oh Wallet Library Wallet B Private data 63 Shared Code

Wallet A Uh, Oh Wallet Library Wallet B Private data 63 Shared Code

This Happened About $300 M (at the time) burned 64

This Happened About $300 M (at the time) burned 64

Prevention Any number of changes to library initializing it no selfdestruct() function Declaring a

Prevention Any number of changes to library initializing it no selfdestruct() function Declaring a contract a library enforces statelessness 65

Ideas we covered in this lecture Smart Contract Language Design Re-Entrancy Attack Silent Overflow

Ideas we covered in this lecture Smart Contract Language Design Re-Entrancy Attack Silent Overflow Attack Unexpected Ether Attack Delegate. Call Attack 66

67

67