mirror of https://github.com/ConsenSys/mythril
commit
aba7e32edf
@ -1,198 +0,0 @@ |
||||
"""This module contains the detection code to find the existence of transaction |
||||
order dependence.""" |
||||
import copy |
||||
import logging |
||||
import re |
||||
|
||||
from mythril.analysis import solver |
||||
from mythril.analysis.modules.base import DetectionModule |
||||
from mythril.analysis.ops import * |
||||
from mythril.analysis.report import Issue |
||||
from mythril.analysis.swc_data import TX_ORDER_DEPENDENCE |
||||
from mythril.exceptions import UnsatError |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
|
||||
class TxOrderDependenceModule(DetectionModule): |
||||
"""This module finds the existence of transaction order dependence.""" |
||||
|
||||
def __init__(self): |
||||
super().__init__( |
||||
name="Transaction Order Dependence", |
||||
swc_id=TX_ORDER_DEPENDENCE, |
||||
description=( |
||||
"This module finds the existance of transaction order dependence " |
||||
"vulnerabilities. The following webpage contains an extensive description " |
||||
"of the vulnerability: " |
||||
"https://consensys.github.io/smart-contract-best-practices/known_attacks/#transaction-ordering-dependence-tod-front-running" |
||||
), |
||||
) |
||||
|
||||
def execute(self, statespace): |
||||
"""Executes the analysis module. |
||||
|
||||
:param statespace: |
||||
:return: |
||||
""" |
||||
log.debug("Executing module: TOD") |
||||
|
||||
issues = [] |
||||
|
||||
for call in statespace.calls: |
||||
# Do analysis |
||||
interesting_storages = list(self._get_influencing_storages(call)) |
||||
changing_sstores = list( |
||||
self._get_influencing_sstores(statespace, interesting_storages) |
||||
) |
||||
|
||||
description_tail = ( |
||||
"A transaction order dependence vulnerability may exist in this contract. The value or " |
||||
"target of the call statement is loaded from a writable storage location." |
||||
) |
||||
|
||||
# Build issue if necessary |
||||
if len(changing_sstores) > 0: |
||||
node = call.node |
||||
instruction = call.state.get_current_instruction() |
||||
issue = Issue( |
||||
contract=node.contract_name, |
||||
function_name=node.function_name, |
||||
address=instruction["address"], |
||||
title="Transaction Order Dependence", |
||||
bytecode=call.state.environment.code.bytecode, |
||||
swc_id=TX_ORDER_DEPENDENCE, |
||||
severity="Medium", |
||||
description_head="The call outcome may depend on transaction order.", |
||||
description_tail=description_tail, |
||||
gas_used=( |
||||
call.state.mstate.min_gas_used, |
||||
call.state.mstate.max_gas_used, |
||||
), |
||||
) |
||||
|
||||
issues.append(issue) |
||||
|
||||
return issues |
||||
|
||||
# TODO: move to __init__ or util module |
||||
@staticmethod |
||||
def _get_states_with_opcode(statespace, opcode): |
||||
"""Gets all (state, node) tuples in statespace with opcode. |
||||
|
||||
:param statespace: |
||||
:param opcode: |
||||
""" |
||||
for k in statespace.nodes: |
||||
node = statespace.nodes[k] |
||||
for state in node.states: |
||||
if state.get_current_instruction()["opcode"] == opcode: |
||||
yield state, node |
||||
|
||||
@staticmethod |
||||
def _dependent_on_storage(expression): |
||||
"""Checks if expression is dependent on a storage symbol and returns |
||||
the influencing storages. |
||||
|
||||
:param expression: |
||||
:return: |
||||
""" |
||||
pattern = re.compile(r"storage_[a-z0-9_&^]*[0-9]+") |
||||
return pattern.findall(str(simplify(expression))) |
||||
|
||||
@staticmethod |
||||
def _get_storage_variable(storage, state): |
||||
"""Get storage z3 object given storage name and the state. |
||||
|
||||
:param storage: storage name example: storage_0 |
||||
:param state: state to retrieve the variable from |
||||
:return: z3 object representing storage |
||||
""" |
||||
index = int(re.search("[0-9]+", storage).group()) |
||||
try: |
||||
return state.environment.active_account.storage[index] |
||||
except KeyError: |
||||
return None |
||||
|
||||
def _can_change(self, constraints, variable): |
||||
"""Checks if the variable can change given some constraints. |
||||
|
||||
:param constraints: |
||||
:param variable: |
||||
:return: |
||||
""" |
||||
_constraints = copy.deepcopy(constraints) |
||||
try: |
||||
model = solver.get_model(_constraints) |
||||
except UnsatError: |
||||
return False |
||||
try: |
||||
initial_value = int(str(model.eval(variable, model_completion=True))) |
||||
return ( |
||||
self._try_constraints(constraints, [variable != initial_value]) |
||||
is not None |
||||
) |
||||
except AttributeError: |
||||
return False |
||||
|
||||
def _get_influencing_storages(self, call): |
||||
"""Examines a Call object and returns an iterator of all storages that |
||||
influence the call value or direction. |
||||
|
||||
:param call: |
||||
""" |
||||
state = call.state |
||||
node = call.node |
||||
|
||||
# Get relevant storages |
||||
to, value = call.to, call.value |
||||
storages = [] |
||||
if to.type == VarType.SYMBOLIC: |
||||
storages += self._dependent_on_storage(to.val) |
||||
if value.type == VarType.SYMBOLIC: |
||||
storages += self._dependent_on_storage(value.val) |
||||
|
||||
# See if they can change within the constraints of the node |
||||
for storage in storages: |
||||
variable = self._get_storage_variable(storage, state) |
||||
can_change = self._can_change(node.constraints, variable) |
||||
if can_change: |
||||
yield storage |
||||
|
||||
def _get_influencing_sstores(self, statespace, interesting_storages): |
||||
"""Gets sstore (state, node) tuples that write to interesting_storages. |
||||
|
||||
:param statespace: |
||||
:param interesting_storages: |
||||
""" |
||||
for sstore_state, node in self._get_states_with_opcode(statespace, "SSTORE"): |
||||
index, value = sstore_state.mstate.stack[-1], sstore_state.mstate.stack[-2] |
||||
try: |
||||
index = util.get_concrete_int(index) |
||||
except TypeError: |
||||
index = str(index) |
||||
if "storage_{}".format(index) not in interesting_storages: |
||||
continue |
||||
|
||||
yield sstore_state, node |
||||
|
||||
# TODO: remove |
||||
@staticmethod |
||||
def _try_constraints(constraints, new_constraints): |
||||
"""Tries new constraints. |
||||
|
||||
:param constraints: |
||||
:param new_constraints: |
||||
:return Model if satisfiable otherwise None |
||||
""" |
||||
_constraints = copy.deepcopy(constraints) |
||||
for constraint in new_constraints: |
||||
_constraints.append(copy.deepcopy(constraint)) |
||||
try: |
||||
model = solver.get_model(_constraints) |
||||
return model |
||||
except UnsatError: |
||||
return None |
||||
|
||||
|
||||
detector = TxOrderDependenceModule() |
@ -0,0 +1,21 @@ |
||||
""" Laser plugins |
||||
|
||||
This module contains everything to do with laser plugins |
||||
|
||||
Laser plugins are a way of extending laser's functionality without complicating the core business logic. |
||||
Different features that have been implemented in the form of plugins are: |
||||
- benchmarking |
||||
- path pruning |
||||
|
||||
Plugins also provide a way to implement optimisations outside of the mythril code base and to inject them. |
||||
The api that laser currently provides is still unstable and will probably change to suit our needs |
||||
as more plugins get developed. |
||||
|
||||
For the implementation of plugins the following modules are of interest: |
||||
- laser.plugins.plugin |
||||
- laser.plugins.signals |
||||
- laser.svm |
||||
|
||||
Which show the basic interfaces with which plugins are able to interact |
||||
""" |
||||
from mythril.laser.ethereum.plugins.signals import PluginSignal |
@ -0,0 +1,7 @@ |
||||
""" Plugin implementations |
||||
|
||||
This module contains the implementation of some features |
||||
|
||||
- benchmarking |
||||
- pruning |
||||
""" |
@ -0,0 +1,3 @@ |
||||
from mythril.laser.ethereum.plugins.implementations.coverage.coverage_plugin import ( |
||||
InstructionCoveragePlugin, |
||||
) |
@ -0,0 +1,98 @@ |
||||
from mythril.laser.ethereum.svm import LaserEVM |
||||
from mythril.laser.ethereum.plugins.plugin import LaserPlugin |
||||
from mythril.laser.ethereum.state.global_state import GlobalState |
||||
|
||||
from typing import Dict, Tuple, List |
||||
|
||||
import logging |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
|
||||
class InstructionCoveragePlugin(LaserPlugin): |
||||
"""InstructionCoveragePlugin |
||||
|
||||
This plugin measures the instruction coverage of mythril. |
||||
The instruction coverage is the ratio between the instructions that have been executed |
||||
and the total amount of instructions. |
||||
|
||||
Note that with lazy constraint solving enabled that this metric will be "unsound" as |
||||
reachability will not be considered for the calculation of instruction coverage. |
||||
|
||||
""" |
||||
|
||||
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): |
||||
"""Initializes the instruction coverage plugin |
||||
|
||||
Introduces hooks for each instruction |
||||
:param symbolic_vm: |
||||
:return: |
||||
""" |
||||
self.coverage = {} |
||||
self.initial_coverage = 0 |
||||
self.tx_id = 0 |
||||
|
||||
@symbolic_vm.laser_hook("stop_sym_exec") |
||||
def stop_sym_exec_hook(): |
||||
# Print results |
||||
for code, code_cov in self.coverage.items(): |
||||
cov_percentage = sum(code_cov[1]) / float(code_cov[0]) * 100 |
||||
|
||||
log.info( |
||||
"Achieved {:.2f}% coverage for code: {}".format( |
||||
cov_percentage, code |
||||
) |
||||
) |
||||
|
||||
@symbolic_vm.laser_hook("execute_state") |
||||
def execute_state_hook(global_state: GlobalState): |
||||
# Record coverage |
||||
code = global_state.environment.code.bytecode |
||||
|
||||
if code not in self.coverage.keys(): |
||||
number_of_instructions = len( |
||||
global_state.environment.code.instruction_list |
||||
) |
||||
self.coverage[code] = ( |
||||
number_of_instructions, |
||||
[False] * number_of_instructions, |
||||
) |
||||
|
||||
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) |
@ -0,0 +1,23 @@ |
||||
from mythril.laser.ethereum.svm import LaserEVM |
||||
|
||||
|
||||
class LaserPlugin: |
||||
""" Base class for laser plugins |
||||
|
||||
Functionality in laser that the symbolic execution process does not need to depend on |
||||
can be implemented in the form of a laser plugin. |
||||
|
||||
Laser plugins implement the function initialize(symbolic_vm) which is called with the laser virtual machine |
||||
when they are loaded. |
||||
Regularly a plugin will introduce several hooks into laser in this function |
||||
|
||||
Plugins can direct actions by raising Signals defined in mythril.laser.ethereum.plugins.signals |
||||
For example, a pruning plugin might raise the PluginSkipWorldState signal. |
||||
""" |
||||
|
||||
def initialize(self, symbolic_vm: LaserEVM) -> None: |
||||
""" Initializes this plugin on the symbolic virtual machine |
||||
|
||||
:param symbolic_vm: symbolic virtual machine to initialize the laser plugin on |
||||
""" |
||||
raise NotImplementedError |
@ -0,0 +1,32 @@ |
||||
from mythril.laser.ethereum.plugins.plugin import LaserPlugin |
||||
|
||||
|
||||
class PluginFactory: |
||||
""" The plugin factory constructs the plugins provided with laser """ |
||||
|
||||
@staticmethod |
||||
def build_benchmark_plugin(name: str) -> LaserPlugin: |
||||
""" Creates an instance of the benchmark plugin with the given name """ |
||||
from mythril.laser.ethereum.plugins.implementations.benchmark import ( |
||||
BenchmarkPlugin, |
||||
) |
||||
|
||||
return BenchmarkPlugin(name) |
||||
|
||||
@staticmethod |
||||
def build_mutation_pruner_plugin() -> LaserPlugin: |
||||
""" Creates an instance of the mutation pruner plugin""" |
||||
from mythril.laser.ethereum.plugins.implementations.mutation_pruner import ( |
||||
MutationPruner, |
||||
) |
||||
|
||||
return MutationPruner() |
||||
|
||||
@staticmethod |
||||
def build_instruction_coverage_plugin() -> LaserPlugin: |
||||
""" Creates an instance of the instruction coverage plugin""" |
||||
from mythril.laser.ethereum.plugins.implementations.coverage import ( |
||||
InstructionCoveragePlugin, |
||||
) |
||||
|
||||
return InstructionCoveragePlugin() |
@ -0,0 +1,38 @@ |
||||
from mythril.laser.ethereum.svm import LaserEVM |
||||
from mythril.laser.ethereum.plugins.plugin import LaserPlugin |
||||
|
||||
from typing import List |
||||
import logging |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
|
||||
class LaserPluginLoader: |
||||
""" |
||||
The LaserPluginLoader is used to abstract the logic relating to plugins. |
||||
Components outside of laser thus don't have to be aware of the interface that plugins provide |
||||
""" |
||||
|
||||
def __init__(self, symbolic_vm: LaserEVM) -> None: |
||||
""" Initializes the plugin loader |
||||
|
||||
:param symbolic_vm: symbolic virtual machine to load plugins for |
||||
""" |
||||
self.symbolic_vm = symbolic_vm |
||||
self.laser_plugins = [] # type: List[LaserPlugin] |
||||
|
||||
def load(self, laser_plugin: LaserPlugin) -> None: |
||||
""" Loads the plugin |
||||
|
||||
: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) |
||||
self.laser_plugins.append(laser_plugin) |
||||
|
||||
def is_enabled(self, laser_plugin: LaserPlugin) -> bool: |
||||
""" Returns whether the plugin is loaded in the symbolic_vm |
||||
|
||||
:param laser_plugin: plugin that will be checked |
||||
""" |
||||
return laser_plugin in self.laser_plugins |
@ -1,454 +0,0 @@ |
||||
"""This module implements classes needed to perform taint analysis.""" |
||||
import copy |
||||
import logging |
||||
from typing import List, Tuple, Union |
||||
|
||||
import mythril.laser.ethereum.util as helper |
||||
from mythril.analysis.symbolic import SymExecWrapper |
||||
from mythril.laser.ethereum.cfg import JumpType, Node |
||||
from mythril.laser.ethereum.state.environment import Environment |
||||
from mythril.laser.ethereum.state.global_state import GlobalState |
||||
from mythril.laser.smt import Expression |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
|
||||
class TaintRecord: |
||||
"""TaintRecord contains tainting information for a specific (state, node) |
||||
the information specifies the taint status before executing the operation |
||||
belonging to the state.""" |
||||
|
||||
def __init__(self): |
||||
"""Builds a taint record.""" |
||||
self.stack = [] |
||||
self.memory = {} |
||||
self.storage = {} |
||||
self.states = [] |
||||
|
||||
def stack_tainted(self, index: int) -> Union[bool, None]: |
||||
"""Returns taint value of stack element at index. |
||||
|
||||
:param index: |
||||
:return: |
||||
""" |
||||
if index < len(self.stack): |
||||
return self.stack[index] |
||||
return None |
||||
|
||||
def memory_tainted(self, index: int) -> bool: |
||||
"""Returns taint value of memory element at index. |
||||
|
||||
:param index: |
||||
:return: |
||||
""" |
||||
if index in self.memory.keys(): |
||||
return self.memory[index] |
||||
return False |
||||
|
||||
def storage_tainted(self, index: int) -> bool: |
||||
"""Returns taint value of storage element at index. |
||||
|
||||
:param index: |
||||
:return: |
||||
""" |
||||
if index in self.storage.keys(): |
||||
return self.storage[index] |
||||
return False |
||||
|
||||
def add_state(self, state: GlobalState) -> None: |
||||
"""Adds state with this taint record. |
||||
|
||||
:param state: |
||||
""" |
||||
self.states.append(state) |
||||
|
||||
def clone(self) -> "TaintRecord": |
||||
"""Clones this record. |
||||
|
||||
:return: |
||||
""" |
||||
clone = TaintRecord() |
||||
clone.stack = copy.deepcopy(self.stack) |
||||
clone.memory = copy.deepcopy(self.memory) |
||||
clone.storage = copy.deepcopy(self.storage) |
||||
return clone |
||||
|
||||
|
||||
class TaintResult: |
||||
"""Taint analysis result obtained after having ran the taint runner.""" |
||||
|
||||
def __init__(self): |
||||
"""Create a new tains result.""" |
||||
self.records = [] |
||||
|
||||
def check(self, state: GlobalState, stack_index: int) -> Union[bool, None]: |
||||
"""Checks if stack variable is tainted, before executing the |
||||
instruction. |
||||
|
||||
:param state: state to check variable in |
||||
:param stack_index: index of stack variable |
||||
:return: tainted |
||||
""" |
||||
record = self._try_get_record(state) |
||||
if record is None: |
||||
return None |
||||
return record.stack_tainted(stack_index) |
||||
|
||||
def add_records(self, records: List[TaintRecord]) -> None: |
||||
"""Adds records to this taint result. |
||||
|
||||
:param records: |
||||
""" |
||||
self.records += records |
||||
|
||||
def _try_get_record(self, state: GlobalState) -> Union[TaintRecord, None]: |
||||
"""Finds record belonging to the state. |
||||
|
||||
:param state: |
||||
:return: |
||||
""" |
||||
for record in self.records: |
||||
if state in record.states: |
||||
return record |
||||
return None |
||||
|
||||
|
||||
class TaintRunner: |
||||
"""Taint runner, is able to run taint analysis on symbolic execution |
||||
result.""" |
||||
|
||||
@staticmethod |
||||
def execute( |
||||
statespace: SymExecWrapper, node: Node, state: GlobalState, initial_stack=None |
||||
) -> TaintResult: |
||||
"""Runs taint analysis on the statespace. |
||||
|
||||
:param initial_stack: |
||||
:param statespace: symbolic statespace to run taint analysis on |
||||
:param node: taint introduction node |
||||
:param state: taint introduction state |
||||
:return: TaintResult object containing analysis results |
||||
""" |
||||
if initial_stack is None: |
||||
initial_stack = [] |
||||
result = TaintResult() |
||||
transaction_stack_length = len(node.states[0].transaction_stack) |
||||
# Build initial current_node |
||||
init_record = TaintRecord() |
||||
init_record.stack = initial_stack |
||||
|
||||
state_index = node.states.index(state) |
||||
|
||||
# List of (Node, TaintRecord, index) |
||||
current_nodes = [(node, init_record, state_index)] |
||||
environment = node.states[0].environment |
||||
|
||||
for node, record, index in current_nodes: |
||||
records = TaintRunner.execute_node(node, record, index) |
||||
|
||||
result.add_records(records) |
||||
if len(records) == 0: # continue if there is no record to work on |
||||
continue |
||||
children = TaintRunner.children( |
||||
node, statespace, environment, transaction_stack_length |
||||
) |
||||
for child in children: |
||||
current_nodes.append((child, records[-1], 0)) |
||||
return result |
||||
|
||||
@staticmethod |
||||
def children( |
||||
node: Node, |
||||
statespace: SymExecWrapper, |
||||
environment: Environment, |
||||
transaction_stack_length: int, |
||||
) -> List[Node]: |
||||
""" |
||||
|
||||
:param node: |
||||
:param statespace: |
||||
:param environment: |
||||
:param transaction_stack_length: |
||||
:return: |
||||
""" |
||||
direct_children = [ |
||||
statespace.nodes[edge.node_to] |
||||
for edge in statespace.edges |
||||
if edge.node_from == node.uid and edge.type != JumpType.Transaction |
||||
] |
||||
children = [] |
||||
for child in direct_children: |
||||
if all( |
||||
len(state.transaction_stack) == transaction_stack_length |
||||
for state in child.states |
||||
): |
||||
children.append(child) |
||||
elif all( |
||||
len(state.transaction_stack) > transaction_stack_length |
||||
for state in child.states |
||||
): |
||||
children += TaintRunner.children( |
||||
child, statespace, environment, transaction_stack_length |
||||
) |
||||
return children |
||||
|
||||
@staticmethod |
||||
def execute_node( |
||||
node: Node, last_record: TaintRecord, state_index=0 |
||||
) -> List[TaintRecord]: |
||||
"""Runs taint analysis on a given node. |
||||
|
||||
:param node: node to analyse |
||||
:param last_record: last taint record to work from |
||||
:param state_index: state index to start from |
||||
:return: List of taint records linked to the states in this node |
||||
""" |
||||
records = [last_record] |
||||
for index in range(state_index, len(node.states)): |
||||
current_state = node.states[index] |
||||
records.append(TaintRunner.execute_state(records[-1], current_state)) |
||||
return records[1:] |
||||
|
||||
@staticmethod |
||||
def execute_state(record: TaintRecord, state: GlobalState) -> TaintRecord: |
||||
""" |
||||
|
||||
:param record: |
||||
:param state: |
||||
:return: |
||||
""" |
||||
assert len(state.mstate.stack) == len(record.stack) |
||||
""" Runs taint analysis on a state """ |
||||
record.add_state(state) |
||||
new_record = record.clone() |
||||
|
||||
# Apply Change |
||||
op = state.get_current_instruction()["opcode"] |
||||
|
||||
if op in TaintRunner.stack_taint_table.keys(): |
||||
mutator = TaintRunner.stack_taint_table[op] |
||||
TaintRunner.mutate_stack(new_record, mutator) |
||||
elif op.startswith("PUSH"): |
||||
TaintRunner.mutate_push(op, new_record) |
||||
elif op.startswith("DUP"): |
||||
TaintRunner.mutate_dup(op, new_record) |
||||
elif op.startswith("SWAP"): |
||||
TaintRunner.mutate_swap(op, new_record) |
||||
elif op is "MLOAD": |
||||
TaintRunner.mutate_mload(new_record, state.mstate.stack[-1]) |
||||
elif op.startswith("MSTORE"): |
||||
TaintRunner.mutate_mstore(new_record, state.mstate.stack[-1]) |
||||
elif op is "SLOAD": |
||||
TaintRunner.mutate_sload(new_record, state.mstate.stack[-1]) |
||||
elif op is "SSTORE": |
||||
TaintRunner.mutate_sstore(new_record, state.mstate.stack[-1]) |
||||
elif op.startswith("LOG"): |
||||
TaintRunner.mutate_log(new_record, op) |
||||
elif op in ("CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"): |
||||
TaintRunner.mutate_call(new_record, op) |
||||
else: |
||||
log.debug("Unknown operation encountered: {}".format(op)) |
||||
|
||||
return new_record |
||||
|
||||
@staticmethod |
||||
def mutate_stack(record: TaintRecord, mutator: Tuple[int, int]) -> None: |
||||
""" |
||||
|
||||
:param record: |
||||
:param mutator: |
||||
""" |
||||
pop, push = mutator |
||||
|
||||
values = [] |
||||
for i in range(pop): |
||||
values.append(record.stack.pop()) |
||||
|
||||
taint = any(values) |
||||
|
||||
for i in range(push): |
||||
record.stack.append(taint) |
||||
|
||||
@staticmethod |
||||
def mutate_push(op: str, record: TaintRecord) -> None: |
||||
""" |
||||
|
||||
:param op: |
||||
:param record: |
||||
""" |
||||
TaintRunner.mutate_stack(record, (0, 1)) |
||||
|
||||
@staticmethod |
||||
def mutate_dup(op: str, record: TaintRecord) -> None: |
||||
""" |
||||
|
||||
:param op: |
||||
:param record: |
||||
""" |
||||
depth = int(op[3:]) |
||||
index = len(record.stack) - depth |
||||
record.stack.append(record.stack[index]) |
||||
|
||||
@staticmethod |
||||
def mutate_swap(op: str, record: TaintRecord) -> None: |
||||
""" |
||||
|
||||
:param op: |
||||
:param record: |
||||
""" |
||||
depth = int(op[4:]) |
||||
l = len(record.stack) - 1 |
||||
i = l - depth |
||||
record.stack[l], record.stack[i] = record.stack[i], record.stack[l] |
||||
|
||||
@staticmethod |
||||
def mutate_mload(record: TaintRecord, op0: Expression) -> None: |
||||
""" |
||||
|
||||
:param record: |
||||
:param op0: |
||||
:return: |
||||
""" |
||||
_ = record.stack.pop() |
||||
try: |
||||
index = helper.get_concrete_int(op0) |
||||
except TypeError: |
||||
log.debug("Can't MLOAD taint track symbolically") |
||||
record.stack.append(False) |
||||
return |
||||
|
||||
record.stack.append(record.memory_tainted(index)) |
||||
|
||||
@staticmethod |
||||
def mutate_mstore(record: TaintRecord, op0: Expression) -> None: |
||||
""" |
||||
|
||||
:param record: |
||||
:param op0: |
||||
:return: |
||||
""" |
||||
_, value_taint = record.stack.pop(), record.stack.pop() |
||||
try: |
||||
index = helper.get_concrete_int(op0) |
||||
except TypeError: |
||||
log.debug("Can't mstore taint track symbolically") |
||||
return |
||||
|
||||
record.memory[index] = value_taint |
||||
|
||||
@staticmethod |
||||
def mutate_sload(record: TaintRecord, op0: Expression) -> None: |
||||
""" |
||||
|
||||
:param record: |
||||
:param op0: |
||||
:return: |
||||
""" |
||||
_ = record.stack.pop() |
||||
try: |
||||
index = helper.get_concrete_int(op0) |
||||
except TypeError: |
||||
log.debug("Can't MLOAD taint track symbolically") |
||||
record.stack.append(False) |
||||
return |
||||
|
||||
record.stack.append(record.storage_tainted(index)) |
||||
|
||||
@staticmethod |
||||
def mutate_sstore(record: TaintRecord, op0: Expression) -> None: |
||||
""" |
||||
|
||||
:param record: |
||||
:param op0: |
||||
:return: |
||||
""" |
||||
_, value_taint = record.stack.pop(), record.stack.pop() |
||||
try: |
||||
index = helper.get_concrete_int(op0) |
||||
except TypeError: |
||||
log.debug("Can't mstore taint track symbolically") |
||||
return |
||||
|
||||
record.storage[index] = value_taint |
||||
|
||||
@staticmethod |
||||
def mutate_log(record: TaintRecord, op: str) -> None: |
||||
""" |
||||
|
||||
:param record: |
||||
:param op: |
||||
""" |
||||
depth = int(op[3:]) |
||||
for _ in range(depth + 2): |
||||
record.stack.pop() |
||||
|
||||
@staticmethod |
||||
def mutate_call(record: TaintRecord, op: str) -> None: |
||||
""" |
||||
|
||||
:param record: |
||||
:param op: |
||||
""" |
||||
pops = 6 |
||||
if op in ("CALL", "CALLCODE"): |
||||
pops += 1 |
||||
for _ in range(pops): |
||||
record.stack.pop() |
||||
|
||||
record.stack.append(False) |
||||
|
||||
stack_taint_table = { |
||||
# instruction: (taint source, taint target) |
||||
"POP": (1, 0), |
||||
"ADD": (2, 1), |
||||
"MUL": (2, 1), |
||||
"SUB": (2, 1), |
||||
"AND": (2, 1), |
||||
"OR": (2, 1), |
||||
"XOR": (2, 1), |
||||
"NOT": (1, 1), |
||||
"BYTE": (2, 1), |
||||
"DIV": (2, 1), |
||||
"MOD": (2, 1), |
||||
"SDIV": (2, 1), |
||||
"SMOD": (2, 1), |
||||
"ADDMOD": (3, 1), |
||||
"MULMOD": (3, 1), |
||||
"EXP": (2, 1), |
||||
"SIGNEXTEND": (2, 1), |
||||
"LT": (2, 1), |
||||
"GT": (2, 1), |
||||
"SLT": (2, 1), |
||||
"SGT": (2, 1), |
||||
"EQ": (2, 1), |
||||
"ISZERO": (1, 1), |
||||
"CALLVALUE": (0, 1), |
||||
"CALLDATALOAD": (1, 1), |
||||
"CALLDATACOPY": (3, 0), # todo |
||||
"CALLDATASIZE": (0, 1), |
||||
"ADDRESS": (0, 1), |
||||
"BALANCE": (1, 1), |
||||
"ORIGIN": (0, 1), |
||||
"CALLER": (0, 1), |
||||
"CODESIZE": (0, 1), |
||||
"SHA3": (2, 1), |
||||
"GASPRICE": (0, 1), |
||||
"CODECOPY": (3, 0), |
||||
"EXTCODESIZE": (1, 1), |
||||
"EXTCODECOPY": (4, 0), |
||||
"RETURNDATASIZE": (0, 1), |
||||
"BLOCKHASH": (1, 1), |
||||
"COINBASE": (0, 1), |
||||
"TIMESTAMP": (0, 1), |
||||
"NUMBER": (0, 1), |
||||
"DIFFICULTY": (0, 1), |
||||
"GASLIMIT": (0, 1), |
||||
"JUMP": (1, 0), |
||||
"JUMPI": (2, 0), |
||||
"PC": (0, 1), |
||||
"MSIZE": (0, 1), |
||||
"GAS": (0, 1), |
||||
"CREATE": (3, 1), |
||||
"CREATE2": (4, 1), |
||||
"RETURN": (2, 0), |
||||
} |
@ -0,0 +1,4 @@ |
||||
from .mythril_disassembler import MythrilDisassembler |
||||
from .mythril_analyzer import MythrilAnalyzer |
||||
from .mythril_config import MythrilConfig |
||||
from .mythril_leveldb import MythrilLevelDB |
@ -0,0 +1,173 @@ |
||||
#!/usr/bin/env python3 |
||||
# -*- coding: utf-8 -*- |
||||
|
||||
import logging |
||||
import traceback |
||||
from typing import Optional, List |
||||
|
||||
from . import MythrilDisassembler |
||||
from mythril.support.source_support import Source |
||||
from mythril.support.loader import DynLoader |
||||
from mythril.analysis.symbolic import SymExecWrapper |
||||
from mythril.analysis.callgraph import generate_graph |
||||
from mythril.analysis.traceexplore import get_serializable_statespace |
||||
from mythril.analysis.security import fire_lasers, retrieve_callback_issues |
||||
from mythril.analysis.report import Report, Issue |
||||
from mythril.ethereum.evmcontract import EVMContract |
||||
from mythril.laser.smt import SolverStatistics |
||||
from mythril.support.start_time import StartTime |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
|
||||
class MythrilAnalyzer: |
||||
""" |
||||
The Mythril Analyzer class |
||||
Responsible for the analysis of the smart contracts |
||||
""" |
||||
|
||||
def __init__( |
||||
self, |
||||
disassembler: MythrilDisassembler, |
||||
requires_dynld: bool = False, |
||||
onchain_storage_access: bool = True, |
||||
strategy: str = "dfs", |
||||
address: Optional[str] = None, |
||||
max_depth: Optional[int] = None, |
||||
execution_timeout: Optional[int] = None, |
||||
create_timeout: Optional[int] = None, |
||||
enable_iprof: bool = False, |
||||
): |
||||
""" |
||||
|
||||
:param disassembler: The MythrilDisassembler class |
||||
:param requires_dynld: whether dynamic loading should be done or not |
||||
:param onchain_storage_access: Whether onchain access should be done or not |
||||
""" |
||||
self.eth = disassembler.eth |
||||
self.contracts = disassembler.contracts or [] # type: List[EVMContract] |
||||
self.enable_online_lookup = disassembler.enable_online_lookup |
||||
self.dynld = requires_dynld |
||||
self.onchain_storage_access = onchain_storage_access |
||||
self.strategy = strategy |
||||
self.address = address |
||||
self.max_depth = max_depth |
||||
self.execution_timeout = execution_timeout |
||||
self.create_timeout = create_timeout |
||||
self.enable_iprof = enable_iprof |
||||
|
||||
def dump_statespace(self, contract: EVMContract = None) -> str: |
||||
""" |
||||
Returns serializable statespace of the contract |
||||
:param contract: The Contract on which the analysis should be done |
||||
:return: The serialized state space |
||||
""" |
||||
sym = SymExecWrapper( |
||||
contract or self.contracts[0], |
||||
self.address, |
||||
self.strategy, |
||||
dynloader=DynLoader( |
||||
self.eth, |
||||
storage_loading=self.onchain_storage_access, |
||||
contract_loading=self.dynld, |
||||
), |
||||
max_depth=self.max_depth, |
||||
execution_timeout=self.execution_timeout, |
||||
create_timeout=self.create_timeout, |
||||
enable_iprof=self.enable_iprof, |
||||
) |
||||
|
||||
return get_serializable_statespace(sym) |
||||
|
||||
def graph_html( |
||||
self, |
||||
contract: EVMContract = None, |
||||
enable_physics: bool = False, |
||||
phrackify: bool = False, |
||||
transaction_count: Optional[int] = None, |
||||
) -> str: |
||||
""" |
||||
|
||||
:param contract: The Contract on which the analysis should be done |
||||
:param enable_physics: If true then enables the graph physics simulation |
||||
:param phrackify: If true generates Phrack-style call graph |
||||
:param transaction_count: The amount of transactions to be executed |
||||
:return: The generated graph in html format |
||||
""" |
||||
sym = SymExecWrapper( |
||||
contract or self.contracts[0], |
||||
self.address, |
||||
self.strategy, |
||||
dynloader=DynLoader( |
||||
self.eth, |
||||
storage_loading=self.onchain_storage_access, |
||||
contract_loading=self.dynld, |
||||
), |
||||
max_depth=self.max_depth, |
||||
execution_timeout=self.execution_timeout, |
||||
transaction_count=transaction_count, |
||||
create_timeout=self.create_timeout, |
||||
enable_iprof=self.enable_iprof, |
||||
) |
||||
return generate_graph(sym, physics=enable_physics, phrackify=phrackify) |
||||
|
||||
def fire_lasers( |
||||
self, |
||||
modules: Optional[List[str]] = None, |
||||
verbose_report: bool = False, |
||||
transaction_count: Optional[int] = None, |
||||
) -> Report: |
||||
""" |
||||
:param modules: The analysis modules which should be executed |
||||
:param verbose_report: Gives out the transaction sequence of the vulnerability |
||||
:param transaction_count: The amount of transactions to be executed |
||||
:return: The Report class which contains the all the issues/vulnerabilities |
||||
""" |
||||
all_issues = [] # type: List[Issue] |
||||
SolverStatistics().enabled = True |
||||
exceptions = [] |
||||
for contract in self.contracts: |
||||
StartTime() # Reinitialize start time for new contracts |
||||
try: |
||||
sym = SymExecWrapper( |
||||
contract, |
||||
self.address, |
||||
self.strategy, |
||||
dynloader=DynLoader( |
||||
self.eth, |
||||
storage_loading=self.onchain_storage_access, |
||||
contract_loading=self.dynld, |
||||
), |
||||
max_depth=self.max_depth, |
||||
execution_timeout=self.execution_timeout, |
||||
create_timeout=self.create_timeout, |
||||
transaction_count=transaction_count, |
||||
modules=modules, |
||||
compulsory_statespace=False, |
||||
enable_iprof=self.enable_iprof, |
||||
) |
||||
issues = fire_lasers(sym, modules) |
||||
except KeyboardInterrupt: |
||||
log.critical("Keyboard Interrupt") |
||||
issues = retrieve_callback_issues(modules) |
||||
except Exception: |
||||
log.critical( |
||||
"Exception occurred, aborting analysis. Please report this issue to the Mythril GitHub page.\n" |
||||
+ traceback.format_exc() |
||||
) |
||||
issues = retrieve_callback_issues(modules) |
||||
exceptions.append(traceback.format_exc()) |
||||
for issue in issues: |
||||
issue.add_code_info(contract) |
||||
|
||||
all_issues += issues |
||||
log.info("Solver statistics: \n{}".format(str(SolverStatistics()))) |
||||
|
||||
source_data = Source() |
||||
source_data.get_source_from_contracts_list(self.contracts) |
||||
# Finally, output the results |
||||
report = Report(verbose_report, source_data, exceptions=exceptions) |
||||
for issue in all_issues: |
||||
report.append_issue(issue) |
||||
|
||||
return report |
@ -0,0 +1,226 @@ |
||||
import codecs |
||||
import logging |
||||
import os |
||||
import platform |
||||
import re |
||||
|
||||
from pathlib import Path |
||||
from shutil import copyfile |
||||
from configparser import ConfigParser |
||||
from typing import Optional |
||||
|
||||
from mythril.exceptions import CriticalError |
||||
from mythril.ethereum.interface.rpc.client import EthJsonRpc |
||||
from mythril.ethereum.interface.leveldb.client import EthLevelDB |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
|
||||
class MythrilConfig: |
||||
""" |
||||
The Mythril Analyzer class |
||||
Responsible for setup of the mythril environment |
||||
""" |
||||
|
||||
def __init__(self): |
||||
self.mythril_dir = self._init_mythril_dir() |
||||
self.config_path = os.path.join(self.mythril_dir, "config.ini") |
||||
self.leveldb_dir = None |
||||
self._init_config() |
||||
self.eth = None # type: Optional[EthJsonRpc] |
||||
self.eth_db = None # type: Optional[EthLevelDB] |
||||
|
||||
@staticmethod |
||||
def _init_mythril_dir() -> str: |
||||
""" |
||||
Initializes the mythril dir and config.ini file |
||||
:return: The mythril dir's path |
||||
""" |
||||
|
||||
try: |
||||
mythril_dir = os.environ["MYTHRIL_DIR"] |
||||
except KeyError: |
||||
mythril_dir = os.path.join(os.path.expanduser("~"), ".mythril") |
||||
|
||||
if not os.path.exists(mythril_dir): |
||||
# Initialize data directory |
||||
log.info("Creating mythril data directory") |
||||
os.mkdir(mythril_dir) |
||||
|
||||
db_path = str(Path(mythril_dir) / "signatures.db") |
||||
if not os.path.exists(db_path): |
||||
# if the default mythril dir doesn't contain a signature DB |
||||
# initialize it with the default one from the project root |
||||
asset_dir = Path(__file__).parent.parent / "support" / "assets" |
||||
copyfile(str(asset_dir / "signatures.db"), db_path) |
||||
|
||||
return mythril_dir |
||||
|
||||
def _init_config(self): |
||||
"""If no config file exists, create it and add default options. |
||||
Defaults:- |
||||
- Default LevelDB path is specified based on OS |
||||
- dynamic loading is set to infura by default in the file |
||||
This function also sets self.leveldb_dir path |
||||
""" |
||||
|
||||
leveldb_default_path = self._get_default_leveldb_path() |
||||
|
||||
if not os.path.exists(self.config_path): |
||||
log.info("No config file found. Creating default: " + self.config_path) |
||||
open(self.config_path, "a").close() |
||||
|
||||
config = ConfigParser(allow_no_value=True) |
||||
|
||||
config.optionxform = str |
||||
config.read(self.config_path, "utf-8") |
||||
if "defaults" not in config.sections(): |
||||
self._add_default_options(config) |
||||
|
||||
if not config.has_option("defaults", "leveldb_dir"): |
||||
self._add_leveldb_option(config, leveldb_default_path) |
||||
|
||||
if not config.has_option("defaults", "dynamic_loading"): |
||||
self._add_dynamic_loading_option(config) |
||||
|
||||
with codecs.open(self.config_path, "w", "utf-8") as fp: |
||||
config.write(fp) |
||||
|
||||
leveldb_dir = config.get( |
||||
"defaults", "leveldb_dir", fallback=leveldb_default_path |
||||
) |
||||
self.leveldb_dir = os.path.expanduser(leveldb_dir) |
||||
|
||||
@staticmethod |
||||
def _get_default_leveldb_path() -> str: |
||||
""" |
||||
Returns the LevelDB path |
||||
:return: The LevelDB path |
||||
""" |
||||
system = platform.system().lower() |
||||
leveldb_fallback_dir = os.path.expanduser("~") |
||||
if system.startswith("darwin"): |
||||
leveldb_fallback_dir = os.path.join( |
||||
leveldb_fallback_dir, "Library", "Ethereum" |
||||
) |
||||
elif system.startswith("windows"): |
||||
leveldb_fallback_dir = os.path.join( |
||||
leveldb_fallback_dir, "AppData", "Roaming", "Ethereum" |
||||
) |
||||
else: |
||||
leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, ".ethereum") |
||||
return os.path.join(leveldb_fallback_dir, "geth", "chaindata") |
||||
|
||||
@staticmethod |
||||
def _add_default_options(config: ConfigParser) -> None: |
||||
""" |
||||
Adds defaults option to config.ini |
||||
:param config: The config file object |
||||
:return: None |
||||
""" |
||||
config.add_section("defaults") |
||||
|
||||
@staticmethod |
||||
def _add_leveldb_option(config: ConfigParser, leveldb_fallback_dir: str) -> None: |
||||
""" |
||||
Sets a default leveldb path in .mythril/config.ini file |
||||
:param config: The config file object |
||||
:param leveldb_fallback_dir: The leveldb dir to use by default for searches |
||||
:return: None |
||||
""" |
||||
config.set("defaults", "#Default chaindata locations:", "") |
||||
config.set("defaults", "#– Mac: ~/Library/Ethereum/geth/chaindata", "") |
||||
config.set("defaults", "#– Linux: ~/.ethereum/geth/chaindata", "") |
||||
config.set( |
||||
"defaults", |
||||
"#– Windows: %USERPROFILE%\\AppData\\Roaming\\Ethereum\\geth\\chaindata", |
||||
"", |
||||
) |
||||
config.set("defaults", "leveldb_dir", leveldb_fallback_dir) |
||||
|
||||
@staticmethod |
||||
def _add_dynamic_loading_option(config: ConfigParser) -> None: |
||||
""" |
||||
Sets the dynamic loading config option in .mythril/config.ini file |
||||
:param config: The config file object |
||||
:return: None |
||||
""" |
||||
config.set( |
||||
"defaults", "#– To connect to Infura use dynamic_loading: infura", "" |
||||
) |
||||
config.set( |
||||
"defaults", |
||||
"#– To connect to Rpc use " |
||||
"dynamic_loading: HOST:PORT / ganache / infura-[network_name]", |
||||
"", |
||||
) |
||||
config.set( |
||||
"defaults", "#– To connect to local host use dynamic_loading: localhost", "" |
||||
) |
||||
config.set("defaults", "dynamic_loading", "infura") |
||||
|
||||
def set_api_leveldb(self, leveldb_path: str) -> None: |
||||
""" |
||||
""" |
||||
self.eth_db = EthLevelDB(leveldb_path) |
||||
|
||||
def set_api_rpc_infura(self) -> None: |
||||
"""Set the RPC mode to INFURA on Mainnet.""" |
||||
log.info("Using INFURA Main Net for RPC queries") |
||||
self.eth = EthJsonRpc("mainnet.infura.io", 443, True) |
||||
|
||||
def set_api_rpc(self, rpc: str = None, rpctls: bool = False) -> None: |
||||
""" |
||||
Sets the RPC mode to either of ganache or infura |
||||
:param rpc: either of the strings - ganache, infura-mainnet, infura-rinkeby, infura-kovan, infura-ropsten |
||||
""" |
||||
if rpc == "ganache": |
||||
rpcconfig = ("localhost", 8545, False) |
||||
else: |
||||
m = re.match(r"infura-(.*)", rpc) |
||||
if m and m.group(1) in ["mainnet", "rinkeby", "kovan", "ropsten"]: |
||||
rpcconfig = (m.group(1) + ".infura.io", 443, True) |
||||
else: |
||||
try: |
||||
host, port = rpc.split(":") |
||||
rpcconfig = (host, int(port), rpctls) |
||||
except ValueError: |
||||
raise CriticalError( |
||||
"Invalid RPC argument, use 'ganache', 'infura-[network]' or 'HOST:PORT'" |
||||
) |
||||
|
||||
if rpcconfig: |
||||
log.info("Using RPC settings: %s" % str(rpcconfig)) |
||||
self.eth = EthJsonRpc(rpcconfig[0], int(rpcconfig[1]), rpcconfig[2]) |
||||
else: |
||||
raise CriticalError("Invalid RPC settings, check help for details.") |
||||
|
||||
def set_api_rpc_localhost(self) -> None: |
||||
"""Set the RPC mode to a local instance.""" |
||||
log.info("Using default RPC settings: http://localhost:8545") |
||||
self.eth = EthJsonRpc("localhost", 8545) |
||||
|
||||
def set_api_from_config_path(self) -> None: |
||||
"""Set the RPC mode based on a given config file.""" |
||||
config = ConfigParser(allow_no_value=False) |
||||
# TODO: Remove this after this issue https://github.com/python/mypy/issues/2427 is closed |
||||
config.optionxform = str # type:ignore |
||||
config.read(self.config_path, "utf-8") |
||||
if config.has_option("defaults", "dynamic_loading"): |
||||
dynamic_loading = config.get("defaults", "dynamic_loading") |
||||
else: |
||||
dynamic_loading = "infura" |
||||
self._set_rpc(dynamic_loading) |
||||
|
||||
def _set_rpc(self, rpc_type: str) -> None: |
||||
""" |
||||
Sets rpc based on the type |
||||
:param rpc_type: The type of connection: like infura, ganache, localhost |
||||
:return: |
||||
""" |
||||
if rpc_type == "infura": |
||||
self.set_api_rpc_infura() |
||||
elif rpc_type == "localhost": |
||||
self.set_api_rpc_localhost() |
||||
else: |
||||
self.set_api_rpc(rpc_type) |
@ -0,0 +1,303 @@ |
||||
import logging |
||||
import re |
||||
import solc |
||||
import os |
||||
|
||||
from ethereum import utils |
||||
from solc.exceptions import SolcError |
||||
from typing import List, Tuple, Optional |
||||
from mythril.ethereum import util |
||||
from mythril.ethereum.interface.rpc.client import EthJsonRpc |
||||
from mythril.exceptions import CriticalError, CompilerError, NoContractFoundError |
||||
from mythril.support import signatures |
||||
from mythril.support.truffle import analyze_truffle_project |
||||
from mythril.ethereum.evmcontract import EVMContract |
||||
from mythril.ethereum.interface.rpc.exceptions import ConnectionError |
||||
from mythril.solidity.soliditycontract import SolidityContract, get_contracts_from_file |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
|
||||
class MythrilDisassembler: |
||||
""" |
||||
The Mythril Disassembler class |
||||
Responsible for generating disassembly of smart contracts |
||||
- Compiles solc code from file/onchain |
||||
- Can also be used to access onchain storage data |
||||
""" |
||||
|
||||
def __init__( |
||||
self, |
||||
eth: Optional[EthJsonRpc] = None, |
||||
solc_version: str = None, |
||||
solc_args: str = None, |
||||
enable_online_lookup: bool = False, |
||||
) -> None: |
||||
self.solc_binary = self._init_solc_binary(solc_version) |
||||
self.solc_args = solc_args |
||||
self.eth = eth |
||||
self.enable_online_lookup = enable_online_lookup |
||||
self.sigs = signatures.SignatureDB(enable_online_lookup=enable_online_lookup) |
||||
self.contracts = [] # type: List[EVMContract] |
||||
|
||||
@staticmethod |
||||
def _init_solc_binary(version: str) -> str: |
||||
""" |
||||
Only proper versions are supported. No nightlies, commits etc (such as available in remix). |
||||
:param version: Version of the solc binary required |
||||
:return: The solc binary of the corresponding version |
||||
""" |
||||
|
||||
if not version: |
||||
return os.environ.get("SOLC") or "solc" |
||||
|
||||
# tried converting input to semver, seemed not necessary so just slicing for now |
||||
main_version = solc.main.get_solc_version_string() |
||||
main_version_number = re.match(r"\d+.\d+.\d+", main_version) |
||||
if main_version is None: |
||||
raise CriticalError( |
||||
"Could not extract solc version from string {}".format(main_version) |
||||
) |
||||
if version == main_version_number: |
||||
log.info("Given version matches installed version") |
||||
solc_binary = os.environ.get("SOLC") or "solc" |
||||
else: |
||||
solc_binary = util.solc_exists(version) |
||||
if solc_binary: |
||||
log.info("Given version is already installed") |
||||
else: |
||||
try: |
||||
solc.install_solc("v" + version) |
||||
solc_binary = util.solc_exists(version) |
||||
if not solc_binary: |
||||
raise SolcError() |
||||
except SolcError: |
||||
raise CriticalError( |
||||
"There was an error when trying to install the specified solc version" |
||||
) |
||||
|
||||
log.info("Setting the compiler to %s", solc_binary) |
||||
|
||||
return solc_binary |
||||
|
||||
def load_from_bytecode( |
||||
self, code: str, bin_runtime: bool = False, address: Optional[str] = None |
||||
) -> Tuple[str, EVMContract]: |
||||
""" |
||||
Returns the address and the contract class for the given bytecode |
||||
:param code: Bytecode |
||||
:param bin_runtime: Whether the code is runtime code or creation code |
||||
:param address: address of contract |
||||
:return: tuple(address, Contract class) |
||||
""" |
||||
if address is None: |
||||
address = util.get_indexed_address(0) |
||||
if bin_runtime: |
||||
self.contracts.append( |
||||
EVMContract( |
||||
code=code, |
||||
name="MAIN", |
||||
enable_online_lookup=self.enable_online_lookup, |
||||
) |
||||
) |
||||
else: |
||||
self.contracts.append( |
||||
EVMContract( |
||||
creation_code=code, |
||||
name="MAIN", |
||||
enable_online_lookup=self.enable_online_lookup, |
||||
) |
||||
) |
||||
return address, self.contracts[-1] # return address and contract object |
||||
|
||||
def load_from_address(self, address: str) -> Tuple[str, EVMContract]: |
||||
""" |
||||
Returns the contract given it's on chain address |
||||
:param address: The on chain address of a contract |
||||
:return: tuple(address, contract) |
||||
""" |
||||
if not re.match(r"0x[a-fA-F0-9]{40}", address): |
||||
raise CriticalError("Invalid contract address. Expected format is '0x...'.") |
||||
|
||||
try: |
||||
code = self.eth.eth_getCode(address) |
||||
except FileNotFoundError as e: |
||||
raise CriticalError("IPC error: " + str(e)) |
||||
except ConnectionError: |
||||
raise CriticalError( |
||||
"Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly." |
||||
) |
||||
except Exception as e: |
||||
raise CriticalError("IPC / RPC error: " + str(e)) |
||||
|
||||
if code == "0x" or code == "0x0": |
||||
raise CriticalError( |
||||
"Received an empty response from eth_getCode. Check the contract address and verify that you are on the correct chain." |
||||
) |
||||
else: |
||||
self.contracts.append( |
||||
EVMContract( |
||||
code, name=address, enable_online_lookup=self.enable_online_lookup |
||||
) |
||||
) |
||||
return address, self.contracts[-1] # return address and contract object |
||||
|
||||
def load_from_solidity( |
||||
self, solidity_files: List[str] |
||||
) -> Tuple[str, List[SolidityContract]]: |
||||
""" |
||||
|
||||
:param solidity_files: List of solidity_files |
||||
:return: tuple of address, contract class list |
||||
""" |
||||
address = util.get_indexed_address(0) |
||||
contracts = [] |
||||
for file in solidity_files: |
||||
if ":" in file: |
||||
file, contract_name = file.split(":") |
||||
else: |
||||
contract_name = None |
||||
|
||||
file = os.path.expanduser(file) |
||||
|
||||
try: |
||||
# import signatures from solidity source |
||||
self.sigs.import_solidity_file( |
||||
file, solc_binary=self.solc_binary, solc_args=self.solc_args |
||||
) |
||||
if contract_name is not None: |
||||
contract = SolidityContract( |
||||
input_file=file, |
||||
name=contract_name, |
||||
solc_args=self.solc_args, |
||||
solc_binary=self.solc_binary, |
||||
) |
||||
self.contracts.append(contract) |
||||
contracts.append(contract) |
||||
else: |
||||
for contract in get_contracts_from_file( |
||||
input_file=file, |
||||
solc_args=self.solc_args, |
||||
solc_binary=self.solc_binary, |
||||
): |
||||
self.contracts.append(contract) |
||||
contracts.append(contract) |
||||
|
||||
except FileNotFoundError: |
||||
raise CriticalError("Input file not found: " + file) |
||||
except CompilerError as e: |
||||
raise CriticalError(e) |
||||
except NoContractFoundError: |
||||
log.error( |
||||
"The file " + file + " does not contain a compilable contract." |
||||
) |
||||
|
||||
return address, contracts |
||||
|
||||
def analyze_truffle_project(self, *args, **kwargs) -> None: |
||||
""" |
||||
:param args: |
||||
:param kwargs: |
||||
:return: |
||||
""" |
||||
analyze_truffle_project( |
||||
self.sigs, *args, **kwargs |
||||
) # just passthru by passing signatures for now |
||||
|
||||
@staticmethod |
||||
def hash_for_function_signature(func: str) -> str: |
||||
""" |
||||
Return function names corresponding signature hash |
||||
:param func: function name |
||||
:return: Its hash signature |
||||
""" |
||||
return "0x%s" % utils.sha3(func)[:4].hex() |
||||
|
||||
def get_state_variable_from_storage( |
||||
self, address: str, params: Optional[List[str]] = None |
||||
) -> str: |
||||
""" |
||||
Get variables from the storage |
||||
:param address: The contract address |
||||
:param params: The list of parameters |
||||
param types: [position, length] or ["mapping", position, key1, key2, ... ] |
||||
or [position, length, array] |
||||
:return: The corresponding storage slot and its value |
||||
""" |
||||
params = params or [] |
||||
(position, length, mappings) = (0, 1, []) |
||||
try: |
||||
if params[0] == "mapping": |
||||
if len(params) < 3: |
||||
raise CriticalError("Invalid number of parameters.") |
||||
position = int(params[1]) |
||||
position_formatted = utils.zpad(utils.int_to_big_endian(position), 32) |
||||
for i in range(2, len(params)): |
||||
key = bytes(params[i], "utf8") |
||||
key_formatted = utils.rzpad(key, 32) |
||||
mappings.append( |
||||
int.from_bytes( |
||||
utils.sha3(key_formatted + position_formatted), |
||||
byteorder="big", |
||||
) |
||||
) |
||||
|
||||
length = len(mappings) |
||||
if length == 1: |
||||
position = mappings[0] |
||||
|
||||
else: |
||||
if len(params) >= 4: |
||||
raise CriticalError("Invalid number of parameters.") |
||||
|
||||
if len(params) >= 1: |
||||
position = int(params[0]) |
||||
if len(params) >= 2: |
||||
length = int(params[1]) |
||||
if len(params) == 3 and params[2] == "array": |
||||
position_formatted = utils.zpad( |
||||
utils.int_to_big_endian(position), 32 |
||||
) |
||||
position = int.from_bytes( |
||||
utils.sha3(position_formatted), byteorder="big" |
||||
) |
||||
|
||||
except ValueError: |
||||
raise CriticalError( |
||||
"Invalid storage index. Please provide a numeric value." |
||||
) |
||||
|
||||
outtxt = [] |
||||
|
||||
try: |
||||
if length == 1: |
||||
outtxt.append( |
||||
"{}: {}".format( |
||||
position, self.eth.eth_getStorageAt(address, position) |
||||
) |
||||
) |
||||
else: |
||||
if len(mappings) > 0: |
||||
for i in range(0, len(mappings)): |
||||
position = mappings[i] |
||||
outtxt.append( |
||||
"{}: {}".format( |
||||
hex(position), |
||||
self.eth.eth_getStorageAt(address, position), |
||||
) |
||||
) |
||||
else: |
||||
for i in range(position, position + length): |
||||
outtxt.append( |
||||
"{}: {}".format( |
||||
hex(i), self.eth.eth_getStorageAt(address, i) |
||||
) |
||||
) |
||||
except FileNotFoundError as e: |
||||
raise CriticalError("IPC error: " + str(e)) |
||||
except ConnectionError: |
||||
raise CriticalError( |
||||
"Could not connect to RPC server. " |
||||
"Make sure that your node is running and that RPC parameters are set correctly." |
||||
) |
||||
return "\n".join(outtxt) |
@ -0,0 +1,49 @@ |
||||
import re |
||||
from mythril.exceptions import CriticalError |
||||
|
||||
|
||||
class MythrilLevelDB: |
||||
""" |
||||
Class which does search operations on leveldb |
||||
There are two DBs |
||||
1) Key value pairs of hashes and it's corresponding address |
||||
2) The LevelDB Trie |
||||
""" |
||||
|
||||
def __init__(self, leveldb): |
||||
""" |
||||
|
||||
:param leveldb: Leveldb path |
||||
""" |
||||
self.leveldb = leveldb |
||||
|
||||
def search_db(self, search): |
||||
""" |
||||
Searches the corresponding code |
||||
:param search: The code part to be searched |
||||
""" |
||||
|
||||
def search_callback(_, address, balance): |
||||
""" |
||||
|
||||
:param _: |
||||
:param address: The address of the contract with the code in search |
||||
:param balance: The balance of the corresponding contract |
||||
""" |
||||
print("Address: " + address + ", balance: " + str(balance)) |
||||
|
||||
try: |
||||
self.leveldb.search(search, search_callback) |
||||
|
||||
except SyntaxError: |
||||
raise CriticalError("Syntax error in search expression.") |
||||
|
||||
def contract_hash_to_address(self, contract_hash): |
||||
""" |
||||
Returns address of the corresponding hash by searching the leveldb |
||||
:param contract_hash: Hash to be searched |
||||
""" |
||||
if not re.match(r"0x[a-fA-F0-9]{64}", contract_hash): |
||||
raise CriticalError("Invalid address hash. Expected format is '0x...'.") |
||||
|
||||
print(self.leveldb.contract_hash_to_address(contract_hash)) |
@ -0,0 +1,31 @@ |
||||
from pathlib import Path |
||||
from mythril.mythril import MythrilDisassembler, MythrilAnalyzer |
||||
from mythril.analysis.report import Issue |
||||
from mock import patch |
||||
|
||||
|
||||
@patch("mythril.analysis.report.Issue.add_code_info", return_value=None) |
||||
@patch( |
||||
"mythril.mythril.mythril_analyzer.fire_lasers", |
||||
return_value=[Issue("", "", "234", "101", "title", "0x02445")], |
||||
) |
||||
@patch("mythril.mythril.mythril_analyzer.SymExecWrapper", return_value=None) |
||||
def test_fire_lasers(mock_sym, mock_fire_lasers, mock_code_info): |
||||
disassembler = MythrilDisassembler(eth=None) |
||||
disassembler.load_from_solidity( |
||||
[ |
||||
str( |
||||
( |
||||
Path(__file__).parent.parent / "testdata/input_contracts/origin.sol" |
||||
).absolute() |
||||
) |
||||
] |
||||
) |
||||
analyzer = MythrilAnalyzer(disassembler, strategy="dfs") |
||||
|
||||
issues = analyzer.fire_lasers(modules=[]).sorted_issues() |
||||
mock_sym.assert_called() |
||||
mock_fire_lasers.assert_called() |
||||
mock_code_info.assert_called() |
||||
assert len(issues) == 1 |
||||
assert issues[0]["swc-id"] == "101" |
@ -0,0 +1,58 @@ |
||||
import pytest |
||||
|
||||
from configparser import ConfigParser |
||||
from pathlib import Path |
||||
|
||||
from mythril.mythril import MythrilConfig |
||||
from mythril.exceptions import CriticalError |
||||
|
||||
|
||||
def test_config_path_dynloading(): |
||||
config = MythrilConfig() |
||||
config.config_path = str( |
||||
Path(__file__).parent.parent / "testdata/mythril_config_inputs/config.ini" |
||||
) |
||||
config.set_api_from_config_path() |
||||
assert config.eth.host == "mainnet.infura.io" |
||||
assert config.eth.port == 443 |
||||
|
||||
|
||||
rpc_types_tests = [ |
||||
("infura", "mainnet.infura.io", 443, True), |
||||
("ganache", "localhost", 8545, True), |
||||
("infura-rinkeby", "rinkeby.infura.io", 443, True), |
||||
("infura-ropsten", "ropsten.infura.io", 443, True), |
||||
("infura-kovan", "kovan.infura.io", 443, True), |
||||
("localhost", "localhost", 8545, True), |
||||
("localhost:9022", "localhost", 9022, True), |
||||
("pinfura", None, None, False), |
||||
("infura-finkeby", None, None, False), |
||||
] |
||||
|
||||
|
||||
@pytest.mark.parametrize("rpc_type,host,port,success", rpc_types_tests) |
||||
def test_set_rpc(rpc_type, host, port, success): |
||||
config = MythrilConfig() |
||||
if success: |
||||
config._set_rpc(rpc_type) |
||||
assert config.eth.host == host |
||||
assert config.eth.port == port |
||||
else: |
||||
with pytest.raises(CriticalError): |
||||
config._set_rpc(rpc_type) |
||||
|
||||
|
||||
def test_leveldb_config_addition(): |
||||
config = ConfigParser() |
||||
config.add_section("defaults") |
||||
MythrilConfig._add_leveldb_option(config, "test") |
||||
assert config.has_section("defaults") |
||||
assert config.get("defaults", "leveldb_dir") == "test" |
||||
|
||||
|
||||
def test_dynld_config_addition(): |
||||
config = ConfigParser() |
||||
config.add_section("defaults") |
||||
MythrilConfig._add_dynamic_loading_option(config) |
||||
assert config.has_section("defaults") |
||||
assert config.get("defaults", "dynamic_loading") == "infura" |
@ -0,0 +1,70 @@ |
||||
import pytest |
||||
from mythril.mythril import MythrilConfig, MythrilDisassembler |
||||
from mythril.exceptions import CriticalError |
||||
|
||||
storage_test = [ |
||||
( |
||||
["438767356", "3"], |
||||
[ |
||||
"0x1a270efc: 0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"0x1a270efd: 0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"0x1a270efe: 0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
], |
||||
), |
||||
( |
||||
["mapping", "4588934759847", "1", "2"], |
||||
[ |
||||
"0x7e523d5aeb10cdb378b0b1f76138c28063a2cb9ec8ff710f42a0972f4d53cf44: " |
||||
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"0xba36da34ceec88853a2ebdde88e023c6919b90348f41e8905b422dc9ce22301c: " |
||||
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
], |
||||
), |
||||
( |
||||
["mapping", "4588934759847", "10"], |
||||
[ |
||||
"45998575720532480608987132552042185415362901038635143236141343153058112000553: " |
||||
"0x0000000000000000000000000000000000000000000000000000000000000000" |
||||
], |
||||
), |
||||
( |
||||
["4588934759847", "1", "array"], |
||||
[ |
||||
"30699902832541380821728647136767910246735388184559883985790189062258823875816: " |
||||
"0x0000000000000000000000000000000000000000000000000000000000000000" |
||||
], |
||||
), |
||||
] |
||||
|
||||
|
||||
@pytest.mark.parametrize("params,ans", storage_test) |
||||
def test_get_data_from_storage(params, ans): |
||||
config = MythrilConfig() |
||||
config.set_api_rpc_infura() |
||||
disassembler = MythrilDisassembler(eth=config.eth, solc_version="0.4.23") |
||||
outtext = disassembler.get_state_variable_from_storage( |
||||
"0x76799f77587738bfeef09452df215b63d2cfb08a", params |
||||
).split("\n") |
||||
assert outtext == ans |
||||
|
||||
|
||||
storage_test_incorrect_params = [ |
||||
(["1", "2", "3", "4"]), |
||||
(["mapping", "1"]), |
||||
(["a", "b", "c"]), |
||||
] |
||||
|
||||
|
||||
@pytest.mark.parametrize("params", storage_test_incorrect_params) |
||||
def test_get_data_from_storage_incorrect_params(params): |
||||
config = MythrilConfig() |
||||
config.set_api_rpc_infura() |
||||
disassembler = MythrilDisassembler(eth=config.eth, solc_version="0.4.23") |
||||
with pytest.raises(CriticalError): |
||||
disassembler.get_state_variable_from_storage( |
||||
"0x76799f77587738bfeef09452df215b63d2cfb08a", params |
||||
) |
||||
|
||||
|
||||
def test_solc_install(): |
||||
MythrilDisassembler(eth=None, solc_version="0.4.19") |
@ -0,0 +1,51 @@ |
||||
import io |
||||
import pytest |
||||
from contextlib import redirect_stdout |
||||
from mock import patch |
||||
|
||||
from mythril.mythril import MythrilLevelDB, MythrilConfig |
||||
from mythril.exceptions import CriticalError |
||||
|
||||
|
||||
@patch("mythril.ethereum.interface.leveldb.client.EthLevelDB.search") |
||||
@patch("mythril.ethereum.interface.leveldb.client.ETH_DB", return_value=None) |
||||
@patch("mythril.ethereum.interface.leveldb.client.LevelDBReader", return_value=None) |
||||
@patch("mythril.ethereum.interface.leveldb.client.LevelDBWriter", return_value=None) |
||||
def test_leveldb_code_search(mock_leveldb, f1, f2, f3): |
||||
config = MythrilConfig() |
||||
config.set_api_leveldb("some path") |
||||
leveldb_search = MythrilLevelDB(leveldb=config.eth_db) |
||||
leveldb_search.search_db("code#PUSH#") |
||||
mock_leveldb.assert_called() |
||||
|
||||
|
||||
@patch("mythril.ethereum.interface.leveldb.client.ETH_DB", return_value=None) |
||||
@patch("mythril.ethereum.interface.leveldb.client.LevelDBReader", return_value=None) |
||||
@patch("mythril.ethereum.interface.leveldb.client.LevelDBWriter", return_value=None) |
||||
def test_leveldb_hash_search_incorrect_input(f1, f2, f3): |
||||
config = MythrilConfig() |
||||
config.set_api_leveldb("some path") |
||||
leveldb_search = MythrilLevelDB(leveldb=config.eth_db) |
||||
with pytest.raises(CriticalError): |
||||
leveldb_search.contract_hash_to_address("0x23") |
||||
|
||||
|
||||
@patch( |
||||
"mythril.ethereum.interface.leveldb.client.EthLevelDB.contract_hash_to_address", |
||||
return_value="0xddbb615cb2ffaff7233d8a6f3601621de94795e1", |
||||
) |
||||
@patch("mythril.ethereum.interface.leveldb.client.ETH_DB", return_value=None) |
||||
@patch("mythril.ethereum.interface.leveldb.client.LevelDBReader", return_value=None) |
||||
@patch("mythril.ethereum.interface.leveldb.client.LevelDBWriter", return_value=None) |
||||
def test_leveldb_hash_search_correct_input(mock_hash_to_address, f1, f2, f3): |
||||
config = MythrilConfig() |
||||
config.set_api_leveldb("some path") |
||||
leveldb_search = MythrilLevelDB(leveldb=config.eth_db) |
||||
f = io.StringIO() |
||||
with redirect_stdout(f): |
||||
leveldb_search.contract_hash_to_address( |
||||
"0x0464e651bcc40de28fc7fcde269218d16850bac9689da5f4a6bd640fd3cdf6aa" |
||||
) |
||||
out = f.getvalue() |
||||
mock_hash_to_address.assert_called() |
||||
assert out == "0xddbb615cb2ffaff7233d8a6f3601621de94795e1\n" |
@ -1,30 +0,0 @@ |
||||
from mythril.laser.ethereum.taint_analysis import * |
||||
|
||||
|
||||
def test_mutate_not_tainted(): |
||||
# Arrange |
||||
record = TaintRecord() |
||||
|
||||
record.stack = [True, False, False] |
||||
# Act |
||||
TaintRunner.mutate_stack(record, (2, 1)) |
||||
|
||||
# Assert |
||||
assert record.stack_tainted(0) |
||||
assert record.stack_tainted(1) is False |
||||
assert record.stack == [True, False] |
||||
|
||||
|
||||
def test_mutate_tainted(): |
||||
# Arrange |
||||
record = TaintRecord() |
||||
|
||||
record.stack = [True, False, True] |
||||
|
||||
# Act |
||||
TaintRunner.mutate_stack(record, (2, 1)) |
||||
|
||||
# Assert |
||||
assert record.stack_tainted(0) |
||||
assert record.stack_tainted(1) |
||||
assert record.stack == [True, True] |
@ -1,36 +0,0 @@ |
||||
from mythril.laser.ethereum.taint_analysis import * |
||||
|
||||
|
||||
def test_record_tainted_check(): |
||||
# arrange |
||||
record = TaintRecord() |
||||
record.stack = [True, False, True] |
||||
|
||||
# act |
||||
tainted = record.stack_tainted(2) |
||||
|
||||
# assert |
||||
assert tainted is True |
||||
|
||||
|
||||
def test_record_untainted_check(): |
||||
# arrange |
||||
record = TaintRecord() |
||||
record.stack = [True, False, False] |
||||
|
||||
# act |
||||
tainted = record.stack_tainted(2) |
||||
|
||||
# assert |
||||
assert tainted is False |
||||
|
||||
|
||||
def test_record_untouched_check(): |
||||
# arrange |
||||
record = TaintRecord() |
||||
|
||||
# act |
||||
tainted = record.stack_tainted(3) |
||||
|
||||
# assert |
||||
assert tainted is None |
@ -1,35 +0,0 @@ |
||||
from mythril.laser.ethereum.taint_analysis import * |
||||
from mythril.laser.ethereum.state.global_state import GlobalState |
||||
|
||||
|
||||
def test_result_state(): |
||||
# arrange |
||||
taint_result = TaintResult() |
||||
record = TaintRecord() |
||||
state = GlobalState(2, None, None) |
||||
state.mstate.stack = [1, 2, 3] |
||||
record.add_state(state) |
||||
record.stack = [False, False, False] |
||||
# act |
||||
taint_result.add_records([record]) |
||||
tainted = taint_result.check(state, 2) |
||||
|
||||
# assert |
||||
assert tainted is False |
||||
assert record in taint_result.records |
||||
|
||||
|
||||
def test_result_no_state(): |
||||
# arrange |
||||
taint_result = TaintResult() |
||||
record = TaintRecord() |
||||
state = GlobalState(2, None, None) |
||||
state.mstate.stack = [1, 2, 3] |
||||
|
||||
# act |
||||
taint_result.add_records([record]) |
||||
tainted = taint_result.check(state, 2) |
||||
|
||||
# assert |
||||
assert tainted is None |
||||
assert record in taint_result.records |
@ -1,99 +0,0 @@ |
||||
import mock |
||||
import pytest |
||||
from pytest_mock import mocker |
||||
from mythril.laser.ethereum.taint_analysis import * |
||||
from mythril.laser.ethereum.cfg import Node, Edge |
||||
from mythril.laser.ethereum.state.account import Account |
||||
from mythril.laser.ethereum.state.environment import Environment |
||||
from mythril.laser.ethereum.state.machine_state import MachineState |
||||
from mythril.laser.ethereum.state.global_state import GlobalState |
||||
from mythril.laser.ethereum.svm import LaserEVM |
||||
|
||||
|
||||
def test_execute_state(mocker): |
||||
record = TaintRecord() |
||||
record.stack = [True, False, True] |
||||
|
||||
state = GlobalState(None, None, None) |
||||
state.mstate.stack = [1, 2, 3] |
||||
mocker.patch.object(state, "get_current_instruction") |
||||
state.get_current_instruction.return_value = {"opcode": "ADD"} |
||||
|
||||
# Act |
||||
new_record = TaintRunner.execute_state(record, state) |
||||
|
||||
# Assert |
||||
assert new_record.stack == [True, True] |
||||
assert record.stack == [True, False, True] |
||||
|
||||
|
||||
def test_execute_node(mocker): |
||||
record = TaintRecord() |
||||
record.stack = [True, True, False, False] |
||||
|
||||
state_1 = GlobalState(None, None, None) |
||||
state_1.mstate.stack = [1, 2, 3, 1] |
||||
state_1.mstate.pc = 1 |
||||
mocker.patch.object(state_1, "get_current_instruction") |
||||
state_1.get_current_instruction.return_value = {"opcode": "SWAP1"} |
||||
|
||||
state_2 = GlobalState(None, 1, None) |
||||
state_2.mstate.stack = [1, 2, 4, 1] |
||||
mocker.patch.object(state_2, "get_current_instruction") |
||||
state_2.get_current_instruction.return_value = {"opcode": "ADD"} |
||||
|
||||
node = Node("Test contract") |
||||
node.states = [state_1, state_2] |
||||
|
||||
# Act |
||||
records = TaintRunner.execute_node(node, record) |
||||
|
||||
# Assert |
||||
assert len(records) == 2 |
||||
|
||||
assert records[0].stack == [True, True, False, False] |
||||
assert records[1].stack == [True, True, False] |
||||
|
||||
assert state_2 in records[0].states |
||||
assert state_1 in record.states |
||||
|
||||
|
||||
def test_execute(mocker): |
||||
active_account = Account("0x00") |
||||
environment = Environment(active_account, None, None, None, None, None) |
||||
state_1 = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) |
||||
state_1.mstate.stack = [1, 2] |
||||
mocker.patch.object(state_1, "get_current_instruction") |
||||
state_1.get_current_instruction.return_value = {"opcode": "PUSH"} |
||||
|
||||
state_2 = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) |
||||
state_2.mstate.stack = [1, 2, 3] |
||||
mocker.patch.object(state_2, "get_current_instruction") |
||||
state_2.get_current_instruction.return_value = {"opcode": "ADD"} |
||||
|
||||
node_1 = Node("Test contract") |
||||
node_1.states = [state_1, state_2] |
||||
|
||||
state_3 = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) |
||||
state_3.mstate.stack = [1, 2] |
||||
mocker.patch.object(state_3, "get_current_instruction") |
||||
state_3.get_current_instruction.return_value = {"opcode": "ADD"} |
||||
|
||||
node_2 = Node("Test contract") |
||||
node_2.states = [state_3] |
||||
|
||||
edge = Edge(node_1.uid, node_2.uid) |
||||
|
||||
statespace = LaserEVM(None) |
||||
statespace.edges = [edge] |
||||
statespace.nodes[node_1.uid] = node_1 |
||||
statespace.nodes[node_2.uid] = node_2 |
||||
|
||||
# Act |
||||
result = TaintRunner.execute(statespace, node_1, state_1, [True, True]) |
||||
|
||||
# Assert |
||||
print(result) |
||||
assert len(result.records) == 3 |
||||
assert result.records[2].states == [] |
||||
assert state_3 in result.records[1].states |
@ -0,0 +1,2 @@ |
||||
[defaults] |
||||
dynamic_loading = infura |
@ -1 +1,148 @@ |
||||
[{"issues": [{"description": {"head": "The contract executes an external message call.", "tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully."}, "extra": {}, "locations": [{"sourceMap": "661:1:0"}], "severity": "Low", "swcID": "SWC-107", "swcTitle": "Reentrancy"}, {"description": {"head": "The contract executes an external message call.", "tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully."}, "extra": {}, "locations": [{"sourceMap": "779:1:0"}], "severity": "Low", "swcID": "SWC-107", "swcTitle": "Reentrancy"}, {"description": {"head": "The contract executes an external message call.", "tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully."}, "extra": {}, "locations": [{"sourceMap": "858:1:0"}], "severity": "Low", "swcID": "SWC-107", "swcTitle": "Reentrancy"}, {"description": {"head": "A call to a user-supplied address is executed.", "tail": "The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state."}, "extra": {}, "locations": [{"sourceMap": "912:1:0"}], "severity": "Medium", "swcID": "SWC-107", "swcTitle": "Reentrancy"}, {"description": {"head": "The return value of a message call is not checked.", "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."}, "extra": {}, "locations": [{"sourceMap": "661:1:0"}], "severity": "Low", "swcID": "SWC-104", "swcTitle": "Unchecked Call Return Value"}, {"description": {"head": "The return value of a message call is not checked.", "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."}, "extra": {}, "locations": [{"sourceMap": "779:1:0"}], "severity": "Low", "swcID": "SWC-104", "swcTitle": "Unchecked Call Return Value"}, {"description": {"head": "The return value of a message call is not checked.", "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."}, "extra": {}, "locations": [{"sourceMap": "858:1:0"}], "severity": "Low", "swcID": "SWC-104", "swcTitle": "Unchecked Call Return Value"}, {"description": {"head": "The return value of a message call is not checked.", "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."}, "extra": {}, "locations": [{"sourceMap": "912:1:0"}], "severity": "Low", "swcID": "SWC-104", "swcTitle": "Unchecked Call Return Value"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0x7cbb77986c6b1bf6e945cd3fba06d3ea3d28cfc49cdfdc9571ec30703ac5862f"], "sourceType": "raw-bytecode"}] |
||||
[ |
||||
{ |
||||
"issues": [ |
||||
{ |
||||
"description": { |
||||
"head": "The contract executes an external message call.", |
||||
"tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "661:1:0" |
||||
} |
||||
], |
||||
"severity": "Low", |
||||
"swcID": "SWC-107", |
||||
"swcTitle": "Reentrancy" |
||||
}, |
||||
{ |
||||
"description": { |
||||
"head": "The contract executes an external message call.", |
||||
"tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "779:1:0" |
||||
} |
||||
], |
||||
"severity": "Low", |
||||
"swcID": "SWC-107", |
||||
"swcTitle": "Reentrancy" |
||||
}, |
||||
{ |
||||
"description": { |
||||
"head": "The contract executes an external message call.", |
||||
"tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "858:1:0" |
||||
} |
||||
], |
||||
"severity": "Low", |
||||
"swcID": "SWC-107", |
||||
"swcTitle": "Reentrancy" |
||||
}, |
||||
{ |
||||
"description": { |
||||
"head": "A call to a user-supplied address is executed.", |
||||
"tail": "The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "912:1:0" |
||||
} |
||||
], |
||||
"severity": "Medium", |
||||
"swcID": "SWC-107", |
||||
"swcTitle": "Reentrancy" |
||||
}, |
||||
{ |
||||
"description": { |
||||
"head": "The return value of a message call is not checked.", |
||||
"tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "661:1:0" |
||||
} |
||||
], |
||||
"severity": "Low", |
||||
"swcID": "SWC-104", |
||||
"swcTitle": "Unchecked Call Return Value" |
||||
}, |
||||
{ |
||||
"description": { |
||||
"head": "The return value of a message call is not checked.", |
||||
"tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "779:1:0" |
||||
} |
||||
], |
||||
"severity": "Low", |
||||
"swcID": "SWC-104", |
||||
"swcTitle": "Unchecked Call Return Value" |
||||
}, |
||||
{ |
||||
"description": { |
||||
"head": "The return value of a message call is not checked.", |
||||
"tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "858:1:0" |
||||
} |
||||
], |
||||
"severity": "Low", |
||||
"swcID": "SWC-104", |
||||
"swcTitle": "Unchecked Call Return Value" |
||||
}, |
||||
{ |
||||
"description": { |
||||
"head": "The return value of a message call is not checked.", |
||||
"tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "912:1:0" |
||||
} |
||||
], |
||||
"severity": "Low", |
||||
"swcID": "SWC-104", |
||||
"swcTitle": "Unchecked Call Return Value" |
||||
} |
||||
], |
||||
"meta": {}, |
||||
"sourceFormat": "evm-byzantium-bytecode", |
||||
"sourceList": [ |
||||
"0x7cbb77986c6b1bf6e945cd3fba06d3ea3d28cfc49cdfdc9571ec30703ac5862f" |
||||
], |
||||
"sourceType": "raw-bytecode" |
||||
} |
||||
] |
@ -1 +1,80 @@ |
||||
[{"issues": [{"description": {"head": "A reachable exception has been detected.", "tail": "It is possible to trigger an exception (opcode 0xfe). Exceptions can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking."}, "extra": {}, "locations": [{"sourceMap": "446:1:0"}], "severity": "Low", "swcID": "SWC-110", "swcTitle": "Assert Violation"}, {"description": {"head": "A reachable exception has been detected.", "tail": "It is possible to trigger an exception (opcode 0xfe). Exceptions can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking."}, "extra": {}, "locations": [{"sourceMap": "484:1:0"}], "severity": "Low", "swcID": "SWC-110", "swcTitle": "Assert Violation"}, {"description": {"head": "A reachable exception has been detected.", "tail": "It is possible to trigger an exception (opcode 0xfe). Exceptions can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking."}, "extra": {}, "locations": [{"sourceMap": "506:1:0"}], "severity": "Low", "swcID": "SWC-110", "swcTitle": "Assert Violation"}, {"description": {"head": "A reachable exception has been detected.", "tail": "It is possible to trigger an exception (opcode 0xfe). Exceptions can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking."}, "extra": {}, "locations": [{"sourceMap": "531:1:0"}], "severity": "Low", "swcID": "SWC-110", "swcTitle": "Assert Violation"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0x4a773a86bc6fb269f88bf09bb3094de29b6073cf13b1760e9d01d957f50a9dfd"], "sourceType": "raw-bytecode"}] |
||||
[ |
||||
{ |
||||
"issues": [ |
||||
{ |
||||
"description": { |
||||
"head": "A reachable exception has been detected.", |
||||
"tail": "It is possible to trigger an exception (opcode 0xfe). Exceptions can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "446:1:0" |
||||
} |
||||
], |
||||
"severity": "Low", |
||||
"swcID": "SWC-110", |
||||
"swcTitle": "Assert Violation" |
||||
}, |
||||
{ |
||||
"description": { |
||||
"head": "A reachable exception has been detected.", |
||||
"tail": "It is possible to trigger an exception (opcode 0xfe). Exceptions can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "484:1:0" |
||||
} |
||||
], |
||||
"severity": "Low", |
||||
"swcID": "SWC-110", |
||||
"swcTitle": "Assert Violation" |
||||
}, |
||||
{ |
||||
"description": { |
||||
"head": "A reachable exception has been detected.", |
||||
"tail": "It is possible to trigger an exception (opcode 0xfe). Exceptions can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "506:1:0" |
||||
} |
||||
], |
||||
"severity": "Low", |
||||
"swcID": "SWC-110", |
||||
"swcTitle": "Assert Violation" |
||||
}, |
||||
{ |
||||
"description": { |
||||
"head": "A reachable exception has been detected.", |
||||
"tail": "It is possible to trigger an exception (opcode 0xfe). Exceptions can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "531:1:0" |
||||
} |
||||
], |
||||
"severity": "Low", |
||||
"swcID": "SWC-110", |
||||
"swcTitle": "Assert Violation" |
||||
} |
||||
], |
||||
"meta": {}, |
||||
"sourceFormat": "evm-byzantium-bytecode", |
||||
"sourceList": [ |
||||
"0x4a773a86bc6fb269f88bf09bb3094de29b6073cf13b1760e9d01d957f50a9dfd" |
||||
], |
||||
"sourceType": "raw-bytecode" |
||||
} |
||||
] |
@ -1 +1,97 @@ |
||||
[{"issues": [{"description": {"head": "Use of callcode is deprecated.", "tail": "The callcode method executes code of another contract in the context of the caller account. Due to a bug in the implementation it does not persist sender and value over the call. It was therefore deprecated and may be removed in the future. Use the delegatecall method instead."}, "extra": {}, "locations": [{"sourceMap": "618:1:0"}], "severity": "Medium", "swcID": "SWC-111", "swcTitle": "Use of Deprecated Solidity Functions"}, {"description": {"head": "A call to a user-supplied address is executed.", "tail": "The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state."}, "extra": {}, "locations": [{"sourceMap": "1038:1:0"}], "severity": "Medium", "swcID": "SWC-107", "swcTitle": "Reentrancy"}, {"description": {"head": "The return value of a message call is not checked.", "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."}, "extra": {}, "locations": [{"sourceMap": "618:1:0"}], "severity": "Low", "swcID": "SWC-104", "swcTitle": "Unchecked Call Return Value"}, {"description": {"head": "The return value of a message call is not checked.", "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."}, "extra": {}, "locations": [{"sourceMap": "849:1:0"}], "severity": "Low", "swcID": "SWC-104", "swcTitle": "Unchecked Call Return Value"}, {"description": {"head": "The return value of a message call is not checked.", "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."}, "extra": {}, "locations": [{"sourceMap": "1038:1:0"}], "severity": "Low", "swcID": "SWC-104", "swcTitle": "Unchecked Call Return Value"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0x6daec61d05d8f1210661e7e7d1ed6d72bd6ade639398fac1e867aff50abfc1c1"], "sourceType": "raw-bytecode"}] |
||||
[ |
||||
{ |
||||
"issues": [ |
||||
{ |
||||
"description": { |
||||
"head": "Use of callcode is deprecated.", |
||||
"tail": "The callcode method executes code of another contract in the context of the caller account. Due to a bug in the implementation it does not persist sender and value over the call. It was therefore deprecated and may be removed in the future. Use the delegatecall method instead." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "618:1:0" |
||||
} |
||||
], |
||||
"severity": "Medium", |
||||
"swcID": "SWC-111", |
||||
"swcTitle": "Use of Deprecated Solidity Functions" |
||||
}, |
||||
{ |
||||
"description": { |
||||
"head": "A call to a user-supplied address is executed.", |
||||
"tail": "The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "1038:1:0" |
||||
} |
||||
], |
||||
"severity": "Medium", |
||||
"swcID": "SWC-107", |
||||
"swcTitle": "Reentrancy" |
||||
}, |
||||
{ |
||||
"description": { |
||||
"head": "The return value of a message call is not checked.", |
||||
"tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "618:1:0" |
||||
} |
||||
], |
||||
"severity": "Low", |
||||
"swcID": "SWC-104", |
||||
"swcTitle": "Unchecked Call Return Value" |
||||
}, |
||||
{ |
||||
"description": { |
||||
"head": "The return value of a message call is not checked.", |
||||
"tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "849:1:0" |
||||
} |
||||
], |
||||
"severity": "Low", |
||||
"swcID": "SWC-104", |
||||
"swcTitle": "Unchecked Call Return Value" |
||||
}, |
||||
{ |
||||
"description": { |
||||
"head": "The return value of a message call is not checked.", |
||||
"tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "1038:1:0" |
||||
} |
||||
], |
||||
"severity": "Low", |
||||
"swcID": "SWC-104", |
||||
"swcTitle": "Unchecked Call Return Value" |
||||
} |
||||
], |
||||
"meta": {}, |
||||
"sourceFormat": "evm-byzantium-bytecode", |
||||
"sourceList": [ |
||||
"0x6daec61d05d8f1210661e7e7d1ed6d72bd6ade639398fac1e867aff50abfc1c1" |
||||
], |
||||
"sourceType": "raw-bytecode" |
||||
} |
||||
] |
@ -1 +1,29 @@ |
||||
[{"issues": [{"description": {"head": "Anyone can withdraw ETH from the contract account.", "tail": "Arbitrary senders other than the contract creator can withdraw ETH from the contract account without previously having sent an equivalent amount of ETH to it. This is likely to be a vulnerability."}, "extra": {}, "locations": [{"sourceMap": "142:1:0"}], "severity": "High", "swcID": "SWC-105", "swcTitle": "Unprotected Ether Withdrawal"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0xbc9c3d9db56d20cf4ca3b6fd88ff9215cf728a092cca1ed8edb83272b933ff5b"], "sourceType": "raw-bytecode"}] |
||||
[ |
||||
{ |
||||
"issues": [ |
||||
{ |
||||
"description": { |
||||
"head": "Anyone can withdraw ETH from the contract account.", |
||||
"tail": "Arbitrary senders other than the contract creator can withdraw ETH from the contract account without previously having sent an equivalent amount of ETH to it. This is likely to be a vulnerability." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "142:1:0" |
||||
} |
||||
], |
||||
"severity": "High", |
||||
"swcID": "SWC-105", |
||||
"swcTitle": "Unprotected Ether Withdrawal" |
||||
} |
||||
], |
||||
"meta": {}, |
||||
"sourceFormat": "evm-byzantium-bytecode", |
||||
"sourceList": [ |
||||
"0xbc9c3d9db56d20cf4ca3b6fd88ff9215cf728a092cca1ed8edb83272b933ff5b" |
||||
], |
||||
"sourceType": "raw-bytecode" |
||||
} |
||||
] |
@ -1 +1,29 @@ |
||||
[{"issues": [{"description": {"head": "Use of tx.origin is deprecated.", "tail": "The smart contract retrieves the transaction origin (tx.origin) using msg.origin. Use of msg.origin is deprecated and the instruction may be removed in the future. Use msg.sender instead.\nSee also: https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin"}, "extra": {}, "locations": [{"sourceMap": "317:1:0"}], "severity": "Medium", "swcID": "SWC-111", "swcTitle": "Use of Deprecated Solidity Functions"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0x25b20ef097dfc0aa56a932c4e09f06ee02a69c005767df86877f48c6c2412f03"], "sourceType": "raw-bytecode"}] |
||||
[ |
||||
{ |
||||
"issues": [ |
||||
{ |
||||
"description": { |
||||
"head": "Use of tx.origin is deprecated.", |
||||
"tail": "The smart contract retrieves the transaction origin (tx.origin) using msg.origin. Use of msg.origin is deprecated and the instruction may be removed in the future. Use msg.sender instead.\nSee also: https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin" |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "317:1:0" |
||||
} |
||||
], |
||||
"severity": "Medium", |
||||
"swcID": "SWC-111", |
||||
"swcTitle": "Use of Deprecated Solidity Functions" |
||||
} |
||||
], |
||||
"meta": {}, |
||||
"sourceFormat": "evm-byzantium-bytecode", |
||||
"sourceList": [ |
||||
"0x25b20ef097dfc0aa56a932c4e09f06ee02a69c005767df86877f48c6c2412f03" |
||||
], |
||||
"sourceType": "raw-bytecode" |
||||
} |
||||
] |
@ -1 +1,46 @@ |
||||
[{"issues": [{"description": {"head": "The binary subtraction can underflow.", "tail": "The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion."}, "extra": {}, "locations": [{"sourceMap": "567:1:0"}], "severity": "High", "swcID": "SWC-101", "swcTitle": "Integer Overflow and Underflow"}, {"description": {"head": "The binary subtraction can underflow.", "tail": "The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion."}, "extra": {}, "locations": [{"sourceMap": "649:1:0"}], "severity": "High", "swcID": "SWC-101", "swcTitle": "Integer Overflow and Underflow"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0xf230bec502569e8b7e7737616d0ad0f200c436624e3c223e5398c0615cd2d6b9"], "sourceType": "raw-bytecode"}] |
||||
[ |
||||
{ |
||||
"issues": [ |
||||
{ |
||||
"description": { |
||||
"head": "The binary subtraction can underflow.", |
||||
"tail": "The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "567:1:0" |
||||
} |
||||
], |
||||
"severity": "High", |
||||
"swcID": "SWC-101", |
||||
"swcTitle": "Integer Overflow and Underflow" |
||||
}, |
||||
{ |
||||
"description": { |
||||
"head": "The binary subtraction can underflow.", |
||||
"tail": "The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "649:1:0" |
||||
} |
||||
], |
||||
"severity": "High", |
||||
"swcID": "SWC-101", |
||||
"swcTitle": "Integer Overflow and Underflow" |
||||
} |
||||
], |
||||
"meta": {}, |
||||
"sourceFormat": "evm-byzantium-bytecode", |
||||
"sourceList": [ |
||||
"0xf230bec502569e8b7e7737616d0ad0f200c436624e3c223e5398c0615cd2d6b9" |
||||
], |
||||
"sourceType": "raw-bytecode" |
||||
} |
||||
] |
@ -1 +1,63 @@ |
||||
[{"issues": [{"description": {"head": "The contract executes an external message call.", "tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully."}, "extra": {}, "locations": [{"sourceMap": "196:1:0"}], "severity": "Low", "swcID": "SWC-107", "swcTitle": "Reentrancy"}, {"description": {"head": "The contract executes an external message call.", "tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully."}, "extra": {}, "locations": [{"sourceMap": "285:1:0"}], "severity": "Low", "swcID": "SWC-107", "swcTitle": "Reentrancy"}, {"description": {"head": "The return value of a message call is not checked.", "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states."}, "extra": {}, "locations": [{"sourceMap": "285:1:0"}], "severity": "Low", "swcID": "SWC-104", "swcTitle": "Unchecked Call Return Value"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0xb191cf6cc0d8cc37a91c9d88019cc011b932169fb5776df616e2bb9cd93b4039"], "sourceType": "raw-bytecode"}] |
||||
[ |
||||
{ |
||||
"issues": [ |
||||
{ |
||||
"description": { |
||||
"head": "The contract executes an external message call.", |
||||
"tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "196:1:0" |
||||
} |
||||
], |
||||
"severity": "Low", |
||||
"swcID": "SWC-107", |
||||
"swcTitle": "Reentrancy" |
||||
}, |
||||
{ |
||||
"description": { |
||||
"head": "The contract executes an external message call.", |
||||
"tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "285:1:0" |
||||
} |
||||
], |
||||
"severity": "Low", |
||||
"swcID": "SWC-107", |
||||
"swcTitle": "Reentrancy" |
||||
}, |
||||
{ |
||||
"description": { |
||||
"head": "The return value of a message call is not checked.", |
||||
"tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "285:1:0" |
||||
} |
||||
], |
||||
"severity": "Low", |
||||
"swcID": "SWC-104", |
||||
"swcTitle": "Unchecked Call Return Value" |
||||
} |
||||
], |
||||
"meta": {}, |
||||
"sourceFormat": "evm-byzantium-bytecode", |
||||
"sourceList": [ |
||||
"0xb191cf6cc0d8cc37a91c9d88019cc011b932169fb5776df616e2bb9cd93b4039" |
||||
], |
||||
"sourceType": "raw-bytecode" |
||||
} |
||||
] |
@ -1 +1,29 @@ |
||||
[{"issues": [{"description": {"head": "The contract can be killed by anyone.", "tail": "Anyone can kill this contract and withdraw its balance to an arbitrary address."}, "extra": {}, "locations": [{"sourceMap": "146:1:0"}], "severity": "High", "swcID": "SWC-106", "swcTitle": "Unprotected SELFDESTRUCT Instruction"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0x2fb801366b61a05b30550481a1c8f7d5f20de0b93d9f2f2ce2b28c4e322033c9"], "sourceType": "raw-bytecode"}] |
||||
[ |
||||
{ |
||||
"issues": [ |
||||
{ |
||||
"description": { |
||||
"head": "The contract can be killed by anyone.", |
||||
"tail": "Anyone can kill this contract and withdraw its balance to an arbitrary address." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "146:1:0" |
||||
} |
||||
], |
||||
"severity": "High", |
||||
"swcID": "SWC-106", |
||||
"swcTitle": "Unprotected SELFDESTRUCT Instruction" |
||||
} |
||||
], |
||||
"meta": {}, |
||||
"sourceFormat": "evm-byzantium-bytecode", |
||||
"sourceList": [ |
||||
"0x2fb801366b61a05b30550481a1c8f7d5f20de0b93d9f2f2ce2b28c4e322033c9" |
||||
], |
||||
"sourceType": "raw-bytecode" |
||||
} |
||||
] |
@ -1 +1,46 @@ |
||||
[{"issues": [{"description": {"head": "The binary subtraction can underflow.", "tail": "The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion."}, "extra": {}, "locations": [{"sourceMap": "567:1:0"}], "severity": "High", "swcID": "SWC-101", "swcTitle": "Integer Overflow and Underflow"}, {"description": {"head": "The binary subtraction can underflow.", "tail": "The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion."}, "extra": {}, "locations": [{"sourceMap": "649:1:0"}], "severity": "High", "swcID": "SWC-101", "swcTitle": "Integer Overflow and Underflow"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0xabef56740bf7795a9f8732e4781ebd27f2977f8a4997e3ff11cee79a4ba6c0ce"], "sourceType": "raw-bytecode"}] |
||||
[ |
||||
{ |
||||
"issues": [ |
||||
{ |
||||
"description": { |
||||
"head": "The binary subtraction can underflow.", |
||||
"tail": "The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "567:1:0" |
||||
} |
||||
], |
||||
"severity": "High", |
||||
"swcID": "SWC-101", |
||||
"swcTitle": "Integer Overflow and Underflow" |
||||
}, |
||||
{ |
||||
"description": { |
||||
"head": "The binary subtraction can underflow.", |
||||
"tail": "The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion." |
||||
}, |
||||
"extra": { |
||||
"discoveryTime": "<DISCOVERY-TIME-DATA>" |
||||
}, |
||||
"locations": [ |
||||
{ |
||||
"sourceMap": "649:1:0" |
||||
} |
||||
], |
||||
"severity": "High", |
||||
"swcID": "SWC-101", |
||||
"swcTitle": "Integer Overflow and Underflow" |
||||
} |
||||
], |
||||
"meta": {}, |
||||
"sourceFormat": "evm-byzantium-bytecode", |
||||
"sourceList": [ |
||||
"0xabef56740bf7795a9f8732e4781ebd27f2977f8a4997e3ff11cee79a4ba6c0ce" |
||||
], |
||||
"sourceType": "raw-bytecode" |
||||
} |
||||
] |
Loading…
Reference in new issue