Created
June 3, 2023 16:57
-
-
Save Shungy/897dfa827ce7a5e56a9885887157abfe to your computer and use it in GitHub Desktop.
slither detector
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from slither.core.solidity_types.elementary_type import ElementaryType, Int, Uint | |
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification | |
from slither.slithir.operations import Assignment, Binary, BinaryType, Return, TypeConversion | |
from slither.slithir.variables import Constant | |
def _is_integer(variable): | |
return str(variable.type) in Uint + Int | |
def _get_variable_size(variable): | |
# Slither erroneously returns size 256 for all Uint and Int literals. | |
if isinstance(variable, Constant) and _is_integer(variable.type): | |
return 8 # TODO: Return the actual size. | |
return variable.type.size | |
def _is_type_with_no_size(variable): | |
return not (isinstance(variable.type, ElementaryType) and not variable.type.is_dynamic) | |
def _has_truncated_output(ir): | |
if not isinstance(ir, Binary): | |
return False | |
# Basic filtering. | |
if not ir.type.can_be_checked_for_overflow(): | |
return False | |
if not isinstance(ir.lvalue.type, ElementaryType): | |
return False | |
if str(ir.lvalue.type) not in Uint + Int: | |
return False | |
if str(ir.variable_left.type) not in Uint + Int: | |
return False | |
if str(ir.variable_right.type) not in Uint + Int: | |
return False | |
if _get_variable_size(ir.variable_left) == 256: | |
return False | |
if _get_variable_size(ir.variable_right) == 256: | |
return False | |
# Modulo cannot overflow. | |
if ir.type == BinaryType.MODULO: | |
return False | |
# Division can only overflow for type Int. | |
if ir.type == BinaryType.DIVISION: | |
if ir.lvalue.type == Uint: | |
return False | |
# This might result us to miss some cases in unchecked operations. | |
if ir.type == BinaryType.SUBTRACTION: | |
if ir.lvalue.type == Uint: | |
return False | |
return True | |
class OverflowBeforeUpcasting(AbstractDetector): | |
""" | |
Documentation | |
""" | |
ARGUMENT = "overflow-before-upcasting" | |
HELP = "Unexpected overflow before upcasting the variable." | |
IMPACT = DetectorClassification.HIGH | |
CONFIDENCE = DetectorClassification.HIGH | |
WIKI = ( | |
"https://github.com/crytic/slither/wiki/Detector-Documentation" | |
) | |
WIKI_TITLE = "Overflow Before Upcasting" | |
# region wiki_description | |
WIKI_DESCRIPTION = """ | |
Detection of an avoidable overflow. If a variable is upcasted right after an operation, it shows the operation was performed in a smaller type size than the desired output. This can lead to unexpected results, such as a revert or an overflow. | |
""" | |
# endregion wiki_description | |
# region wiki_exploit_scenario | |
WIKI_EXPLOIT_SCENARIO = """ | |
```solidity | |
uint256 a = type(248).max + 1; | |
``` | |
`a` fits 256-bits, but the operation is performed in 248-bits.""" | |
# endregion wiki_exploit_scenario | |
WIKI_RECOMMENDATION = "Explicitly cast one of the operands to the type of the output variable." | |
def _detect(self): # pylint: disable=too-many-branches,too-many-statements | |
results = [] | |
functions = [] | |
# Gather all functions in the contract. | |
# TODO: Include other units where this detector can be useful. | |
functions = self.compilation_unit.functions_top_level | |
for contract in self.contracts: | |
functions += contract.functions_and_modifiers_declared | |
raw_results = [] | |
for function in functions: # pylint: disable=too-many-nested-blocks | |
for node in function.nodes: | |
# Reset at every node because upcasting in a separate node indicates deliberation. | |
truncated_variables = [] | |
for ir in node.irs: | |
# Find all binary operations that operate in less-than-256-bits space. | |
if _has_truncated_output(ir): | |
truncated_variables.append(ir.lvalue) | |
# Find all instances of upcasting made on the result of the truncated variable. | |
if isinstance(ir, Binary): | |
# Basic filtering. | |
if not ir.type.can_be_checked_for_overflow(): | |
continue | |
if not isinstance(ir.lvalue.type, ElementaryType): | |
continue | |
if str(ir.lvalue.type) not in Uint + Int: | |
continue | |
if str(ir.variable_left.type) not in Uint + Int: | |
continue | |
if str(ir.variable_right.type) not in Uint + Int: | |
continue | |
if _get_variable_size(ir.variable_left) == _get_variable_size(ir.variable_right): | |
continue | |
if _get_variable_size(ir.variable_left) < _get_variable_size(ir.variable_right): | |
if ir.variable_left in truncated_variables: | |
raw_results.append((function, node)) | |
else: | |
if ir.variable_right in truncated_variables: | |
raw_results.append((function, node)) | |
elif isinstance(ir, Return): | |
# SlithIR does not show conversion to return type, so we have to check that explicitly. | |
for intermediate_value, return_value in zip(ir.values, function.returns): | |
if intermediate_value not in truncated_variables: | |
continue | |
if _is_type_with_no_size(return_value): | |
continue | |
if _is_type_with_no_size(intermediate_value): | |
continue | |
if return_value.type.size > _get_variable_size(intermediate_value): | |
raw_results.append((function, node)) | |
elif isinstance(ir, Assignment): | |
if ir.rvalue not in truncated_variables: | |
continue | |
if _is_type_with_no_size(ir.lvalue): | |
continue | |
if _is_type_with_no_size(ir.rvalue): | |
continue | |
if ir.lvalue.type.size > _get_variable_size(ir.rvalue): | |
raw_results.append((function, node)) | |
elif isinstance(ir, TypeConversion): | |
if ir.variable not in truncated_variables: | |
continue | |
if _is_type_with_no_size(ir): | |
continue | |
if _is_type_with_no_size(ir.variable): | |
continue | |
if ir.type.size > _get_variable_size(ir.variable): | |
raw_results.append((function, node)) | |
else: continue | |
for raw_result in raw_results: | |
function = raw_result[0] | |
node = raw_result[1] | |
info = [function, ' truncates result before upcasting:\n\t- ', node, '\n'] | |
result = self.generate_result(info) | |
results.append(result) | |
return results |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment