mirror of https://github.com/ConsenSys/mythril
parent
57dbd0cfd6
commit
13969337fd
@ -0,0 +1,85 @@ |
|||||||
|
"""This module contains the detection code for requirement violations in a call""" |
||||||
|
import logging |
||||||
|
|
||||||
|
from mythril.analysis import solver |
||||||
|
from mythril.analysis.module.base import DetectionModule |
||||||
|
from mythril.analysis.report import Issue |
||||||
|
from mythril.analysis.issue_annotation import IssueAnnotation |
||||||
|
from mythril.laser.smt import And |
||||||
|
from mythril.analysis.swc_data import REQUIREMENT_VIOLATION |
||||||
|
from mythril.exceptions import UnsatError |
||||||
|
from mythril.laser.ethereum.state.global_state import GlobalState |
||||||
|
|
||||||
|
from typing import List |
||||||
|
|
||||||
|
log = logging.getLogger(__name__) |
||||||
|
|
||||||
|
|
||||||
|
class RequirementsViolation(DetectionModule): |
||||||
|
"""This module detects transaction order dependence""" |
||||||
|
|
||||||
|
name = "Requirement Violation" |
||||||
|
swc_id = REQUIREMENT_VIOLATION |
||||||
|
description = "Checks whether any requirements violate in a call." |
||||||
|
entrypoint = "callback" |
||||||
|
pre_hooks = ["REVERT"] |
||||||
|
plugin_default_enabled = True |
||||||
|
|
||||||
|
def _execute(self, state: GlobalState) -> List[Issue]: |
||||||
|
""" |
||||||
|
|
||||||
|
:param state: |
||||||
|
:return: |
||||||
|
""" |
||||||
|
issues = self._analyze_state(state) |
||||||
|
for issue in issues: |
||||||
|
self.cache.add((issue.source_location, issue.bytecode_hash)) |
||||||
|
return issues |
||||||
|
|
||||||
|
def _analyze_state(self, state) -> List[Issue]: |
||||||
|
""" |
||||||
|
|
||||||
|
:param state: |
||||||
|
:return: |
||||||
|
""" |
||||||
|
|
||||||
|
if len(state.transaction_stack) < 2: |
||||||
|
return [] |
||||||
|
|
||||||
|
try: |
||||||
|
address = state.get_current_instruction()["address"] |
||||||
|
description_head = "A requirement was violated in a nested call and the call was reverted as a result." |
||||||
|
description_tail = "Make sure valid inputs are provided to the nested call (for instance, via passed arguments)." |
||||||
|
transaction_sequence = solver.get_transaction_sequence( |
||||||
|
state, state.world_state.constraints |
||||||
|
) |
||||||
|
issue = Issue( |
||||||
|
contract=state.environment.active_account.contract_name, |
||||||
|
function_name=state.environment.active_function_name, |
||||||
|
address=address, |
||||||
|
swc_id=REQUIREMENT_VIOLATION, |
||||||
|
title="requirement violation", |
||||||
|
severity="Medium", |
||||||
|
description_head=description_head, |
||||||
|
description_tail=description_tail, |
||||||
|
bytecode=state.environment.code.bytecode, |
||||||
|
transaction_sequence=transaction_sequence, |
||||||
|
gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), |
||||||
|
) |
||||||
|
state.annotate( |
||||||
|
IssueAnnotation( |
||||||
|
conditions=[And(*state.world_state.constraints)], |
||||||
|
issue=issue, |
||||||
|
detector=self, |
||||||
|
) |
||||||
|
) |
||||||
|
|
||||||
|
return [issue] |
||||||
|
|
||||||
|
except UnsatError: |
||||||
|
log.debug("no model found") |
||||||
|
|
||||||
|
return [] |
||||||
|
|
||||||
|
|
||||||
|
detector = RequirementsViolation() |
@ -0,0 +1,138 @@ |
|||||||
|
"""This module contains the detection code for transaction order dependence.""" |
||||||
|
|
||||||
|
from mythril.analysis import solver |
||||||
|
from mythril.analysis.potential_issues import ( |
||||||
|
PotentialIssue, |
||||||
|
get_potential_issues_annotation, |
||||||
|
) |
||||||
|
from mythril.analysis.swc_data import TX_ORDER_DEPENDENCE |
||||||
|
from mythril.laser.ethereum.transaction.symbolic import ACTORS |
||||||
|
from mythril.analysis.module.base import DetectionModule |
||||||
|
from mythril.laser.smt import Or, Bool |
||||||
|
from mythril.laser.ethereum.state.global_state import GlobalState |
||||||
|
from mythril.exceptions import UnsatError |
||||||
|
import logging |
||||||
|
|
||||||
|
log = logging.getLogger(__name__) |
||||||
|
|
||||||
|
DESCRIPTION = """ |
||||||
|
|
||||||
|
Search for calls whose value depends on balance or storage. |
||||||
|
|
||||||
|
""" |
||||||
|
|
||||||
|
|
||||||
|
class BalanceAnnotation: |
||||||
|
def __init__(self, caller): |
||||||
|
self.caller = caller |
||||||
|
|
||||||
|
|
||||||
|
class StorageAnnotation: |
||||||
|
def __init__(self, caller): |
||||||
|
self.caller = caller |
||||||
|
|
||||||
|
|
||||||
|
class TransactionOrderDependence(DetectionModule): |
||||||
|
"""This module detects transaction order dependence.""" |
||||||
|
|
||||||
|
name = "Transaction Order Dependence" |
||||||
|
swc_id = TX_ORDER_DEPENDENCE |
||||||
|
description = DESCRIPTION |
||||||
|
entrypoint = "callback" |
||||||
|
pre_hooks = ["CALL"] |
||||||
|
post_hooks = ["BALANCE", "SLOAD"] |
||||||
|
plugin_default_enabled = True |
||||||
|
|
||||||
|
def _execute(self, state: GlobalState) -> None: |
||||||
|
""" |
||||||
|
|
||||||
|
:param state: |
||||||
|
:return: |
||||||
|
""" |
||||||
|
potential_issues = self._analyze_state(state) |
||||||
|
|
||||||
|
annotation = get_potential_issues_annotation(state) |
||||||
|
annotation.potential_issues.extend(potential_issues) |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def annotate_balance_storage_vals(state, opcode): |
||||||
|
val = state.mstate.stack[-1] |
||||||
|
if opcode == "BALANCE": |
||||||
|
annotation = BalanceAnnotation |
||||||
|
else: |
||||||
|
annotation = StorageAnnotation |
||||||
|
if len(val.get_annotations(annotation)) == 0: |
||||||
|
state.mstate.stack[-1].annotate(annotation(state.environment.sender)) |
||||||
|
return [] |
||||||
|
|
||||||
|
def _analyze_state(self, state: GlobalState): |
||||||
|
""" |
||||||
|
|
||||||
|
:param state: |
||||||
|
:return: |
||||||
|
""" |
||||||
|
opcode = state.get_current_instruction()["opcode"] |
||||||
|
if opcode != "CALL": |
||||||
|
opcode = state.environment.code.instruction_list[state.mstate.pc - 1][ |
||||||
|
"opcode" |
||||||
|
] |
||||||
|
if opcode in ("BALANCE", "SLOAD"): |
||||||
|
self.annotate_balance_storage_vals(state, opcode) |
||||||
|
return [] |
||||||
|
|
||||||
|
value = state.mstate.stack[-3] |
||||||
|
if ( |
||||||
|
len(value.get_annotations(StorageAnnotation)) |
||||||
|
+ len(value.get_annotations(BalanceAnnotation)) |
||||||
|
== 0 |
||||||
|
): |
||||||
|
return [] |
||||||
|
callers = [] |
||||||
|
|
||||||
|
storage_annotations = value.get_annotations(StorageAnnotation) |
||||||
|
if len(storage_annotations) == 1: |
||||||
|
callers.append(storage_annotations[0].caller) |
||||||
|
balance_annotations = value.get_annotations(BalanceAnnotation) |
||||||
|
if len(balance_annotations) == 1: |
||||||
|
callers.append(balance_annotations[0].caller) |
||||||
|
|
||||||
|
address = state.get_current_instruction()["address"] |
||||||
|
call_constraint = Bool(False) |
||||||
|
for caller in callers: |
||||||
|
call_constraint = Or(call_constraint, ACTORS.attacker == caller) |
||||||
|
|
||||||
|
try: |
||||||
|
constraints = state.world_state.constraints + [call_constraint] |
||||||
|
|
||||||
|
solver.get_transaction_sequence(state, constraints) |
||||||
|
|
||||||
|
description_head = ( |
||||||
|
"The value of the call is dependent on balance or storage write" |
||||||
|
) |
||||||
|
description_tail = ( |
||||||
|
"This can lead to race conditions. An attacker may be able to run a transaction after our transaction " |
||||||
|
"which can change the value of the call" |
||||||
|
) |
||||||
|
|
||||||
|
issue = PotentialIssue( |
||||||
|
contract=state.environment.active_account.contract_name, |
||||||
|
function_name=state.environment.active_function_name, |
||||||
|
address=address, |
||||||
|
swc_id=TX_ORDER_DEPENDENCE, |
||||||
|
title="Transaction Order Dependence", |
||||||
|
bytecode=state.environment.code.bytecode, |
||||||
|
severity="Medium", |
||||||
|
description_head=description_head, |
||||||
|
description_tail=description_tail, |
||||||
|
constraints=constraints, |
||||||
|
detector=self, |
||||||
|
) |
||||||
|
|
||||||
|
except UnsatError: |
||||||
|
log.debug("[Transaction Order Dependence] No model found.") |
||||||
|
return [] |
||||||
|
|
||||||
|
return [issue] |
||||||
|
|
||||||
|
|
||||||
|
detector = TransactionOrderDependence() |
@ -0,0 +1,142 @@ |
|||||||
|
"""This module contains the detection code for unexpected ether balance.""" |
||||||
|
from mythril.analysis.report import Issue |
||||||
|
from mythril.analysis.issue_annotation import IssueAnnotation |
||||||
|
from mythril.analysis.swc_data import UNEXPECTED_ETHER_BALANCE |
||||||
|
from mythril.analysis.module.base import DetectionModule |
||||||
|
from mythril.analysis.module.module_helpers import is_prehook |
||||||
|
from mythril.laser.smt import BitVec, And |
||||||
|
from mythril.analysis.solver import UnsatError, get_transaction_sequence |
||||||
|
from mythril.laser.ethereum.state.annotation import StateAnnotation |
||||||
|
from mythril.laser.ethereum.state.global_state import GlobalState |
||||||
|
|
||||||
|
import logging |
||||||
|
|
||||||
|
log = logging.getLogger(__name__) |
||||||
|
|
||||||
|
DESCRIPTION = """ |
||||||
|
Check for strict equality checks with contract balance |
||||||
|
""" |
||||||
|
|
||||||
|
|
||||||
|
class EtherBalanceCheckAnnotation(StateAnnotation): |
||||||
|
"""State Annotation used if an overflow is both possible and used in the annotated path""" |
||||||
|
|
||||||
|
def __init__(self, balance: BitVec) -> None: |
||||||
|
self.balance = balance |
||||||
|
|
||||||
|
|
||||||
|
class BalanceConditionAnnotation: |
||||||
|
"""""" |
||||||
|
|
||||||
|
def __init__(self, address=None) -> None: |
||||||
|
self.address = address |
||||||
|
|
||||||
|
|
||||||
|
class UnexpectedEther(DetectionModule): |
||||||
|
"""This module checks for strict equality checks with contract balance.""" |
||||||
|
|
||||||
|
author = "MythX Team" |
||||||
|
plugin_license = "All rights reserved - ConsenSys" |
||||||
|
plugin_type = "Detection Module" |
||||||
|
plugin_description = DESCRIPTION |
||||||
|
plugin_default_enabled = True |
||||||
|
|
||||||
|
name = "Unexpected Ether Balance" |
||||||
|
swc_id = "132" |
||||||
|
description = DESCRIPTION |
||||||
|
pre_hooks = ["INVALID", "EQ", "RETURN", "STOP"] |
||||||
|
post_hooks = ["BALANCE"] |
||||||
|
|
||||||
|
def _execute(self, state: GlobalState) -> None: |
||||||
|
""" |
||||||
|
|
||||||
|
:param state: |
||||||
|
:return: |
||||||
|
""" |
||||||
|
|
||||||
|
return self._analyze_state(state) |
||||||
|
|
||||||
|
def _analyze_state(self, state): |
||||||
|
""" |
||||||
|
|
||||||
|
:param state: |
||||||
|
:return: |
||||||
|
""" |
||||||
|
node = state.node |
||||||
|
instruction = state.get_current_instruction() |
||||||
|
if is_prehook() is False: |
||||||
|
balance = state.mstate.stack[-1] |
||||||
|
annotations = state.get_annotations(EtherBalanceCheckAnnotation) |
||||||
|
for annotation in annotations: |
||||||
|
if annotation.balance == balance: |
||||||
|
return [] |
||||||
|
|
||||||
|
state.annotate(EtherBalanceCheckAnnotation(balance)) |
||||||
|
return [] |
||||||
|
|
||||||
|
if instruction["opcode"] == "EQ": |
||||||
|
op1, op2 = state.mstate.stack[-2:] |
||||||
|
annotations = list(state.get_annotations(EtherBalanceCheckAnnotation)) |
||||||
|
op = None |
||||||
|
for annotation in annotations: |
||||||
|
if hash(annotation.balance) != hash(op1) and hash( |
||||||
|
annotation.balance |
||||||
|
) != hash(op2): |
||||||
|
continue |
||||||
|
if hash(annotation.balance) == hash(op1): |
||||||
|
op = op1 |
||||||
|
else: |
||||||
|
op = op2 |
||||||
|
break |
||||||
|
if op is None: |
||||||
|
return [] |
||||||
|
op.annotate( |
||||||
|
BalanceConditionAnnotation( |
||||||
|
address=state.get_current_instruction()["address"] |
||||||
|
) |
||||||
|
) |
||||||
|
log.debug( |
||||||
|
"Equality check for contract balance in function " + node.function_name |
||||||
|
) |
||||||
|
return [] |
||||||
|
|
||||||
|
for constraint in state.world_state.constraints: |
||||||
|
for annotation in constraint.get_annotations(BalanceConditionAnnotation): |
||||||
|
if annotation.address in self.cache: |
||||||
|
continue |
||||||
|
try: |
||||||
|
transaction_sequence = get_transaction_sequence( |
||||||
|
state, state.world_state.constraints |
||||||
|
) |
||||||
|
except UnsatError: |
||||||
|
continue |
||||||
|
log.debug("Detected a strict equality check") |
||||||
|
title = "Strict Ether balance check" |
||||||
|
description_head = "Use of strict ether balance checking" |
||||||
|
description_tail = "Ether can be forcefully sent to this contract, This may make the contract unusable." |
||||||
|
|
||||||
|
issue = Issue( |
||||||
|
contract=state.environment.active_account.contract_name, |
||||||
|
function_name=state.environment.active_function_name, |
||||||
|
address=annotation.address, |
||||||
|
title=title, |
||||||
|
bytecode=state.environment.code.bytecode, |
||||||
|
swc_id=UNEXPECTED_ETHER_BALANCE, |
||||||
|
severity="Low", |
||||||
|
description_head=description_head, |
||||||
|
description_tail=description_tail, |
||||||
|
transaction_sequence=transaction_sequence, |
||||||
|
) |
||||||
|
state.annotate( |
||||||
|
IssueAnnotation( |
||||||
|
conditions=[And(*state.world_state.constraints)], |
||||||
|
issue=issue, |
||||||
|
detector=self, |
||||||
|
) |
||||||
|
) |
||||||
|
|
||||||
|
return [issue] |
||||||
|
return [] |
||||||
|
|
||||||
|
|
||||||
|
detector = UnexpectedEther() |
@ -0,0 +1,15 @@ |
|||||||
|
pragma solidity ^0.4.25; |
||||||
|
|
||||||
|
contract Bar { |
||||||
|
Foo private f = new Foo(); |
||||||
|
function doubleBaz() public view returns (int256) { |
||||||
|
return 2 * f.baz(1); //Changes the external contract to not hit the overly strong requirement. |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
contract Foo { |
||||||
|
function baz(int256 x) public pure returns (int256) { |
||||||
|
require(0 < x); //You can also fix the contract by changing the input to the uint type and removing the require |
||||||
|
return 42; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
pragma solidity ^0.4.25; |
||||||
|
|
||||||
|
contract Bar { |
||||||
|
Foo private f = new Foo(); |
||||||
|
function doubleBaz() public view returns (int256) { |
||||||
|
return 2 * f.baz(0); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
contract Foo { |
||||||
|
function baz(int256 x) public pure returns (int256) { |
||||||
|
require(0 < x); |
||||||
|
return 42; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
|
||||||
|
pragma solidity ^0.4.16; |
||||||
|
|
||||||
|
contract EthTxOrderDependenceMinimal { |
||||||
|
address public owner; |
||||||
|
bool public claimed; |
||||||
|
uint public reward; |
||||||
|
|
||||||
|
function EthTxOrderDependenceMinimal() public { |
||||||
|
owner = msg.sender; |
||||||
|
} |
||||||
|
|
||||||
|
function setReward() public payable { |
||||||
|
require (!claimed); |
||||||
|
|
||||||
|
require(msg.sender == owner); |
||||||
|
owner.transfer(reward); |
||||||
|
reward = msg.value; |
||||||
|
} |
||||||
|
|
||||||
|
function claimReward(uint256 submission) { |
||||||
|
require (!claimed); |
||||||
|
require(submission < 10); |
||||||
|
|
||||||
|
msg.sender.transfer(reward); |
||||||
|
claimed = true; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
pragma solidity ^0.5.0; |
||||||
|
|
||||||
|
contract Lockdrop { |
||||||
|
|
||||||
|
function lock() |
||||||
|
external |
||||||
|
payable |
||||||
|
{ |
||||||
|
uint256 eth = msg.value; |
||||||
|
address owner = msg.sender; |
||||||
|
assert(address(0x0).balance > msg.value); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
pragma solidity ^0.5.0; |
||||||
|
|
||||||
|
contract Lockdrop { |
||||||
|
|
||||||
|
function lock() |
||||||
|
external |
||||||
|
payable |
||||||
|
{ |
||||||
|
uint256 eth = msg.value; |
||||||
|
address owner = msg.sender; |
||||||
|
assert(address(0x0).balance == msg.value); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue