diff --git a/mythril/analysis/modules/delegatecall.py b/mythril/analysis/modules/delegatecall.py new file mode 100644 index 00000000..8475f7b1 --- /dev/null +++ b/mythril/analysis/modules/delegatecall.py @@ -0,0 +1,78 @@ +from z3 import * +import re +from mythril.analysis.ops import * +from mythril.analysis.report import Issue +import logging + + +''' +MODULE DESCRIPTION: + +Check for invocations of delegatecall(msg.data) in the fallback function. +''' + + +def execute(statespace): + + logging.debug("Executing module: DELEGATECALL") + + issues = [] + visited = [] + + for call in statespace.calls: + + state = call.state + address = state.get_current_instruction()['address'] + + if (call.type == "DELEGATECALL"): + + if (call.node.function_name == "fallback"): + + stack = call.state.mstate.stack + + meminstart = get_variable(stack[-3]) + + if meminstart.type == VarType.CONCRETE: + + if (re.search(r'calldata.*_0', str(state.mstate.memory[meminstart.val]))): + + issue = Issue(call.node.contract_name, call.node.function_name, address, "CALLDATA forwarded with delegatecall()", "Informational") + + issue.description = \ + "This contract forwards its calldata via DELEGATECALL in its fallback function. " \ + "This means that any function in the called contract can be executed. Note that the callee contract will have access to the storage of the calling contract.\n" + + if (call.to.type == VarType.CONCRETE): + issue.description += ("DELEGATECALL target: " + hex(call.to.val)) + else: + issue.description += "DELEGATECALL target: " + str(call.to) + + issues.append(issue) + + if (call.to.type == VarType.SYMBOLIC): + + issue = Issue(call.node.contract_name, call.node.function_name, address, call.type + " to a user-supplied address") + + if ("calldata" in str(call.to)): + + issue.description = \ + "This contract delegates execution to a contract address obtained from calldata. " + + else: + m = re.search(r'storage_([a-z0-9_&^]+)', str(call.to)) + + if (m): + index = m.group(1) + + func = statespace.find_storage_write(idx) + + if (func): + issue.description = "This contract delegates execution to a contract address in storage slot " + str(index) + ". This storage slot can be written to by calling the function '" + func + "'. " + + else: + logging.debug("[DELEGATECALL] No storage writes to index " + str(index)) + + issue.description += "Be aware that the called contract gets unrestricted access to this contract's state." + issues.append(issue) + + return issues diff --git a/mythril/analysis/modules/delegatecall_forward.py b/mythril/analysis/modules/delegatecall_forward.py deleted file mode 100644 index 30393ecd..00000000 --- a/mythril/analysis/modules/delegatecall_forward.py +++ /dev/null @@ -1,57 +0,0 @@ -from z3 import * -import re -from mythril.analysis.ops import * -from mythril.analysis.report import Issue -import logging - - -''' -MODULE DESCRIPTION: - -Check for invocations of delegatecall(msg.data) in the fallback function. -''' - - -def execute(statespace): - - logging.debug("Executing module: DELEGATECALL_FORWARD") - - issues = [] - visited = [] - - for call in statespace.calls: - - state = call.state - address = state.get_current_instruction()['address'] - - # Only needs to be checked once per call instructions (essentially just static analysis) - - if address in visited: - continue - else: - visited.append(address) - - if (call.type == "DELEGATECALL") and (call.node.function_name == "fallback"): - - stack = call.state.mstate.stack - - meminstart = get_variable(stack[-3]) - - if meminstart.type == VarType.CONCRETE: - - if (re.search(r'calldata.*_0', str(state.mstate.memory[meminstart.val]))): - - issue = Issue(call.node.contract_name, call.node.function_name, address, "CALLDATA forwarded with delegatecall()", "Informational") - - issue.description = \ - "This contract forwards its calldata via DELEGATECALL in its fallback function. " \ - "This means that any function in the called contract can be executed. Note that the callee contract will have access to the storage of the calling contract.\n" - - if (call.to.type == VarType.CONCRETE): - issue.description += ("DELEGATECALL target: " + hex(call.to.val)) - else: - issue.description += "DELEGATECALL target: " + str(call.to) - - issues.append(issue) - - return issues diff --git a/mythril/analysis/modules/delegatecall_to_dynamic.py b/mythril/analysis/modules/delegatecall_to_dynamic.py deleted file mode 100644 index 0c688d90..00000000 --- a/mythril/analysis/modules/delegatecall_to_dynamic.py +++ /dev/null @@ -1,71 +0,0 @@ -from z3 import * -from mythril.analysis.ops import * -from mythril.analysis.report import Issue -import re -import logging - - -''' -MODULE DESCRIPTION: - -Check for invocations of delegatecall/callcode to a user-supplied address -''' - - -def execute(statespace): - - logging.debug("Executing module: DELEGATECALL_TO_DYNAMIC") - - issues = [] - - for call in statespace.calls: - - state = call.state - address = state.get_current_instruction()['address'] - - if (call.type == "DELEGATECALL" or call.type == "CALLCODE"): - - if (call.to.type == VarType.SYMBOLIC): - - if ("calldata" in str(call.to)): - issue = Issue(call.node.contract_name, call.node.function_name, address, call.type + " to dynamic address") - - issue.description = \ - "The function " + call.node.function_name + " delegates execution to a contract address obtained from calldata.\n" \ - "Recipient address: " + str(call.to) - - issues.append(issue) - else: - m = re.search(r'storage_([a-z0-9_&^]+)', str(call.to)) - - if (m): - index = m.group(1) - logging.debug("DELEGATECALL to contract address in storage") - - try: - - for s in statespace.sstors[index]: - - if s.tainted: - issue = Issue(call.type + " to dynamic address in storage", "Warning") - issue.description = \ - "The function " + call.node.function_name + " in contract '" + call.node.contract_name + " delegates execution to a contract address stored in a state variable. " \ - "There is a check on storage index " + str(index) + ". This storage index can be written to by calling the function '" + s.node.function_name + "'.\n" \ - "Make sure that the contract address cannot be set by untrusted users." - issues.append(issue) - break - - except KeyError: - logging.debug("[ETHER_SEND] No storage writes to index " + str(index)) - - else: - - issue = Issue(call.node.contract_name, call.node.function_name, address, "DELEGATECALL to dynamic address", "Informational") - - issue.description = \ - "The function " + call.node.function_name + " in contract '" + call.node.contract_name + " delegates execution to a contract with a dynamic address." \ - "To address:" + str(call.to) - - issues.append(issue) - - return issues diff --git a/mythril/analysis/modules/call_to_dynamic_with_gas.py b/mythril/analysis/modules/external_calls.py similarity index 91% rename from mythril/analysis/modules/call_to_dynamic_with_gas.py rename to mythril/analysis/modules/external_calls.py index cbbaa091..5dc6248b 100644 --- a/mythril/analysis/modules/call_to_dynamic_with_gas.py +++ b/mythril/analysis/modules/external_calls.py @@ -14,7 +14,7 @@ Check for call.value()() to an untrusted address def execute(statespace): - logging.debug("Executing module: CALL_TO_DYNAMIC_WITH_GAS") + logging.debug("Executing module: EXTERNAL_CALLS") issues = [] @@ -25,7 +25,7 @@ def execute(statespace): if (call.type == "CALL"): - logging.debug("[CALL_TO_DYNAMIC_WITH_GAS] Call to: " + str(call.to) + ", value " + str(call.value) + ", gas = " + str(call.gas)) + logging.debug("[EXTERNAL_CALLS] Call to: " + str(call.to) + ", value " + str(call.value) + ", gas = " + str(call.gas)) if (call.to.type == VarType.SYMBOLIC and (call.gas.type == VarType.CONCRETE and call.gas.val > 2300) or (call.gas.type == VarType.SYMBOLIC and "2300" not in str(call.gas))):