Merge branch 'develop' into enhance/886

pull/919/head
JoranHonig 6 years ago committed by GitHub
commit e314bfc990
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      mythril/analysis/symbolic.py
  2. 1
      mythril/laser/ethereum/plugins/implementations/benchmark.py
  3. 3
      mythril/laser/ethereum/plugins/implementations/coverage/__init__.py
  4. 49
      mythril/laser/ethereum/plugins/implementations/coverage/coverage_plugin.py
  5. 43
      mythril/laser/ethereum/plugins/implementations/coverage/coverage_strategy.py
  6. 19
      mythril/laser/ethereum/plugins/plugin_factory.py
  7. 6
      mythril/laser/ethereum/plugins/plugin_loader.py
  8. 4
      mythril/laser/ethereum/strategy/__init__.py
  9. 59
      mythril/laser/ethereum/svm.py
  10. 2
      mythril/version.py
  11. 1
      requirements.txt
  12. 1
      setup.py

@ -12,9 +12,9 @@ from mythril.laser.ethereum.strategy.basic import (
ReturnWeightedRandomStrategy, ReturnWeightedRandomStrategy,
) )
from mythril.laser.ethereum.plugins.implementations.mutation_pruner import ( from mythril.laser.ethereum.plugins.plugin_factory import PluginFactory
MutationPruner, from mythril.laser.ethereum.plugins.plugin_loader import LaserPluginLoader
)
from mythril.solidity.soliditycontract import EVMContract, SolidityContract from mythril.solidity.soliditycontract import EVMContract, SolidityContract
from .ops import Call, SStore, VarType, get_variable from .ops import Call, SStore, VarType, get_variable
@ -86,9 +86,10 @@ class SymExecWrapper:
requires_statespace=requires_statespace, requires_statespace=requires_statespace,
enable_iprof=enable_iprof, enable_iprof=enable_iprof,
) )
mutation_plugin = MutationPruner()
mutation_plugin.initialize(self.laser) plugin_loader = LaserPluginLoader(self.laser)
plugin_loader.load(PluginFactory.build_mutation_pruner_plugin())
plugin_loader.load(PluginFactory.build_instruction_coverage_plugin())
self.laser.register_hooks( self.laser.register_hooks(
hook_type="pre", hook_type="pre",

@ -7,6 +7,7 @@ import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
# TODO: introduce dependency on coverage plugin
class BenchmarkPlugin(LaserPlugin): class BenchmarkPlugin(LaserPlugin):
"""Benchmark Plugin """Benchmark Plugin

@ -0,0 +1,3 @@
from mythril.laser.ethereum.plugins.implementations.coverage.coverage_plugin import (
InstructionCoveragePlugin,
)

@ -21,6 +21,11 @@ class InstructionCoveragePlugin(LaserPlugin):
""" """
def __init__(self):
self.coverage = {} # type: Dict[str, Tuple[int, List[bool]]]
self.initial_coverage = 0
self.tx_id = 0
def initialize(self, symbolic_vm: LaserEVM): def initialize(self, symbolic_vm: LaserEVM):
"""Initializes the instruction coverage plugin """Initializes the instruction coverage plugin
@ -28,12 +33,14 @@ class InstructionCoveragePlugin(LaserPlugin):
:param symbolic_vm: :param symbolic_vm:
:return: :return:
""" """
coverage = {} # type: Dict[str, Tuple[int, List[bool]]] self.coverage = {}
self.initial_coverage = 0
self.tx_id = 0
@symbolic_vm.laser_hook("stop_sym_exec") @symbolic_vm.laser_hook("stop_sym_exec")
def stop_sym_exec_hook(): def stop_sym_exec_hook():
# Print results # Print results
for code, code_cov in coverage.items(): for code, code_cov in self.coverage.items():
cov_percentage = sum(code_cov[1]) / float(code_cov[0]) * 100 cov_percentage = sum(code_cov[1]) / float(code_cov[0]) * 100
log.info( log.info(
@ -47,13 +54,45 @@ class InstructionCoveragePlugin(LaserPlugin):
# Record coverage # Record coverage
code = global_state.environment.code.bytecode code = global_state.environment.code.bytecode
if code not in coverage.keys(): if code not in self.coverage.keys():
number_of_instructions = len( number_of_instructions = len(
global_state.environment.code.instruction_list global_state.environment.code.instruction_list
) )
coverage[code] = ( self.coverage[code] = (
number_of_instructions, number_of_instructions,
[False] * number_of_instructions, [False] * number_of_instructions,
) )
coverage[code][1][global_state.mstate.pc] = True self.coverage[code][1][global_state.mstate.pc] = True
@symbolic_vm.laser_hook("start_sym_trans")
def execute_start_sym_trans_hook():
self.initial_coverage = self._get_covered_instructions()
@symbolic_vm.laser_hook("stop_sym_trans")
def execute_stop_sym_trans_hook():
end_coverage = self._get_covered_instructions()
log.info(
"Number of new instructions covered in tx %d: %d"
% (self.tx_id, end_coverage - self.initial_coverage)
)
self.tx_id += 1
def _get_covered_instructions(self) -> int:
"""Gets the total number of covered instructions for all accounts in
the svm.
:return:
"""
total_covered_instructions = 0
for _, cv in self.coverage.items():
total_covered_instructions += sum(cv[1])
return total_covered_instructions
def is_instruction_covered(self, bytecode, index):
if bytecode not in self.coverage.keys():
return False
try:
return self.coverage[bytecode][index]
except IndexError:
return False

@ -0,0 +1,43 @@
from mythril.laser.ethereum.strategy import BasicSearchStrategy
from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.laser.ethereum.plugins.implementations.coverage import (
InstructionCoveragePlugin,
)
class CoverageStrategy(BasicSearchStrategy):
"""Implements a instruction coverage based search strategy
This strategy is quite simple and effective, it prioritizes the execution of instructions that have previously been
uncovered. Once there is no such global state left in the work list, it will resort to using the super_strategy.
This strategy is intended to be used "on top of" another one
"""
def __init__(
self,
super_strategy: BasicSearchStrategy,
instruction_coverage_plugin: InstructionCoveragePlugin,
):
self.super_strategy = super_strategy
self.instruction_coverage_plugin = instruction_coverage_plugin
BasicSearchStrategy.__init__(
self, super_strategy.work_list, super_strategy.max_depth
)
def get_strategic_global_state(self) -> GlobalState:
"""
Returns the first uncovered global state in the work list if it exists,
otherwise super_strategy.get_strategic_global_state() is returned.
"""
for global_state in self.work_list:
if not self._is_covered(global_state):
self.work_list.remove(global_state)
return global_state
return self.super_strategy.get_strategic_global_state()
def _is_covered(self, global_state: GlobalState) -> bool:
""" Checks if the instruction for the given global state is already covered"""
bytecode = global_state.environment.code.bytecode
index = global_state.mstate.pc
return self.instruction_coverage_plugin.is_instruction_covered(bytecode, index)

@ -1,11 +1,4 @@
from mythril.laser.ethereum.plugins.plugin import LaserPlugin from mythril.laser.ethereum.plugins.plugin import LaserPlugin
from mythril.laser.ethereum.plugins.implementations.benchmark import BenchmarkPlugin
from mythril.laser.ethereum.plugins.implementations.mutation_pruner import (
MutationPruner,
)
from mythril.laser.ethereum.plugins.implementations.coverage import (
InstructionCoveragePlugin,
)
class PluginFactory: class PluginFactory:
@ -14,14 +7,26 @@ class PluginFactory:
@staticmethod @staticmethod
def build_benchmark_plugin(name: str) -> LaserPlugin: def build_benchmark_plugin(name: str) -> LaserPlugin:
""" Creates an instance of the benchmark plugin with the given name """ """ Creates an instance of the benchmark plugin with the given name """
from mythril.laser.ethereum.plugins.implementations.benchmark import (
BenchmarkPlugin,
)
return BenchmarkPlugin(name) return BenchmarkPlugin(name)
@staticmethod @staticmethod
def build_mutation_pruner_plugin() -> LaserPlugin: def build_mutation_pruner_plugin() -> LaserPlugin:
""" Creates an instance of the mutation pruner plugin""" """ Creates an instance of the mutation pruner plugin"""
from mythril.laser.ethereum.plugins.implementations.mutation_pruner import (
MutationPruner,
)
return MutationPruner() return MutationPruner()
@staticmethod @staticmethod
def build_instruction_coverage_plugin() -> LaserPlugin: def build_instruction_coverage_plugin() -> LaserPlugin:
""" Creates an instance of the instruction coverage plugin""" """ Creates an instance of the instruction coverage plugin"""
from mythril.laser.ethereum.plugins.implementations.coverage import (
InstructionCoveragePlugin,
)
return InstructionCoveragePlugin() return InstructionCoveragePlugin()

@ -2,6 +2,9 @@ from mythril.laser.ethereum.svm import LaserEVM
from mythril.laser.ethereum.plugins.plugin import LaserPlugin from mythril.laser.ethereum.plugins.plugin import LaserPlugin
from typing import List from typing import List
import logging
log = logging.getLogger(__name__)
class LaserPluginLoader: class LaserPluginLoader:
@ -16,13 +19,14 @@ class LaserPluginLoader:
:param symbolic_vm: symbolic virtual machine to load plugins for :param symbolic_vm: symbolic virtual machine to load plugins for
""" """
self.symbolic_vm = symbolic_vm self.symbolic_vm = symbolic_vm
self.laser_plugins: List[LaserPlugin] = [] self.laser_plugins = [] # type: List[LaserPlugin]
def load(self, laser_plugin: LaserPlugin) -> None: def load(self, laser_plugin: LaserPlugin) -> None:
""" Loads the plugin """ Loads the plugin
:param laser_plugin: plugin that will be loaded in the symbolic virtual machine :param laser_plugin: plugin that will be loaded in the symbolic virtual machine
""" """
log.info("Loading plugin: {}".format(str(laser_plugin)))
laser_plugin.initialize(self.symbolic_vm) laser_plugin.initialize(self.symbolic_vm)
self.laser_plugins.append(laser_plugin) self.laser_plugins.append(laser_plugin)

@ -1,4 +1,6 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import List
from mythril.laser.ethereum.state.global_state import GlobalState
class BasicSearchStrategy(ABC): class BasicSearchStrategy(ABC):
@ -7,7 +9,7 @@ class BasicSearchStrategy(ABC):
__slots__ = "work_list", "max_depth" __slots__ = "work_list", "max_depth"
def __init__(self, work_list, max_depth): def __init__(self, work_list, max_depth):
self.work_list = work_list self.work_list = work_list # type: List[GlobalState]
self.max_depth = max_depth self.max_depth = max_depth
def __iter__(self): def __iter__(self):

@ -72,8 +72,6 @@ class LaserEVM:
self.world_state = world_state self.world_state = world_state
self.open_states = [world_state] self.open_states = [world_state]
self.coverage = {} # type: Dict[str, Tuple[int, List[bool]]]
self.total_states = 0 self.total_states = 0
self.dynamic_loader = dynamic_loader self.dynamic_loader = dynamic_loader
@ -97,6 +95,10 @@ class LaserEVM:
self._add_world_state_hooks = [] # type: List[Callable] self._add_world_state_hooks = [] # type: List[Callable]
self._execute_state_hooks = [] # type: List[Callable] self._execute_state_hooks = [] # type: List[Callable]
self._start_sym_trans_hooks = [] # type: List[Callable]
self._stop_sym_trans_hooks = [] # type: List[Callable]
self._start_sym_exec_hooks = [] # type: List[Callable] self._start_sym_exec_hooks = [] # type: List[Callable]
self._stop_sym_exec_hooks = [] # type: List[Callable] self._stop_sym_exec_hooks = [] # type: List[Callable]
@ -158,10 +160,6 @@ class LaserEVM:
len(self.edges), len(self.edges),
self.total_states, self.total_states,
) )
for code, coverage in self.coverage.items():
cov = sum(coverage[1]) / float(coverage[0]) * 100
log.info("Achieved {:.2f}% coverage for code: {}".format(cov, code))
if self.iprof is not None: if self.iprof is not None:
log.info("Instruction Statistics:\n{}".format(self.iprof)) log.info("Instruction Statistics:\n{}".format(self.iprof))
@ -170,42 +168,25 @@ class LaserEVM:
hook() hook()
def _execute_transactions(self, address): def _execute_transactions(self, address):
"""This function executes multiple transactions on the address based on """This function executes multiple transactions on the address
the coverage.
:param address: Address of the contract :param address: Address of the contract
:return: :return:
""" """
self.coverage = {}
for i in range(self.transaction_count): for i in range(self.transaction_count):
initial_coverage = self._get_covered_instructions()
self.time = datetime.now() self.time = datetime.now()
log.info( log.info(
"Starting message call transaction, iteration: {}, {} initial states".format( "Starting message call transaction, iteration: {}, {} initial states".format(
i, len(self.open_states) i, len(self.open_states)
) )
) )
for hook in self._start_sym_trans_hooks:
hook()
execute_message_call(self, address) execute_message_call(self, address)
end_coverage = self._get_covered_instructions() for hook in self._stop_sym_trans_hooks:
hook()
log.info(
"Number of new instructions covered in tx %d: %d"
% (i, end_coverage - initial_coverage)
)
def _get_covered_instructions(self) -> int:
"""Gets the total number of covered instructions for all accounts in
the svm.
:return:
"""
total_covered_instructions = 0
for _, cv in self.coverage.items():
total_covered_instructions += sum(cv[1])
return total_covered_instructions
def exec(self, create=False, track_gas=False) -> Union[List[GlobalState], None]: def exec(self, create=False, track_gas=False) -> Union[List[GlobalState], None]:
""" """
@ -284,7 +265,6 @@ class LaserEVM:
self._execute_pre_hook(op_code, global_state) self._execute_pre_hook(op_code, global_state)
try: try:
self._measure_coverage(global_state)
new_global_states = Instruction( new_global_states = Instruction(
op_code, self.dynamic_loader, self.iprof op_code, self.dynamic_loader, self.iprof
).evaluate(global_state) ).evaluate(global_state)
@ -389,23 +369,6 @@ class LaserEVM:
return new_global_states return new_global_states
def _measure_coverage(self, global_state: GlobalState) -> None:
"""
:param global_state:
"""
code = global_state.environment.code.bytecode
number_of_instructions = len(global_state.environment.code.instruction_list)
instruction_index = global_state.mstate.pc
if code not in self.coverage.keys():
self.coverage[code] = (
number_of_instructions,
[False] * number_of_instructions,
)
self.coverage[code][1][instruction_index] = True
def manage_cfg(self, opcode: str, new_states: List[GlobalState]) -> None: def manage_cfg(self, opcode: str, new_states: List[GlobalState]) -> None:
""" """
@ -527,6 +490,10 @@ class LaserEVM:
self._start_sym_exec_hooks.append(hook) self._start_sym_exec_hooks.append(hook)
elif hook_type == "stop_sym_exec": elif hook_type == "stop_sym_exec":
self._stop_sym_exec_hooks.append(hook) self._stop_sym_exec_hooks.append(hook)
elif hook_type == "start_sym_trans":
self._start_sym_trans_hooks.append(hook)
elif hook_type == "stop_sym_trans":
self._stop_sym_trans_hooks.append(hook)
else: else:
raise ValueError( raise ValueError(
"Invalid hook type %s. Must be one of {add_world_state}", hook_type "Invalid hook type %s. Must be one of {add_world_state}", hook_type

@ -4,4 +4,4 @@ This file is suitable for sourcing inside POSIX shell, e.g. bash as well
as for importing into Python. as for importing into Python.
""" """
VERSION = "v0.20.2" # NOQA VERSION = "v0.20.3" # NOQA

@ -27,3 +27,4 @@ rlp>=1.0.1
transaction>=2.2.1 transaction>=2.2.1
z3-solver-mythril>=4.8.4.1 z3-solver-mythril>=4.8.4.1
pysha3 pysha3
matplotlib

@ -97,6 +97,7 @@ setup(
"configparser>=3.5.0", "configparser>=3.5.0",
"persistent>=4.2.0", "persistent>=4.2.0",
"ethereum-input-decoder>=0.2.2", "ethereum-input-decoder>=0.2.2",
"matplotlib",
], ],
tests_require=["mypy", "pytest>=3.6.0", "pytest_mock", "pytest-cov"], tests_require=["mypy", "pytest>=3.6.0", "pytest_mock", "pytest-cov"],
python_requires=">=3.5", python_requires=">=3.5",

Loading…
Cancel
Save