Fix storage

pull/1102/head
Nikhil Parasaram 6 years ago
parent 9b960a2985
commit dd7b255fc9
  1. 118
      mythril/laser/ethereum/instructions.py
  2. 105
      mythril/laser/ethereum/state/account.py
  3. 6
      mythril/laser/ethereum/state/constraints.py
  4. 1
      mythril/laser/ethereum/state/global_state.py
  5. 6
      mythril/laser/smt/array.py
  6. 5
      mythril/laser/smt/bitvecfunc.py
  7. 10
      mythril/laser/smt/bool.py
  8. 6
      mythril/laser/smt/expression.py
  9. 10
      tests/instructions/codecopy_test.py
  10. 9
      tests/instructions/sar_test.py
  11. 8
      tests/instructions/shl_test.py
  12. 8
      tests/instructions/shr_test.py
  13. 6
      tests/laser/evm_testsuite/evm_test.py
  14. 53
      tests/laser/state/storage_test.py
  15. 16
      tests/testdata/outputs_expected/metacoin.sol.o.json
  16. 21
      tests/testdata/outputs_expected/metacoin.sol.o.jsonv2
  17. 15
      tests/testdata/outputs_expected/metacoin.sol.o.markdown
  18. 12
      tests/testdata/outputs_expected/metacoin.sol.o.text

@ -19,6 +19,8 @@ from mythril.laser.smt import (
UGT, UGT,
BitVec, BitVec,
is_true, is_true,
Bool,
BitVecFunc,
is_false, is_false,
URem, URem,
SRem, SRem,
@ -1369,42 +1371,20 @@ class Instruction:
state = global_state.mstate state = global_state.mstate
index = state.stack.pop() index = state.stack.pop()
log.debug("Storage access at index " + str(index)) log.debug("Storage access at index " + str(index))
"""
try: if index not in global_state.environment.active_account.storage.keys():
index = util.get_concrete_int(index) for key in global_state.environment.active_account.storage.keys():
return self._sload_helper(global_state, index) if not isinstance(index, BitVecFunc) or not isinstance(key, BitVecFunc):
global_state.mstate.constraints.append(Bool(key.raw != index.raw))
except TypeError: continue
if not keccak_function_manager.is_keccak(index): key_map = Extract(255, 0, key.input_)
return self._sload_helper(global_state, str(index)) index_map = Extract(255, 0, index.input_)
if simplify(key_map == index_map):
storage_keys = global_state.environment.active_account.storage.keys() continue
keccak_keys = list(filter(keccak_function_manager.is_keccak, storage_keys)) global_state.mstate.constraints.append(key != index)
"""
results = [] # type: List[GlobalState] state.stack.append(global_state.environment.active_account.storage[index])
constraints = [] return [global_state]
for keccak_key in keccak_keys:
key_argument = keccak_function_manager.get_argument(keccak_key)
index_argument = keccak_function_manager.get_argument(index)
constraints.append((keccak_key, key_argument == index_argument))
for (keccak_key, constraint) in constraints:
if constraint in state.constraints:
results += self._sload_helper(
global_state, keccak_key, [constraint]
)
if len(results) > 0:
return results
for (keccak_key, constraint) in constraints:
results += self._sload_helper(
copy(global_state), keccak_key, [constraint]
)
if len(results) > 0:
return results
return self._sload_helper(global_state, str(index))
@staticmethod @staticmethod
def _sload_helper( def _sload_helper(
@ -1454,59 +1434,21 @@ class Instruction:
global keccak_function_manager global keccak_function_manager
state = global_state.mstate state = global_state.mstate
index, value = state.stack.pop(), state.stack.pop() index, value = state.stack.pop(), state.stack.pop()
"""
if index not in global_state.environment.active_account.storage.keys():
for key in global_state.environment.active_account.storage.keys():
if not isinstance(index, BitVecFunc) or not isinstance(key, BitVecFunc):
global_state.mstate.constraints.append(Bool(key.raw != index.raw))
continue
key_map = Extract(255, 0, key.input_)
index_map = Extract(255, 0, index.input_)
if simplify(key_map == index_map):
continue
global_state.mstate.constraints.append(key != index)
"""
log.debug("Write to storage[" + str(index) + "]") log.debug("Write to storage[" + str(index) + "]")
global_state.environment.active_account.storage[index] = value
try: return [global_state]
index = util.get_concrete_int(index)
return self._sstore_helper(global_state, index, value)
except TypeError:
is_keccak = keccak_function_manager.is_keccak(index)
if not is_keccak:
return self._sstore_helper(global_state, str(index), value)
storage_keys = global_state.environment.active_account.storage.keys()
keccak_keys = filter(keccak_function_manager.is_keccak, storage_keys)
results = [] # type: List[GlobalState]
new = symbol_factory.Bool(False)
for keccak_key in keccak_keys:
key_argument = keccak_function_manager.get_argument(
keccak_key
) # type: Expression
index_argument = keccak_function_manager.get_argument(
index
) # type: Expression
condition = key_argument == index_argument
condition = (
condition
if type(condition) == bool
else is_true(simplify(cast(Bool, condition)))
)
if condition:
return self._sstore_helper(
copy(global_state),
keccak_key,
value,
key_argument == index_argument,
)
results += self._sstore_helper(
copy(global_state),
keccak_key,
value,
key_argument == index_argument,
)
new = Or(new, cast(Bool, key_argument != index_argument))
if len(results) > 0:
results += self._sstore_helper(
copy(global_state), str(index), value, new
)
return results
return self._sstore_helper(global_state, str(index), value)
@staticmethod @staticmethod
def _sstore_helper(global_state, index, value, constraint=None): def _sstore_helper(global_state, index, value, constraint=None):

@ -3,11 +3,10 @@
This includes classes representing accounts and their storage. This includes classes representing accounts and their storage.
""" """
from copy import deepcopy, copy from copy import deepcopy, copy
from typing import Any, Dict, KeysView, Union from typing import Any, Dict, Union
from z3 import ExprRef
from mythril.laser.smt import Array, symbol_factory, BitVec from mythril.laser.smt import Array, K, BitVec, simplify, BitVecFunc, Extract
from mythril.disassembler.disassembly import Disassembly from mythril.disassembler.disassembly import Disassembly
from mythril.laser.smt import symbol_factory from mythril.laser.smt import symbol_factory
@ -15,66 +14,74 @@ from mythril.laser.smt import symbol_factory
class Storage: class Storage:
"""Storage class represents the storage of an Account.""" """Storage class represents the storage of an Account."""
def __init__(self, concrete=False, address=None, dynamic_loader=None) -> None: def __init__(
self, concrete=False, address=None, dynamic_loader=None, keys=None
) -> None:
"""Constructor for Storage. """Constructor for Storage.
:param concrete: bool indicating whether to interpret uninitialized storage as concrete versus symbolic :param concrete: bool indicating whether to interpret uninitialized storage as concrete versus symbolic
""" """
self._storage = {} # type: Dict[Union[int, str], Any] if concrete:
self.concrete = concrete self._standard_storage = K(256, 256, 0)
self._map_storage = {}
else:
self._standard_storage = Array("Storage", 256, 256)
self._map_storage = {}
self._keys = keys or set()
self.dynld = dynamic_loader self.dynld = dynamic_loader
self.address = address self.address = address
def __getitem__(self, item: Union[str, int]) -> Any: def __getitem__(self, item: Union[str, int]) -> Any:
try: self._keys.add(item)
return self._storage[item] storage = self._get_corresponding_storage(item)
except KeyError: return simplify(storage[item])
if (
self.address @staticmethod
and self.address.value != 0 def get_map_index(key):
and (self.dynld and self.dynld.storage_loading) if not isinstance(key, BitVecFunc) or key.func_name != "keccak256":
): return None
try: index = Extract(255, 0, key.input_)
self._storage[item] = symbol_factory.BitVecVal( print(simplify(index), key.input_)
int( return simplify(index)
self.dynld.read_storage(
contract_address=self.address, index=int(item) def _get_corresponding_storage(self, key):
), index = self.get_map_index(key)
16, if index is None:
), storage = self._standard_storage
256, else:
) try:
return self._storage[item] storage = self._map_storage[index]
except ValueError: except KeyError:
pass if isinstance(self._standard_storage, Array):
self._map_storage[index] = Array("Storage", 256, 256)
if self.concrete: else:
return symbol_factory.BitVecVal(0, 256) self._map_storage[index] = K(256, 256, 0)
storage = self._map_storage[index]
self._storage[item] = symbol_factory.BitVecSym( return storage
"storage_{}_{}".format(str(item), str(self.address)), 256
)
return self._storage[item]
def __setitem__(self, key: Union[int, str], value: Any) -> None:
self._storage[key] = value
def keys(self) -> KeysView:
"""
:return: def __setitem__(self, key, value: Any) -> None:
""" self._keys.add(key)
return self._storage.keys() storage = self._get_corresponding_storage(key)
storage[key] = value
def __deepcopy__(self, memodict={}): def __deepcopy__(self, memodict={}):
concrete = isinstance(self._standard_storage, K)
storage = Storage( storage = Storage(
concrete=self.concrete, address=self.address, dynamic_loader=self.dynld concrete=concrete,
address=self.address,
dynamic_loader=self.dynld,
keys=deepcopy(self._keys),
) )
storage._storage = copy(self._storage) storage._standard_storage = deepcopy(self._standard_storage)
storage._map_storage = deepcopy(self._map_storage)
return storage return storage
def __str__(self): def __str__(self):
return str(self._storage) return str(self._standard_storage)
def keys(self):
return self._keys
class Account: class Account:
@ -158,13 +165,13 @@ class Account:
"storage": self.storage, "storage": self.storage,
} }
def __deepcopy__(self, memodict={}): def __copy__(self, memodict={}):
new_account = Account( new_account = Account(
address=self.address, address=self.address,
code=self.code, code=self.code,
contract_name=self.contract_name, contract_name=self.contract_name,
balances=self._balances, balances=self._balances,
) )
new_account.storage = deepcopy(self.storage) new_account.storage = copy(self.storage)
new_account.code = self.code new_account.code = self.code
return new_account return new_account

@ -1,7 +1,7 @@
"""This module contains the class used to represent state-change constraints in """This module contains the class used to represent state-change constraints in
the call graph.""" the call graph."""
from mythril.laser.smt import Solver, Bool, symbol_factory from mythril.laser.smt import Solver, Bool, symbol_factory, simplify
from typing import Iterable, List, Optional, Union from typing import Iterable, List, Optional, Union
from z3 import unsat from z3 import unsat
@ -54,7 +54,9 @@ class Constraints(list):
:param constraint: The constraint to be appended :param constraint: The constraint to be appended
""" """
constraint = constraint if isinstance(constraint, Bool) else Bool(constraint) constraint = (
simplify(constraint) if isinstance(constraint, Bool) else Bool(constraint)
)
super(Constraints, self).append(constraint) super(Constraints, self).append(constraint)
self._is_possible = None self._is_possible = None

@ -61,6 +61,7 @@ class GlobalState:
environment = copy(self.environment) environment = copy(self.environment)
mstate = deepcopy(self.mstate) mstate = deepcopy(self.mstate)
transaction_stack = copy(self.transaction_stack) transaction_stack = copy(self.transaction_stack)
environment.active_account = world_state[environment.active_account.address]
return GlobalState( return GlobalState(
world_state, world_state,
environment, environment,

@ -8,7 +8,8 @@ default values over a certain range.
from typing import cast from typing import cast
import z3 import z3
from mythril.laser.smt.bitvec import BitVec from mythril.laser.smt.bitvec import BitVec, If
from mythril.laser.smt.bool import Bool
class BaseArray: class BaseArray:
@ -24,6 +25,9 @@ class BaseArray:
def __setitem__(self, key: BitVec, value: BitVec) -> None: def __setitem__(self, key: BitVec, value: BitVec) -> None:
"""Sets an item in the array, key can be symbolic.""" """Sets an item in the array, key can be symbolic."""
if isinstance(value, Bool):
value = If(value, 1, 0)
self.raw = z3.Store(self.raw, key.raw, value.raw) # type: ignore self.raw = z3.Store(self.raw, key.raw, value.raw) # type: ignore

@ -205,7 +205,7 @@ class BitVecFunc(BitVec):
:return: The resulting Bool :return: The resulting Bool
""" """
return _comparison_helper( return _comparison_helper(
self, other, operator.eq, default_value=True, inputs_equal=False self, other, operator.ne, default_value=True, inputs_equal=False
) )
def __lshift__(self, other: Union[int, "BitVec"]) -> "BitVec": def __lshift__(self, other: Union[int, "BitVec"]) -> "BitVec":
@ -223,3 +223,6 @@ class BitVecFunc(BitVec):
:return The resulting right shifted output: :return The resulting right shifted output:
""" """
return _arithmetic_helper(self, other, operator.rshift) return _arithmetic_helper(self, other, operator.rshift)
def __hash__(self) -> int:
return self.raw.__hash__()

@ -80,6 +80,9 @@ class Bool(Expression[z3.BoolRef]):
else: else:
return False return False
def __hash__(self) -> int:
return self.raw.__hash__()
def And(*args: Union[Bool, bool]) -> Bool: def And(*args: Union[Bool, bool]) -> Bool:
"""Create an And expression.""" """Create an And expression."""
@ -90,6 +93,13 @@ def And(*args: Union[Bool, bool]) -> Bool:
return Bool(z3.And([a.raw for a in args_list]), union) return Bool(z3.And([a.raw for a in args_list]), union)
def Xor(a: Bool, b: Bool) -> Bool:
"""Create an And expression."""
union = a.annotations + b.annotations
return Bool(z3.Xor(a.raw, b.raw), union)
def Or(*args: Union[Bool, bool]) -> Bool: def Or(*args: Union[Bool, bool]) -> Bool:
"""Create an or expression. """Create an or expression.

@ -45,6 +45,12 @@ class Expression(Generic[T]):
def __repr__(self) -> str: def __repr__(self) -> str:
return repr(self.raw) return repr(self.raw)
def size(self):
return self.raw.size()
def __hash__(self) -> int:
return self.raw.__hash__()
G = TypeVar("G", bound=Expression) G = TypeVar("G", bound=Expression)

@ -10,9 +10,13 @@ from mythril.laser.ethereum.transaction.transaction_models import MessageCallTra
def test_codecopy_concrete(): def test_codecopy_concrete():
# Arrange # Arrange
active_account = Account("0x0", code=Disassembly("60606040")) world_state = WorldState()
environment = Environment(active_account, None, None, None, None, None) account = world_state.create_account(balance=10, address=101)
og_state = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) account.code = Disassembly("60606040")
environment = Environment(account, None, None, None, None, None)
og_state = GlobalState(
world_state, environment, None, MachineState(gas_limit=8000000)
)
og_state.transaction_stack.append( og_state.transaction_stack.append(
(MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None) (MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None)
) )

@ -2,6 +2,7 @@ import pytest
from mythril.disassembler.disassembly import Disassembly from mythril.disassembler.disassembly import Disassembly
from mythril.laser.ethereum.state.environment import Environment from mythril.laser.ethereum.state.environment import Environment
from mythril.laser.ethereum.state.world_state import WorldState
from mythril.laser.ethereum.state.account import Account from mythril.laser.ethereum.state.account import Account
from mythril.laser.ethereum.state.machine_state import MachineState from mythril.laser.ethereum.state.machine_state import MachineState
from mythril.laser.ethereum.state.global_state import GlobalState from mythril.laser.ethereum.state.global_state import GlobalState
@ -12,9 +13,11 @@ from mythril.laser.smt import symbol_factory, simplify
def get_state(): def get_state():
active_account = Account("0x0", code=Disassembly("60606040")) world_state = WorldState()
environment = Environment(active_account, None, None, None, None, None) account = world_state.create_account(balance=10, address=101)
state = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) account.code = Disassembly("60606040")
environment = Environment(account, None, None, None, None, None)
state = GlobalState(world_state, environment, None, MachineState(gas_limit=8000000))
state.transaction_stack.append( state.transaction_stack.append(
(MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None) (MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None)
) )

@ -12,9 +12,11 @@ from mythril.laser.smt import symbol_factory, simplify
def get_state(): def get_state():
active_account = Account("0x0", code=Disassembly("60606040")) world_state = WorldState()
environment = Environment(active_account, None, None, None, None, None) account = world_state.create_account(balance=10, address=101)
state = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) account.code = Disassembly("60606040")
environment = Environment(account, None, None, None, None, None)
state = GlobalState(world_state, environment, None, MachineState(gas_limit=8000000))
state.transaction_stack.append( state.transaction_stack.append(
(MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None) (MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None)
) )

