Presentation is loading. Please wait.

Presentation is loading. Please wait.

Solidity Pitfalls and Hazards

Similar presentations


Presentation on theme: "Solidity Pitfalls and Hazards"— Presentation transcript:

1 Solidity Pitfalls and Hazards
(Part Two) CS1951 L Spring 2019 26 February 2019 Maurice Herlihy Brown University

2 Today’s Pitfalls: Default Visibility False Randomness Unchecked return values Parameter Attack

3 Default Visibility Attack
External: only from outside Private: only current not derived contracts Internal: only current or derived contracts Publ ic: anyone can call

4 Default Visibility Attack
External: only from outside Private: only current not derived contracts Internal: only current or derived contracts Public: anyone can call default

5 Fallback Function Remember, transaction data includes:
Function selector (hashed name) Function arguments

6 Fallback Function contract Example { uint x = 0;
function foo() public { x = 1; } function () public payable { x = 2;

7 Fallback Function contract Example { uint x = 0;
function foo() public { x = 1; } function () public payable { x = 2; named regular function

8 Fallback Function Often used to receive payments contract Example {
uint x = 0; function foo() public { x = 1; } function () public payable { x = 2; named regular function nameless fallback function Often used to receive payments

9 Function Call fn foo() fn bar() fn () A calls B’s “bar” function …
Contract A Contract B not me! call “bar” fn foo() me! fn bar() fn () A calls B’s “bar” function …

10 Fallback Function fn foo() fn bar() fn () A sends ETH to B Contract A
Contract B call.value(100) fn foo() fn bar() fn () I handle pure ETH transfers A sends ETH to B

11 Wallet Library Does most of the work
fn init () fn transfer() fn credit() Delegatecall: updates wallet internal state only Also, init() function called internally by constructor to initialize owners, etc.

12 Wallet Library Security Audit
contract WalletLibrary { address owner; // called by constructor function initWallet(address _owner) { owner = _owner; // ... more setup ... }

13 Wallet Library Security Audit
contract WalletLibrary { address owner; // called by constructor function initWallet(address _owner) { owner = _owner; // ... more setup ... } Initialization code called only by constructor.

14 Wallet Library Security Audit
contract WalletLibrary { function changeOwner(address _new_owner) external { if (msg.sender == owner) { owner = _new_owner; } Owner can be changed only by current owner

15 Wallet Library Security Audit
contract WalletLibrary { function () payable { // ... receive money, log events, ... } Anyone can deposit money in wallet

16 Wallet Library Security Audit
contract WalletLibrary { function withdraw(uint amount) external returns (bool success) { if (msg.sender == owner) { return owner.send(amount); } else { return false; } Only owner can withdraw money

17 Composite Wallet Most calls just forwarded to library Wallet
Wallet Library fn transfer() fn credit() fn () fn init () call “transfer” Most calls just forwarded to library

18 Wallet Fallback Function
function() payable { // just being sent some cash? if (msg.value > 0) Deposit(msg.sender, msg.value); else if (msg.data.length > 0) _walletLibrary.delegatecall(msg.data); }

19 Wallet Fallback Function
function() payable { // just being sent some cash? if (msg.value > 0) Deposit(msg.sender, msg.value); else if (msg.data.length > 0) _walletLibrary.delegatecall(msg.data); } Mostly used to send cash to wallet

20 Wallet Fallback Function
function() payable { // just being sent some cash? if (msg.value > 0) Deposit(msg.sender, msg.value); else if (msg.data.length > 0) _walletLibrary.delegatecall(msg.data); } If not cash deposit, just forward message to library …

21 Wallet Fallback Function
All public functions in library callable by anyone! function() payable { // just being sent some cash? if (msg.value > 0) Deposit(msg.sender, msg.value); else if (msg.data.length > 0) _walletLibrary.delegatecall(msg.data); } If not cash deposit, just forward message to library …

22 Uh, Oh contract WalletLibrary {
address owner; // called by constructor function initWallet(address _owner) { owner = _owner; // ... more setup ... } Oh wait, where is this function’s visibility declared? It isn’t. It has default public visibility!

23 This Happened Someone exploited exactly this opening
Started pumping money out … “White hat” hackers notice, exploit same opening to pump money out to save victims Black hats: 153K ETH, White Hats: 377K ETH Final Score

24 Designing languages for mission-critical apps is not for amateurs.
Prevention Solidity now requires visibility declarations to be explicit Designing languages for mission-critical apps is not for amateurs. Moral

25 False Randomness Attack
Gambling is very popular in Ethereum Gambling requires randomness Blockchains require determinism Check your wallet

26 What are the Chances? Clients bet or RED or BLACK
Last block hash even: RED Last block hash odd: BLACK What could go wrong?

27 Mining bet on RED

28 Mining Try nonce = 1 bet on RED Try nonce = 2 But hash is odd
Eureka! I mined a block! Suppress that block, try again …

29 Examined 3,649 smart contracts
78 unique PNRG implementations 43 identified as vulnerable A. Reutov, Predicting Random Numbers in Ethereum Smart Contracts

30 Not just miners can predict …
Block Variables block.number block.coinbase block.gaslimit block.difficulty block.timestamp Not just miners can predict … Exploit contract makes delegatecall to target contract, gets same “random” values

31 Hash of Current Block function random(uint64 upper) public
returns (uint64 randomNumber) { _seed = uint64( sha3( sha3( block.blockhash(block.number), _seed), now)); return _seed % upper; }

32 Hash of Current Block function random(uint64 upper) public
returns (uint64 randomNumber) { _seed = uint64( sha3( sha3( block.blockhash(block.number), _seed), now)); return _seed % upper; } start with hash of current block …

33 something is wrong here …
Hash of Current Block function random(uint64 upper) public returns (uint64 randomNumber) { _seed = uint64( sha3( sha3( block.blockhash(block.number), _seed), now)); return _seed % upper; } … hash it a few more times something is wrong here …

34 block hash of block being created is (obviously) unknown to EVM
Hash of Current Block block hash of block being created is (obviously) unknown to EVM function random(uint64 upper) public returns (uint64 randomNumber) { _seed = uint64( sha3( sha3( block.blockhash(block.number), _seed), now)); return _seed % upper; } This expression always evaluates to 0!

35 Attacker can delegatecall contract to predict its “random” number
Hash of Earlier Block block.blockhash(block.number - 1) Attacker can delegatecall contract to predict its “random” number

36 Hash of Future Block Let’s play a game
Player makes bet, house stores transaction’s block.number Player later requests house announce winning number House computes blockhash from saved block.number, then generates pseudo-random number

37 Hash of Future Block “The block hashes are not available for all bocks for scalability reasons. You can only access the hashes of the most 256 blocks, all other values will be zero.” Player requests winning number 256 blocks later House computes blockhash 0, then generates totally predictable pseudo-random number

38 This Happened Huge ICO “provably fair” lottery
Issued a “hackathon challenge” Hacked by 256-block delay described Arguably, the challenge was a success!

39 Blockhash with Private Seed
From the Slotthereum Lottery bytes32 _a = block.blockhash(block.number - pointer); for (uint i = 31; i >= 1; i--) { if ((uint8(_a[i]) >= 48) && (uint8(_a[i]) <= 57)) { return uint8(_a[i]) - 48; } } this variable declared private so can’t be read by another contract Like all blockchain data, trivial to read from off-chain

40 Prevention Use External Oracle for Randomness (trusted by all parties)

41 Algorithms we will discuss later
Prevention Algorithms we will discuss later

42 Unchecked CALL Return Values
Inconsistent interfaces for ether transfer: If transfer() fails, caller reverts If send() or call() fails, caller does not revert, instead gets Boolean return code Unchecked return codes as attack vector

43 If something goes terribly wrong …
Out of gas Assertion violated stack overflow The function call reverts Rolling back that transaction’s effects

44 call(), delegatecall(), codecall()
Normally if a function call reverts: contract.longCall(); // runs out of gas The entire call-chain reverts Exceptions: call(), delegatecall(), codecall() Return Booleans instead of propagating

45 Gas Trap Might take all your gas Max 2300 gas (not much)
if (! someAddr.call.value(100)()) { // some failure code } Might take all your gas if (! someAddr.send(100)) { // some failure code } Max 2300 gas (not much)

46 Prevention If possible, use transfer() instead of send()
Otherwise, check return codes! Use withdrawal pattern, where clients claim funds via their function calls

47 This Happened Suppose claim price for throne is 10 ETH
You want to be King/Queen, so send10 ETH KotET sends your 10 ETH minus comission to previous monarch You are now monarch Claim Price goes up to 15 ETH You get paid when someone else becomes K/Q

48 This Happened Payments were sent by unchecked sends
send() did not include enough gas for payment processing by wallet accounts Recipient reverted, and payment was returned to contract KotET contract did not revert, nor check return code Proceeded without paying previous monarch

49 Stack Depth Attack Stack Depth cannot exceed 1024
Artificially create stack depth of 1023, then pay to become king. Refund payments to predecessor guaranteed to fail at depth 1025 Unchecked send()not just bug, but attack vector

50 Stack Depth Attack Stack Depth attack no longer feasible
New rule: call cannot consume more then 63/64 of parent’s gas If start with g gas, after n calls you have at most g (63/64)n gas available

51 Parameter Attack Parameters to calls encoded by “ABI”specification
Possible to send shorter than expected encodings EVM silently pads 0s to the end of short parameters

52 Generate an Ethereum adress with trailing 0
On average, takes 256 tries Find an exchange with 25,600 tokens Send 100 tokens to my account Withdraw 100 tokens, leave off last byte in my address

53 Dear Exchange, Please Transfer 100 Tokens
address 0xcafecafecafecafecafecafecafecafecafe0000  number tokens 100 order of arguments: address then amount function transfer(address to, uint tokens) public returns (bool success);

54 encoding of message to contract
functionaddressamount a9059cbb cafecafecafecafecafecafecafecafecafe bc75e2d

55 Attacker drops one Byte
Attacker sends address: 0xcafecafecafecafecafecafecafecafecafe00  instead of: 0xcafecafecafecafecafecafecafecafecafe0000  Exchange does not validate parameters

56 Message Padded with 00 This missing byte… Replaced with this
Encoded as a9059cbb cafecafecafecafecafecafecafecafecafe bc75e2d instead of: a9059cbb cafecafecafecafecafecafecafecafecafe bc75e2d This missing byte… Replaced with this

57 Dear Contract, Please Transfer 25600 Tokens
address 0xcafecafecafecafecafecafecafecafecafe0000  number tokens 256 X 100

58 Validate inputs before sending anything to a blockchain
Prevention Validate inputs before sending anything to a blockchain

59 Ideas we covered in this lecture
Default Visibility False Randomness Unchecked return values Parameter Attack

60


Download ppt "Solidity Pitfalls and Hazards"

Similar presentations


Ads by Google