From 8571d301d2102208bc1668bd3121c22cce0330cb Mon Sep 17 00:00:00 2001 From: Nathan Date: Tue, 17 Sep 2019 10:01:32 -0400 Subject: [PATCH 1/5] Implement user supplied assertion module --- mythril/analysis/modules/user_assertions.py | 106 ++++++++++++++++++++ mythril/laser/ethereum/instructions.py | 2 +- 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 mythril/analysis/modules/user_assertions.py diff --git a/mythril/analysis/modules/user_assertions.py b/mythril/analysis/modules/user_assertions.py new file mode 100644 index 00000000..0c87798f --- /dev/null +++ b/mythril/analysis/modules/user_assertions.py @@ -0,0 +1,106 @@ +"""This module contains the detection code for potentially insecure low-level +calls.""" + +from mythril.analysis.potential_issues import ( + PotentialIssue, + get_potential_issues_annotation, +) +from mythril.analysis.swc_data import ASSERT_VIOLATION +from mythril.analysis.modules.base import DetectionModule +from mythril.laser.ethereum.state.global_state import GlobalState +import logging +import eth_abi + +log = logging.getLogger(__name__) + +DESCRIPTION = """ + +Search for reachable user-supplied exceptions. +Report a warning if an log message is emitted: 'emit AssertionFailed(string)' + +""" + +assertion_failed_hash = ( + 0xB42604CB105A16C8F6DB8A41E6B00C0C1B4826465E8BC504B3EB3E88B3E6A4A0 +) + + +class UserAssertions(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=ASSERT_VIOLATION, + description=DESCRIPTION, + entrypoint="callback", + pre_hooks=["LOG1"], + ) + + 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) + + def _analyze_state(self, state: GlobalState): + """ + + :param state: + :return: + """ + mem_start = state.mstate.stack[-1] + size = state.mstate.stack[-2] + topic = state.mstate.stack[-3] + + if topic.symbolic: + return [] + + if topic.value != assertion_failed_hash: + return [] + + message = None + if not mem_start.symbolic and not size.symbolic: + message = eth_abi.decode_single( + "string", + bytes( + state.mstate.memory[ + mem_start.value + 32 : mem_start.value + size.value + ] + ), + ).decode("utf8") + + description_head = "A user-provided assertion failed. Make sure the user-provided assertion is correct." + if message: + description_tail = "A user-provided assertion failed with message '{}'. Make sure the user-provided assertion is correct.".format( + message + ) + else: + description_tail = "A user-provided assertion failed. Make sure the user-provided assertion is correct." + + address = state.get_current_instruction()["address"] + issue = PotentialIssue( + contract=state.environment.active_account.contract_name, + function_name=state.environment.active_function_name, + address=address, + swc_id=ASSERT_VIOLATION, + title="Assertion Failed", + bytecode=state.environment.code.bytecode, + severity="Medium", + description_head=description_head, + description_tail=description_tail, + constraints=[], + detector=self, + ) + + return [issue] + + +detector = UserAssertions() diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index 4bd8436f..75af57e3 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -1624,7 +1624,7 @@ class Instruction: # TODO: implement me state = global_state.mstate dpth = int(self.op_code[3:]) - state.stack.pop(), state.stack.pop() + mem_start, size = state.stack.pop(), state.stack.pop() log_data = [state.stack.pop() for _ in range(dpth)] # Not supported return [global_state] From f781ce3604e11c2475e59e8f412746f9cc0067de Mon Sep 17 00:00:00 2001 From: Nathan Date: Tue, 17 Sep 2019 10:11:15 -0400 Subject: [PATCH 2/5] Add user supplied assertions module to documentation --- docs/source/module-list.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/source/module-list.rst b/docs/source/module-list.rst index 11c40d02..101ff94f 100644 --- a/docs/source/module-list.rst +++ b/docs/source/module-list.rst @@ -67,3 +67,9 @@ Unchecked Retval **************** The `unchecked retval module `_ detects `SWC-104 (Unchecked Call Return Value) `_. + +**************** +Unchecked Retval +**************** + +The `user supplied assertion module `_ detects `SWC-110 (Assert Violation) `_ for user-supplied assertions. User supplied assertions should be log messages of the form: :code:`emit AssertionFailed(string)`. From 3cab4beb768b3ff23bd6491664735815faf376af Mon Sep 17 00:00:00 2001 From: Nathan Date: Thu, 19 Sep 2019 17:33:36 -0400 Subject: [PATCH 3/5] Minor improvements to user_assertions.py --- mythril/analysis/modules/user_assertions.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mythril/analysis/modules/user_assertions.py b/mythril/analysis/modules/user_assertions.py index 0c87798f..0e68475e 100644 --- a/mythril/analysis/modules/user_assertions.py +++ b/mythril/analysis/modules/user_assertions.py @@ -26,8 +26,7 @@ assertion_failed_hash = ( class UserAssertions(DetectionModule): - """This module searches for low level calls (e.g. call.value()) that - forward all gas to the callee.""" + """This module searches for user supplied exceptions: emit AssertionFailed("Error").""" def __init__(self): """""" @@ -77,7 +76,7 @@ class UserAssertions(DetectionModule): ), ).decode("utf8") - description_head = "A user-provided assertion failed. Make sure the user-provided assertion is correct." + description_head = "A user-provided assertion failed." if message: description_tail = "A user-provided assertion failed with message '{}'. Make sure the user-provided assertion is correct.".format( message From 2a34a5d71207b0e4a68ebab9676129c5c271f1aa Mon Sep 17 00:00:00 2001 From: Nathan Date: Mon, 23 Sep 2019 12:50:58 -0400 Subject: [PATCH 4/5] Remove unused variables --- mythril/laser/ethereum/instructions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index 75af57e3..4bd8436f 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -1624,7 +1624,7 @@ class Instruction: # TODO: implement me state = global_state.mstate dpth = int(self.op_code[3:]) - mem_start, size = state.stack.pop(), state.stack.pop() + state.stack.pop(), state.stack.pop() log_data = [state.stack.pop() for _ in range(dpth)] # Not supported return [global_state] From bd8c3f8cd4d9c0911546393094d4a18457792a94 Mon Sep 17 00:00:00 2001 From: Nathan Date: Tue, 24 Sep 2019 09:13:37 -0400 Subject: [PATCH 5/5] Minor improvements to user_assertions.py --- mythril/analysis/modules/user_assertions.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/mythril/analysis/modules/user_assertions.py b/mythril/analysis/modules/user_assertions.py index 0e68475e..bef9c33a 100644 --- a/mythril/analysis/modules/user_assertions.py +++ b/mythril/analysis/modules/user_assertions.py @@ -55,14 +55,9 @@ class UserAssertions(DetectionModule): :param state: :return: """ - mem_start = state.mstate.stack[-1] - size = state.mstate.stack[-2] - topic = state.mstate.stack[-3] + topic, size, mem_start = state.mstate.stack[-3:] - if topic.symbolic: - return [] - - if topic.value != assertion_failed_hash: + if topic.symbolic or topic.value != assertion_failed_hash: return [] message = None