Breaking change in the way Slither handled codebase with name reused

- Add NameReused detector to report the name's reuse
- Slither will analyze the codebase that contains the issue, with partial context. As a result, the analyze might not be correct
pull/413/head
Josselin 5 years ago
parent d76bec9850
commit 1b6f50ce29
  1. 2
      slither/all_exceptions.py
  2. 17
      slither/core/declarations/contract.py
  3. 20
      slither/core/slither_core.py
  4. 1
      slither/detectors/all_detectors.py
  5. 0
      slither/detectors/slither/__init__.py
  6. 75
      slither/detectors/slither/name_reused.py
  7. 2
      slither/slither.py
  8. 88
      slither/solc_parsing/declarations/contract.py
  9. 4
      slither/solc_parsing/exceptions.py
  10. 41
      slither/solc_parsing/slitherSolc.py

@ -2,6 +2,6 @@
This module import all slither exceptions This module import all slither exceptions
""" """
from slither.slithir.exceptions import SlithIRError from slither.slithir.exceptions import SlithIRError
from slither.solc_parsing.exceptions import ParsingError, ParsingContractNotFound, ParsingNameReuse, VariableNotFound from slither.solc_parsing.exceptions import ParsingError, VariableNotFound
from slither.core.exceptions import SlitherCoreError from slither.core.exceptions import SlitherCoreError
from slither.exceptions import SlitherException from slither.exceptions import SlitherException

@ -50,6 +50,8 @@ class Contract(ChildSlither, SourceMapping):
self._initial_state_variables = [] # ssa self._initial_state_variables = [] # ssa
self._is_incorrectly_parsed = False
################################################################################### ###################################################################################
################################################################################### ###################################################################################
# region General's properties # region General's properties
@ -874,6 +876,21 @@ class Contract(ChildSlither, SourceMapping):
return self._is_upgradeable_proxy return self._is_upgradeable_proxy
return self._is_upgradeable_proxy return self._is_upgradeable_proxy
# endregion
###################################################################################
###################################################################################
# region Internals
###################################################################################
###################################################################################
@property
def is_incorrectly_constructed(self):
"""
Return true if there was an internal Slither's issue when analyzing the contract
:return:
"""
return self._is_incorrectly_parsed
# endregion # endregion
################################################################################### ###################################################################################
################################################################################### ###################################################################################

@ -5,6 +5,8 @@ import os
import logging import logging
import json import json
import re import re
from collections import defaultdict
from slither.core.context.context import Context from slither.core.context.context import Context
from slither.slithir.operations import InternalCall from slither.slithir.operations import InternalCall
from slither.utils.colors import red from slither.utils.colors import red
@ -43,6 +45,9 @@ class Slither(Context):
self._markdown_root = "" self._markdown_root = ""
self._contract_name_collisions = defaultdict(list)
self._contract_with_missing_inheritance = set()
################################################################################### ###################################################################################
################################################################################### ###################################################################################
# region Source code # region Source code
@ -300,4 +305,19 @@ class Slither(Context):
def generate_patches(self, p): def generate_patches(self, p):
self._generate_patches = p self._generate_patches = p
# endregion
###################################################################################
###################################################################################
# region Internals
###################################################################################
###################################################################################
@property
def contract_name_collisions(self):
return self._contract_name_collisions
@property
def contracts_with_missing_inheritance(self):
return self._contract_with_missing_inheritance
# endregion # endregion

@ -44,5 +44,6 @@ from .statements.type_based_tautology import TypeBasedTautology
from .statements.boolean_constant_equality import BooleanEquality from .statements.boolean_constant_equality import BooleanEquality
from .statements.boolean_constant_misuse import BooleanConstantMisuse from .statements.boolean_constant_misuse import BooleanConstantMisuse
from .statements.divide_before_multiply import DivideBeforeMultiply from .statements.divide_before_multiply import DivideBeforeMultiply
from .slither.name_reused import NameReused
# #
# #

