Enhance delegate call (#1042)

* Enhance delegate call

* Slightly update report texts

* Black thou shalst use

* Upate tests
pull/1047/head
Nikhil Parasaram 6 years ago committed by GitHub
parent d25cd7a47e
commit 9dfd2aa604
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 143
      mythril/analysis/modules/delegatecall.py
  2. 1
      mythril/laser/ethereum/instructions.py
  3. 13
      tests/testdata/outputs_expected/kinds_of_calls.sol.o.json
  4. 17
      tests/testdata/outputs_expected/kinds_of_calls.sol.o.jsonv2
  5. 13
      tests/testdata/outputs_expected/kinds_of_calls.sol.o.markdown
  6. 11
      tests/testdata/outputs_expected/kinds_of_calls.sol.o.text

@ -1,19 +1,72 @@
"""This module contains the detection code for insecure delegate call usage.""" """This module contains the detection code for insecure delegate call usage."""
import re import json
import logging import logging
from typing import List from copy import copy
from typing import List, cast
from mythril.analysis import solver
from mythril.analysis.swc_data import DELEGATECALL_TO_UNTRUSTED_CONTRACT from mythril.analysis.swc_data import DELEGATECALL_TO_UNTRUSTED_CONTRACT
from mythril.analysis.ops import get_variable, VarType, Call, Variable
from mythril.analysis.report import Issue from mythril.analysis.report import Issue
from mythril.analysis.call_helpers import get_call_from_state
from mythril.analysis.modules.base import DetectionModule from mythril.analysis.modules.base import DetectionModule
from mythril.exceptions import UnsatError
from mythril.laser.ethereum.state.annotation import StateAnnotation
from mythril.laser.ethereum.state.global_state import GlobalState from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.laser.smt import symbol_factory, UGT
from mythril.laser.smt import symbol_factory, UGT
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class DelegateCallAnnotation(StateAnnotation):
def __init__(self, call_state: GlobalState) -> None:
"""
Initialize DelegateCall Annotation
:param call_state: Call state
"""
self.call_state = call_state
self.return_value = call_state.new_bitvec(
"retval_{}".format(call_state.get_current_instruction()["address"]), 256
)
def get_issue(self, global_state: GlobalState, transaction_sequence: str) -> Issue:
"""
Returns Issue for the annotation
:param global_state: Global State
:param transaction_sequence: Transaction sequence
:return: Issue
"""
address = self.call_state.get_current_instruction()["address"]
logging.debug(
"[DELEGATECALL] Detected delegatecall to a user-supplied address : {}".format(
address
)
)
description_head = "The contract delegates execution to another contract with a user-supplied address."
description_tail = (
"The smart contract delegates execution to a user-supplied address. Note that callers "
"can execute arbitrary contracts and that the callee contract "
"can access the storage of the calling contract. "
)
return Issue(
contract=self.call_state.environment.active_account.contract_name,
function_name=self.call_state.environment.active_function_name,
address=address,
swc_id=DELEGATECALL_TO_UNTRUSTED_CONTRACT,
title="Delegatecall Proxy To User-Supplied Address",
bytecode=global_state.environment.code.bytecode,
severity="Medium",
description_head=description_head,
description_tail=description_tail,
debug=transaction_sequence,
gas_used=(
global_state.mstate.min_gas_used,
global_state.mstate.max_gas_used,
),
)
class DelegateCallModule(DetectionModule): class DelegateCallModule(DetectionModule):
"""This module detects calldata being forwarded using DELEGATECALL.""" """This module detects calldata being forwarded using DELEGATECALL."""
@ -24,7 +77,7 @@ class DelegateCallModule(DetectionModule):
swc_id=DELEGATECALL_TO_UNTRUSTED_CONTRACT, swc_id=DELEGATECALL_TO_UNTRUSTED_CONTRACT,
description="Check for invocations of delegatecall(msg.data) in the fallback function.", description="Check for invocations of delegatecall(msg.data) in the fallback function.",
entrypoint="callback", entrypoint="callback",
pre_hooks=["DELEGATECALL"], pre_hooks=["DELEGATECALL", "RETURN", "STOP"],
) )
def execute(self, state: GlobalState) -> list: def execute(self, state: GlobalState) -> list:
@ -43,59 +96,47 @@ def _analyze_states(state: GlobalState) -> List[Issue]:
:param state: the current state :param state: the current state
:return: returns the issues for that corresponding state :return: returns the issues for that corresponding state
""" """
call = get_call_from_state(state) issues = []
if call is None: op_code = state.get_current_instruction()["opcode"]
return [] annotations = cast(
issues = [] # type: List[Issue] List[DelegateCallAnnotation],
list(state.get_annotations(DelegateCallAnnotation)),
)
if call.type is not "DELEGATECALL": if len(annotations) == 0 and op_code in ("RETURN", "STOP"):
return []
if state.environment.active_function_name is not "fallback":
return [] return []
state = call.state if op_code == "DELEGATECALL":
address = state.get_current_instruction()["address"] gas = state.mstate.stack[-1]
meminstart = get_variable(state.mstate.stack[-3]) to = state.mstate.stack[-2]
if meminstart.type == VarType.CONCRETE: constraints = copy(state.mstate.constraints)
issues += _concrete_call(call, state, address, meminstart) # Check whether we can also set the callee address
return issues constraints += [
to == 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF,
UGT(gas, symbol_factory.BitVecVal(2300, 256)),
]
try:
solver.get_model(constraints)
state.annotate(DelegateCallAnnotation(call_state=state))
except UnsatError:
log.debug("[DELEGATECALL] Annotation skipped.")
def _concrete_call(
call: Call, state: GlobalState, address: int, meminstart: Variable
) -> List[Issue]:
"""
:param call: The current call's information
:param state: The current state
:param address: The PC address
:param meminstart: memory starting position
:return: issues
"""
if not re.search(r"calldata.*\[0", str(state.mstate.memory[meminstart.val])):
return [] return []
else:
issue = Issue( for annotation in annotations:
contract=state.environment.active_account.contract_name, try:
function_name=state.environment.active_function_name, transaction_sequence = solver.get_transaction_sequence(
address=address, state, state.mstate.constraints + [annotation.return_value == 1]
swc_id=DELEGATECALL_TO_UNTRUSTED_CONTRACT, )
bytecode=state.environment.code.bytecode, debug = json.dumps(transaction_sequence, indent=4)
title="Delegatecall Proxy", issues.append(annotation.get_issue(state, transaction_sequence=debug))
severity="Low", except UnsatError:
description_head="The contract implements a delegatecall proxy.", continue
description_tail="The smart contract forwards the received calldata via delegatecall. Note that callers "
"can execute arbitrary functions in the callee contract and that the callee contract " return issues
"can access the storage of the calling contract. "
"Make sure that the callee contract is audited properly.",
gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used),
)
target = hex(call.to.val) if call.to.type == VarType.CONCRETE else str(call.to)
issue.description += "DELEGATECALL target: {}".format(target)
return [issue]
detector = DelegateCallModule() detector = DelegateCallModule()

