mirror of https://github.com/ConsenSys/mythril
parent
a175d268cb
commit
571c8708ba
@ -0,0 +1,93 @@ |
||||
"""This module checks for the Constantinople reentrancy vulnerability reported by ChainSecurity.""" |
||||
|
||||
from mythril.analysis import solver |
||||
from mythril.analysis.swc_data import REENTRANCY |
||||
from mythril.analysis.modules.base import DetectionModule |
||||
from mythril.laser.ethereum.plugins.mutation_pruner import MutationAnnotation |
||||
from mythril.analysis.report import Issue |
||||
from mythril.laser.ethereum.state.global_state import GlobalState |
||||
from mythril.exceptions import UnsatError |
||||
import logging |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
DESCRIPTION = """ |
||||
|
||||
This module reports an issue when: |
||||
|
||||
1. The STOP or RETURN instruction is reached AND |
||||
2. A state variable has been modified during the transaction AND |
||||
2. The minimum gas used by the transaction is less than or equal to 1600. |
||||
|
||||
|
||||
""" |
||||
|
||||
|
||||
def _analyze_state(state): |
||||
""" |
||||
:param state: |
||||
:return: |
||||
""" |
||||
node = state.node |
||||
|
||||
if len(list(state.get_annotations(MutationAnnotation))) > 0: |
||||
|
||||
if state.mstate.min_gas_used <= 1600: |
||||
|
||||
# Verify that this state is reachable |
||||
|
||||
try: |
||||
constraints = node.constraints |
||||
transaction_sequence = solver.get_transaction_sequence( |
||||
state, constraints |
||||
) |
||||
|
||||
except UnsatError: |
||||
log.debug("State is unreachable.") |
||||
return [] |
||||
|
||||
issue = Issue( |
||||
contract=node.contract_name, |
||||
function_name=node.function_name, |
||||
address=state.instruction["address"], |
||||
swc_id=REENTRANCY, |
||||
title="State write for 1600 gas or less", |
||||
severity="Medium", |
||||
bytecode=state.environment.code.bytecode, |
||||
description_head="Caller can modify state for 1600 gas or less.", |
||||
description_tail="The planned Constantinople hard fork reduces the gas cost for writing to state " |
||||
+"variables that have been written to in the same transaction. In some cases " |
||||
+"this may cause re-rentrancy vulnerabilities in previously safe contracts.", |
||||
debug=transaction_sequence, |
||||
gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), |
||||
) |
||||
|
||||
return [issue] |
||||
|
||||
return [] |
||||
|
||||
|
||||
class ConstantinopleReentrancy(DetectionModule): |
||||
|
||||
def __init__(self): |
||||
"""""" |
||||
super().__init__( |
||||
name="Constantinople reentrancy vulnerability", |
||||
swc_id=REENTRANCY, |
||||
description=DESCRIPTION, |
||||
entrypoint="callback", |
||||
pre_hooks=["STOP", "RETURN"], |
||||
) |
||||
|
||||
def execute(self, state: GlobalState): |
||||
""" |
||||
|
||||
:param state: |
||||
:return: |
||||
""" |
||||
|
||||
self._issues.extend(_analyze_state(state)) |
||||
return self.issues |
||||
|
||||
|
||||
detector = ConstantinopleReentrancy() |
@ -0,0 +1,96 @@ |
||||
"""This module contains the detection code for potentially insecure low-level |
||||
calls.""" |
||||
|
||||
from mythril.analysis import solver |
||||
from mythril.analysis.swc_data import REENTRANCY |
||||
from mythril.analysis.modules.base import DetectionModule |
||||
from mythril.analysis.report import Issue |
||||
from mythril.laser.smt import UGT, symbol_factory |
||||
from mythril.laser.ethereum.state.global_state import GlobalState |
||||
from mythril.laser.ethereum.state.annotation import StateAnnotation |
||||
from mythril.exceptions import UnsatError |
||||
import logging |
||||
import json |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
DESCRIPTION = """ |
||||
|
||||
Search for external calls followed by a state change (CALL / SSTORE). |
||||
|
||||
""" |
||||
|
||||
class PostCallAnnotation(StateAnnotation): |
||||
pass |
||||
|
||||
|
||||
def _analyze_state(state): |
||||
""" |
||||
:param state: |
||||
:return: |
||||
""" |
||||
node = state.node |
||||
|
||||
if len(list(state.get_annotations(PostCallAnnotation))) > 0: |
||||
|
||||
# Verify that this state is reachable |
||||
|
||||
try: |
||||
constraints = node.constraints |
||||
transaction_sequence = solver.get_transaction_sequence( |
||||
state, constraints |
||||
) |
||||
|
||||
except UnsatError: |
||||
log.debug("State is unreachable.") |
||||
return [] |
||||
|
||||
issue = Issue( |
||||
contract=node.contract_name, |
||||
function_name=node.function_name, |
||||
address=state.instruction["address"], |
||||
swc_id=REENTRANCY, |
||||
title="State change after external call", |
||||
severity="Medium", |
||||
bytecode=state.environment.code.bytecode, |
||||
description_head="State change after external call.", |
||||
description_tail="The contract account state is changed after an external call. Consider that the " |
||||
"called contract could re-enter the function before this state change takes place. This can lead to " |
||||
"business logic vulnerabilities.", |
||||
debug=transaction_sequence, |
||||
gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), |
||||
) |
||||
|
||||
return [issue] |
||||
|
||||
elif state.instruction["opcode"] == "CALL": |
||||
state.annotate(PostCallAnnotation()) |
||||
|
||||
return [] |
||||
|
||||
|
||||
class ExternalCalls(DetectionModule): |
||||
"""This module searches for low level calls (e.g. call.value()) that |
||||
forward all gas to the callee.""" |
||||
|
||||
def __init__(self): |
||||
"""""" |
||||
super().__init__( |
||||
name="External calls", |
||||
swc_id=REENTRANCY, |
||||
description=DESCRIPTION, |
||||
entrypoint="callback", |
||||
pre_hooks=["CALL", "SSTORE"] |
||||
) |
||||
|
||||
def execute(self, state: GlobalState): |
||||
""" |
||||
|
||||
:param state: |
||||
:return: |
||||
""" |
||||
self._issues.extend(_analyze_state(state)) |
||||
return self.issues |
||||
|
||||
|
||||
detector = ExternalCalls() |
Loading…
Reference in new issue