@ -0,0 +1,75 @@
from collections import defaultdict
from slither.detectors.abstract_detector import (AbstractDetector,
DetectorClassification)
def _find_missing_inheritance(slither):
missings = slither.contracts_with_missing_inheritance
ret = []
for b in missings:
is_most_base = True
for inheritance in b.immediate_inheritance:
if inheritance in missings:
is_most_base = False
if is_most_base:
ret.append(b)
return ret
class NameReused(AbstractDetector):
ARGUMENT = 'name-reused'
HELP = "Contract's name reused"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#name-reused'
WIKI_TITLE = 'Name reused'
WIKI_DESCRIPTION = 'todo'
WIKI_EXPLOIT_SCENARIO = 'todo'
WIKI_RECOMMENDATION = 'Rename the contract.'
def _detect(self):
results = []
names_reused = self.slither.contract_name_collisions
incorrectly_constructed = [contract for contract in self.contracts
if contract.is_incorrectly_constructed]
inheritance_corrupted = defaultdict(list)
for contract in incorrectly_constructed:
for father in contract.inheritance:
inheritance_corrupted[father.name].append(contract)
for contract_name, files in names_reused.items():
info = [contract_name, ' is re-used:\n']
for file in files:
if file is None:
info += ['\t- In an file not found, most likely in\n']
else:
info += ['\t- ', file, '\n']
if contract_name in inheritance_corrupted:
info += ['\tAs a result, the inherited contracts are not correctly analyzed:\n']
for corrupted in inheritance_corrupted[contract_name]:
info += ["\t\t- ", corrupted, '\n']
res = self.generate_result(info)
results.append(res)
most_base_with_missing_inheritance = _find_missing_inheritance(self.slither)
for b in most_base_with_missing_inheritance:
info = [b, ' inherits from a contract for which the name is reused.\n',
'Slither could not determine the contract, but it is either:\n']
for inheritance in b.immediate_inheritance:
info += ['\t-', inheritance, '\n']
info += [b, ' and all the contracts inheriting from it are not correctly analyzed:\n']
for derived in b.derived_contracts:
info += ['\t-', derived, '\n']
res = self.generate_result(info)
results.append(res)
return results

@ -10,6 +10,7 @@ from crytic_compile import CryticCompile, InvalidCompilation
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.printers.abstract_printer import AbstractPrinter from slither.printers.abstract_printer import AbstractPrinter
from .solc_parsing.exceptions import VariableNotFound
from .solc_parsing.slitherSolc import SlitherSolc from .solc_parsing.slitherSolc import SlitherSolc
from .exceptions import SlitherError from .exceptions import SlitherError
@ -84,6 +85,7 @@ class Slither(SlitherSolc):
self._analyze_contracts() self._analyze_contracts()
def _init_from_raw_json(self, filename): def _init_from_raw_json(self, filename):
if not os.path.isfile(filename): if not os.path.isfile(filename):
raise SlitherError('{} does not exist (are you in the correct directory?)'.format(filename)) raise SlitherError('{} does not exist (are you in the correct directory?)'.format(filename))

@ -12,15 +12,15 @@ from slither.solc_parsing.declarations.modifier import ModifierSolc
from slither.solc_parsing.declarations.structure import StructureSolc from slither.solc_parsing.declarations.structure import StructureSolc
from slither.solc_parsing.solidity_types.type_parsing import parse_type from slither.solc_parsing.solidity_types.type_parsing import parse_type
from slither.solc_parsing.variables.state_variable import StateVariableSolc from slither.solc_parsing.variables.state_variable import StateVariableSolc
from slither.solc_parsing.exceptions import ParsingError from slither.solc_parsing.exceptions import ParsingError, VariableNotFound
logger = logging.getLogger("ContractSolcParsing") logger = logging.getLogger("ContractSolcParsing")
class ContractSolc04(Contract):
class ContractSolc04(Contract):
def __init__(self, slitherSolc, data): def __init__(self, slitherSolc, data):
#assert slitherSolc.solc_version.startswith('0.4') # assert slitherSolc.solc_version.startswith('0.4')
super(ContractSolc04, self).__init__() super(ContractSolc04, self).__init__()
self.set_slither(slitherSolc) self.set_slither(slitherSolc)
@ -54,7 +54,6 @@ class ContractSolc04(Contract):
self._parse_contract_info() self._parse_contract_info()
self._parse_contract_items() self._parse_contract_items()
################################################################################### ###################################################################################
################################################################################### ###################################################################################
# region General Properties # region General Properties
@ -190,7 +189,7 @@ class ContractSolc04(Contract):
elif item[self.get_key()] == 'UsingForDirective': elif item[self.get_key()] == 'UsingForDirective':
self._usingForNotParsed.append(item) self._usingForNotParsed.append(item)
else: else:
raise ParsingError('Unknown contract item: '+item[self.get_key()]) raise ParsingError('Unknown contract item: ' + item[self.get_key()])
return return
def _parse_struct(self, struct): def _parse_struct(self, struct):
@ -263,7 +262,6 @@ class ContractSolc04(Contract):
for function in self._functionsNotParsed: for function in self._functionsNotParsed:
self._parse_function(function) self._parse_function(function)
self._functionsNotParsed = None self._functionsNotParsed = None
return return
@ -275,41 +273,51 @@ class ContractSolc04(Contract):
################################################################################### ###################################################################################
################################################################################### ###################################################################################
def log_incorrect_parsing(self):
self._is_incorrectly_parsed = True
def analyze_content_modifiers(self): def analyze_content_modifiers(self):
try:
for modifier in self.modifiers: for modifier in self.modifiers:
modifier.analyze_content() modifier.analyze_content()
except (VariableNotFound, KeyError):
self.log_incorrect_parsing()
return return
def analyze_content_functions(self): def analyze_content_functions(self):
try:
for function in self.functions: for function in self.functions:
function.analyze_content() function.analyze_content()
except (VariableNotFound, KeyError):
self.log_incorrect_parsing()
return return
def analyze_params_modifiers(self): def analyze_params_modifiers(self):
try:
elements_no_params = self._modifiers_no_params elements_no_params = self._modifiers_no_params
getter = lambda f: f.modifiers getter = lambda f: f.modifiers
getter_available = lambda f: f.modifiers_declared getter_available = lambda f: f.modifiers_declared
Cls = ModifierSolc Cls = ModifierSolc
self._modifiers = self._analyze_params_elements(elements_no_params, getter, getter_available, Cls) self._modifiers = self._analyze_params_elements(elements_no_params, getter, getter_available, Cls)
except (VariableNotFound, KeyError):
self.log_incorrect_parsing()
self._modifiers_no_params = [] self._modifiers_no_params = []
return return
def analyze_params_functions(self): def analyze_params_functions(self):
try:
elements_no_params = self._functions_no_params elements_no_params = self._functions_no_params
getter = lambda f: f.functions getter = lambda f: f.functions
getter_available = lambda f: f.functions_declared getter_available = lambda f: f.functions_declared
Cls = FunctionSolc Cls = FunctionSolc
self._functions = self._analyze_params_elements(elements_no_params, getter, getter_available, Cls) self._functions = self._analyze_params_elements(elements_no_params, getter, getter_available, Cls)
except (VariableNotFound, KeyError):
self.log_incorrect_parsing()
self._functions_no_params = [] self._functions_no_params = []
return return
def _analyze_params_elements(self, elements_no_params, getter, getter_available, Cls): def _analyze_params_elements(self, elements_no_params, getter, getter_available, Cls):
""" """
Analyze the parameters of the given elements (Function or Modifier). Analyze the parameters of the given elements (Function or Modifier).
@ -323,6 +331,7 @@ class ContractSolc04(Contract):
""" """
all_elements = {} all_elements = {}
try:
for father in self.inheritance: for father in self.inheritance:
for element in getter(father): for element in getter(father):
elem = Cls(element._functionNotParsed, self, element.contract_declarer) elem = Cls(element._functionNotParsed, self, element.contract_declarer)
@ -355,23 +364,20 @@ class ContractSolc04(Contract):
if accessible_elements[element.full_name] != all_elements[element.canonical_name]: if accessible_elements[element.full_name] != all_elements[element.canonical_name]:
element.is_shadowed = True element.is_shadowed = True
accessible_elements[element.full_name].shadows = True accessible_elements[element.full_name].shadows = True
except (VariableNotFound, KeyError):
self.log_incorrect_parsing()
return all_elements return all_elements
def analyze_constant_state_variables(self): def analyze_constant_state_variables(self):
from slither.solc_parsing.expressions.expression_parsing import VariableNotFound
for var in self.variables: for var in self.variables:
if var.is_constant: if var.is_constant:
# cant parse constant expression based on function calls # cant parse constant expression based on function calls
try: try:
var.analyze(self) var.analyze(self)
except VariableNotFound: except (VariableNotFound, KeyError):
pass pass
return return
def _create_node(self, func, counter, variable): def _create_node(self, func, counter, variable):
# Function uses to create node for state variable declaration statements # Function uses to create node for state variable declaration statements
node = Node(NodeType.OTHER_ENTRYPOINT, counter) node = Node(NodeType.OTHER_ENTRYPOINT, counter)
@ -405,7 +411,7 @@ class ContractSolc04(Contract):
prev_node = self._create_node(constructor_variable, 0, variable_candidate) prev_node = self._create_node(constructor_variable, 0, variable_candidate)
variable_candidate.node_initialization = prev_node variable_candidate.node_initialization = prev_node
counter = 1 counter = 1
for v in self.state_variables[idx+1:]: for v in self.state_variables[idx + 1:]:
if v.expression and not v.is_constant: if v.expression and not v.is_constant:
next_node = self._create_node(constructor_variable, counter, v) next_node = self._create_node(constructor_variable, counter, v)
v.node_initialization = next_node v.node_initialization = next_node
@ -430,7 +436,7 @@ class ContractSolc04(Contract):
prev_node = self._create_node(constructor_variable, 0, variable_candidate) prev_node = self._create_node(constructor_variable, 0, variable_candidate)
variable_candidate.node_initialization = prev_node variable_candidate.node_initialization = prev_node
counter = 1 counter = 1
for v in self.state_variables[idx+1:]: for v in self.state_variables[idx + 1:]:
if v.expression and v.is_constant: if v.expression and v.is_constant:
next_node = self._create_node(constructor_variable, counter, v) next_node = self._create_node(constructor_variable, counter, v)
v.node_initialization = next_node v.node_initialization = next_node
@ -440,16 +446,16 @@ class ContractSolc04(Contract):
break break
def analyze_state_variables(self): def analyze_state_variables(self):
try:
for var in self.variables: for var in self.variables:
var.analyze(self) var.analyze(self)
return return
except (VariableNotFound, KeyError):
self.log_incorrect_parsing()
def analyze_using_for(self): def analyze_using_for(self):
try:
for father in self.inheritance: for father in self.inheritance:
self._using_for.update(father.using_for) self._using_for.update(father.using_for)
@ -477,9 +483,11 @@ class ContractSolc04(Contract):
self.using_for[old] = [] self.using_for[old] = []
self._using_for[old].append(new) self._using_for[old].append(new)
self._usingForNotParsed = [] self._usingForNotParsed = []
except (VariableNotFound, KeyError):
self.log_incorrect_parsing()
def analyze_enums(self): def analyze_enums(self):
try:
for father in self.inheritance: for father in self.inheritance:
self._enums.update(father.enums_as_dict()) self._enums.update(father.enums_as_dict())
@ -488,6 +496,8 @@ class ContractSolc04(Contract):
# at the same time # at the same time
self._analyze_enum(enum) self._analyze_enum(enum)
self._enumsNotParsed = None self._enumsNotParsed = None
except (VariableNotFound, KeyError):
self.log_incorrect_parsing()
def _analyze_enum(self, enum): def _analyze_enum(self, enum):
# Enum can be parsed in one pass # Enum can be parsed in one pass
@ -517,10 +527,14 @@ class ContractSolc04(Contract):
struct.analyze() struct.analyze()
def analyze_structs(self): def analyze_structs(self):
try:
for struct in self.structures: for struct in self.structures:
self._analyze_struct(struct) self._analyze_struct(struct)
except (VariableNotFound, KeyError):
self.log_incorrect_parsing()
def analyze_events(self): def analyze_events(self):
try:
for father in self.inheritance_reverse: for father in self.inheritance_reverse:
self._events.update(father.events_as_dict()) self._events.update(father.events_as_dict())
@ -530,11 +544,11 @@ class ContractSolc04(Contract):
event.set_contract(self) event.set_contract(self)
event.set_offset(event_to_parse['src'], self.slither) event.set_offset(event_to_parse['src'], self.slither)
self._events[event.full_name] = event self._events[event.full_name] = event
except (VariableNotFound, KeyError):
self.log_incorrect_parsing()
self._eventsNotParsed = None self._eventsNotParsed = None
# endregion # endregion
################################################################################### ###################################################################################
################################################################################### ###################################################################################
@ -578,6 +592,28 @@ class ContractSolc04(Contract):
for func in self.functions + self.modifiers: for func in self.functions + self.modifiers:
func.fix_phi(last_state_variables_instances, initial_state_variables_instances) func.fix_phi(last_state_variables_instances, initial_state_variables_instances)
# endregion
###################################################################################
###################################################################################
# region Internal
###################################################################################
###################################################################################
def delete_content(self):
"""
Remove everything not parsed from the contract
This is used only if something went wrong with the inheritance parsing
:return:
"""
self._functionsNotParsed = []
self._modifiersNotParsed = []
self._functions_no_params = []
self._modifiers_no_params = []
self._eventsNotParsed = []
self._variablesNotParsed = []
self._enumsNotParsed = []
self._structuresNotParsed = []
self._usingForNotParsed = []
# endregion # endregion
################################################################################### ###################################################################################

