Skip to content

Instantly share code, notes, and snippets.

@Shungy
Created June 3, 2023 16:57
Show Gist options
  • Save Shungy/897dfa827ce7a5e56a9885887157abfe to your computer and use it in GitHub Desktop.
Save Shungy/897dfa827ce7a5e56a9885887157abfe to your computer and use it in GitHub Desktop.
slither detector
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