@ -2109,7 +2109,6 @@ class Instruction:
"retval_" + str(instr["address"]), 256 "retval_" + str(instr["address"]), 256
) )
global_state.mstate.stack.append(return_value) global_state.mstate.stack.append(return_value)
global_state.mstate.constraints.append(return_value == 0)
return [global_state] return [global_state]
try: try:

@ -27,6 +27,19 @@
"swc-id": "111", "swc-id": "111",
"title": "Use of callcode" "title": "Use of callcode"
}, },
{
"address": 849,
"contract": "Unknown",
"debug": "<DEBUG-DATA>",
"description": "The contract delegates execution to another contract with a user-supplied address.\nThe smart contract delegates execution to a user-supplied address. Note that callers can execute arbitrary contracts and that the callee contract can access the storage of the calling contract. ",
"function": "_function_0x9b58bc26",
"max_gas_used": 35928,
"min_gas_used": 1176,
"severity": "Medium",
"sourceMap": null,
"swc-id": "112",
"title": "Delegatecall Proxy To User-Supplied Address"
},
{ {
"address": 849, "address": 849,
"contract": "Unknown", "contract": "Unknown",

@ -1,6 +1,23 @@
[ [
{ {
"issues": [ "issues": [
{
"description": {
"head": "The contract delegates execution to another contract with a user-supplied address.",
"tail": "The smart contract delegates execution to a user-supplied address. Note that callers can execute arbitrary contracts and that the callee contract can access the storage of the calling contract. "
},
"extra": {
"discoveryTime": "<DISCOVERY-TIME-DATA>"
},
"locations": [
{
"sourceMap": "849:1:0"
}
],
"severity": "Medium",
"swcID": "SWC-112",
"swcTitle": "Delegatecall to Untrusted Callee"
},
{ {
"description": { "description": {
"head": "Use of callcode is deprecated.", "head": "Use of callcode is deprecated.",

@ -26,6 +26,19 @@ External calls return a boolean value. If the callee contract halts with an exce
Use of callcode is deprecated. Use of callcode is deprecated.
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. 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.
## Delegatecall Proxy To User-Supplied Address
- SWC ID: 112
- Severity: Medium
- Contract: Unknown
- Function name: `_function_0x9b58bc26`
- PC address: 849
- Estimated Gas Usage: 1176 - 35928
### Description
The contract delegates execution to another contract with a user-supplied address.
The smart contract delegates execution to a user-supplied address. Note that callers can execute arbitrary contracts and that the callee contract can access the storage of the calling contract.
## Unchecked Call Return Value ## Unchecked Call Return Value
- SWC ID: 104 - SWC ID: 104
- Severity: Low - Severity: Low

@ -20,6 +20,17 @@ Use of callcode is deprecated.
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. 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.
-------------------- --------------------
==== Delegatecall Proxy To User-Supplied Address ====
SWC ID: 112
Severity: Medium
Contract: Unknown
Function name: _function_0x9b58bc26
PC address: 849
Estimated Gas Usage: 1176 - 35928
The contract delegates execution to another contract with a user-supplied address.
The smart contract delegates execution to a user-supplied address. Note that callers can execute arbitrary contracts and that the callee contract can access the storage of the calling contract.
--------------------
==== Unchecked Call Return Value ==== ==== Unchecked Call Return Value ====
SWC ID: 104 SWC ID: 104
Severity: Low Severity: Low

Loading…
Cancel
Save