@ -2,8 +2,4 @@ from slither.exceptions import SlitherException
class ParsingError(SlitherException): pass class ParsingError(SlitherException): pass
class ParsingNameReuse(SlitherException): pass
class ParsingContractNotFound(SlitherException): pass
class VariableNotFound(SlitherException): pass class VariableNotFound(SlitherException): pass

@ -13,8 +13,6 @@ from slither.core.declarations.pragma_directive import Pragma
from slither.core.declarations.import_directive import Import from slither.core.declarations.import_directive import Import
from slither.analyses.data_dependency.data_dependency import compute_dependency from slither.analyses.data_dependency.data_dependency import compute_dependency
from slither.utils.colors import red
from .exceptions import ParsingNameReuse, ParsingContractNotFound
class SlitherSolc(Slither): class SlitherSolc(Slither):
@ -27,7 +25,6 @@ class SlitherSolc(Slither):
self._is_compact_ast = False self._is_compact_ast = False
################################################################################### ###################################################################################
################################################################################### ###################################################################################
# region AST # region AST
@ -126,7 +123,6 @@ class SlitherSolc(Slither):
import_directive.set_offset(contract_data['src'], self) import_directive.set_offset(contract_data['src'], self)
self._import_directives.append(import_directive) self._import_directives.append(import_directive)
def _parse_source_unit(self, data, filename): def _parse_source_unit(self, data, filename):
if data[self.get_key()] != 'SourceUnit': if data[self.get_key()] != 'SourceUnit':
return -1 # handle solc prior 0.3.6 return -1 # handle solc prior 0.3.6
@ -184,11 +180,9 @@ class SlitherSolc(Slither):
for contract in self._contractsNotParsed: for contract in self._contractsNotParsed:
if contract.name in self._contracts: if contract.name in self._contracts:
if contract.id != self._contracts[contract.name].id: if contract.id != self._contracts[contract.name].id:
info = 'Slither does not handle projects with contract names re-use' self._contract_name_collisions[contract.name].append(contract.source_mapping_str)
info += '\n{} is defined in:'.format(contract.name) self._contract_name_collisions[contract.name].append(
info += '\n- {}\n- {}'.format(contract.source_mapping_str,
self._contracts[contract.name].source_mapping_str) self._contracts[contract.name].source_mapping_str)
raise ParsingNameReuse(info)
else: else:
self._contracts_by_id[contract.id] = contract self._contracts_by_id[contract.id] = contract
self._contracts[contract.name] = contract self._contracts[contract.name] = contract
@ -199,36 +193,44 @@ class SlitherSolc(Slither):
ancestors = [] ancestors = []
fathers = [] fathers = []
father_constructors = [] father_constructors = []
try: # try:
# Resolve linearized base contracts. # Resolve linearized base contracts.
missing_inheritance = False
for i in contract.linearizedBaseContracts[1:]: for i in contract.linearizedBaseContracts[1:]:
if i in contract.remapping: if i in contract.remapping:
ancestors.append(self.get_contract_from_name(contract.remapping[i])) ancestors.append(self.get_contract_from_name(contract.remapping[i]))
else: elif i in self._contracts_by_id:
ancestors.append(self._contracts_by_id[i]) ancestors.append(self._contracts_by_id[i])
else:
missing_inheritance = True
# Resolve immediate base contracts # Resolve immediate base contracts
for i in contract.baseContracts: for i in contract.baseContracts:
if i in contract.remapping: if i in contract.remapping:
fathers.append(self.get_contract_from_name(contract.remapping[i])) fathers.append(self.get_contract_from_name(contract.remapping[i]))
else: elif i in self._contracts_by_id:
fathers.append(self._contracts_by_id[i]) fathers.append(self._contracts_by_id[i])
else:
missing_inheritance = True
# Resolve immediate base constructor calls # Resolve immediate base constructor calls
for i in contract.baseConstructorContractsCalled: for i in contract.baseConstructorContractsCalled:
if i in contract.remapping: if i in contract.remapping:
father_constructors.append(self.get_contract_from_name(contract.remapping[i])) father_constructors.append(self.get_contract_from_name(contract.remapping[i]))
else: elif i in self._contracts_by_id:
father_constructors.append(self._contracts_by_id[i]) father_constructors.append(self._contracts_by_id[i])
else:
missing_inheritance = True
except KeyError as e:
txt = f'A contract was not found (id {e}), it is likely that your codebase contains muliple contracts with the same name. '
txt += 'Truffle does not handle this case during compilation. '
txt += 'Please read https://github.com/trailofbits/slither/wiki#keyerror-or-nonetype-error, '
txt += 'and update your code to remove the duplicate'
raise ParsingContractNotFound(txt)
contract.setInheritance(ancestors, fathers, father_constructors) contract.setInheritance(ancestors, fathers, father_constructors)
if missing_inheritance:
self._contract_with_missing_inheritance.add(contract)
contract.log_incorrect_parsing()
contract.set_is_analyzed(True)
contract.delete_content()
contracts_to_be_analyzed = self.contracts contracts_to_be_analyzed = self.contracts
# Any contract can refer another contract enum without need for inheritance # Any contract can refer another contract enum without need for inheritance
@ -257,7 +259,6 @@ class SlitherSolc(Slither):
compute_dependency(self) compute_dependency(self)
def _analyze_all_enums(self, contracts_to_be_analyzed): def _analyze_all_enums(self, contracts_to_be_analyzed):
while contracts_to_be_analyzed: while contracts_to_be_analyzed:
contract = contracts_to_be_analyzed[0] contract = contracts_to_be_analyzed[0]
@ -370,8 +371,6 @@ class SlitherSolc(Slither):
contract.analyze_content_modifiers() contract.analyze_content_modifiers()
contract.analyze_content_functions() contract.analyze_content_functions()
contract.set_is_analyzed(True) contract.set_is_analyzed(True)
def _convert_to_slithir(self): def _convert_to_slithir(self):

Loading…
Cancel
Save