@ -12,9 +12,11 @@ from mythril.laser.smt import symbol_factory, simplify, LShR
def get_state(): def get_state():
active_account = Account("0x0", code=Disassembly("60606040")) world_state = WorldState()
environment = Environment(active_account, None, None, None, None, None) account = world_state.create_account(balance=10, address=101)
state = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) account.code = Disassembly("60606040")
environment = Environment(account, None, None, None, None, None)
state = GlobalState(world_state, environment, None, MachineState(gas_limit=8000000))
state.transaction_stack.append( state.transaction_stack.append(
(MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None) (MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None)
) )

@ -125,7 +125,8 @@ def test_vmtest(
account.code = Disassembly(details["code"][2:]) account.code = Disassembly(details["code"][2:])
account.nonce = int(details["nonce"], 16) account.nonce = int(details["nonce"], 16)
for key, value in details["storage"].items(): for key, value in details["storage"].items():
account.storage[int(key, 16)] = int(value, 16) key_bitvec = symbol_factory.BitVecVal(int(key, 16), 256)
account.storage[key_bitvec] = symbol_factory.BitVecVal(int(value, 16), 256)
world_state.put_account(account) world_state.put_account(account)
account.set_balance(int(details["balance"], 16)) account.set_balance(int(details["balance"], 16))
@ -175,8 +176,7 @@ def test_vmtest(
for index, value in details["storage"].items(): for index, value in details["storage"].items():
expected = int(value, 16) expected = int(value, 16)
actual = account.storage[int(index, 16)] actual = account.storage[symbol_factory.BitVecVal(int(index, 16), 256)]
if isinstance(actual, Expression): if isinstance(actual, Expression):
actual = actual.value actual = actual.value
actual = 1 if actual is True else 0 if actual is False else actual actual = 1 if actual is True else 0 if actual is False else actual

@ -1,53 +0,0 @@
import pytest
from mythril.laser.ethereum.state.account import Storage
from mythril.laser.smt import Expression
storage_uninitialized_test_data = [({}, 1), ({1: 5}, 2), ({1: 5, 3: 10}, 2)]
@pytest.mark.parametrize("initial_storage,key", storage_uninitialized_test_data)
def test_concrete_storage_uninitialized_index(initial_storage, key):
# Arrange
storage = Storage(concrete=True)
storage._storage = initial_storage
# Act
value = storage[key]
# Assert
assert value == 0
@pytest.mark.parametrize("initial_storage,key", storage_uninitialized_test_data)
def test_symbolic_storage_uninitialized_index(initial_storage, key):
# Arrange
storage = Storage(concrete=False)
storage._storage = initial_storage
# Act
value = storage[key]
# Assert
assert isinstance(value, Expression)
def test_storage_set_item():
# Arrange
storage = Storage()
# Act
storage[1] = 13
# Assert
assert storage[1] == 13
def test_storage_change_item():
# Arrange
storage = Storage()
storage._storage = {1: 12}
# Act
storage[1] = 14
# Assert
assert storage[1] == 14

@ -1,5 +1,19 @@
{ {
"error": null, "error": null,
"issues": [], "issues": [
{
"address": 498,
"contract": "Unknown",
"description": "The binary addition can overflow.\nThe operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion.",
"function": "sendToken(address,uint256)",
"max_gas_used": 52806,
"min_gas_used": 11860,
"severity": "High",
"sourceMap": null,
"swc-id": "101",
"title": "Integer Overflow",
"tx_sequence": "<TX-DATA>"
}
],
"success": true "success": true
} }

@ -1,6 +1,25 @@
[ [
{ {
"issues": [], "issues": [
{
"description": {
"head": "The binary addition can overflow.",
"tail": "The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion."
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>",
"testCase": "<TEST-CASE>"
},
"locations": [
{
"sourceMap": "498:1:0"
}
],
"severity": "High",
"swcID": "SWC-101",
"swcTitle": "Integer Overflow and Underflow"
}
],
"meta": {}, "meta": {},
"sourceFormat": "evm-byzantium-bytecode", "sourceFormat": "evm-byzantium-bytecode",
"sourceList": [ "sourceList": [

@ -1,3 +1,14 @@
# Analysis results for None # Analysis results for test-filename.sol
The analysis was completed successfully. No issues were detected. ## Integer Overflow
- SWC ID: 101
- Severity: High
- Contract: Unknown
- Function name: `sendToken(address,uint256)`
- PC address: 498
- Estimated Gas Usage: 11860 - 52806
### Description
The binary addition can overflow.
The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion.

@ -1 +1,11 @@
The analysis was completed successfully. No issues were detected. ==== Integer Overflow ====
SWC ID: 101
Severity: High
Contract: Unknown
Function name: sendToken(address,uint256)
PC address: 498
Estimated Gas Usage: 11860 - 52806
The binary addition can overflow.
The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion.
--------------------

Loading…
Cancel
Save