mirror of https://github.com/ConsenSys/mythril
parent
40d02c662a
commit
999e1a22c8
@ -1,453 +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), |
||||
"RETURN": (2, 0), |
||||
} |
@ -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 |
Loading…
Reference in new issue