Static Analyzer for Solidity
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
slither/slither/detectors/abstract_detector.py

232 lines
9.2 KiB

import abc
import re
from slither.utils.colors import green, yellow, red
from collections import OrderedDict
class IncorrectDetectorInitialization(Exception):
pass
class DetectorClassification:
HIGH = 0
MEDIUM = 1
LOW = 2
INFORMATIONAL = 3
classification_colors = {
DetectorClassification.INFORMATIONAL: green,
DetectorClassification.LOW: green,
DetectorClassification.MEDIUM: yellow,
DetectorClassification.HIGH: red,
}
classification_txt = {
DetectorClassification.INFORMATIONAL: 'Informational',
DetectorClassification.LOW: 'Low',
DetectorClassification.MEDIUM: 'Medium',
DetectorClassification.HIGH: 'High',
}
class AbstractDetector(metaclass=abc.ABCMeta):
ARGUMENT = '' # run the detector with slither.py --ARGUMENT
HELP = '' # help information
IMPACT = None
CONFIDENCE = None
WIKI = ''
WIKI_TITLE = ''
WIKI_DESCRIPTION = ''
WIKI_EXPLOIT_SCENARIO = ''
WIKI_RECOMMENDATION = ''
def __init__(self, slither, logger):
self.slither = slither
self.contracts = slither.contracts
self.filename = slither.filename
self.logger = logger
if not self.HELP:
raise IncorrectDetectorInitialization('HELP is not initialized {}'.format(self.__class__.__name__))
if not self.ARGUMENT:
raise IncorrectDetectorInitialization('ARGUMENT is not initialized {}'.format(self.__class__.__name__))
if not self.WIKI:
raise IncorrectDetectorInitialization('WIKI is not initialized {}'.format(self.__class__.__name__))
if not self.WIKI_TITLE:
raise IncorrectDetectorInitialization('WIKI_TITLE is not initialized {}'.format(self.__class__.__name__))
if not self.WIKI_DESCRIPTION:
raise IncorrectDetectorInitialization('WIKI_DESCRIPTION is not initialized {}'.format(self.__class__.__name__))
if not self.WIKI_EXPLOIT_SCENARIO and self.IMPACT != DetectorClassification.INFORMATIONAL:
raise IncorrectDetectorInitialization('WIKI_EXPLOIT_SCENARIO is not initialized {}'.format(self.__class__.__name__))
if not self.WIKI_RECOMMENDATION:
raise IncorrectDetectorInitialization('WIKI_RECOMMENDATION is not initialized {}'.format(self.__class__.__name__))
if re.match('^[a-zA-Z0-9_-]*$', self.ARGUMENT) is None:
raise IncorrectDetectorInitialization('ARGUMENT has illegal character {}'.format(self.__class__.__name__))
if self.IMPACT not in [DetectorClassification.LOW,
DetectorClassification.MEDIUM,
DetectorClassification.HIGH,
DetectorClassification.INFORMATIONAL]:
raise IncorrectDetectorInitialization('IMPACT is not initialized {}'.format(self.__class__.__name__))
if self.CONFIDENCE not in [DetectorClassification.LOW,
DetectorClassification.MEDIUM,
DetectorClassification.HIGH,
DetectorClassification.INFORMATIONAL]:
raise IncorrectDetectorInitialization('CONFIDENCE is not initialized {}'.format(self.__class__.__name__))
6 years ago
# def log(self, info):
# if self.logger:
# info = "\n"+info
# if self.WIKI != '':
# info += 'Reference: {}'.format(self.WIKI)
# self.logger.info(self.color(info))
def _log(self, info):
self.logger.info(self.color(info))
@abc.abstractmethod
6 years ago
def _detect(self):
"""TODO Documentation"""
return
6 years ago
def detect(self):
all_results = self._detect()
results = []
# only keep valid result, and remove dupplicate
[results.append(r) for r in all_results if self.slither.valid_result(r) and r not in results]
6 years ago
if results:
if self.logger:
info = '\n'
for idx, result in enumerate(results):
if self.slither.triage_mode:
info += '{}: '.format(idx)
info += result['description']
6 years ago
info += 'Reference: {}'.format(self.WIKI)
self._log(info)
if results and self.slither.triage_mode:
while True:
indexes = input('Results to hide during next runs: "0,1,..." or "All" (enter to not hide results): '.format(len(results)))
if indexes == 'All':
self.slither.save_results_to_hide(results)
return []
if indexes == '':
return results
if indexes.startswith('['):
indexes = indexes[1:]
if indexes.endswith(']'):
indexes = indexes[:-1]
try:
indexes = [int(i) for i in indexes.split(',')]
self.slither.save_results_to_hide([r for (idx, r) in enumerate(results) if idx in indexes])
return [r for (idx, r) in enumerate(results) if idx not in indexes]
except ValueError:
self.logger.error(yellow('Malformed input. Example of valid input: 0,1,2,3'))
6 years ago
return results
@property
def color(self):
return classification_colors[self.IMPACT]
def generate_json_result(self, info, additional_fields={}):
d = OrderedDict()
d['check'] = self.ARGUMENT
d['impact'] = classification_txt[self.IMPACT]
d['confidence'] = classification_txt[self.CONFIDENCE]
d['description'] = info
d['elements'] = []
if additional_fields:
d['additional_fields'] = additional_fields
return d
@staticmethod
def _create_base_element(type, name, source_mapping, additional_fields={}):
element = {'type': type,
'name': name,
'source_mapping': source_mapping}
if additional_fields:
element['additional_fields'] = additional_fields
return element
@staticmethod
def add_variable_to_json(variable, d, additional_fields={}):
element = AbstractDetector._create_base_element('variable', variable.name, variable.source_mapping, additional_fields)
d['elements'].append(element)
@staticmethod
def add_variables_to_json(variables, d):
for variable in sorted(variables, key=lambda x:x.name):
AbstractDetector.add_variable_to_json(variable, d)
@staticmethod
def add_contract_to_json(contract, d, additional_fields={}):
element = AbstractDetector._create_base_element('contract', contract.name, contract.source_mapping, additional_fields)
d['elements'].append(element)
@staticmethod
def add_function_to_json(function, d, additional_fields={}):
element = AbstractDetector._create_base_element('function', function.name, function.source_mapping, additional_fields)
contract = {'elements':[]}
AbstractDetector.add_contract_to_json(function.contract, contract)
element['contract'] = contract['elements'][0]
d['elements'].append(element)
@staticmethod
def add_functions_to_json(functions, d):
for function in sorted(functions, key=lambda x: x.name):
AbstractDetector.add_function_to_json(function, d)
@staticmethod
def add_enum_to_json(enum, d, additional_fields={}):
element = AbstractDetector._create_base_element('enum', enum.name, enum.source_mapping, additional_fields)
d['elements'].append(element)
@staticmethod
def add_struct_to_json(struct, d, additional_fields={}):
element = AbstractDetector._create_base_element('struct', struct.name, struct.source_mapping, additional_fields)
d['elements'].append(element)
@staticmethod
def add_event_to_json(event, d, additional_fields={}):
element = AbstractDetector._create_base_element('event', event.name, event.source_mapping, additional_fields)
contract = {'elements':[]}
AbstractDetector.add_contract_to_json(event.contract, contract)
element['contract'] = contract['elements'][0]
d['elements'].append(element)
@staticmethod
def add_node_to_json(node, d, additional_fields={}):
node_name = str(node.expression) if node.expression else ""
element = AbstractDetector._create_base_element('node', node_name, node.source_mapping, additional_fields)
if node.function:
function = {'elements': []}
AbstractDetector.add_function_to_json(node.function, function)
element['function'] = function['elements'][0]
d['elements'].append(element)
@staticmethod
def add_nodes_to_json(nodes, d):
for node in sorted(nodes, key=lambda x: x.node_id):
AbstractDetector.add_node_to_json(node, d)
@staticmethod
def add_other_to_json(name, source_mapping, d, additional_fields={}):
# Verify the type of object provided
assert isinstance(name, str), "'name' should be a string when adding an element to a JSON result"
# Add the other object to JSON
element = AbstractDetector._create_base_element('other', name, source_mapping, additional_fields)
d['elements'].append(element)