From 1c189aa61014275418de6d992dca97ae649d337b Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 10 Sep 2018 13:35:56 +0100 Subject: [PATCH 001/308] Fix bug in localvariable init expression parsing --- slither/core/cfg/node.py | 7 ++++++- slither/core/variables/variable.py | 3 +-- slither/solcParsing/cfg/nodeSolc.py | 10 ++++++++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index b94c1025a..3917c733d 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -126,7 +126,12 @@ class Node(SourceMapping, ChildFunction): def add_variable_declaration(self, var): assert self._variable_declaration is None self._variable_declaration = var - self._expression = var.expression + if var.expression: + self._vars_written += [var] + + @property + def variable_declaration(self): + return self._variable_declaration def __str__(self): txt = NodeType.str(self._node_type) + ' '+ str(self.expression) diff --git a/slither/core/variables/variable.py b/slither/core/variables/variable.py index a2ac28954..e45bb4522 100644 --- a/slither/core/variables/variable.py +++ b/slither/core/variables/variable.py @@ -17,7 +17,6 @@ class Variable(SourceMapping): self._mappingTo = None self._initial_expression = None self._type = None - self._expression = None self._initialized = None self._visibility = None @@ -26,7 +25,7 @@ class Variable(SourceMapping): """ Expression: Expression of the node (if initialized) """ - return self._expression + return self._initial_expression @property def initialized(self): diff --git a/slither/solcParsing/cfg/nodeSolc.py b/slither/solcParsing/cfg/nodeSolc.py index 1e953085b..08cfd64a7 100644 --- a/slither/solcParsing/cfg/nodeSolc.py +++ b/slither/solcParsing/cfg/nodeSolc.py @@ -1,4 +1,5 @@ from slither.core.cfg.node import Node +from slither.core.cfg.nodeType import NodeType from slither.solcParsing.expressions.expressionParsing import parse_expression from slither.visitors.expression.readVar import ReadVar from slither.visitors.expression.writeVar import WriteVar @@ -22,9 +23,15 @@ class NodeSolc(Node): self._unparsed_expression = expression def analyze_expressions(self, caller_context): + if self.type == NodeType.VARIABLE: + self._expression = self.variable_declaration.expression if self._unparsed_expression: expression = parse_expression(self._unparsed_expression, caller_context) + self._expression = expression + self._unparsed_expression = None + if self.expression: + expression = self.expression pp = ReadVar(expression) self._expression_vars_read = pp.result() vars_read = [ExportValues(v).result() for v in self._expression_vars_read] @@ -47,5 +54,4 @@ class NodeSolc(Node): calls = [item for sublist in calls for item in sublist] self._calls = [c for c in calls if isinstance(c, (Function, SolidityFunction))] - self._unparsed_expression = None - self._expression = expression + From 4eceed57cfada1443772d49f0a00e4bb1532c27e Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 10 Sep 2018 13:38:26 +0100 Subject: [PATCH 002/308] Update travis with slither package --- scripts/travis_install.sh | 2 +- scripts/travis_test.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/travis_install.sh b/scripts/travis_install.sh index 93a6ca45d..21387729f 100755 --- a/scripts/travis_install.sh +++ b/scripts/travis_install.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -pip install -r requirements.txt +python setup.py install function install_solc { sudo wget -O /usr/bin/solc https://github.com/ethereum/solidity/releases/download/v0.4.24/solc-static-linux diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index e09e17dbb..55b38b2af 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -1,11 +1,11 @@ #!/usr/bin/env bash -./slither.py examples/bugs/uninitialized.sol --disable-solc-warnings +slither examples/bugs/uninitialized.sol --disable-solc-warnings if [ $? -ne 1 ]; then exit 1 fi -./slither.py examples/bugs/backdoor.sol --disable-solc-warnings +slither examples/bugs/backdoor.sol --disable-solc-warnings if [ $? -ne 1 ]; then exit 1 fi From a97ea53fbd5e5f06dcc0db2a9c2cc6a29d687dea Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 10 Sep 2018 14:53:08 +0100 Subject: [PATCH 003/308] Parse ImportDirective and PragmaDirective Add CODE_QUALITY detector type Add detector to check that the same version of solidity is always used --- examples/bugs/pragma.0.4.23.sol | 1 + examples/bugs/pragma.0.4.24.sol | 5 +++ scripts/travis_test.sh | 5 +++ slither/__main__.py | 4 +-- slither/core/slitherCore.py | 12 +++++++ slither/detectors/abstractDetector.py | 9 ++++-- slither/detectors/attributes/__init__.py | 0 .../detectors/attributes/constant_pragma.py | 32 +++++++++++++++++++ slither/detectors/detectorClassification.py | 1 + slither/detectors/detectors.py | 4 +++ slither/solcParsing/slitherSolc.py | 4 +++ 11 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 examples/bugs/pragma.0.4.23.sol create mode 100644 examples/bugs/pragma.0.4.24.sol create mode 100644 slither/detectors/attributes/__init__.py create mode 100644 slither/detectors/attributes/constant_pragma.py diff --git a/examples/bugs/pragma.0.4.23.sol b/examples/bugs/pragma.0.4.23.sol new file mode 100644 index 000000000..6e6a5000f --- /dev/null +++ b/examples/bugs/pragma.0.4.23.sol @@ -0,0 +1 @@ +pragma solidity ^0.4.23; diff --git a/examples/bugs/pragma.0.4.24.sol b/examples/bugs/pragma.0.4.24.sol new file mode 100644 index 000000000..46e9a7dc5 --- /dev/null +++ b/examples/bugs/pragma.0.4.24.sol @@ -0,0 +1,5 @@ +pragma solidity 0.4.24; + +import "pragma.0.4.23.sol"; + +contract Test{} diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 55b38b2af..11754cf36 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -10,4 +10,9 @@ if [ $? -ne 1 ]; then exit 1 fi +slither examples/bugs/pragma.0.4.24.sol --disable-solc-warnings +if [ $? -ne 1 ]; then + exit 1 +fi + exit 0 diff --git a/slither/__main__.py b/slither/__main__.py index 1e9dfdf23..739392327 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -26,7 +26,7 @@ def determineChecks(detectors, args): elif args.detectors_to_run: return args.detectors_to_run else: - return detectors.high + detectors.medium + detectors.low + return detectors.high + detectors.medium + detectors.low + detectors.code_quality def process(filename, args, detectors, printers): @@ -173,4 +173,4 @@ def main(): if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/slither/core/slitherCore.py b/slither/core/slitherCore.py index 15d8fe9ed..10e937cb6 100644 --- a/slither/core/slitherCore.py +++ b/slither/core/slitherCore.py @@ -14,6 +14,8 @@ class Slither: self._filename = None self._source_units = {} self._solc_version = None # '0.3' or '0.4':! + self._pragma_directives = [] + self._import_directives = [] @property def contracts(self): @@ -41,6 +43,16 @@ class Slither: """str: Solidity version.""" return self._solc_version + @property + def pragma_directives(self): + """ list(list(str)): Pragma directives. Example [['solidity', '^', '0.4', '.24']]""" + return self._pragma_directives + + @property + def import_directives(self): + """ list(str): Import directives""" + return self._import_directives + def get_contract_from_name(self, contract_name): """ Return a contract from a name diff --git a/slither/detectors/abstractDetector.py b/slither/detectors/abstractDetector.py index 2d86c32a0..1bccd3b61 100644 --- a/slither/detectors/abstractDetector.py +++ b/slither/detectors/abstractDetector.py @@ -1,7 +1,7 @@ import abc import re from slither.detectors.detectorClassification import DetectorClassification -from slither.utils.colors import green, yellow, red +from slither.utils.colors import green, yellow, red, blue class IncorrectDetectorInitialization(Exception): pass @@ -26,7 +26,8 @@ class AbstractDetector(object, metaclass=abc.ABCMeta): raise IncorrectDetectorInitialization('ARGUMENT has illegal character') if not self.CLASSIFICATION in [DetectorClassification.LOW, DetectorClassification.MEDIUM, - DetectorClassification.HIGH]: + DetectorClassification.HIGH, + DetectorClassification.CODE_QUALITY]: raise IncorrectDetectorInitialization('CLASSIFICATION is not initialized') def log(self, info): @@ -41,8 +42,10 @@ class AbstractDetector(object, metaclass=abc.ABCMeta): @property def color(self): if self.CLASSIFICATION == DetectorClassification.LOW: - return green + return blue if self.CLASSIFICATION == DetectorClassification.MEDIUM: return yellow if self.CLASSIFICATION == DetectorClassification.HIGH: return red + if self.CLASSIFICATION == DetectorClassification.CODE_QUALITY: + return green diff --git a/slither/detectors/attributes/__init__.py b/slither/detectors/attributes/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/detectors/attributes/constant_pragma.py b/slither/detectors/attributes/constant_pragma.py new file mode 100644 index 000000000..9f789d848 --- /dev/null +++ b/slither/detectors/attributes/constant_pragma.py @@ -0,0 +1,32 @@ +""" + Check that the same pragma is used in all the files +""" + +from slither.detectors.abstractDetector import AbstractDetector +from slither.detectors.detectorClassification import DetectorClassification + +class ConstantPragma(AbstractDetector): + """ + Check that the same pragma is used in all the files + """ + + ARGUMENT = 'pragma' + HELP = 'Different pragma directives used' + CLASSIFICATION = DetectorClassification.CODE_QUALITY + + + def detect(self): + """ + """ + + results = [] + pragma = self.slither.pragma_directives + pragma = [''.join(p[1:]) for p in pragma] + pragma = list(set(pragma)) + if len(pragma) > 1: + info = "Different version of Solidity used in {}: {}".format(self.filename, pragma) + self.log(info) + + results.append({'vuln':'ConstantPragma', 'pragma': pragma}) + + return results diff --git a/slither/detectors/detectorClassification.py b/slither/detectors/detectorClassification.py index 52dde20a9..24fe5ba2e 100644 --- a/slither/detectors/detectorClassification.py +++ b/slither/detectors/detectorClassification.py @@ -2,3 +2,4 @@ class DetectorClassification: LOW = 0 MEDIUM = 1 HIGH = 2 + CODE_QUALITY = 3 diff --git a/slither/detectors/detectors.py b/slither/detectors/detectors.py index 7e79b8443..cf19a36de 100644 --- a/slither/detectors/detectors.py +++ b/slither/detectors/detectors.py @@ -8,6 +8,7 @@ from slither.detectors.detectorClassification import DetectorClassification # Detectors must be imported here from slither.detectors.examples.backdoor import Backdoor from slither.detectors.variables.uninitializedStateVarsDetection import UninitializedStateVarsDetection +from slither.detectors.attributes.constant_pragma import ConstantPragma ### @@ -20,6 +21,7 @@ class Detectors: self.low = [] self.medium = [] self.high = [] + self.code_quality = [] self._load_detectors() @@ -38,6 +40,8 @@ class Detectors: self.medium.append(name) elif obj.CLASSIFICATION == DetectorClassification.HIGH: self.high.append(name) + elif obj.CLASSIFICATION == DetectorClassification.CODE_QUALITY: + self.code_quality.append(name) else: raise Exception('Unknown classification') diff --git a/slither/solcParsing/slitherSolc.py b/slither/solcParsing/slitherSolc.py index e56d01984..15a04a35c 100644 --- a/slither/solcParsing/slitherSolc.py +++ b/slither/solcParsing/slitherSolc.py @@ -44,6 +44,10 @@ class SlitherSolc(Slither): if contract_data['name'] == 'ContractDefinition': contract = ContractSolc04(self, contract_data) self._contractsNotParsed.append(contract) + elif contract_data['name'] == 'PragmaDirective': + self._pragma_directives.append(contract_data['attributes']["literals"]) + elif contract_data['name'] == 'ImportDirective': + self._import_directives.append(contract_data['attributes']["absolutePath"]) return True return False From 8ec219b5e218b8d77e533c35d304868767c37e0b Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 10 Sep 2018 15:09:34 +0100 Subject: [PATCH 004/308] Update Readme --- README.md | 1 + slither/detectors/attributes/constant_pragma.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 24979729c..06d1d0a2c 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ For more information about printers, see the [Printers documentation](docs/PRINT Check | Purpose | Severity | Confidence --- | --- | --- | --- `--uninitialized`| Detect uninitialized variables | High | High +`--pragma`| Detect if different pragma directives are used | Code Quality | High ## License diff --git a/slither/detectors/attributes/constant_pragma.py b/slither/detectors/attributes/constant_pragma.py index 9f789d848..d068c9a90 100644 --- a/slither/detectors/attributes/constant_pragma.py +++ b/slither/detectors/attributes/constant_pragma.py @@ -11,7 +11,7 @@ class ConstantPragma(AbstractDetector): """ ARGUMENT = 'pragma' - HELP = 'Different pragma directives used' + HELP = 'different pragma directives' CLASSIFICATION = DetectorClassification.CODE_QUALITY From cfe987ef527531f03249393a4e324a65313bfba7 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 10 Sep 2018 15:32:50 +0100 Subject: [PATCH 005/308] Fix import in examples/bug/pragma.0.4.24.sol --- examples/bugs/old_solc.sol | 5 +++++ examples/bugs/pragma.0.4.24.sol | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 examples/bugs/old_solc.sol diff --git a/examples/bugs/old_solc.sol b/examples/bugs/old_solc.sol new file mode 100644 index 000000000..291d4c96a --- /dev/null +++ b/examples/bugs/old_solc.sol @@ -0,0 +1,5 @@ +pragma solidity 0.4.21; + +contract Contract{ + +} diff --git a/examples/bugs/pragma.0.4.24.sol b/examples/bugs/pragma.0.4.24.sol index 46e9a7dc5..cd946840b 100644 --- a/examples/bugs/pragma.0.4.24.sol +++ b/examples/bugs/pragma.0.4.24.sol @@ -1,5 +1,5 @@ pragma solidity 0.4.24; -import "pragma.0.4.23.sol"; +import "./pragma.0.4.23.sol"; contract Test{} From ef32f484e095f19d9770ac835696665cc8ba34e0 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 10 Sep 2018 15:33:48 +0100 Subject: [PATCH 006/308] Add code quality detector to check if an old version of solc is used --- examples/bugs/old_solc.sol.json | 67 ++++++++++++++++++++++++ scripts/travis_test.sh | 5 ++ slither/detectors/attributes/old_solc.py | 36 +++++++++++++ slither/detectors/detectors.py | 1 + 4 files changed, 109 insertions(+) create mode 100644 examples/bugs/old_solc.sol.json create mode 100644 slither/detectors/attributes/old_solc.py diff --git a/examples/bugs/old_solc.sol.json b/examples/bugs/old_solc.sol.json new file mode 100644 index 000000000..2b784ba3a --- /dev/null +++ b/examples/bugs/old_solc.sol.json @@ -0,0 +1,67 @@ +JSON AST: + + +======= old_solc.sol ======= +{ + "attributes" : + { + "absolutePath" : "old_solc.sol", + "exportedSymbols" : + { + "Contract" : + [ + 2 + ] + } + }, + "children" : + [ + { + "attributes" : + { + "literals" : + [ + "solidity", + "0.4", + ".21" + ] + }, + "id" : 1, + "name" : "PragmaDirective", + "src" : "0:23:0" + }, + { + "attributes" : + { + "baseContracts" : + [ + null + ], + "contractDependencies" : + [ + null + ], + "contractKind" : "contract", + "documentation" : null, + "fullyImplemented" : true, + "linearizedBaseContracts" : + [ + 2 + ], + "name" : "Contract", + "nodes" : + [ + null + ], + "scope" : 3 + }, + "id" : 2, + "name" : "ContractDefinition", + "src" : "25:21:0" + } + ], + "id" : 3, + "name" : "SourceUnit", + "src" : "0:47:0" +} +======= old_solc.sol:Contract ======= diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 11754cf36..3bb856e02 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -15,4 +15,9 @@ if [ $? -ne 1 ]; then exit 1 fi +slither examples/bugs/old_solc.sol.json --solc-ast +if [ $? -ne 1 ]; then + exit 1 +fi + exit 0 diff --git a/slither/detectors/attributes/old_solc.py b/slither/detectors/attributes/old_solc.py new file mode 100644 index 000000000..340234c03 --- /dev/null +++ b/slither/detectors/attributes/old_solc.py @@ -0,0 +1,36 @@ +""" + Check if an old version of solc is used + Solidity >= 0.4.23 should be used +""" + +from slither.detectors.abstractDetector import AbstractDetector +from slither.detectors.detectorClassification import DetectorClassification + +class OldSolc(AbstractDetector): + """ + Check if an old version of solc is used + """ + + ARGUMENT = 'solc-version' + HELP = 'an old version of Solidity used (<0.4.23)' + CLASSIFICATION = DetectorClassification.CODE_QUALITY + + + def detect(self): + """ + """ + + results = [] + pragma = self.slither.pragma_directives + pragma = [''.join(p[1:]) for p in pragma] + pragma = [p.replace('solidity','').replace('^','') for p in pragma] + pragma = list(set(pragma)) + old_pragma = [p for p in pragma if p not in ['0.4.23', '0.4.24']] + + if old_pragma: + info = "Old version of Solidity used in {}: {}".format(self.filename, old_pragma) + self.log(info) + + results.append({'vuln':'OldPragma', 'pragma': old_pragma}) + + return results diff --git a/slither/detectors/detectors.py b/slither/detectors/detectors.py index cf19a36de..19826f2ae 100644 --- a/slither/detectors/detectors.py +++ b/slither/detectors/detectors.py @@ -9,6 +9,7 @@ from slither.detectors.detectorClassification import DetectorClassification from slither.detectors.examples.backdoor import Backdoor from slither.detectors.variables.uninitializedStateVarsDetection import UninitializedStateVarsDetection from slither.detectors.attributes.constant_pragma import ConstantPragma +from slither.detectors.attributes.old_solc import OldSolc ### From 871c08f0a4fc5d77d46d56ab119685f1b773a4fb Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 10 Sep 2018 15:34:55 +0100 Subject: [PATCH 007/308] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 06d1d0a2c..e70d50edc 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ Check | Purpose | Severity | Confidence --- | --- | --- | --- `--uninitialized`| Detect uninitialized variables | High | High `--pragma`| Detect if different pragma directives are used | Code Quality | High +`--solc-version`| Detect if an old version of Solidity is used (<0.4.23) | Code Quality | High ## License From fbaf655c5a3af03e6566173a213610a18b4fff9c Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 10 Sep 2018 15:56:05 +0100 Subject: [PATCH 008/308] Update README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e70d50edc..986babed1 100644 --- a/README.md +++ b/README.md @@ -74,11 +74,11 @@ For more information about printers, see the [Printers documentation](docs/PRINT ## Checks available -Check | Purpose | Severity | Confidence +Check | Purpose | Impact | Confidence --- | --- | --- | --- `--uninitialized`| Detect uninitialized variables | High | High -`--pragma`| Detect if different pragma directives are used | Code Quality | High -`--solc-version`| Detect if an old version of Solidity is used (<0.4.23) | Code Quality | High +`--pragma`| Detect if different pragma directives are used | Informational | High +`--solc-version`| Detect if an old version of Solidity is used (<0.4.23) | Informational | High ## License From d7e15590dfdb07839ed1d2b012206394ad434faf Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 10 Sep 2018 17:38:58 +0100 Subject: [PATCH 009/308] Command line changes: - Add exclude flag for each detector - replace -low, -medium, -high by -exclude-low, ... - clean detectors/printers information and flags - detector follow this format '--detect-name' - close #1 and #10 Update README --- README.md | 40 +++++------ slither/__main__.py | 67 ++++++++++++------- slither/detectors/examples/backdoor.py | 2 +- slither/printers/functions/authorization.py | 4 +- .../inheritance/printerInheritance.py | 4 +- .../printers/summary/printerQuickSummary.py | 4 +- slither/printers/summary/printerSummary.py | 4 +- 7 files changed, 73 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 986babed1..39e77953a 100644 --- a/README.md +++ b/README.md @@ -43,43 +43,45 @@ $ slither file.sol ``` ``` -$ slither examples/uninitialized.sol +$ slither examples/bugs/uninitialized.sol [..] -INFO:Detectors:Uninitialized state variables in examples/uninitialized.sol, Contract: Uninitialized, Vars: destination, Used in ['transfer'] +INFO:Detectors:Uninitialized state variables in examples/bugs/uninitialized.sol, Contract: Uninitialized, Vars: destination, Used in ['transfer'] [..] ``` If Slither is applied on a directory, it will run on every `.sol` file of the directory. -## Options +## Checks available + +By default, all the checks are run. -### Configuration +Check | Purpose | Impact | Confidence +--- | --- | --- | --- +`--detect-uninitialized`| Detect uninitialized variables | High | High +`--detect-pragma`| Detect if different pragma directives are used | Informational | High +`--detect-solc-version`| Detect if an old version of Solidity is used (<0.4.23) | Informational | High + +### Exclude analyses +* `--exclude-informational`: Exclude informational impact analyses +* `--exclude-low`: Exclude low impact analyses +* `--exclude-medium`: Exclude medium impact analyses +* `--exclude-high`: Exclude high impact analyses +* `--exclude-name` will exclude the detector `name` + +## Configuration * `--solc SOLC`: Path to `solc` (default 'solc') +* `--solc-args SOLC_ARGS`: Add custom solc arguments. `SOLC_ARGS` can contain multiple arguments. * `--disable-solc-warnings`: Do not print solc warnings * `--solc-ast`: Use the solc AST file as input (`solc file.sol --ast-json > file.ast.json`) * `--json FILE`: Export results as JSON -* `--solc-args SOLC_ARGS`: Add custom solc arguments. `SOLC_ARGS` can contain multiple arguments. -### Analyses -* `--high`: Run only medium/high severity checks with high confidence -* `--medium`: Run only medium/high severity checks with medium confidence -* `--low`: Run only low severity checks - -### Printers +## Printers * `--print-summary`: Print a summary of the contracts * `--print-quick-summary`: Print a quick summary of the contracts * `--print-inheritance`: Print the inheritance graph For more information about printers, see the [Printers documentation](docs/PRINTERS.md) -## Checks available - -Check | Purpose | Impact | Confidence ---- | --- | --- | --- -`--uninitialized`| Detect uninitialized variables | High | High -`--pragma`| Detect if different pragma directives are used | Informational | High -`--solc-version`| Detect if an old version of Solidity is used (<0.4.23) | Informational | High - ## License diff --git a/slither/__main__.py b/slither/__main__.py index 739392327..639bd05d6 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -17,16 +17,20 @@ logger = logging.getLogger("Slither") def determineChecks(detectors, args): - if args.low: - return detectors.low - elif args.medium: - return detectors.medium + detectors.high - elif args.high: - return detectors.high - elif args.detectors_to_run: + if args.detectors_to_run: return args.detectors_to_run - else: - return detectors.high + detectors.medium + detectors.low + detectors.code_quality + all_detectors = detectors.high + detectors.medium + detectors.low + detectors.code_quality + if args.exclude_informational: + all_detectors = [d for d in all_detectors if d not in detectors.code_quality] + if args.exclude_low: + all_detectors = [d for d in all_detectors if d not in detectors.low] + if args.exclude_medium: + all_detectors = [d for d in all_detectors if d not in detectors.medium] + if args.exclude_high: + all_detectors = [d for d in all_detectors if d not in detectors.high] + if args.detectors_to_exclude: + all_detectors = [d for d in all_detectors if d not in args.detectors_to_exclude] + return all_detectors def process(filename, args, detectors, printers): @@ -58,7 +62,7 @@ def main(): printers = Printers() parser = argparse.ArgumentParser(description='Slither', - usage="slither.py contract.sol [flag]") + usage="slither.py contract.sol [flag]", formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=35)) parser.add_argument('filename', help='contract.sol file') @@ -83,38 +87,53 @@ def main(): action='store_true', default=False) - parser.add_argument('--low', - help='Only low analyses', + parser.add_argument('--json', + help='Export results as JSON', + action='store', + default=None) + + parser.add_argument('--exclude-informational', + help='Exclude informational impact analyses', action='store_true', default=False) - parser.add_argument('--medium', - help='Only medium and high analyses', + parser.add_argument('--exclude-low', + help='Exclude low impact analyses', action='store_true', default=False) - parser.add_argument('--high', - help='Only high analyses', + parser.add_argument('--exclude-medium', + help='Exclude medium impact analyses', + action='store_true', + default=False) + + parser.add_argument('--exclude-high', + help='Exclude high impact analyses', action='store_true', default=False) - parser.add_argument('--json', - help='Export results as JSON', - action='store', - default=None) for detector_name, Detector in detectors.detectors.items(): - detector_arg = '--{}'.format(Detector.ARGUMENT) - detector_help = 'Detection of ' + Detector.HELP + detector_arg = '--detect-{}'.format(Detector.ARGUMENT) + detector_help = 'Detection of {}'.format(Detector.HELP) parser.add_argument(detector_arg, help=detector_help, action="append_const", dest="detectors_to_run", const=detector_name) + for detector_name, Detector in detectors.detectors.items(): + exclude_detector_arg = '--exclude-{}'.format(Detector.ARGUMENT) + exclude_detector_help = 'Exclude {} detector'.format(Detector.ARGUMENT) + parser.add_argument(exclude_detector_arg, + help=exclude_detector_help, + action="append_const", + dest="detectors_to_exclude", + const=detector_name) + for printer_name, Printer in printers.printers.items(): - printer_arg = '--{}'.format(Printer.ARGUMENT) - printer_help = Printer.HELP + printer_arg = '--print-{}'.format(Printer.ARGUMENT) + printer_help = 'Print {}'.format(Printer.HELP) parser.add_argument(printer_arg, help=printer_help, action="append_const", diff --git a/slither/detectors/examples/backdoor.py b/slither/detectors/examples/backdoor.py index e4c7d41a7..4c650807e 100644 --- a/slither/detectors/examples/backdoor.py +++ b/slither/detectors/examples/backdoor.py @@ -7,7 +7,7 @@ class Backdoor(AbstractDetector): """ ARGUMENT = 'backdoor' # slither will launch the detector with slither.py --mydetector - HELP = 'Function named backdoor (detector example)' + HELP = 'function named backdoor (detector example)' CLASSIFICATION = DetectorClassification.HIGH def detect(self): diff --git a/slither/printers/functions/authorization.py b/slither/printers/functions/authorization.py index b382e9498..b49e57c9c 100644 --- a/slither/printers/functions/authorization.py +++ b/slither/printers/functions/authorization.py @@ -8,8 +8,8 @@ from slither.core.declarations.function import Function class PrinterWrittenVariablesAndAuthorization(AbstractPrinter): - ARGUMENT = 'print-variables-written-and-authorization' - HELP = 'Print the the variables written and the authorization of the functions' + ARGUMENT = 'vars-and-auth' + HELP = 'the state variables written and the authorization of the functions' @staticmethod def get_msg_sender_checks(function): diff --git a/slither/printers/inheritance/printerInheritance.py b/slither/printers/inheritance/printerInheritance.py index 9170be9e5..888800607 100644 --- a/slither/printers/inheritance/printerInheritance.py +++ b/slither/printers/inheritance/printerInheritance.py @@ -13,8 +13,8 @@ from slither.core.declarations.contract import Contract class PrinterInheritance(AbstractPrinter): - ARGUMENT = 'print-inheritance' - HELP = 'Print the inheritance graph' + ARGUMENT = 'inheritance' + HELP = 'the inheritance graph' def __init__(self, slither, logger): super(PrinterInheritance, self).__init__(slither, logger) diff --git a/slither/printers/summary/printerQuickSummary.py b/slither/printers/summary/printerQuickSummary.py index 4c7a2219e..17a9ea1da 100644 --- a/slither/printers/summary/printerQuickSummary.py +++ b/slither/printers/summary/printerQuickSummary.py @@ -7,8 +7,8 @@ from slither.utils.colors import blue, green, magenta class PrinterQuickSummary(AbstractPrinter): - ARGUMENT = 'print-quick-summary' - HELP = 'Print a quick summary of the contract' + ARGUMENT = 'quick-summary' + HELP = 'a quick summary of the contract' def output(self, _filename): """ diff --git a/slither/printers/summary/printerSummary.py b/slither/printers/summary/printerSummary.py index d68dea9dc..1727d61a2 100644 --- a/slither/printers/summary/printerSummary.py +++ b/slither/printers/summary/printerSummary.py @@ -7,8 +7,8 @@ from slither.printers.abstractPrinter import AbstractPrinter class PrinterSummary(AbstractPrinter): - ARGUMENT = 'print-summary' - HELP = 'Print the summary of the contract' + ARGUMENT = 'summary' + HELP = 'the summary of the contract' @staticmethod def _convert(l): From b7078b758616e7e040d0654df02ca027a0f64d25 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 10 Sep 2018 17:57:26 +0100 Subject: [PATCH 010/308] Show the number of contracts analyzed in output (fix #9) --- slither/__main__.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/slither/__main__.py b/slither/__main__.py index 639bd05d6..6f6ec8396 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -34,16 +34,22 @@ def determineChecks(detectors, args): def process(filename, args, detectors, printers): + """ + + Returns: + list(result), int: Result list and number of contracts analyzed + """ slither = Slither(filename, args.solc, args.disable_solc_warnings, args.solc_args) + number_contract = len(slither.contracts) if args.printers_to_run: [printers.run_printer(slither, p) for p in args.printers_to_run] - return [] + return ([], number_contract) else: checks = determineChecks(detectors, args) results = [detectors.run_detector(slither, c) for c in checks] results = [x for x in results if x] # remove empty results results = [item for sublist in results for item in sublist] #flatten - return results + return (results, number_contract) def output_json(results, filename): @@ -168,12 +174,16 @@ def main(): filename = sys.argv[1] if os.path.isfile(filename): - results = process(filename, args, detectors, printers) + (results, number_contracts) = process(filename, args, detectors, printers) elif os.path.isdir(filename): extension = "*.sol" if not args.solc_ast else "*.json" filenames = glob.glob(os.path.join(filename, extension)) - results = [process(filename, args, detectors, printers) for filename in filenames] - results = [item for sublist in results for item in sublist] #flatten + number_contracts = 0 + results = [] + for filename in filenames: + (results_tmp, number_contracts_tmp) = process(filename, args, detectors, printers) + number_contracts += number_contracts_tmp + results += results_tmp #if args.json: # output_json(results, args.json) #exit(results) @@ -182,7 +192,7 @@ def main(): if args.json: output_json(results, args.json) - logger.info('%s analyzed, %d result(s) found', filename, len(results)) + logger.info('%s analyzed (%d contracts), %d result(s) found', filename, number_contracts, len(results)) exit(results) except Exception as e: From cfb94e05b6eb4036ea8c58c2accec08fd51c90b5 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 10 Sep 2018 20:52:24 +0100 Subject: [PATCH 011/308] Fix bug in contract.contracts_derived --- slither/core/slitherCore.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/core/slitherCore.py b/slither/core/slitherCore.py index 10e937cb6..ff3d96ebe 100644 --- a/slither/core/slitherCore.py +++ b/slither/core/slitherCore.py @@ -26,7 +26,7 @@ class Slither: def contracts_derived(self): """list(Contract): List of contracts that are derived and not inherited.""" inheritances = (x.inheritances for x in self.contracts) - inheritances = (item for sublist in inheritances for item in sublist) + inheritances = [item for sublist in inheritances for item in sublist] return [c for c in self._contracts.values() if c not in inheritances] def contracts_as_dict(self): From 0c772b2dd7cba85f24cb30d61d568f20e218943f Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 10 Sep 2018 21:28:49 +0100 Subject: [PATCH 012/308] Update travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 665ef18f1..461ff4174 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ os: - linux language: python python: - - 2.7.12 + - 3.6 branches: only: From 16963d17b05c9c0fa00dadea51eb5c178ae725ee Mon Sep 17 00:00:00 2001 From: disconnect3d Date: Tue, 11 Sep 2018 02:57:11 +0200 Subject: [PATCH 013/308] Make detectors and printers registrable Thx to this PR Slither users will be able to plug-in their own detectors and printers. --- slither/__main__.py | 243 +++++++++++------- slither/core/slitherCore.py | 4 +- ...stractDetector.py => abstract_detector.py} | 48 ++-- .../detectors/attributes/constant_pragma.py | 11 +- slither/detectors/attributes/old_solc.py | 12 +- slither/detectors/detectorClassification.py | 5 - slither/detectors/detectors.py | 52 ---- slither/detectors/examples/backdoor.py | 9 +- .../shadowing/shadowingFunctionsDetection.py | 6 +- .../uninitializedStateVarsDetection.py | 23 +- ...abstractPrinter.py => abstract_printer.py} | 14 +- slither/printers/functions/authorization.py | 2 +- .../inheritance/printerInheritance.py | 51 ++-- slither/printers/printers.py | 31 --- .../printers/summary/printerQuickSummary.py | 2 +- slither/printers/summary/printerSummary.py | 2 +- slither/slither.py | 84 +++++- slither/solcParsing/slitherSolc.py | 30 ++- 18 files changed, 334 insertions(+), 295 deletions(-) rename slither/detectors/{abstractDetector.py => abstract_detector.py} (57%) delete mode 100644 slither/detectors/detectorClassification.py delete mode 100644 slither/detectors/detectors.py rename slither/printers/{abstractPrinter.py => abstract_printer.py} (74%) delete mode 100644 slither/printers/printers.py diff --git a/slither/__main__.py b/slither/__main__.py index 6f6ec8396..a003afffe 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -1,55 +1,51 @@ #!/usr/bin/env python3 -import sys import argparse -import logging -import traceback -import os import glob import json +import logging +import os +import sys +import traceback from slither.slither import Slither -from slither.detectors.detectors import Detectors -from slither.printers.printers import Printers logging.basicConfig() logger = logging.getLogger("Slither") -def determineChecks(detectors, args): - if args.detectors_to_run: - return args.detectors_to_run - all_detectors = detectors.high + detectors.medium + detectors.low + detectors.code_quality - if args.exclude_informational: - all_detectors = [d for d in all_detectors if d not in detectors.code_quality] - if args.exclude_low: - all_detectors = [d for d in all_detectors if d not in detectors.low] - if args.exclude_medium: - all_detectors = [d for d in all_detectors if d not in detectors.medium] - if args.exclude_high: - all_detectors = [d for d in all_detectors if d not in detectors.high] - if args.detectors_to_exclude: - all_detectors = [d for d in all_detectors if d not in args.detectors_to_exclude] - return all_detectors - - -def process(filename, args, detectors, printers): +def process(filename, args, detector_classes, printer_classes): """ + The core high-level code for running Slither static analysis. Returns: list(result), int: Result list and number of contracts analyzed """ slither = Slither(filename, args.solc, args.disable_solc_warnings, args.solc_args) - number_contract = len(slither.contracts) - if args.printers_to_run: - [printers.run_printer(slither, p) for p in args.printers_to_run] - return ([], number_contract) - else: - checks = determineChecks(detectors, args) - results = [detectors.run_detector(slither, c) for c in checks] - results = [x for x in results if x] # remove empty results - results = [item for sublist in results for item in sublist] #flatten - return (results, number_contract) + + for detector_cls in detector_classes: + slither.register_detector(detector_cls) + + for printer_cls in printer_classes: + slither.register_printer(printer_cls) + + slither.analyze_contracts() + + analyzed_contracts_count = len(slither.contracts) + + results = [] + + if printer_classes: + slither.run_printers() # Currently printers does not return results + + if detector_classes: + detector_results = slither.run_detectors() + detector_results = [x for x in detector_results if x] # remove empty results + detector_results = [item for sublist in detector_results for item in sublist] # flatten + + results.extend(detector_results) + + return results, analyzed_contracts_count def output_json(results, filename): @@ -64,11 +60,88 @@ def exit(results): def main(): - detectors = Detectors() - printers = Printers() + """ + NOTE: This contains just a few detectors and printers that we made public. + """ + from slither.detectors.examples.backdoor import Backdoor + from slither.detectors.variables.uninitializedStateVarsDetection import UninitializedStateVarsDetection + from slither.detectors.attributes.constant_pragma import ConstantPragma + from slither.detectors.attributes.old_solc import OldSolc + + detectors = [Backdoor, UninitializedStateVarsDetection, ConstantPragma, OldSolc] + + from slither.printers.summary.printerSummary import PrinterSummary + from slither.printers.summary.printerQuickSummary import PrinterQuickSummary + from slither.printers.inheritance.printerInheritance import PrinterInheritance + from slither.printers.functions.authorization import PrinterWrittenVariablesAndAuthorization + + printers = [PrinterSummary, PrinterQuickSummary, PrinterInheritance, PrinterWrittenVariablesAndAuthorization] + + main_impl(all_detector_classes=detectors, all_printer_classes=printers) + + +def main_impl(all_detector_classes, all_printer_classes): + """ + :param all_detector_classes: A list of all detectors that can be included/excluded. + :param all_printer_classes: A list of all printers that can be included. + """ + args = parse_args(all_detector_classes, all_printer_classes) + detector_classes = choose_detectors(args, all_detector_classes) + printer_classes = choose_printers(args, all_printer_classes) + + default_log = logging.INFO if not args.debug else logging.DEBUG + + for (l_name, l_level) in [('Slither', default_log), + ('Contract', default_log), + ('Function', default_log), + ('Node', default_log), + ('Parsing', default_log), + ('Detectors', default_log), + ('FunctionSolc', default_log), + ('ExpressionParsing', default_log), + ('TypeParsing', default_log), + ('Printers', default_log)]: + l = logging.getLogger(l_name) + l.setLevel(l_level) + + try: + filename = args.filename + + if os.path.isfile(filename): + (results, number_contracts) = process(filename, args, detector_classes, printer_classes) + + elif os.path.isdir(filename): + extension = "*.sol" if not args.solc_ast else "*.json" + filenames = glob.glob(os.path.join(filename, extension)) + number_contracts = 0 + results = [] + for filename in filenames: + (results_tmp, number_contracts_tmp) = process(filename, args, detector_classes, printer_classes) + number_contracts += number_contracts_tmp + results += results_tmp + # if args.json: + # output_json(results, args.json) + # exit(results) + + else: + raise Exception("Unrecognised file/dir path: '#{filename}'".format(filename=filename)) + + if args.json: + output_json(results, args.json) + logger.info('%s analyzed (%d contracts), %d result(s) found', filename, number_contracts, len(results)) + exit(results) + + except Exception: + logging.error('Error in %s' % args.filename) + logging.error(traceback.format_exc()) + sys.exit(-1) + + +def parse_args(detector_classes, printer_classes): parser = argparse.ArgumentParser(description='Slither', - usage="slither.py contract.sol [flag]", formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=35)) + usage="slither.py contract.sol [flag]", + formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=35)) parser.add_argument('filename', help='contract.sol file') @@ -118,87 +191,61 @@ def main(): action='store_true', default=False) - - for detector_name, Detector in detectors.detectors.items(): - detector_arg = '--detect-{}'.format(Detector.ARGUMENT) - detector_help = 'Detection of {}'.format(Detector.HELP) + for detector_cls in detector_classes: + detector_arg = '--detect-{}'.format(detector_cls.ARGUMENT) + detector_help = 'Detection of {}'.format(detector_cls.HELP) parser.add_argument(detector_arg, help=detector_help, action="append_const", dest="detectors_to_run", - const=detector_name) + const=detector_cls.ARGUMENT) - for detector_name, Detector in detectors.detectors.items(): - exclude_detector_arg = '--exclude-{}'.format(Detector.ARGUMENT) - exclude_detector_help = 'Exclude {} detector'.format(Detector.ARGUMENT) + exclude_detector_arg = '--exclude-{}'.format(detector_cls.ARGUMENT) + exclude_detector_help = 'Exclude {} detector'.format(detector_cls.ARGUMENT) parser.add_argument(exclude_detector_arg, help=exclude_detector_help, action="append_const", dest="detectors_to_exclude", - const=detector_name) + const=detector_cls.ARGUMENT) - for printer_name, Printer in printers.printers.items(): - printer_arg = '--print-{}'.format(Printer.ARGUMENT) - printer_help = 'Print {}'.format(Printer.HELP) + for printer_cls in printer_classes: + printer_arg = '--print-{}'.format(printer_cls.ARGUMENT) + printer_help = 'Print {}'.format(printer_cls.HELP) parser.add_argument(printer_arg, help=printer_help, action="append_const", dest="printers_to_run", - const=printer_name) + const=printer_cls.ARGUMENT) - # Debug parser.add_argument('--debug', help='Debug mode', action="store_true", default=False) - args = parser.parse_args() - default_log = logging.INFO - if args.debug: - default_log = logging.DEBUG - - for (l_name, l_level) in [('Slither', default_log), - ('Contract', default_log), - ('Function', default_log), - ('Node', default_log), - ('Parsing', default_log), - ('Detectors', default_log), - ('FunctionSolc', default_log), - ('ExpressionParsing', default_log), - ('TypeParsing', default_log), - ('Printers', default_log)]: - l = logging.getLogger(l_name) - l.setLevel(l_level) - - try: - filename = sys.argv[1] - - if os.path.isfile(filename): - (results, number_contracts) = process(filename, args, detectors, printers) - elif os.path.isdir(filename): - extension = "*.sol" if not args.solc_ast else "*.json" - filenames = glob.glob(os.path.join(filename, extension)) - number_contracts = 0 - results = [] - for filename in filenames: - (results_tmp, number_contracts_tmp) = process(filename, args, detectors, printers) - number_contracts += number_contracts_tmp - results += results_tmp - #if args.json: - # output_json(results, args.json) - #exit(results) - else: - raise Exception("Unrecognised file/dir path: '#{filename}'".format(filename=filename)) - - if args.json: - output_json(results, args.json) - logger.info('%s analyzed (%d contracts), %d result(s) found', filename, number_contracts, len(results)) - exit(results) - - except Exception as e: - logging.error('Error in %s'%sys.argv[1]) - logging.error(traceback.format_exc()) - sys.exit(-1) + return parser.parse_args() + + +def choose_detectors(args, all_detector_classes): + # TODO / FIXME: choose those =) + # if args.detectors_to_run: + # return args.detectors_to_run + # all_detectors = detectors.high + detectors.medium + detectors.low + detectors.code_quality + # if args.exclude_informational: + # all_detectors = [d for d in all_detectors if d not in detectors.code_quality] + # if args.exclude_low: + # all_detectors = [d for d in all_detectors if d not in detectors.low] + # if args.exclude_medium: + # all_detectors = [d for d in all_detectors if d not in detectors.medium] + # if args.exclude_high: + # all_detectors = [d for d in all_detectors if d not in detectors.high] + # if args.detectors_to_exclude: + # all_detectors = [d for d in all_detectors if d not in args.detectors_to_exclude] + return all_detector_classes + + +def choose_printers(args, all_printer_classes): + # TODO / FIXME: choose those =) + return all_printer_classes if __name__ == '__main__': diff --git a/slither/core/slitherCore.py b/slither/core/slitherCore.py index ff3d96ebe..4e711e802 100644 --- a/slither/core/slitherCore.py +++ b/slither/core/slitherCore.py @@ -3,11 +3,11 @@ """ import os + class Slither: """ Slither static analyzer """ - name_class = 'Slither' def __init__(self): self._contracts = {} @@ -69,4 +69,4 @@ class Slither: """ for c in self.contracts: for f in c.functions: - f.cfg_to_dot(os.path.join(d,'{}.{}.dot'.format(c.name, f.name))) + f.cfg_to_dot(os.path.join(d, '{}.{}.dot'.format(c.name, f.name))) diff --git a/slither/detectors/abstractDetector.py b/slither/detectors/abstract_detector.py similarity index 57% rename from slither/detectors/abstractDetector.py rename to slither/detectors/abstract_detector.py index 1bccd3b61..b05cdf9d0 100644 --- a/slither/detectors/abstractDetector.py +++ b/slither/detectors/abstract_detector.py @@ -1,30 +1,51 @@ import abc import re -from slither.detectors.detectorClassification import DetectorClassification -from slither.utils.colors import green, yellow, red, blue + +from slither.utils.colors import green, yellow, red + class IncorrectDetectorInitialization(Exception): pass -class AbstractDetector(object, metaclass=abc.ABCMeta): - ARGUMENT = '' # run the detector with slither.py --ARGUMENT - HELP = '' # help information + +class DetectorClassification: + LOW = 0 + MEDIUM = 1 + HIGH = 2 + CODE_QUALITY = 3 + + +classification_colors = { + DetectorClassification.CODE_QUALITY: green, + DetectorClassification.LOW: green, + DetectorClassification.MEDIUM: yellow, + DetectorClassification.HIGH: red, +} + + +class AbstractDetector(metaclass=abc.ABCMeta): + ARGUMENT = '' # run the detector with slither.py --ARGUMENT + HELP = '' # help information CLASSIFICATION = None - HIDDEN_DETECTOR = False # yes if the detector should not be showed + HIDDEN_DETECTOR = False # yes if the detector should not be showed def __init__(self, slither, logger): self.slither = slither self.contracts = slither.contracts self.filename = slither.filename self.logger = logger - if self.HELP == '': + + if not self.HELP: raise IncorrectDetectorInitialization('HELP is not initialized') - if self.ARGUMENT == '': + + if not self.ARGUMENT: raise IncorrectDetectorInitialization('ARGUMENT is not initialized') + if re.match('^[a-zA-Z0-9_-]*$', self.ARGUMENT) is None: raise IncorrectDetectorInitialization('ARGUMENT has illegal character') - if not self.CLASSIFICATION in [DetectorClassification.LOW, + + if self.CLASSIFICATION not in [DetectorClassification.LOW, DetectorClassification.MEDIUM, DetectorClassification.HIGH, DetectorClassification.CODE_QUALITY]: @@ -41,11 +62,4 @@ class AbstractDetector(object, metaclass=abc.ABCMeta): @property def color(self): - if self.CLASSIFICATION == DetectorClassification.LOW: - return blue - if self.CLASSIFICATION == DetectorClassification.MEDIUM: - return yellow - if self.CLASSIFICATION == DetectorClassification.HIGH: - return red - if self.CLASSIFICATION == DetectorClassification.CODE_QUALITY: - return green + return classification_colors[self.CLASSIFICATION] diff --git a/slither/detectors/attributes/constant_pragma.py b/slither/detectors/attributes/constant_pragma.py index d068c9a90..963dce137 100644 --- a/slither/detectors/attributes/constant_pragma.py +++ b/slither/detectors/attributes/constant_pragma.py @@ -2,8 +2,8 @@ Check that the same pragma is used in all the files """ -from slither.detectors.abstractDetector import AbstractDetector -from slither.detectors.detectorClassification import DetectorClassification +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification + class ConstantPragma(AbstractDetector): """ @@ -14,19 +14,16 @@ class ConstantPragma(AbstractDetector): HELP = 'different pragma directives' CLASSIFICATION = DetectorClassification.CODE_QUALITY - def detect(self): - """ - """ - results = [] pragma = self.slither.pragma_directives pragma = [''.join(p[1:]) for p in pragma] pragma = list(set(pragma)) + if len(pragma) > 1: info = "Different version of Solidity used in {}: {}".format(self.filename, pragma) self.log(info) - results.append({'vuln':'ConstantPragma', 'pragma': pragma}) + results.append({'vuln': 'ConstantPragma', 'pragma': pragma}) return results diff --git a/slither/detectors/attributes/old_solc.py b/slither/detectors/attributes/old_solc.py index 340234c03..4af4d9bfc 100644 --- a/slither/detectors/attributes/old_solc.py +++ b/slither/detectors/attributes/old_solc.py @@ -3,8 +3,8 @@ Solidity >= 0.4.23 should be used """ -from slither.detectors.abstractDetector import AbstractDetector -from slither.detectors.detectorClassification import DetectorClassification +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification + class OldSolc(AbstractDetector): """ @@ -15,15 +15,11 @@ class OldSolc(AbstractDetector): HELP = 'an old version of Solidity used (<0.4.23)' CLASSIFICATION = DetectorClassification.CODE_QUALITY - def detect(self): - """ - """ - results = [] pragma = self.slither.pragma_directives pragma = [''.join(p[1:]) for p in pragma] - pragma = [p.replace('solidity','').replace('^','') for p in pragma] + pragma = [p.replace('solidity', '').replace('^', '') for p in pragma] pragma = list(set(pragma)) old_pragma = [p for p in pragma if p not in ['0.4.23', '0.4.24']] @@ -31,6 +27,6 @@ class OldSolc(AbstractDetector): info = "Old version of Solidity used in {}: {}".format(self.filename, old_pragma) self.log(info) - results.append({'vuln':'OldPragma', 'pragma': old_pragma}) + results.append({'vuln': 'OldPragma', 'pragma': old_pragma}) return results diff --git a/slither/detectors/detectorClassification.py b/slither/detectors/detectorClassification.py deleted file mode 100644 index 24fe5ba2e..000000000 --- a/slither/detectors/detectorClassification.py +++ /dev/null @@ -1,5 +0,0 @@ -class DetectorClassification: - LOW = 0 - MEDIUM = 1 - HIGH = 2 - CODE_QUALITY = 3 diff --git a/slither/detectors/detectors.py b/slither/detectors/detectors.py deleted file mode 100644 index 19826f2ae..000000000 --- a/slither/detectors/detectors.py +++ /dev/null @@ -1,52 +0,0 @@ -import sys, inspect -import os -import logging - -from slither.detectors.abstractDetector import AbstractDetector -from slither.detectors.detectorClassification import DetectorClassification - -# Detectors must be imported here -from slither.detectors.examples.backdoor import Backdoor -from slither.detectors.variables.uninitializedStateVarsDetection import UninitializedStateVarsDetection -from slither.detectors.attributes.constant_pragma import ConstantPragma -from slither.detectors.attributes.old_solc import OldSolc - -### - -logger_detector = logging.getLogger("Detectors") - -class Detectors: - - def __init__(self): - self.detectors = {} - self.low = [] - self.medium = [] - self.high = [] - self.code_quality = [] - - self._load_detectors() - - def _load_detectors(self): - for name, obj in inspect.getmembers(sys.modules[__name__]): - if inspect.isclass(obj): - if issubclass(obj, AbstractDetector) and name != 'AbstractDetector': - if obj.HIDDEN_DETECTOR: - continue - if name in self.detectors: - raise Exception('Detector name collision: {}'.format(name)) - self.detectors[name] = obj - if obj.CLASSIFICATION == DetectorClassification.LOW: - self.low.append(name) - elif obj.CLASSIFICATION == DetectorClassification.MEDIUM: - self.medium.append(name) - elif obj.CLASSIFICATION == DetectorClassification.HIGH: - self.high.append(name) - elif obj.CLASSIFICATION == DetectorClassification.CODE_QUALITY: - self.code_quality.append(name) - else: - raise Exception('Unknown classification') - - def run_detector(self, slither, name): - Detector = self.detectors[name] - instance = Detector(slither, logger_detector) - return instance.detect() diff --git a/slither/detectors/examples/backdoor.py b/slither/detectors/examples/backdoor.py index 4c650807e..dc1e857f6 100644 --- a/slither/detectors/examples/backdoor.py +++ b/slither/detectors/examples/backdoor.py @@ -1,12 +1,12 @@ -from slither.detectors.abstractDetector import AbstractDetector -from slither.detectors.detectorClassification import DetectorClassification +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification + class Backdoor(AbstractDetector): """ Detect function named backdoor """ - ARGUMENT = 'backdoor' # slither will launch the detector with slither.py --mydetector + ARGUMENT = 'backdoor' # slither will launch the detector with slither.py --mydetector HELP = 'function named backdoor (detector example)' CLASSIFICATION = DetectorClassification.HIGH @@ -21,5 +21,6 @@ class Backdoor(AbstractDetector): # Print the info self.log(info) # Add the result in ret - ret.append({'vuln':'backdoor', 'contract':contract.name}) + ret.append({'vuln': 'backdoor', 'contract': contract.name}) + return ret diff --git a/slither/detectors/shadowing/shadowingFunctionsDetection.py b/slither/detectors/shadowing/shadowingFunctionsDetection.py index e41bc3ee0..e2535aaff 100644 --- a/slither/detectors/shadowing/shadowingFunctionsDetection.py +++ b/slither/detectors/shadowing/shadowingFunctionsDetection.py @@ -3,8 +3,8 @@ It is more useful as summary printer than as vuln detection """ -from slither.detectors.abstractDetector import AbstractDetector -from slither.detectors.detectorClassification import DetectorClassification +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification + class ShadowingFunctionsDetection(AbstractDetector): """ @@ -42,7 +42,7 @@ class ShadowingFunctionsDetection(AbstractDetector): shadowing = self.detect_shadowing(c) if shadowing: for contract, funcs in shadowing.items(): - results.append({'vuln':self.vuln_name, + results.append({'vuln': self.vuln_name, 'filename': self.filename, 'contractShadower': c.name, 'contract': contract.name, diff --git a/slither/detectors/variables/uninitializedStateVarsDetection.py b/slither/detectors/variables/uninitializedStateVarsDetection.py index b47641975..1c55d9493 100644 --- a/slither/detectors/variables/uninitializedStateVarsDetection.py +++ b/slither/detectors/variables/uninitializedStateVarsDetection.py @@ -9,11 +9,11 @@ Only analyze "leaf" contracts (contracts that are not inherited by another contract) """ -from slither.detectors.abstractDetector import AbstractDetector -from slither.detectors.detectorClassification import DetectorClassification +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.visitors.expression.findPush import FindPush + class UninitializedStateVarsDetection(AbstractDetector): """ Constant function detector @@ -40,14 +40,13 @@ class UninitializedStateVarsDetection(AbstractDetector): # flat list all_push = [item for sublist in all_push for item in sublist] - uninitialized_vars = list(set([v for v in var_read if\ - v not in var_written and\ - v not in all_push and\ - str(v.type) not in contract.using_for])) # Note: does not handle using X for * + uninitialized_vars = list(set([v for v in var_read if \ + v not in var_written and \ + v not in all_push and \ + str(v.type) not in contract.using_for])) # Note: does not handle using X for * return [(v, contract.get_functions_reading_variable(v)) for v in uninitialized_vars] - def detect(self): """ Detect uninitialized state variables @@ -59,13 +58,13 @@ class UninitializedStateVarsDetection(AbstractDetector): for c in self.slither.contracts_derived: ret = self.detect_uninitialized(c) for variable, functions in ret: - info = "Uninitialized state variables in %s, "%self.filename +\ - "Contract: %s, Vars: %s, Used in %s"%(c.name, - str(variable), - [str(f) for f in functions]) + info = "Uninitialized state variables in %s, " % self.filename + \ + "Contract: %s, Vars: %s, Used in %s" % (c.name, + str(variable), + [str(f) for f in functions]) self.log(info) - results.append({'vuln':'UninitializedStateVars', + results.append({'vuln': 'UninitializedStateVars', 'sourceMapping': c.source_mapping, 'filename': self.filename, 'contract': c.name, diff --git a/slither/printers/abstractPrinter.py b/slither/printers/abstract_printer.py similarity index 74% rename from slither/printers/abstractPrinter.py rename to slither/printers/abstract_printer.py index 06ca714ee..839bb725e 100644 --- a/slither/printers/abstractPrinter.py +++ b/slither/printers/abstract_printer.py @@ -1,20 +1,24 @@ import abc + class IncorrectPrinterInitialization(Exception): pass -class AbstractPrinter(object, metaclass=abc.ABCMeta): - ARGUMENT = '' # run the printer with slither.py --ARGUMENT - HELP = '' # help information + +class AbstractPrinter(metaclass=abc.ABCMeta): + ARGUMENT = '' # run the printer with slither.py --ARGUMENT + HELP = '' # help information def __init__(self, slither, logger): self.slither = slither self.contracts = slither.contracts self.filename = slither.filename self.logger = logger - if self.HELP == '': + + if not self.HELP: raise IncorrectPrinterInitialization('HELP is not initialized') - if self.ARGUMENT == '': + + if not self.ARGUMENT: raise IncorrectPrinterInitialization('ARGUMENT is not initialized') def info(self, info): diff --git a/slither/printers/functions/authorization.py b/slither/printers/functions/authorization.py index b49e57c9c..8a85b6d4e 100644 --- a/slither/printers/functions/authorization.py +++ b/slither/printers/functions/authorization.py @@ -3,7 +3,7 @@ """ from prettytable import PrettyTable -from slither.printers.abstractPrinter import AbstractPrinter +from slither.printers.abstract_printer import AbstractPrinter from slither.core.declarations.function import Function class PrinterWrittenVariablesAndAuthorization(AbstractPrinter): diff --git a/slither/printers/inheritance/printerInheritance.py b/slither/printers/inheritance/printerInheritance.py index 888800607..ba21ddd08 100644 --- a/slither/printers/inheritance/printerInheritance.py +++ b/slither/printers/inheritance/printerInheritance.py @@ -6,13 +6,12 @@ The output is a dot file named filename.dot """ -from slither.printers.abstractPrinter import AbstractPrinter +from slither.core.declarations.contract import Contract from slither.detectors.shadowing.shadowingFunctionsDetection import ShadowingFunctionsDetection +from slither.printers.abstract_printer import AbstractPrinter -from slither.core.declarations.contract import Contract class PrinterInheritance(AbstractPrinter): - ARGUMENT = 'inheritance' HELP = 'the inheritance graph' @@ -38,22 +37,22 @@ class PrinterInheritance(AbstractPrinter): pattern_shadow = ' %s' if contract.name in self.functions_shadowed: if func_name in self.functions_shadowed[contract.name]: - return pattern_shadow%func_name - return pattern%func_name + return pattern_shadow % func_name + return pattern % func_name def _get_pattern_var(self, var, contract): # Html pattern, each line is a row in a table var_name = var.name pattern = ' %s' - pattern_contract = ' %s (%s)' - #pattern_arrow = ' %s' + pattern_contract = ' %s (%s)' + # pattern_arrow = ' %s' if isinstance(var.type, Contract): - return pattern_contract%(var_name, str(var.type)) - #return pattern_arrow%(self._get_port_id(var, contract), var_name) - return pattern%var_name + return pattern_contract % (var_name, str(var.type)) + # return pattern_arrow%(self._get_port_id(var, contract), var_name) + return pattern % var_name def _get_port_id(self, var, contract): - return "%s%s"%(var.name, contract.name) + return "%s%s" % (var.name, contract.name) def _summary(self, contract): """ @@ -62,43 +61,47 @@ class PrinterInheritance(AbstractPrinter): ret = '' # Add arrows for i in contract.inheritances: - ret += '%s -> %s;\n'%(contract.name, i) + ret += '%s -> %s;\n' % (contract.name, i) # Functions visibilities = ['public', 'external'] - public_functions = [self._get_pattern_func(f, contract) for f in contract.functions if not f.is_constructor and f.contract == contract and f.visibility in visibilities] + public_functions = [self._get_pattern_func(f, contract) for f in contract.functions if + not f.is_constructor and f.contract == contract and f.visibility in visibilities] public_functions = ''.join(public_functions) - private_functions = [self._get_pattern_func(f, contract) for f in contract.functions if not f.is_constructor and f.contract == contract and f.visibility not in visibilities] + private_functions = [self._get_pattern_func(f, contract) for f in contract.functions if + not f.is_constructor and f.contract == contract and f.visibility not in visibilities] private_functions = ''.join(private_functions) # Modifiers modifiers = [self._get_pattern_func(m, contract) for m in contract.modifiers if m.contract == contract] modifiers = ''.join(modifiers) # Public variables - public_variables = [self._get_pattern_var(v, contract) for v in contract.variables if v.visibility in visibilities] + public_variables = [self._get_pattern_var(v, contract) for v in contract.variables if + v.visibility in visibilities] public_variables = ''.join(public_variables) - private_variables = [self._get_pattern_var(v, contract) for v in contract.variables if not v.visibility in visibilities] + private_variables = [self._get_pattern_var(v, contract) for v in contract.variables if + not v.visibility in visibilities] private_variables = ''.join(private_variables) # Build the node label - ret += '%s[shape="box"'%contract.name + ret += '%s[shape="box"' % contract.name ret += 'label=< ' - ret += ''%contract.name + ret += '' % contract.name if public_functions: ret += '' - ret += '%s'%public_functions + ret += '%s' % public_functions if private_functions: ret += '' - ret += '%s'%private_functions + ret += '%s' % private_functions if modifiers: ret += '' - ret += '%s'%modifiers + ret += '%s' % modifiers if public_variables: ret += '' - ret += '%s'%public_variables + ret += '%s' % public_variables if private_variables: ret += '' - ret += '%s'%private_variables + ret += '%s' % private_variables ret += '
%s
%s
Public Functions:
Private Functions:
Modifiers:
Public Variables:
Private Variables:
>];\n' return ret @@ -111,7 +114,7 @@ class PrinterInheritance(AbstractPrinter): """ if not filename.endswith('.dot'): filename += ".dot" - info = 'Inheritance Graph: '+filename + info = 'Inheritance Graph: ' + filename self.info(info) with open(filename, 'w') as f: f.write('digraph{\n') diff --git a/slither/printers/printers.py b/slither/printers/printers.py deleted file mode 100644 index dbe5fe0e0..000000000 --- a/slither/printers/printers.py +++ /dev/null @@ -1,31 +0,0 @@ -import sys, inspect -import logging - -from slither.printers.abstractPrinter import AbstractPrinter - -# Printer must be imported here -from slither.printers.summary.printerSummary import PrinterSummary -from slither.printers.summary.printerQuickSummary import PrinterQuickSummary -from slither.printers.inheritance.printerInheritance import PrinterInheritance -from slither.printers.functions.authorization import PrinterWrittenVariablesAndAuthorization - -logger_printer = logging.getLogger("Printers") - -class Printers: - - def __init__(self): - self.printers = {} - self._load_printers() - - def _load_printers(self): - for name, obj in inspect.getmembers(sys.modules[__name__]): - if inspect.isclass(obj): - if issubclass(obj, AbstractPrinter) and name != 'AbstractPrinter': - if name in self.printers: - raise Exception('Printer name collision: {}'.format(name)) - self.printers[name] = obj - - def run_printer(self, slither, name): - Printer = self.printers[name] - instance = Printer(slither, logger_printer) - return instance.output(slither.filename) diff --git a/slither/printers/summary/printerQuickSummary.py b/slither/printers/summary/printerQuickSummary.py index 17a9ea1da..e8e6f40bb 100644 --- a/slither/printers/summary/printerQuickSummary.py +++ b/slither/printers/summary/printerQuickSummary.py @@ -2,7 +2,7 @@ Module printing summary of the contract """ -from slither.printers.abstractPrinter import AbstractPrinter +from slither.printers.abstract_printer import AbstractPrinter from slither.utils.colors import blue, green, magenta class PrinterQuickSummary(AbstractPrinter): diff --git a/slither/printers/summary/printerSummary.py b/slither/printers/summary/printerSummary.py index 1727d61a2..24f36cfa6 100644 --- a/slither/printers/summary/printerSummary.py +++ b/slither/printers/summary/printerSummary.py @@ -3,7 +3,7 @@ """ from prettytable import PrettyTable -from slither.printers.abstractPrinter import AbstractPrinter +from slither.printers.abstract_printer import AbstractPrinter class PrinterSummary(AbstractPrinter): diff --git a/slither/slither.py b/slither/slither.py index 5918795a9..869a42e88 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -1,19 +1,86 @@ -import os -import sys import logging +import os import subprocess +import sys +from slither.detectors.abstract_detector import AbstractDetector +from slither.printers.abstract_printer import AbstractPrinter from .solcParsing.slitherSolc import SlitherSolc from .utils.colors import red logger = logging.getLogger("Slither") logging.basicConfig() +logger_detector = logging.getLogger("Detectors") +logger_printer = logging.getLogger("Printers") + class Slither(SlitherSolc): - def __init__(self, filename, solc='solc', disable_solc_warnings=False ,solc_arguments=''): + def __init__(self, filename, solc='solc', disable_solc_warnings=False, solc_arguments=''): + self._detectors = [] + self._printers = [] + + stdout = self._run_solc(filename, solc, disable_solc_warnings, solc_arguments) + + super(Slither, self).__init__(filename) + + for d in stdout: + self.parse_contracts_from_json(d) + def register_detector(self, detector_class): + """ + :param detector_class: Class inheriting from `AbstractDetector`. + """ + self._check_common_things('detector', detector_class, AbstractDetector, self._detectors) + + instance = detector_class(self, logger_detector) + self._detectors.append(instance) + + def register_printer(self, printer_class): + """ + :param printer_class: Class inheriting from `AbstractPrinter`. + """ + self._check_common_things('printer', printer_class, AbstractPrinter, self._printers) + + instance = printer_class(self, logger_printer) + self._printers.append(instance) + + def run_detectors(self): + """ + :return: List of registered detectors results. + """ + if not self.analyzed: + raise Exception('Launch analysis first by calling {}!'.format(self.analyze_contracts.__name__)) + + return [d.detect() for d in self._detectors] + + def run_printers(self): + """ + :return: List of registered printers outputs. + """ + if not self.analyzed: + raise Exception('Launch analysis first by calling {}!'.format(self.analyze_contracts.__name__)) + + return [p.output(self.filename) for p in self._printers] + + def _check_common_things(self, thing_name, cls, base_cls, instances_list): + if self.analyzed: + raise Exception('There is no point of registering {} after running the analyzis.'.format(thing_name)) + + if not issubclass(cls, base_cls) or cls is base_cls: + raise Exception( + "You can't register {!r} as a {}. You need to pass a class that inherits from {}".format( + cls, thing_name, base_cls.__name__ + ) + ) + + if any(isinstance(obj, cls) for obj in instances_list): + raise Exception( + "You can't register {!r} twice.".format(cls) + ) + + def _run_solc(self, filename, solc, disable_solc_warnings, solc_arguments): if not os.path.isfile(filename): logger.error('{} does not exist (are you in the correct directory?)'.format(filename)) exit(-1) @@ -38,7 +105,7 @@ class Slither(SlitherSolc): # One solc option may have multiple argument sepparated with ' ' # For example: --allow-paths /tmp . # split() removes the delimiter, so we add it again - solc_args = [('--'+x).split(' ', 1) for x in solc_args if x] + solc_args = [('--' + x).split(' ', 1) for x in solc_args if x] # Flat the list of list solc_args = [item for sublist in solc_args for item in sublist] cmd += solc_args @@ -59,11 +126,4 @@ class Slither(SlitherSolc): stdout = stdout.split('\n=') - super(Slither, self).__init__(filename) - for d in stdout: - self.parse_contracts_from_json(d) - - self.analyze_contracts() - - - + return stdout diff --git a/slither/solcParsing/slitherSolc.py b/slither/solcParsing/slitherSolc.py index 15a04a35c..edce679ca 100644 --- a/slither/solcParsing/slitherSolc.py +++ b/slither/solcParsing/slitherSolc.py @@ -7,6 +7,7 @@ logger = logging.getLogger("SlitherSolcParsing") from slither.solcParsing.declarations.contractSolc04 import ContractSolc04 from slither.core.slitherCore import Slither + class SlitherSolc(Slither): def __init__(self, filename): @@ -14,11 +15,12 @@ class SlitherSolc(Slither): self._filename = filename self._contractsNotParsed = [] self._contracts_by_id = {} + self._analyzed = False def parse_contracts_from_json(self, json_data): first = json_data.find('{') if first != -1: - last = json_data.rfind('}') +1 + last = json_data.rfind('}') + 1 filename = json_data[0:first] json_data = json_data[first:last] @@ -26,19 +28,19 @@ class SlitherSolc(Slither): if data_loaded['name'] == 'root': self._solc_version = '0.3' - logger.error('solc <0.4 not supported') + logger.error('solc <0.4 is not supported') exit(-1) elif data_loaded['name'] == 'SourceUnit': self._solc_version = '0.4' self._parse_source_unit(data_loaded, filename) else: - logger.error('solc version not supported') + logger.error('solc version is not supported') exit(-1) for contract_data in data_loaded['children']: -# if self.solc_version == '0.3': -# assert contract_data['name'] == 'Contract' - # contract = ContractSolc03(self, contract_data) + # if self.solc_version == '0.3': + # assert contract_data['name'] == 'Contract' + # contract = ContractSolc03(self, contract_data) if self.solc_version == '0.4': assert contract_data['name'] in ['ContractDefinition', 'PragmaDirective', 'ImportDirective'] if contract_data['name'] == 'ContractDefinition': @@ -54,7 +56,7 @@ class SlitherSolc(Slither): def _parse_source_unit(self, data, filename): if data['name'] != 'SourceUnit': - return -1 # handle solc prior 0.3.6 + return -1 # handle solc prior 0.3.6 # match any char for filename # filename can contain space, /, -, .. @@ -62,7 +64,7 @@ class SlitherSolc(Slither): assert len(name) == 1 name = name[0] - sourceUnit = -1 # handle old solc, or error + sourceUnit = -1 # handle old solc, or error if 'src' in data: sourceUnit = re.findall('[0-9]*:[0-9]*:([0-9]*)', data['src']) if len(sourceUnit) == 1: @@ -71,6 +73,8 @@ class SlitherSolc(Slither): self._source_units[sourceUnit] = name def analyze_contracts(self): + if self._analyzed: + raise Exception('Contract analysis can be run only once!') # First we save all the contracts in a dict # the key is the contractid @@ -105,9 +109,14 @@ class SlitherSolc(Slither): # Then we analyse state variables, functions and modifiers self._analyze_third_part(contracts_to_be_analyzed, libraries) + self._analyzed = True # TODO refactor the following functions, and use a lambda function + @property + def analyzed(self): + return self._analyzed + def _analyze_all_enums(self, contracts_to_be_analyzed): while contracts_to_be_analyzed: contract = contracts_to_be_analyzed[0] @@ -121,7 +130,6 @@ class SlitherSolc(Slither): contracts_to_be_analyzed += [contract] return - def _analyze_first_part(self, contracts_to_be_analyzed, libraries): for lib in libraries: self._parse_struct_var_modifiers_functions(lib) @@ -143,7 +151,6 @@ class SlitherSolc(Slither): contracts_to_be_analyzed += [contract] return - def _analyze_second_part(self, contracts_to_be_analyzed, libraries): for lib in libraries: self._analyze_struct_events(lib) @@ -192,7 +199,7 @@ class SlitherSolc(Slither): contract.set_is_analyzed(True) def _parse_struct_var_modifiers_functions(self, contract): - contract.parse_structs() # struct can refer another struct + contract.parse_structs() # struct can refer another struct contract.parse_state_variables() contract.parse_modifiers() contract.parse_functions() @@ -220,4 +227,3 @@ class SlitherSolc(Slither): contract.analyze_content_functions() contract.set_is_analyzed(True) - From a6f08ed6aa6d3279a82e9408285b6b1c8c15bbd8 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 11 Sep 2018 09:21:11 +0100 Subject: [PATCH 014/308] Move contract analysis to Slither.__init__. Remove useless check --- slither/__main__.py | 2 -- slither/slither.py | 8 ++------ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/slither/__main__.py b/slither/__main__.py index a003afffe..c65ab0a28 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -29,8 +29,6 @@ def process(filename, args, detector_classes, printer_classes): for printer_cls in printer_classes: slither.register_printer(printer_cls) - slither.analyze_contracts() - analyzed_contracts_count = len(slither.contracts) results = [] diff --git a/slither/slither.py b/slither/slither.py index 869a42e88..67290718a 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -28,6 +28,8 @@ class Slither(SlitherSolc): for d in stdout: self.parse_contracts_from_json(d) + self.analyze_contracts() + def register_detector(self, detector_class): """ :param detector_class: Class inheriting from `AbstractDetector`. @@ -50,8 +52,6 @@ class Slither(SlitherSolc): """ :return: List of registered detectors results. """ - if not self.analyzed: - raise Exception('Launch analysis first by calling {}!'.format(self.analyze_contracts.__name__)) return [d.detect() for d in self._detectors] @@ -59,14 +59,10 @@ class Slither(SlitherSolc): """ :return: List of registered printers outputs. """ - if not self.analyzed: - raise Exception('Launch analysis first by calling {}!'.format(self.analyze_contracts.__name__)) return [p.output(self.filename) for p in self._printers] def _check_common_things(self, thing_name, cls, base_cls, instances_list): - if self.analyzed: - raise Exception('There is no point of registering {} after running the analyzis.'.format(thing_name)) if not issubclass(cls, base_cls) or cls is base_cls: raise Exception( From 95bba51e199ff8891870422f7044e8af56560746 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 11 Sep 2018 09:48:12 +0100 Subject: [PATCH 015/308] Implement choose_detectors / choose_printers --- slither/__main__.py | 49 +++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/slither/__main__.py b/slither/__main__.py index c65ab0a28..b465a4cbf 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -10,6 +10,8 @@ import traceback from slither.slither import Slither +from slither.detectors.abstract_detector import DetectorClassification + logging.basicConfig() logger = logging.getLogger("Slither") @@ -198,6 +200,8 @@ def parse_args(detector_classes, printer_classes): dest="detectors_to_run", const=detector_cls.ARGUMENT) + # Second loop so that the --exclude are shown after all the detectors + for detector_cls in detector_classes: exclude_detector_arg = '--exclude-{}'.format(detector_cls.ARGUMENT) exclude_detector_help = 'Exclude {} detector'.format(detector_cls.ARGUMENT) parser.add_argument(exclude_detector_arg, @@ -224,26 +228,37 @@ def parse_args(detector_classes, printer_classes): def choose_detectors(args, all_detector_classes): - # TODO / FIXME: choose those =) - # if args.detectors_to_run: - # return args.detectors_to_run - # all_detectors = detectors.high + detectors.medium + detectors.low + detectors.code_quality - # if args.exclude_informational: - # all_detectors = [d for d in all_detectors if d not in detectors.code_quality] - # if args.exclude_low: - # all_detectors = [d for d in all_detectors if d not in detectors.low] - # if args.exclude_medium: - # all_detectors = [d for d in all_detectors if d not in detectors.medium] - # if args.exclude_high: - # all_detectors = [d for d in all_detectors if d not in detectors.high] - # if args.detectors_to_exclude: - # all_detectors = [d for d in all_detectors if d not in args.detectors_to_exclude] - return all_detector_classes + # If detectors are specified, run only these ones + if args.detectors_to_run: + return [d for d in all_detector_classes if d.ARGUMENT in args.detectors_to_run] + + detectors_to_run = all_detector_classes + + if args.exclude_informational: + detectors_to_run = [d for d in detectors_to_run if + d.CLASSIFICATION != DetectorClassification.CODE_QUALITY] + if args.exclude_low: + detectors_to_run = [d for d in detectors_to_run if + d.CLASSIFICATION != DetectorClassification.LOW] + if args.exclude_medium: + detectors_to_run = [d for d in detectors_to_run if + d.CLASSIFICATION != DetectorClassification.MEDIUM] + if args.exclude_high: + detectors_to_run = [d for d in detectors_to_run if + d.CLASSIFICATION != DetectorClassification.HIGH] + if args.detectors_to_exclude: + detectors_to_run = [d for d in detectors_to_run if + d.ARGUMENT not in args.detectors_to_exclude] + return detectors_to_run def choose_printers(args, all_printer_classes): - # TODO / FIXME: choose those =) - return all_printer_classes + # by default, dont run any printer + printers_to_run = [] + if args.printers_to_run: + printers_to_run = [p for p in all_printer_classes if + p.ARGUMENT in args.printers_to_run] + return printers_to_run if __name__ == '__main__': From 94c01145360b505677a04e1efdabc45c6165dbab Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 11 Sep 2018 10:07:24 +0100 Subject: [PATCH 016/308] Fix incorrect summary in printerQuickSummary Typo in contract.get_summary documentation Clean code --- slither/core/declarations/contract.py | 2 +- slither/printers/summary/printerQuickSummary.py | 6 +++--- slither/printers/summary/printerSummary.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index ac2868eec..a34ed3d4d 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -290,7 +290,7 @@ class Contract(ChildSlither, SourceMapping): """ Return the function summary Returns: - (str, list, list, list): (name, variables, fuction summaries, modifier summaries) + (str, list, list, list, list): (name, inheritances, variables, fuction summaries, modifier summaries) """ func_summaries = [f.get_summary() for f in self.functions] modif_summaries = [f.get_summary() for f in self.modifiers] diff --git a/slither/printers/summary/printerQuickSummary.py b/slither/printers/summary/printerQuickSummary.py index 17a9ea1da..91c5c786b 100644 --- a/slither/printers/summary/printerQuickSummary.py +++ b/slither/printers/summary/printerQuickSummary.py @@ -19,13 +19,13 @@ class PrinterQuickSummary(AbstractPrinter): txt = "" for c in self.contracts: - (name, var, func_summaries, modif_summaries) = c.get_summary() + (name, _inheritances, _var, func_summaries, _modif_summaries) = c.get_summary() txt += blue("\n+ Contract %s\n"%name) - for (f_name, visi, modifiers, read, write, calls) in func_summaries: + for (f_name, visi, _modifiers, _read, _write, _calls) in func_summaries: txt += " - " if visi in ['external', 'public']: txt += green("%s (%s)\n"%(f_name, visi)) - elif visi in ['internal','private']: + elif visi in ['internal', 'private']: txt += magenta("%s (%s)\n"%(f_name, visi)) else: txt += "%s (%s)\n"%(f_name, visi) diff --git a/slither/printers/summary/printerSummary.py b/slither/printers/summary/printerSummary.py index 1727d61a2..c8186f267 100644 --- a/slither/printers/summary/printerSummary.py +++ b/slither/printers/summary/printerSummary.py @@ -27,7 +27,7 @@ class PrinterSummary(AbstractPrinter): """ for c in self.contracts: - (name, var, inheritances, func_summaries, modif_summaries) = c.get_summary() + (name, inheritances, var, func_summaries, modif_summaries) = c.get_summary() txt = "\nContract %s"%name txt += '\nContract vars: '+str(var) txt += '\nInheritances:: '+str(inheritances) From 32ade9df333ab46aba834f1914bfd99bb950f9e8 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 11 Sep 2018 10:36:34 +0100 Subject: [PATCH 017/308] Make SlitherSolc parse_contracts_from_json / analyze_contracts private --- slither/slither.py | 4 ++-- slither/solcParsing/slitherSolc.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/slither/slither.py b/slither/slither.py index 67290718a..6e4d67b59 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -26,9 +26,9 @@ class Slither(SlitherSolc): super(Slither, self).__init__(filename) for d in stdout: - self.parse_contracts_from_json(d) + self._parse_contracts_from_json(d) - self.analyze_contracts() + self._analyze_contracts() def register_detector(self, detector_class): """ diff --git a/slither/solcParsing/slitherSolc.py b/slither/solcParsing/slitherSolc.py index edce679ca..029c135b5 100644 --- a/slither/solcParsing/slitherSolc.py +++ b/slither/solcParsing/slitherSolc.py @@ -17,7 +17,7 @@ class SlitherSolc(Slither): self._contracts_by_id = {} self._analyzed = False - def parse_contracts_from_json(self, json_data): + def _parse_contracts_from_json(self, json_data): first = json_data.find('{') if first != -1: last = json_data.rfind('}') + 1 @@ -72,7 +72,7 @@ class SlitherSolc(Slither): self._source_units[sourceUnit] = name - def analyze_contracts(self): + def _analyze_contracts(self): if self._analyzed: raise Exception('Contract analysis can be run only once!') From d2c33dce48eaa35c0cec898a6bbb24b062db1ca8 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 11 Sep 2018 10:51:43 +0100 Subject: [PATCH 018/308] Remove travis.yml --- .travis.yml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 461ff4174..000000000 --- a/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -sudo: required -os: - - linux -language: python -python: - - 3.6 - -branches: - only: - - master - -install: - - scripts/travis_install.sh - -script: - - scripts/travis_test.sh - - From bed743c5db06b46033af0bdc118d74b7a76a6bca Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 11 Sep 2018 10:53:04 +0100 Subject: [PATCH 019/308] Re-add travis.yml --- .travis.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..461ff4174 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +sudo: required +os: + - linux +language: python +python: + - 3.6 + +branches: + only: + - master + +install: + - scripts/travis_install.sh + +script: + - scripts/travis_test.sh + + From ee8f531f2094ac6a582efe31c9dc5fbdfcdcec25 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 11 Sep 2018 11:37:57 +0100 Subject: [PATCH 020/308] Add dev branch to travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 461ff4174..5f5c642cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ python: branches: only: - master + - dev install: - scripts/travis_install.sh From deedfd695e5ab8ac3d56249796c480bb86bf985c Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 11 Sep 2018 15:26:40 +0100 Subject: [PATCH 021/308] Use parse_type to parse using for type Add __eq__ and __hash__ functions to solidityType Update uninitialized var detector with new using_for --- slither/core/solidityTypes/arrayType.py | 8 ++++++++ slither/core/solidityTypes/elementaryType.py | 8 ++++++++ slither/core/solidityTypes/functionType.py | 7 +++++++ slither/core/solidityTypes/mappingType.py | 9 +++++++++ slither/core/solidityTypes/userDefinedType.py | 9 +++++++++ .../variables/uninitializedStateVarsDetection.py | 2 +- slither/solcParsing/declarations/contractSolc04.py | 8 +++++--- 7 files changed, 47 insertions(+), 4 deletions(-) diff --git a/slither/core/solidityTypes/arrayType.py b/slither/core/solidityTypes/arrayType.py index 1eec105d1..f76cd364a 100644 --- a/slither/core/solidityTypes/arrayType.py +++ b/slither/core/solidityTypes/arrayType.py @@ -24,3 +24,11 @@ class ArrayType(Type): return str(self._type)+'[{}]'.format(str(self._length)) return str(self._type)+'[]' + + def __eq__(self, other): + if not isinstance(other, ArrayType): + return False + return self._type == other.type and self.length == other.length + + def __hash__(self): + return hash(str(self)) diff --git a/slither/core/solidityTypes/elementaryType.py b/slither/core/solidityTypes/elementaryType.py index a2354685b..06955541c 100644 --- a/slither/core/solidityTypes/elementaryType.py +++ b/slither/core/solidityTypes/elementaryType.py @@ -48,3 +48,11 @@ class ElementaryType(Type): def __str__(self): return self._type + def __eq__(self, other): + if not isinstance(other, ElementaryType): + return False + return self.type == other.type + + def __hash__(self): + return hash(str(self)) + diff --git a/slither/core/solidityTypes/functionType.py b/slither/core/solidityTypes/functionType.py index 90b7cbf21..0b4586d1d 100644 --- a/slither/core/solidityTypes/functionType.py +++ b/slither/core/solidityTypes/functionType.py @@ -24,3 +24,10 @@ class FunctionType(Type): return_values = ".".join([str(x) for x in self._return_values]) return 'function({}) returns ({})'.format(params, return_values) + def __eq__(self, other): + if not isinstance(other, FunctionType): + return False + return self.params == other.params and self.return_values == other.return_values + + def __hash__(self): + return hash(str(self)) diff --git a/slither/core/solidityTypes/mappingType.py b/slither/core/solidityTypes/mappingType.py index c718c8f81..fb7604267 100644 --- a/slither/core/solidityTypes/mappingType.py +++ b/slither/core/solidityTypes/mappingType.py @@ -19,3 +19,12 @@ class MappingType(Type): def __str__(self): return 'mapping({} => {}'.format(str(self._from), str(self._to)) + + def __eq__(self, other): + if not isinstance(other, MappingType): + return False + return self.type_from == other.type_from and self.type_to == other.type_to + + def __hash__(self): + return hash(str(self)) + diff --git a/slither/core/solidityTypes/userDefinedType.py b/slither/core/solidityTypes/userDefinedType.py index f0ac1d6d2..b97fcf014 100644 --- a/slither/core/solidityTypes/userDefinedType.py +++ b/slither/core/solidityTypes/userDefinedType.py @@ -21,3 +21,12 @@ class UserDefinedType(Type): return str(self.type.contract)+'.'+str(self.type.name) return str(self.type.name) + def __eq__(self, other): + if not isinstance(other, UserDefinedType): + return False + return self.type == other.type + + + def __hash__(self): + return hash(str(self)) + diff --git a/slither/detectors/variables/uninitializedStateVarsDetection.py b/slither/detectors/variables/uninitializedStateVarsDetection.py index 1c55d9493..4e9a5b076 100644 --- a/slither/detectors/variables/uninitializedStateVarsDetection.py +++ b/slither/detectors/variables/uninitializedStateVarsDetection.py @@ -43,7 +43,7 @@ class UninitializedStateVarsDetection(AbstractDetector): uninitialized_vars = list(set([v for v in var_read if \ v not in var_written and \ v not in all_push and \ - str(v.type) not in contract.using_for])) # Note: does not handle using X for * + v.type not in contract.using_for])) # Note: does not handle using X for * return [(v, contract.get_functions_reading_variable(v)) for v in uninitialized_vars] diff --git a/slither/solcParsing/declarations/contractSolc04.py b/slither/solcParsing/declarations/contractSolc04.py index 4caba8e2d..a37d8f052 100644 --- a/slither/solcParsing/declarations/contractSolc04.py +++ b/slither/solcParsing/declarations/contractSolc04.py @@ -10,6 +10,8 @@ from slither.solcParsing.declarations.functionSolc import FunctionSolc from slither.solcParsing.variables.stateVariableSolc import StateVariableSolc +from slither.solcParsing.solidityTypes.typeParsing import parse_type + logger = logging.getLogger("ContractSolcParsing") class ContractSolc04(Contract): @@ -94,10 +96,10 @@ class ContractSolc04(Contract): children = using_for['children'] assert children and len(children) <= 2 if len(children) == 2: - new = children[0]['attributes']['name'] - old = children[1]['attributes']['name'] + new = parse_type(children[0], self) + old = parse_type(children[1], self) else: - new = children[0]['attributes']['name'] + new = parse_type(children[0], self) old = '*' self._using_for[old] = new self._usingForNotParsed = [] From 05c7dbe8690cd0d9d8644173913423300ad77f37 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 11 Sep 2018 15:45:01 +0100 Subject: [PATCH 022/308] Fix missing char for call parsing --- slither/solcParsing/expressions/expressionParsing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/solcParsing/expressions/expressionParsing.py b/slither/solcParsing/expressions/expressionParsing.py index 0b24efde9..33cd4582a 100644 --- a/slither/solcParsing/expressions/expressionParsing.py +++ b/slither/solcParsing/expressions/expressionParsing.py @@ -254,7 +254,7 @@ def parse_expression(expression, caller_context): if 'type' in expression['attributes']: t = expression['attributes']['type'] if t: - found = re.findall('[struct|enum|function|modifier] \(([\[\] a-zA-Z0-9\.,]*)\)', t) + found = re.findall('[struct|enum|function|modifier] \(([\[\] a-zA-Z0-9\.,_]*)\)', t) if found: value = value+'('+found[0]+')' value = filter_name(value) From 36102142a0456d7cc1f4675db127f2649af94cf6 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 11 Sep 2018 18:04:04 +0100 Subject: [PATCH 023/308] Better handling of function pointer Add return value information for solidity inbuilt function --- .../core/declarations/solidityVariables.py | 36 ++++++++++++++-- slither/core/solidityTypes/functionType.py | 35 ++++++++++++++-- .../expressions/expressionParsing.py | 41 ++++++++++++++++++- .../solcParsing/solidityTypes/typeParsing.py | 40 ++++++++++-------- .../variables/variableDeclarationSolc.py | 2 + 5 files changed, 128 insertions(+), 26 deletions(-) diff --git a/slither/core/declarations/solidityVariables.py b/slither/core/declarations/solidityVariables.py index 7d537f127..81c840dec 100644 --- a/slither/core/declarations/solidityVariables.py +++ b/slither/core/declarations/solidityVariables.py @@ -5,7 +5,39 @@ SOLIDITY_VARIABLES = ["block", "msg", "now", "tx", "this", "super", 'abi'] SOLIDITY_VARIABLES_COMPOSED = ["block.coinbase", "block.difficulty", "block.gaslimit", "block.number", "block.timestamp", "msg.data", "msg.gas", "msg.sender", "msg.sig", "msg.value", "tx.gasprice", "tx.origin"] -SOLIDITY_FUNCTIONS = ["gasleft()", "assert(bool)", "require(bool)", "require(bool,string)", "revert()", "revert(string)", "addmod(uint256,uint256,uint256)", "mulmod(uint256,uint256,uint256)", "keccak256()", "sha256()", "sha3()", "ripemd160()", "ecrecover(bytes32,uint8,bytes32,bytes32)", "selfdestruct(address)", "suicide(address)", "log0(bytes32)", "log1(bytes32,bytes32)", "log2(bytes32,bytes32,bytes32)", "log3(bytes32,bytes32,bytes32,bytes32)", "blockhash(uint256)"] + +SOLIDITY_FUNCTIONS = {"gasleft()":['uint256'], + "assert(bool)":[], + "require(bool)":[], + "require(bool,string)":[], + "revert()":[], + "revert(string)":[], + "addmod(uint256,uint256,uint256)":['uint256'], + "mulmod(uint256,uint256,uint256)":['uint256'], + "keccak256()":['bytes32'], + "sha256()":['bytes32'], + "sha3()":['bytes32'], + "ripemd160()":['bytes32'], + "ecrecover(bytes32,uint8,bytes32,bytes32)":['address'], + "selfdestruct(address)":[], + "suicide(address)":[], + "log0(bytes32)":[], + "log1(bytes32,bytes32)":[], + "log2(bytes32,bytes32,bytes32)":[], + "log3(bytes32,bytes32,bytes32,bytes32)":[], + "blockhash(uint256)":['bytes32']} + +def solidity_function_signature(name): + """ + Return the function signature (containing the return value) + It is useful if a solidity function is used as a pointer + (see exoressionParsing.find_variable documentation) + Args: + name(str): + Returns: + str + """ + return name+' returns({})'.format(','.join(SOLIDITY_FUNCTIONS[name])) class SolidityVariable: @@ -32,8 +64,6 @@ class SolidityVariableComposed(SolidityVariable): def __str__(self): return self._name - - class SolidityFunction: def __init__(self, name): diff --git a/slither/core/solidityTypes/functionType.py b/slither/core/solidityTypes/functionType.py index 0b4586d1d..2b652eb86 100644 --- a/slither/core/solidityTypes/functionType.py +++ b/slither/core/solidityTypes/functionType.py @@ -20,9 +20,38 @@ class FunctionType(Type): return self._return_values def __str__(self): - params = ".".join([str(x) for x in self._params]) - return_values = ".".join([str(x) for x in self._return_values]) - return 'function({}) returns ({})'.format(params, return_values) + # Use x.type + # x.name may be empty + params = ".".join([str(x.type) for x in self._params]) + return_values = ".".join([str(x.type) for x in self._return_values]) + if return_values: + return 'function({}) returns({})'.format(params, return_values) + return 'function({})'.format(params) + + @property + def parameters_signature(self): + ''' + Return the parameters signature(without the return statetement) + ''' + # Use x.type + # x.name may be empty + params = ".".join([str(x.type) for x in self._params]) + return '({})'.format(params) + + @property + def signature(self): + ''' + Return the signature(with the return statetement if it exists) + ''' + # Use x.type + # x.name may be empty + params = ".".join([str(x.type) for x in self._params]) + return_values = ".".join([str(x.type) for x in self._return_values]) + if return_values: + return '({}) returns({})'.format(params, return_values) + return '({})'.format(params) + + def __eq__(self, other): if not isinstance(other, FunctionType): diff --git a/slither/solcParsing/expressions/expressionParsing.py b/slither/solcParsing/expressions/expressionParsing.py index 33cd4582a..194fa3928 100644 --- a/slither/solcParsing/expressions/expressionParsing.py +++ b/slither/solcParsing/expressions/expressionParsing.py @@ -24,9 +24,10 @@ from slither.core.declarations.contract import Contract from slither.core.declarations.function import Function from slither.core.declarations.solidityVariables import SOLIDITY_VARIABLES, SOLIDITY_FUNCTIONS, SOLIDITY_VARIABLES_COMPOSED -from slither.core.declarations.solidityVariables import SolidityVariable, SolidityFunction, SolidityVariableComposed +from slither.core.declarations.solidityVariables import SolidityVariable, SolidityFunction, SolidityVariableComposed, solidity_function_signature from slither.core.solidityTypes.elementaryType import ElementaryType +from slither.core.solidityTypes.functionType import FunctionType logger = logging.getLogger("ExpressionParsing") @@ -48,6 +49,20 @@ def find_variable(var_name, caller_context): func_variables = function.variables_as_dict() if var_name in func_variables: return func_variables[var_name] + # A local variable can be a pointer + # for example + # function test(function(uint) internal returns(bool) t) internal{ + # Will have a local variable t which will match the signature + # t(uint256) + func_variables_ptr = {f.name + f.type.parameters_signature : f for f in function.variables + if isinstance(f.type, FunctionType)} + if var_name in func_variables_ptr: + return func_variables_ptr[var_name] + # if it contains the return value + func_variables_ptr = {f.name + f.type.signature : f for f in function.variables + if isinstance(f.type, FunctionType)} + if var_name in func_variables_ptr: + return func_variables_ptr[var_name] contract_variables = contract.variables_as_dict() if var_name in contract_variables: @@ -57,6 +72,17 @@ def find_variable(var_name, caller_context): if var_name in functions: return functions[var_name] + # With funciton pointer, the signature with the return value is used + # function test(function(uint) internal returns(bool) t) internal{ + # .. + # test(called) + # .. + # funcion called(uint) returns(bool) + # called is looked as 'called(uint256) returns(bool) + functions = {f.signature_str:f for f in contract.functions} + if var_name in functions: + return functions[var_name] + modifiers = contract.modifiers_as_dict() if var_name in modifiers: return modifiers[var_name] @@ -90,6 +116,11 @@ def find_variable(var_name, caller_context): if var_name in SOLIDITY_FUNCTIONS: return SolidityFunction(var_name) + # With funciton pointer, the signature with the return value is used + solidity_signatures = {solidity_function_signature(f):f for f in SOLIDITY_FUNCTIONS} + if var_name in solidity_signatures: + return SolidityFunction(solidity_signatures[var_name]) + contracts = contract.slither.contracts_as_dict() if var_name in contracts: return contracts[var_name] @@ -153,6 +184,11 @@ def filter_name(value): value = value.replace('enum ', '') value = value.replace(' ref', '') value = value.replace(' pointer', '') + value = value.replace(' pure ', ' ') + value = value.replace(' view ', ' ') + value = value.replace('function (', 'function(') + value = value.replace('returns (', 'returns(') + return value def parse_expression(expression, caller_context): @@ -254,7 +290,8 @@ def parse_expression(expression, caller_context): if 'type' in expression['attributes']: t = expression['attributes']['type'] if t: - found = re.findall('[struct|enum|function|modifier] \(([\[\] a-zA-Z0-9\.,_]*)\)', t) + found = re.findall('[struct|enum|function|modifier] \(([\[\] ()a-zA-Z0-9\.,_]*)\)', t) + assert len(found) <= 1 if found: value = value+'('+found[0]+')' value = filter_name(value) diff --git a/slither/solcParsing/solidityTypes/typeParsing.py b/slither/solcParsing/solidityTypes/typeParsing.py index 612aa9d61..6816e1d30 100644 --- a/slither/solcParsing/solidityTypes/typeParsing.py +++ b/slither/solcParsing/solidityTypes/typeParsing.py @@ -78,23 +78,28 @@ def _find_from_type_name(name, contract, contracts, structures, enums): var_type = next((f for f in contract.functions if f.name == name), None) if not var_type: if name.startswith('function '): - found = re.findall('function \(([a-zA-Z0-9\.,]*)\) returns \(([a-zA-Z0-9\.,]*)\)', name) - assert len(found) == 1 - params = found[0][0].split(',') - return_values = found[0][1].split(',') - params = [_find_from_type_name(p, contract, contracts, structures, enums) for p in params] - return_values = [_find_from_type_name(r, contract, contracts, structures, enums) for r in return_values] - params_vars = [] - return_vars = [] - for p in params: - var = FunctionTypeVariable() - var.set_type(p) - params_vars.append(var) - for r in return_values: - var = FunctionTypeVariable() - var.set_type(r) - return_vars.append(var) - return FunctionType(params_vars, return_vars) + print(name) + found = re.findall('function \(([ ()a-zA-Z0-9\.,]*)\) returns \(([a-zA-Z0-9\.,]*)\)', name) + print(found) + assert len(found) == 1 + params = found[0][0].split(',') + return_values = found[0][1].split(',') + params = [_find_from_type_name(p, contract, contracts, structures, enums) for p in params] + return_values = [_find_from_type_name(r, contract, contracts, structures, enums) for r in return_values] + params_vars = [] + return_vars = [] + for p in params: + var = FunctionTypeVariable() + var.set_type(p) + params_vars.append(var) + for r in return_values: + var = FunctionTypeVariable() + var.set_type(r) + return_vars.append(var) + print('Parse {}'.format(name)) + print(params) + print(params_vars) + return FunctionType(params_vars, return_vars) if not var_type: if name.startswith('mapping('): found = re.findall('mapping\(([a-zA-Z0-9\.]*) => ([a-zA-Z0-9\.]*)\)', name) @@ -171,7 +176,6 @@ def parse_type(t, caller_context): return_values_vars = [] for p in params['children']: var = FunctionTypeVariableSolc(p) - var.set_offset(p['src']) var.analyze(caller_context) params_vars.append(var) diff --git a/slither/solcParsing/variables/variableDeclarationSolc.py b/slither/solcParsing/variables/variableDeclarationSolc.py index bd703cd92..78a6cd428 100644 --- a/slither/solcParsing/variables/variableDeclarationSolc.py +++ b/slither/solcParsing/variables/variableDeclarationSolc.py @@ -30,6 +30,8 @@ class VariableDeclarationSolc(Variable): super(VariableDeclarationSolc, self).__init__() self._was_analyzed = False + self._elem_to_parse = None + self._initializedNotParsed = None if var['name'] in ['VariableDeclarationStatement', 'VariableDefinitionStatement']: if len(var['children']) == 2: From 8465d9282e8329494a5781e3a935238f5f1584d3 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 11 Sep 2018 19:06:08 +0100 Subject: [PATCH 024/308] Add constant keyword to filter_name --- slither/solcParsing/expressions/expressionParsing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/slither/solcParsing/expressions/expressionParsing.py b/slither/solcParsing/expressions/expressionParsing.py index 194fa3928..8878438ec 100644 --- a/slither/solcParsing/expressions/expressionParsing.py +++ b/slither/solcParsing/expressions/expressionParsing.py @@ -186,6 +186,7 @@ def filter_name(value): value = value.replace(' pointer', '') value = value.replace(' pure ', ' ') value = value.replace(' view ', ' ') + value = value.replace(' constant ', ' ') value = value.replace('function (', 'function(') value = value.replace('returns (', 'returns(') From 7c5af946ad92cded4bb75a8546f42d8e7cc30787 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 11 Sep 2018 19:16:50 +0100 Subject: [PATCH 025/308] Typo + clean code --- slither/core/solidityTypes/functionType.py | 10 +++++----- slither/solcParsing/solidityTypes/typeParsing.py | 5 ----- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/slither/core/solidityTypes/functionType.py b/slither/core/solidityTypes/functionType.py index 2b652eb86..af4421122 100644 --- a/slither/core/solidityTypes/functionType.py +++ b/slither/core/solidityTypes/functionType.py @@ -22,8 +22,8 @@ class FunctionType(Type): def __str__(self): # Use x.type # x.name may be empty - params = ".".join([str(x.type) for x in self._params]) - return_values = ".".join([str(x.type) for x in self._return_values]) + params = ",".join([str(x.type) for x in self._params]) + return_values = ",".join([str(x.type) for x in self._return_values]) if return_values: return 'function({}) returns({})'.format(params, return_values) return 'function({})'.format(params) @@ -35,7 +35,7 @@ class FunctionType(Type): ''' # Use x.type # x.name may be empty - params = ".".join([str(x.type) for x in self._params]) + params = ",".join([str(x.type) for x in self._params]) return '({})'.format(params) @property @@ -45,8 +45,8 @@ class FunctionType(Type): ''' # Use x.type # x.name may be empty - params = ".".join([str(x.type) for x in self._params]) - return_values = ".".join([str(x.type) for x in self._return_values]) + params = ",".join([str(x.type) for x in self._params]) + return_values = ",".join([str(x.type) for x in self._return_values]) if return_values: return '({}) returns({})'.format(params, return_values) return '({})'.format(params) diff --git a/slither/solcParsing/solidityTypes/typeParsing.py b/slither/solcParsing/solidityTypes/typeParsing.py index 6816e1d30..07625c01e 100644 --- a/slither/solcParsing/solidityTypes/typeParsing.py +++ b/slither/solcParsing/solidityTypes/typeParsing.py @@ -78,9 +78,7 @@ def _find_from_type_name(name, contract, contracts, structures, enums): var_type = next((f for f in contract.functions if f.name == name), None) if not var_type: if name.startswith('function '): - print(name) found = re.findall('function \(([ ()a-zA-Z0-9\.,]*)\) returns \(([a-zA-Z0-9\.,]*)\)', name) - print(found) assert len(found) == 1 params = found[0][0].split(',') return_values = found[0][1].split(',') @@ -96,9 +94,6 @@ def _find_from_type_name(name, contract, contracts, structures, enums): var = FunctionTypeVariable() var.set_type(r) return_vars.append(var) - print('Parse {}'.format(name)) - print(params) - print(params_vars) return FunctionType(params_vars, return_vars) if not var_type: if name.startswith('mapping('): From 5fb14cadc434ab15772dd85b041258db14aa1396 Mon Sep 17 00:00:00 2001 From: disconnect3d Date: Tue, 11 Sep 2018 21:44:30 +0200 Subject: [PATCH 026/308] Rename Slither package to slither So it is more Pythonic. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b4425acf8..c6f6d0fb7 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup, find_packages setup( - name='Slither', + name='slither', description='Slither is a Solidity static analysis framework written in Python 3.', url='https://github.com/trailofbits/slither', author='Trail of Bits', From ad3f50d911ab7ad10d2b3bdb6a426dbdad59e826 Mon Sep 17 00:00:00 2001 From: disconnect3d Date: Tue, 11 Sep 2018 22:19:00 +0200 Subject: [PATCH 027/308] Rename slither package to slither-sol As there is already a slither pypi package. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c6f6d0fb7..8ab6a4b2f 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup, find_packages setup( - name='slither', + name='slither-sol', description='Slither is a Solidity static analysis framework written in Python 3.', url='https://github.com/trailofbits/slither', author='Trail of Bits', From 1d45b4aa1f6efe7435d8b40c22b7ebf2cb0e9709 Mon Sep 17 00:00:00 2001 From: Mark Mossberg Date: Tue, 11 Sep 2018 22:36:41 +0200 Subject: [PATCH 028/308] Update README.md (#15) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 39e77953a..15c2e7b60 100644 --- a/README.md +++ b/README.md @@ -85,4 +85,4 @@ For more information about printers, see the [Printers documentation](docs/PRINT ## License -Slither is licensed and distributed under AGPLv3. [Contact us](mailto:opensource@trailofbits.com) if you're looking for an exception to the terms. +Slither is licensed and distributed under the AGPLv3 license. [Contact us](mailto:opensource@trailofbits.com) if you're looking for an exception to the terms. From 4705499f206247384a7467ce7b2a59e2f21cc11f Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 12 Sep 2018 08:13:58 +0100 Subject: [PATCH 029/308] ExpressionParsing: Remove return value in case if function pointer --- .../expressions/expressionParsing.py | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/slither/solcParsing/expressions/expressionParsing.py b/slither/solcParsing/expressions/expressionParsing.py index 8878438ec..a8e8db4fd 100644 --- a/slither/solcParsing/expressions/expressionParsing.py +++ b/slither/solcParsing/expressions/expressionParsing.py @@ -51,18 +51,13 @@ def find_variable(var_name, caller_context): return func_variables[var_name] # A local variable can be a pointer # for example - # function test(function(uint) internal returns(bool) t) internal{ + # function test(function(uint) internal returns(bool) t) interna{ # Will have a local variable t which will match the signature # t(uint256) func_variables_ptr = {f.name + f.type.parameters_signature : f for f in function.variables if isinstance(f.type, FunctionType)} if var_name in func_variables_ptr: return func_variables_ptr[var_name] - # if it contains the return value - func_variables_ptr = {f.name + f.type.signature : f for f in function.variables - if isinstance(f.type, FunctionType)} - if var_name in func_variables_ptr: - return func_variables_ptr[var_name] contract_variables = contract.variables_as_dict() if var_name in contract_variables: @@ -72,17 +67,6 @@ def find_variable(var_name, caller_context): if var_name in functions: return functions[var_name] - # With funciton pointer, the signature with the return value is used - # function test(function(uint) internal returns(bool) t) internal{ - # .. - # test(called) - # .. - # funcion called(uint) returns(bool) - # called is looked as 'called(uint256) returns(bool) - functions = {f.signature_str:f for f in contract.functions} - if var_name in functions: - return functions[var_name] - modifiers = contract.modifiers_as_dict() if var_name in modifiers: return modifiers[var_name] @@ -116,11 +100,6 @@ def find_variable(var_name, caller_context): if var_name in SOLIDITY_FUNCTIONS: return SolidityFunction(var_name) - # With funciton pointer, the signature with the return value is used - solidity_signatures = {solidity_function_signature(f):f for f in SOLIDITY_FUNCTIONS} - if var_name in solidity_signatures: - return SolidityFunction(solidity_signatures[var_name]) - contracts = contract.slither.contracts_as_dict() if var_name in contracts: return contracts[var_name] @@ -190,6 +169,21 @@ def filter_name(value): value = value.replace('function (', 'function(') value = value.replace('returns (', 'returns(') + # remove the text remaining after functio(...) + # which should only be ..returns(...) + # nested parenthesis so we use a system of counter on parenthesis + idx = value.find('(') + if idx: + counter = 1 + max_idx = len(value) + while counter: + assert idx < max_idx + idx = idx +1 + if value[idx] == '(': + counter += 1 + elif value[idx] == ')': + counter -= 1 + value = value[:idx+1] return value def parse_expression(expression, caller_context): From 51b7378759c376c6313eed523a68d01d03244f51 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 12 Sep 2018 08:30:12 +0100 Subject: [PATCH 030/308] Add external/internal keyword to filter_name --- slither/solcParsing/expressions/expressionParsing.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/slither/solcParsing/expressions/expressionParsing.py b/slither/solcParsing/expressions/expressionParsing.py index a8e8db4fd..54c91096a 100644 --- a/slither/solcParsing/expressions/expressionParsing.py +++ b/slither/solcParsing/expressions/expressionParsing.py @@ -158,6 +158,8 @@ def parse_super_name(expression): def filter_name(value): value = value.replace(' memory', '') value = value.replace(' storage', '') + value = value.replace(' external', '') + value = value.replace(' internal', '') value = value.replace('struct ', '') value = value.replace('contract ', '') value = value.replace('enum ', '') From 2699248e4711048bb6820944c00685fd6a9be16f Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 12 Sep 2018 09:25:46 +0100 Subject: [PATCH 031/308] Improve filter_name + fix bug in inheritances --- slither/solcParsing/expressions/expressionParsing.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/slither/solcParsing/expressions/expressionParsing.py b/slither/solcParsing/expressions/expressionParsing.py index 54c91096a..eef81e695 100644 --- a/slither/solcParsing/expressions/expressionParsing.py +++ b/slither/solcParsing/expressions/expressionParsing.py @@ -165,9 +165,9 @@ def filter_name(value): value = value.replace('enum ', '') value = value.replace(' ref', '') value = value.replace(' pointer', '') - value = value.replace(' pure ', ' ') - value = value.replace(' view ', ' ') - value = value.replace(' constant ', ' ') + value = value.replace(' pure', '') + value = value.replace(' view', '') + value = value.replace(' constant', '') value = value.replace('function (', 'function(') value = value.replace('returns (', 'returns(') @@ -315,7 +315,11 @@ def parse_expression(expression, caller_context): member_expression = parse_expression(children[0], caller_context) if str(member_expression) == 'super': super_name = parse_super_name(expression) - inheritances = caller_context.contract.inheritances + if isinstance(caller_context, Contract): + inheritances = caller_context.inheritances + else: + assert isinstance(caller_context, Function) + inheritances = caller_context.contract.inheritances var = None for father in inheritances: try: From 96e8da1411c45b2e4b4565f93ecad4cba1c8d5ec Mon Sep 17 00:00:00 2001 From: disconnect3d Date: Wed, 12 Sep 2018 12:32:17 +0200 Subject: [PATCH 032/308] Plugin architecture works --- setup.py | 2 +- slither/__main__.py | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 8ab6a4b2f..9b2010206 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup, find_packages setup( - name='slither-sol', + name='slither-analyzer', description='Slither is a Solidity static analysis framework written in Python 3.', url='https://github.com/trailofbits/slither', author='Trail of Bits', diff --git a/slither/__main__.py b/slither/__main__.py index b465a4cbf..bd8c787a2 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -8,9 +8,11 @@ import os import sys import traceback -from slither.slither import Slither +from pkg_resources import iter_entry_points -from slither.detectors.abstract_detector import DetectorClassification +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.printers.abstract_printer import AbstractPrinter +from slither.slither import Slither logging.basicConfig() logger = logging.getLogger("Slither") @@ -77,6 +79,22 @@ def main(): printers = [PrinterSummary, PrinterQuickSummary, PrinterInheritance, PrinterWrittenVariablesAndAuthorization] + # Handle plugins! + for entry_point in iter_entry_points(group='slither_analyzer.plugin', name=None): + make_plugin = entry_point.load() + + plugin_detectors, plugin_printers = make_plugin() + + if not all(issubclass(d, AbstractDetector) for d in plugin_detectors): + raise Exception('Error when loading plugin %s, %r is not a detector' % (entry_point, d)) + + if not all(issubclass(p, AbstractPrinter) for p in plugin_printers): + raise Exception('Error when loading plugin %s, %r is not a printer' % (entry_point, p)) + + # We convert those to lists in case someone returns a tuple + detectors += list(plugin_detectors) + printers += list(plugin_printers) + main_impl(all_detector_classes=detectors, all_printer_classes=printers) From 1e90bb0c8ed2c333c8f3f4344d0515c179ba2798 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 12 Sep 2018 14:11:32 +0100 Subject: [PATCH 033/308] Add external calls information --- slither/core/cfg/node.py | 16 +++-- slither/core/declarations/contract.py | 2 +- slither/core/declarations/function.py | 72 ++++++++++++------- slither/printers/summary/printerSummary.py | 29 +++++--- slither/solcParsing/cfg/nodeSolc.py | 9 ++- .../{findCalls.py => find_calls.py} | 0 6 files changed, 85 insertions(+), 43 deletions(-) rename slither/visitors/expression/{findCalls.py => find_calls.py} (100%) diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index 3917c733d..884f1caec 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -36,7 +36,8 @@ class Node(SourceMapping, ChildFunction): self._node_id = node_id self._vars_written = [] self._vars_read = [] - self._calls = [] + self._internal_calls = [] + self._external_calls = [] self._state_vars_written = [] self._state_vars_read = [] @@ -102,11 +103,18 @@ class Node(SourceMapping, ChildFunction): return self._expression_vars_written @property - def calls(self): + def internal_calls(self): """ - list(Function or SolidityFunction): List of calls + list(Function or SolidityFunction): List of function calls (that does not create a transaction) """ - return self._calls + return self._internal_calls + + @property + def external_calls(self): + """ + list(CallExpression): List of message calls (that creates a transaction) + """ + return self._external_calls @property def calls_as_expression(self): diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index a34ed3d4d..b021159ab 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -114,7 +114,7 @@ class Contract(ChildSlither, SourceMapping): ''' list(Function): List of functions reachable from the contract (include super) ''' - all_calls = (f.all_calls() for f in self.functions) + all_calls = (f.all_internal_calls() for f in self.functions) all_calls = [item for sublist in all_calls for item in sublist] + self.functions all_calls = set(all_calls) return [c for c in all_calls if isinstance(c, Function)] diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index cf4de158a..496bf453f 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -42,7 +42,8 @@ class Function(ChildContract, SourceMapping): self._vars_read_or_written = [] self._solidity_vars_read = [] self._state_vars_written = [] - self._calls = [] + self._internal_calls = [] + self._external_calls = [] self._expression_vars_read = [] self._expression_vars_written = [] self._expression_calls = [] @@ -211,11 +212,18 @@ class Function(ChildContract, SourceMapping): return self._expression_vars_written @property - def calls(self): + def internal_calls(self): """ - list(Function or SolidityFunction): List of calls + list(Function or SolidityFunction): List of function calls (that does not create a transaction) """ - return self._calls + return self._internal_calls + + @property + def external_calls(self): + """ + list(ExpressionCall): List of message calls (that creates a transaction) + """ + return self._external_calls @property def calls_as_expression(self): @@ -324,19 +332,26 @@ class Function(ChildContract, SourceMapping): groupby(sorted(calls, key=lambda x: str(x)), lambda x: str(x))] self._expression_calls = calls - calls = [x.calls for x in self.nodes] - calls = [x for x in calls if x] - calls = [item for sublist in calls for item in sublist] - calls = [next(obj) for i, obj in\ - groupby(sorted(calls, key=lambda x: str(x)), lambda x: str(x))] - self._calls = [c for c in calls if isinstance(c, (Function, SolidityFunction))] + internal_calls = [x.internal_calls for x in self.nodes] + internal_calls = [x for x in internal_calls if x] + internal_calls = [item for sublist in internal_calls for item in sublist] + internal_calls = [next(obj) for i, obj in + groupby(sorted(internal_calls, key=lambda x: str(x)), lambda x: str(x))] + self._internal_calls = internal_calls + + external_calls = [x.external_calls for x in self.nodes] + external_calls = [x for x in external_calls if x] + external_calls = [item for sublist in external_calls for item in sublist] + external_calls = [next(obj) for i, obj in + groupby(sorted(external_calls, key=lambda x: str(x)), lambda x: str(x))] + self._external_calls = external_calls def all_state_variables_read(self): """ recursive version of variables_read """ variables = self.state_variables_read explored = [self] - to_explore = [c for c in self.calls if isinstance(c, Function) and c not in explored] + to_explore = [c for c in self.internal_calls if isinstance(c, Function) and c not in explored] to_explore += [m for m in self.modifiers if m not in explored] while to_explore: @@ -346,7 +361,7 @@ class Function(ChildContract, SourceMapping): continue explored.append(f) variables += f.state_variables_read - to_explore += [c for c in f.calls if\ + to_explore += [c for c in f.internal_calls if\ isinstance(c, Function) and c not in explored and c not in to_explore] to_explore += [m for m in f.modifiers if m not in explored and m not in to_explore] @@ -357,7 +372,7 @@ class Function(ChildContract, SourceMapping): """ variables = self.solidity_variables_read explored = [self] - to_explore = [c for c in self.calls if isinstance(c, Function) and c not in explored] + to_explore = [c for c in self.internal_calls if isinstance(c, Function) and c not in explored] to_explore += [m for m in self.modifiers if m not in explored] while to_explore: @@ -367,7 +382,7 @@ class Function(ChildContract, SourceMapping): continue explored.append(f) variables += f.solidity_variables_read - to_explore += [c for c in f.calls if\ + to_explore += [c for c in f.internal_calls if\ isinstance(c, Function) and c not in explored and c not in to_explore] to_explore += [m for m in f.modifiers if m not in explored and m not in to_explore] @@ -378,7 +393,7 @@ class Function(ChildContract, SourceMapping): """ variables = self.expressions explored = [self] - to_explore = [c for c in self.calls if isinstance(c, Function) and c not in explored] + to_explore = [c for c in self.internal_calls if isinstance(c, Function) and c not in explored] to_explore += [m for m in self.modifiers if m not in explored] while to_explore: @@ -388,7 +403,7 @@ class Function(ChildContract, SourceMapping): continue explored.append(f) variables += f.expressions - to_explore += [c for c in f.calls if\ + to_explore += [c for c in f.internal_calls if\ isinstance(c, Function) and c not in explored and c not in to_explore] to_explore += [m for m in f.modifiers if m not in explored and m not in to_explore] @@ -399,7 +414,8 @@ class Function(ChildContract, SourceMapping): """ variables = self.state_variables_written explored = [self] - to_explore = [c for c in self.calls if isinstance(c, Function) and c not in explored] + to_explore = [c for c in self.internal_calls if + isinstance(c, Function) and c not in explored] to_explore += [m for m in self.modifiers if m not in explored] while to_explore: @@ -409,18 +425,19 @@ class Function(ChildContract, SourceMapping): continue explored.append(f) variables += f.state_variables_written - to_explore += [c for c in f.calls if\ + to_explore += [c for c in f.internal_calls if\ isinstance(c, Function) and c not in explored and c not in to_explore] to_explore += [m for m in f.modifiers if m not in explored and m not in to_explore] return list(set(variables)) - def all_calls(self): - """ recursive version of calls + def all_internal_calls(self): + """ recursive version of internal_calls """ - calls = self.calls + calls = self.internal_calls explored = [self] - to_explore = [c for c in self.calls if isinstance(c, Function) and c not in explored] + to_explore = [c for c in self.internal_calls if + isinstance(c, Function) and c not in explored] to_explore += [m for m in self.modifiers if m not in explored] while to_explore: @@ -429,8 +446,8 @@ class Function(ChildContract, SourceMapping): if f in explored: continue explored.append(f) - calls += f.calls - to_explore += [c for c in f.calls if\ + calls += f.internal_calls + to_explore += [c for c in f.internal_calls if\ isinstance(c, Function) and c not in explored and c not in to_explore] to_explore += [m for m in f.modifiers if m not in explored and m not in to_explore] @@ -512,11 +529,12 @@ class Function(ChildContract, SourceMapping): """ Return the function summary Returns: - (str, str, list(str), list(str), listr(str), list(str); - name, visibility, modifiers, variables read, variables written, calls + (str, str, list(str), list(str), listr(str), list(str), list(str); + name, visibility, modifiers, vars read, vars written, internal_calls, external_calls """ return (self.name, self.visibility, [str(x) for x in self.modifiers], [str(x) for x in self.state_variables_read + self.solidity_variables_read], [str(x) for x in self.state_variables_written], - [str(x) for x in self.calls]) + [str(x) for x in self.internal_calls], + [str(x) for x in self.external_calls]) diff --git a/slither/printers/summary/printerSummary.py b/slither/printers/summary/printerSummary.py index 860801ad5..901827746 100644 --- a/slither/printers/summary/printerSummary.py +++ b/slither/printers/summary/printerSummary.py @@ -31,19 +31,32 @@ class PrinterSummary(AbstractPrinter): txt = "\nContract %s"%name txt += '\nContract vars: '+str(var) txt += '\nInheritances:: '+str(inheritances) - table = PrettyTable(["Function", "Visibility", "Modifiers", "Read", "Write", "Calls"]) - for (f_name, visi, modifiers, read, write, calls) in func_summaries: + table = PrettyTable(["Function", + "Visibility", + "Modifiers", + "Read", + "Write", + "Internal Calls", + "External Calls"]) + for (f_name, visi, modifiers, read, write, internal_calls, external_calls) in func_summaries: read = self._convert(read) write = self._convert(write) - calls = self._convert(calls) - table.add_row([f_name, visi, modifiers, read, write, calls]) + internal_calls = self._convert(internal_calls) + external_calls = self._convert(external_calls) + table.add_row([f_name, visi, modifiers, read, write, internal_calls, external_calls]) txt += "\n \n"+str(table) - table = PrettyTable(["Modifiers", "Visibility", "Read", "Write", "Calls"]) - for (f_name, visi, _, read, write, calls) in modif_summaries: + table = PrettyTable(["Modifiers", + "Visibility", + "Read", + "Write", + "Internal Calls", + "External Calls"]) + for (f_name, visi, _, read, write, internal_calls, external_calls) in modif_summaries: read = self._convert(read) write = self._convert(write) - calls = self._convert(calls) - table.add_row([f_name, visi, read, write, calls]) + internal_calls = self._convert(internal_calls) + external_calls = self._convert(external_calls) + table.add_row([f_name, visi, read, write, internal_calls, external_calls]) txt += "\n\n"+str(table) txt += "\n" self.info(txt) diff --git a/slither/solcParsing/cfg/nodeSolc.py b/slither/solcParsing/cfg/nodeSolc.py index 08cfd64a7..bad721d9b 100644 --- a/slither/solcParsing/cfg/nodeSolc.py +++ b/slither/solcParsing/cfg/nodeSolc.py @@ -3,15 +3,16 @@ from slither.core.cfg.nodeType import NodeType from slither.solcParsing.expressions.expressionParsing import parse_expression from slither.visitors.expression.readVar import ReadVar from slither.visitors.expression.writeVar import WriteVar -from slither.visitors.expression.findCalls import FindCalls +from slither.visitors.expression.find_calls import FindCalls from slither.visitors.expression.exportValues import ExportValues from slither.core.declarations.solidityVariables import SolidityVariable, SolidityFunction from slither.core.declarations.function import Function - from slither.core.variables.stateVariable import StateVariable +from slither.core.expressions.identifier import Identifier + class NodeSolc(Node): def __init__(self, nodeType, nodeId): @@ -52,6 +53,8 @@ class NodeSolc(Node): self._expression_calls = pp.result() calls = [ExportValues(c).result() for c in self.calls_as_expression] calls = [item for sublist in calls for item in sublist] - self._calls = [c for c in calls if isinstance(c, (Function, SolidityFunction))] + self._internal_calls = [c for c in calls if isinstance(c, (Function, SolidityFunction))] + + self._external_calls = [c for c in self.calls_as_expression if not isinstance(c.called, Identifier)] diff --git a/slither/visitors/expression/findCalls.py b/slither/visitors/expression/find_calls.py similarity index 100% rename from slither/visitors/expression/findCalls.py rename to slither/visitors/expression/find_calls.py From 6ac4b563267fb2a045b1fecdb7ec825284c2710c Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 12 Sep 2018 14:14:05 +0100 Subject: [PATCH 034/308] Fix node.contain_require_or_assert --- slither/core/cfg/node.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index 884f1caec..855b1989d 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -151,10 +151,10 @@ class Node(SourceMapping, ChildFunction): Returns: bool: True if the node has a require or assert call """ - return self.calls and\ + return self.internal_calls and\ any(isinstance(c, SolidityFunction) and\ (c.name in ['require(bool)', 'require(bool,string)', 'assert(bool)'])\ - for c in self.calls) + for c in self.internal_calls) def contains_if(self): """ From e18be0b483560a364aa5cf16446f9bb8112a1a49 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 12 Sep 2018 17:39:01 +0100 Subject: [PATCH 035/308] Add re-entrancy detector Add Prioritization information to README --- README.md | 21 +-- examples/bugs/reentrancy.sol | 51 ++++++++ scripts/travis_test.sh | 5 + slither/__main__.py | 3 +- slither/detectors/reentrancy/__init__.py | 0 slither/detectors/reentrancy/reentrancy.py | 142 +++++++++++++++++++++ 6 files changed, 212 insertions(+), 10 deletions(-) create mode 100644 examples/bugs/reentrancy.sol create mode 100644 slither/detectors/reentrancy/__init__.py create mode 100644 slither/detectors/reentrancy/reentrancy.py diff --git a/README.md b/README.md index 15c2e7b60..c08857209 100644 --- a/README.md +++ b/README.md @@ -55,17 +55,20 @@ If Slither is applied on a directory, it will run on every `.sol` file of the di By default, all the checks are run. -Check | Purpose | Impact | Confidence ---- | --- | --- | --- -`--detect-uninitialized`| Detect uninitialized variables | High | High -`--detect-pragma`| Detect if different pragma directives are used | Informational | High -`--detect-solc-version`| Detect if an old version of Solidity is used (<0.4.23) | Informational | High +Check | Purpose | Impact | Confidence | Prioritization +--- | --- | --- | --- | --- +`--detect-uninitialized`| Detect uninitialized variables | High | High | High +`--detect-pragma`| Detect if different pragma directives are used | Informational | High | Informational +`--detect-reentrancy`| Detect if different pragma directives are used | High | Medium | Medium +`--detect-solc-version`| Detect if an old version of Solidity is used (<0.4.23) | Informational | High | Informational + +A high prioritization check is likely to be a true positive with a severe impact. ### Exclude analyses -* `--exclude-informational`: Exclude informational impact analyses -* `--exclude-low`: Exclude low impact analyses -* `--exclude-medium`: Exclude medium impact analyses -* `--exclude-high`: Exclude high impact analyses +* `--exclude-informational`: Exclude informational prioritization analyses +* `--exclude-low`: Exclude low prioritization analyses +* `--exclude-medium`: Exclude medium prioritization impact analyses +* `--exclude-high`: Exclude high impact prioritization analyses * `--exclude-name` will exclude the detector `name` ## Configuration diff --git a/examples/bugs/reentrancy.sol b/examples/bugs/reentrancy.sol new file mode 100644 index 000000000..9f79587a2 --- /dev/null +++ b/examples/bugs/reentrancy.sol @@ -0,0 +1,51 @@ +pragma solidity ^0.4.24; + +contract Reentrancy { + mapping (address => uint) userBalance; + + function getBalance(address u) view public returns(uint){ + return userBalance[u]; + } + + function addToBalance() payable public{ + userBalance[msg.sender] += msg.value; + } + + function withdrawBalance() public{ + // send userBalance[msg.sender] ethers to msg.sender + // if mgs.sender is a contract, it will call its fallback function + if( ! (msg.sender.call.value(userBalance[msg.sender])() ) ){ + revert(); + } + userBalance[msg.sender] = 0; + } + + function withdrawBalance_fixed() public{ + // To protect against re-entrancy, the state variable + // has to be change before the call + uint amount = userBalance[msg.sender]; + userBalance[msg.sender] = 0; + if( ! (msg.sender.call.value(amount)() ) ){ + revert(); + } + } + + function withdrawBalance_fixed_2() public{ + // send() and transfer() are safe against reentrancy + // they do not transfer the remaining gas + // and they give just enough gas to execute few instructions + // in the fallback function (no further call possible) + msg.sender.transfer(userBalance[msg.sender]); + userBalance[msg.sender] = 0; + } + + function withdrawBalance_fixed_3() public{ + // The state can be changed + // But it is fine, as it can only occur if the transaction fails + uint amount = userBalance[msg.sender]; + userBalance[msg.sender] = 0; + if( ! (msg.sender.call.value(amount)() ) ){ + userBalance[msg.sender] = amount; + } + } +} diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 3bb856e02..2de418830 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -20,4 +20,9 @@ if [ $? -ne 1 ]; then exit 1 fi +slither examples/bugs/reentrancy.sol --disable-solc-warnings +if [ $? -ne 1 ]; then + exit 1 +fi + exit 0 diff --git a/slither/__main__.py b/slither/__main__.py index bd8c787a2..c81371e1d 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -69,8 +69,9 @@ def main(): from slither.detectors.variables.uninitializedStateVarsDetection import UninitializedStateVarsDetection from slither.detectors.attributes.constant_pragma import ConstantPragma from slither.detectors.attributes.old_solc import OldSolc + from slither.detectors.reentrancy.reentrancy import Reentrancy - detectors = [Backdoor, UninitializedStateVarsDetection, ConstantPragma, OldSolc] + detectors = [Backdoor, UninitializedStateVarsDetection, ConstantPragma, OldSolc, Reentrancy] from slither.printers.summary.printerSummary import PrinterSummary from slither.printers.summary.printerQuickSummary import PrinterQuickSummary diff --git a/slither/detectors/reentrancy/__init__.py b/slither/detectors/reentrancy/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/detectors/reentrancy/reentrancy.py b/slither/detectors/reentrancy/reentrancy.py new file mode 100644 index 000000000..754b6b3e0 --- /dev/null +++ b/slither/detectors/reentrancy/reentrancy.py @@ -0,0 +1,142 @@ +"""" + Re-entrancy detection + + Based on heuristics, it may lead to FP and FN +""" + +from slither.core.declarations.function import Function +from slither.core.declarations.solidityVariables import SolidityFunction +from slither.core.expressions.unaryOperation import UnaryOperation, UnaryOperationType +from slither.core.cfg.node import NodeType +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.visitors.expression.exportValues import ExportValues + + +class Reentrancy(AbstractDetector): + ARGUMENT = 'reentrancy' + HELP = 'Re-entrancy' + # Classified as medium, as the confience on the heuristic is not high + CLASSIFICATION = DetectorClassification.MEDIUM + + @staticmethod + def _is_legit_call(call_name): + """ + Detect if the call seems legit + Slither has no taint analysis, and do not make yet the link + to the libraries. As a result, we look for any low-level calls + """ + call_str = str(call_name) + return not ('.call(' in call_str or + '.call.' in call_str or + 'delegatecall' in call_str or + 'callcode' in call_str or + '.value(' in call_str) + + key = 'REENTRANCY' + + def _check_on_call_returned(self, node): + """ + Check if the node is a condtional node where + there is an external call checked + Heuristic: + - The call is a IF node + - It contains a, external call + - The condition is the negation (!) + + This will work only on naive implementation + """ + if node.type == NodeType.IF: + external_calls = node.external_calls + if any(not self._is_legit_call(call) for call in external_calls): + return isinstance(node.expression, UnaryOperation)\ + and node.expression.type == UnaryOperationType.BANG + return False + + def _explore(self, node, visited): + """ + Explore the CFG and look for re-entrancy + Heuristic: There is a re-entrancy if a state variable is written + after an external call + + node.context will contains the external calls executed + It contains the calls executed in father nodes + + if node.context is not empty, and variables are written, a re-entrancy is possible + """ + if node in visited: + return + visited = visited + [node] + + # First we add the external calls executed in previous nodes + node.context[self.key] = [] + for father in node.fathers: + if self.key in father.context: + node.context[self.key] += father.context[self.key] + + # Get all the new external calls + for call in node.external_calls: + if self._is_legit_call(call): + continue + node.context[self.key] += [str(call)] + + # All the state variables written + state_vars_written = node.state_variables_written + # Add the state variables written in internal calls + for internal_call in node.internal_calls: + # Filter to Function, as internal_call can be a solidity call + if isinstance(internal_call, Function): + state_vars_written += internal_call.all_state_variables_written() + + # If a state variables is written, and there was an external call + # We found a potential re-entrancy bug + if state_vars_written and node.context[self.key]: + # we save the result wth (contract, func, calls) as key + # calls are ordered + finding_key = (node.function.contract.name, + node.function.name, + tuple(set(node.context[self.key]))) + finding_vars = state_vars_written + if finding_key not in self.result: + self.result[finding_key] = [] + self.result[finding_key] = list(set(self.result[finding_key] + finding_vars)) + + sons = node.sons + if self._check_on_call_returned(node): + sons = sons[1:] + + for son in sons: + self._explore(son, visited) + + def detect_reentrancy(self, contract): + """ + """ + for function in contract.functions: + if function.is_implemented: + self._explore(function.entry_point, []) + + def detect(self): + """ + """ + self.result = {} + for c in self.contracts: + self.detect_reentrancy(c) + + results = [] + + for (contract, func, calls), varsWritten in self.result.items(): + varsWritten = list(set([str(x) for x in list(varsWritten)])) + calls = list(set([str(x) for x in list(calls)])) + info = 'Reentrancy in %s, Contract: %s, ' % (self.filename, contract) + \ + 'Func: %s, Call: %s, ' % (func, calls) + \ + 'Vars Written:%s' % (str(varsWritten)) + self.log(info) + + results.append({'vuln': 'Reentrancy', + # 'sourceMapping': sourceMapping, + 'filename': self.filename, + 'contract': contract, + 'function_name': func, + 'call': calls, + 'varsWritten': varsWritten}) + + return results From 45062efb9b80eb5d5438be5e225546a0db25eede Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 12 Sep 2018 17:48:41 +0100 Subject: [PATCH 036/308] Update printer to last API changes Update printer documentation --- README.md | 7 +- docs/PRINTERS.md | 65 ++++++++----------- slither/__main__.py | 2 +- slither/printers/functions/authorization.py | 4 +- .../printers/summary/printerQuickSummary.py | 2 +- 5 files changed, 35 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index c08857209..e8d0dbaef 100644 --- a/README.md +++ b/README.md @@ -79,9 +79,10 @@ A high prioritization check is likely to be a true positive with a severe impact * `--json FILE`: Export results as JSON ## Printers -* `--print-summary`: Print a summary of the contracts -* `--print-quick-summary`: Print a quick summary of the contracts -* `--print-inheritance`: Print the inheritance graph +* `--printer-summary`: Print a summary of the contracts +* `--printer-quick-summary`: Print a quick summary of the contracts +* `--printer-inheritance`: Print the inheritance graph +* `--printer-vars-and-auth`: Print the variables written and the check on `msg.sender` of each function. For more information about printers, see the [Printers documentation](docs/PRINTERS.md) diff --git a/docs/PRINTERS.md b/docs/PRINTERS.md index 30340089c..b8b0cb880 100644 --- a/docs/PRINTERS.md +++ b/docs/PRINTERS.md @@ -8,7 +8,7 @@ Slither allows printing contracts information through its printers. Output a quick summary of the contract. Example: ``` -$ slither.py vulns/0x01293cd77f68341635814c35299ed30ae212789e.sol --print-quick-summary +$ slither vulns/0x01293cd77f68341635814c35299ed30ae212789e.sol --printer-quick-summary ``` @@ -22,40 +22,29 @@ Output a summary of the contract showing for each function: Example: ``` -$ slither.py vulns/0x01293cd77f68341635814c35299ed30ae212789e.sol --print-summary +$ slither examples/bugs/backdoor.sol --printer-summary ``` ``` [...] -INFO:Slither:Contract NBACrypto -Contract vars: [u'ceoAddress', u'cfoAddress', u'teams', u'players', u'teamsAreInitiated', u'playersAreInitiated', u'isPaused'] -+--------------------+------------+--------------+------------------------+---------------+----------------------------------------------+ -| Function | Visibility | Modifiers | Read | Write | Calls | -+--------------------+------------+--------------+------------------------+---------------+----------------------------------------------+ -| pauseGame | public | [u'onlyCeo'] | [] | [u'isPaused'] | [] | -| unPauseGame | public | [u'onlyCeo'] | [] | [u'isPaused'] | [] | -| GetIsPauded | public | [] | [u'isPaused'] | [] | [] | -| purchaseCountry | public | [] | [u'isPaused'] | [u'teams'] | [u'cfoAddress.transfer', u'mul'] | -| | | | | | [u'require', u'teams.ownerAddress.transfer'] | -| purchasePlayer | public | [] | [u'isPaused'] | [u'players'] | [u'cfoAddress.transfer', u'mul'] | -| | | | | | [u'require', u'teams.ownerAddress.transfer'] | -| | | | | | [u'players.ownerAddress.transfer'] | -| modifyPriceCountry | public | [] | [] | [u'teams'] | [u'require'] | -| getTeam | public | [] | [u'teams'] | [] | [] | -| getPlayer | public | [] | [u'players'] | [] | [] | -| getTeamPrice | public | [] | [] | [] | [] | -| getPlayerPrice | public | [] | [] | [] | [] | -| getTeamOwner | public | [] | [] | [] | [] | -| getPlayerOwner | public | [] | [] | [] | [] | -| mul | internal | [] | [] | [] | [u'assert'] | -| div | internal | [] | [] | [] | [] | -| InitiateTeams | public | [u'onlyCeo'] | [u'teamsAreInitiated'] | [] | [u'require', u'teams.push'] | -| addPlayer | public | [u'onlyCeo'] | [] | [] | [u'players.push'] | -+--------------------+------------+--------------+------------------------+---------------+----------------------------------------------+ +Contract C +Contract vars: [] +Inheritances:: [] + ++-----------------+------------+-----------+----------------+-------+---------------------------+----------------+ +| Function | Visibility | Modifiers | Read | Write | Internal Calls | External Calls | ++-----------------+------------+-----------+----------------+-------+---------------------------+----------------+ +| i_am_a_backdoor | public | [] | ['msg.sender'] | [] | ['selfdestruct(address)'] | [] | ++-----------------+------------+-----------+----------------+-------+---------------------------+----------------+ + ++-----------+------------+------+-------+----------------+----------------+ +| Modifiers | Visibility | Read | Write | Internal Calls | External Calls | ++-----------+------------+------+-------+----------------+----------------+ ++-----------+------------+------+-------+----------------+----------------+ ``` ## Inheritance Graph -`slither.py file.sol --print-inheritance` +`slither file.sol --printer-inheritance` Output a graph showing the inheritance interaction between the contracts. Example: @@ -76,21 +65,21 @@ Functions in orange override a parent's functions. If a variable points to anoth ## Variables written and authorization -`slither.py file.sol --print-variables-written-and-authorization` +`slither file.sol --printer-vars-and-auth` Print the variables written and the check on `msg.sender` of each function. ``` ... INFO:Printers: Contract MyNewBank -+----------+------------------------+-------------------------+ -| Function | State variable written | Condition on msg.sender | -+----------+------------------------+-------------------------+ -| kill | [] | ['msg.sender != owner'] | -| withdraw | [] | ['msg.sender != owner'] | -| init | [u'owner'] | [] | -| owned | [u'owner'] | [] | -| fallback | [u'deposits'] | [] | -+----------+------------------------+-------------------------+ ++----------+-------------------------+--------------------------+ +| Function | State variables written | Conditions on msg.sender | ++----------+-------------------------+--------------------------+ +| kill | [] | ['msg.sender != owner'] | +| withdraw | [] | ['msg.sender != owner'] | +| init | [u'owner'] | [] | +| owned | [u'owner'] | [] | +| fallback | [u'deposits'] | [] | ++----------+-------------------------+--------------------------+ ``` diff --git a/slither/__main__.py b/slither/__main__.py index c81371e1d..0a7dae3a2 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -230,7 +230,7 @@ def parse_args(detector_classes, printer_classes): const=detector_cls.ARGUMENT) for printer_cls in printer_classes: - printer_arg = '--print-{}'.format(printer_cls.ARGUMENT) + printer_arg = '--printer-{}'.format(printer_cls.ARGUMENT) printer_help = 'Print {}'.format(printer_cls.HELP) parser.add_argument(printer_arg, help=printer_help, diff --git a/slither/printers/functions/authorization.py b/slither/printers/functions/authorization.py index 8a85b6d4e..5880d6c11 100644 --- a/slither/printers/functions/authorization.py +++ b/slither/printers/functions/authorization.py @@ -13,7 +13,7 @@ class PrinterWrittenVariablesAndAuthorization(AbstractPrinter): @staticmethod def get_msg_sender_checks(function): - all_functions = function.all_calls() + [function] + function.modifiers + all_functions = function.all_internal_calls() + [function] + function.modifiers all_nodes = [f.nodes for f in all_functions if isinstance(f, Function)] all_nodes = [item for sublist in all_nodes for item in sublist] @@ -33,7 +33,7 @@ class PrinterWrittenVariablesAndAuthorization(AbstractPrinter): for contract in self.contracts: txt = "\nContract %s\n"%contract.name - table = PrettyTable(["Function", "State variable written", "Condition on msg.sender"]) + table = PrettyTable(["Function", "State variables written", "Conditions on msg.sender"]) for function in contract.functions: state_variables_written = [v.name for v in function.all_state_variables_written()] diff --git a/slither/printers/summary/printerQuickSummary.py b/slither/printers/summary/printerQuickSummary.py index 93b6819bc..00a2fe58c 100644 --- a/slither/printers/summary/printerQuickSummary.py +++ b/slither/printers/summary/printerQuickSummary.py @@ -21,7 +21,7 @@ class PrinterQuickSummary(AbstractPrinter): for c in self.contracts: (name, _inheritances, _var, func_summaries, _modif_summaries) = c.get_summary() txt += blue("\n+ Contract %s\n"%name) - for (f_name, visi, _modifiers, _read, _write, _calls) in func_summaries: + for (f_name, visi, _, _, _, _, _) in func_summaries: txt += " - " if visi in ['external', 'public']: txt += green("%s (%s)\n"%(f_name, visi)) From 889a49c0e1c9aacc6b79002a026e1fb99dbc18e5 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 13 Sep 2018 10:00:07 +0100 Subject: [PATCH 037/308] Clean documentation Always return a fresh list on getters to prevent modifications from caller --- slither/core/cfg/node.py | 27 ++++++++++++++++----------- slither/core/declarations/contract.py | 6 +++--- slither/core/declarations/function.py | 24 ++++++++++++------------ 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index 855b1989d..f131ce6a3 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -64,21 +64,21 @@ class Node(SourceMapping, ChildFunction): """ list(Variable): Variables read (local/state/solidity) """ - return self._vars_read + return list(self._vars_read) @property def state_variables_read(self): """ list(StateVariable): State variables read """ - return self._state_vars_read + return list(self._state_vars_read) @property def solidity_variables_read(self): """ list(SolidityVariable): State variables read """ - return self._solidity_vars_read + return list(self._solidity_vars_read) @property def variables_read_as_expression(self): @@ -89,14 +89,14 @@ class Node(SourceMapping, ChildFunction): """ list(Variable): Variables written (local/state/solidity) """ - return self._vars_written + return list(self._vars_written) @property def state_variables_written(self): """ list(StateVariable): State variables written """ - return self._state_vars_written + return list(self._state_vars_written) @property def variables_written_as_expression(self): @@ -107,7 +107,7 @@ class Node(SourceMapping, ChildFunction): """ list(Function or SolidityFunction): List of function calls (that does not create a transaction) """ - return self._internal_calls + return list(self._internal_calls) @property def external_calls(self): @@ -118,7 +118,7 @@ class Node(SourceMapping, ChildFunction): @property def calls_as_expression(self): - return self._expression_calls + return list(self._expression_calls) @property def expression(self): @@ -139,6 +139,10 @@ class Node(SourceMapping, ChildFunction): @property def variable_declaration(self): + """ + Returns: + LocalVariable + """ return self._variable_declaration def __str__(self): @@ -185,9 +189,10 @@ class Node(SourceMapping, ChildFunction): """ Returns the father nodes Returns: - fathers: list of fathers + list(Node): list of fathers """ - return self._fathers + return list(self._fathers) + def remove_father(self, father): """ Remove the father node. Do nothing if the node is not a father @@ -219,7 +224,7 @@ class Node(SourceMapping, ChildFunction): """ Returns the son nodes Returns: - sons: list of sons + list(Node): list of sons """ - return self._sons + return list(self._sons) diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index b021159ab..a0607c6c5 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -56,7 +56,7 @@ class Contract(ChildSlither, SourceMapping): ''' list(Contract): Inheritances list. Order: the first elem is the first father to be executed ''' - return self._inheritances + return list(self._inheritances) @property def inheritances_reverse(self): @@ -135,7 +135,7 @@ class Contract(ChildSlither, SourceMapping): @property def state_variables(self): ''' - list(StateVariable): List of the state variables. + list(StateVariable): List of the state variables. ''' return list(self._variables.values()) @@ -144,7 +144,7 @@ class Contract(ChildSlither, SourceMapping): ''' list(StateVariable): List of the state variables. Alias to self.state_variables ''' - return self.state_variables + return list(self.state_variables) def variables_as_dict(self): return self._variables diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 496bf453f..30e458bd3 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -68,7 +68,7 @@ class Function(ChildContract, SourceMapping): """ list(Node): List of the nodes """ - return self._nodes + return list(self._nodes) @property def entry_point(self): @@ -131,21 +131,21 @@ class Function(ChildContract, SourceMapping): """ list(LocalVariable): List of the parameters """ - return self._parameters + return list(self._parameters) @property def returns(self): """ list(LocalVariable): List of the return variables """ - return self._returns + return list(self._returns) @property def modifiers(self): """ list(Modifier): List of the modifiers """ - return self._modifiers + return list(self._modifiers) def __str__(self): return self._name @@ -166,42 +166,42 @@ class Function(ChildContract, SourceMapping): """ list(Variable): Variables read (local/state/solidity) """ - return self._vars_read + return list(self._vars_read) @property def variables_written(self): """ list(Variable): Variables written (local/state/solidity) """ - return self._vars_written + return list(self._vars_written) @property def state_variables_read(self): """ list(StateVariable): State variables read """ - return self._state_vars_read + return list(self._state_vars_read) @property def solidity_variables_read(self): """ list(SolidityVariable): Solidity variables read """ - return self._solidity_vars_read + return list(self._solidity_vars_read) @property def state_variables_written(self): """ list(StateVariable): State variables written """ - return self._state_vars_written + return list(self._state_vars_written) @property def variables_read_or_written(self): """ list(Variable): Variables read or written (local/state/solidity) """ - return self._vars_read_or_written + return list(self._vars_read_or_written) @property def variables_read_as_expression(self): @@ -216,14 +216,14 @@ class Function(ChildContract, SourceMapping): """ list(Function or SolidityFunction): List of function calls (that does not create a transaction) """ - return self._internal_calls + return list(self._internal_calls) @property def external_calls(self): """ list(ExpressionCall): List of message calls (that creates a transaction) """ - return self._external_calls + return list(self._external_calls) @property def calls_as_expression(self): From fdf6534cbeef3da3671579bc2ec1c5af9e8d0886 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 13 Sep 2018 11:57:43 +0100 Subject: [PATCH 038/308] Re-enable source mapping Add pragma and import as classes in core.declarations --- slither/core/declarations/function.py | 4 +++ slither/core/declarations/import_directive.py | 14 ++++++++ slither/core/declarations/pragma_directive.py | 21 ++++++++++++ slither/core/slitherCore.py | 4 +++ slither/core/sourceMapping/sourceMapping.py | 34 +++++++++++++++---- .../detectors/attributes/constant_pragma.py | 14 +++++--- slither/detectors/attributes/old_solc.py | 13 ++++--- slither/detectors/examples/backdoor.py | 16 +++++---- slither/detectors/reentrancy/reentrancy.py | 17 +++++++--- .../uninitializedStateVarsDetection.py | 5 ++- .../declarations/contractSolc04.py | 10 +++--- .../solcParsing/declarations/functionSolc.py | 8 ++--- .../solcParsing/declarations/structureSolc.py | 2 +- slither/solcParsing/slitherSolc.py | 13 +++++-- .../solcParsing/solidityTypes/typeParsing.py | 4 +-- 15 files changed, 135 insertions(+), 44 deletions(-) create mode 100644 slither/core/declarations/import_directive.py create mode 100644 slither/core/declarations/pragma_directive.py diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 30e458bd3..afec66f30 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -264,6 +264,10 @@ class Function(ChildContract, SourceMapping): return name+'('+','.join(parameters)+')' + @property + def slither(self): + return self.contract.slither + def _filter_state_variables_written(self, expressions): ret =[] for expression in expressions: diff --git a/slither/core/declarations/import_directive.py b/slither/core/declarations/import_directive.py new file mode 100644 index 000000000..06e4c2b6e --- /dev/null +++ b/slither/core/declarations/import_directive.py @@ -0,0 +1,14 @@ +from slither.core.sourceMapping.sourceMapping import SourceMapping + +class Import(SourceMapping): + + def __init__(self, filename): + super(Import, self).__init__() + self._fimename = filename + + @property + def filename(self): + return self._filename + + def __str__(self): + return self.filename diff --git a/slither/core/declarations/pragma_directive.py b/slither/core/declarations/pragma_directive.py new file mode 100644 index 000000000..aefc112cf --- /dev/null +++ b/slither/core/declarations/pragma_directive.py @@ -0,0 +1,21 @@ +from slither.core.sourceMapping.sourceMapping import SourceMapping + +class Pragma(SourceMapping): + + def __init__(self, directive): + super(Pragma, self).__init__() + self._directive = directive + + @property + def directive(self): + ''' + list(str) + ''' + return self._directive + + @property + def version(self): + return ''.join(self.directive[1:]) + + def __str__(self): + return 'pragma '+str(self.directive) diff --git a/slither/core/slitherCore.py b/slither/core/slitherCore.py index 4e711e802..b96a6a89a 100644 --- a/slither/core/slitherCore.py +++ b/slither/core/slitherCore.py @@ -17,6 +17,10 @@ class Slither: self._pragma_directives = [] self._import_directives = [] + @property + def source_units(self): + return self._source_units + @property def contracts(self): """list(Contract): List of contracts.""" diff --git a/slither/core/sourceMapping/sourceMapping.py b/slither/core/sourceMapping/sourceMapping.py index 14b900522..fc546d425 100644 --- a/slither/core/sourceMapping/sourceMapping.py +++ b/slither/core/sourceMapping/sourceMapping.py @@ -1,3 +1,4 @@ +import re from slither.core.context.context import Context class SourceMapping(Context): @@ -5,14 +6,35 @@ class SourceMapping(Context): def __init__(self): super(SourceMapping, self).__init__() self._source_mapping = None - self._offset = None - - def set_source_mapping(self, source_mapping): - self._source_mapping = source_mapping @property def source_mapping(self): return self._source_mapping - def set_offset(self, offset): - self._offset = offset + @staticmethod + def _convert_source_mapping(offset, slither): + ''' + Convert a text offset to a real offset + see https://solidity.readthedocs.io/en/develop/miscellaneous.html#source-mappings + Returns: + (dict): {'start':0, 'length':0, 'filename': 'file.sol'} + ''' + sourceUnits = slither.source_units + + position = re.findall('([0-9]*):([0-9]*):([-]?[0-9]*)', offset) + if len(position) != 1: + return {} + + s, l, f = position[0] + s = int(s) + l = int(l) + f = int(f) + + if f not in sourceUnits: + return {'start':s, 'length':l} + filename = sourceUnits[f] + return {'start':s, 'length':l, 'filename': filename} + + def set_offset(self, offset, slither): + self._source_mapping = self._convert_source_mapping(offset, slither) + diff --git a/slither/detectors/attributes/constant_pragma.py b/slither/detectors/attributes/constant_pragma.py index 963dce137..c68724045 100644 --- a/slither/detectors/attributes/constant_pragma.py +++ b/slither/detectors/attributes/constant_pragma.py @@ -17,13 +17,17 @@ class ConstantPragma(AbstractDetector): def detect(self): results = [] pragma = self.slither.pragma_directives - pragma = [''.join(p[1:]) for p in pragma] - pragma = list(set(pragma)) + versions = [p.version for p in pragma] + versions = list(set(versions)) - if len(pragma) > 1: - info = "Different version of Solidity used in {}: {}".format(self.filename, pragma) + if len(versions) > 1: + info = "Different version of Solidity used in {}: {}".format(self.filename, versions) self.log(info) - results.append({'vuln': 'ConstantPragma', 'pragma': pragma}) + source = [p.source_mapping for p in pragma] + + results.append({'vuln': 'ConstantPragma', + 'versions': versions, + 'sourceMapping': source}) return results diff --git a/slither/detectors/attributes/old_solc.py b/slither/detectors/attributes/old_solc.py index 4af4d9bfc..c5220d42e 100644 --- a/slither/detectors/attributes/old_solc.py +++ b/slither/detectors/attributes/old_solc.py @@ -18,15 +18,18 @@ class OldSolc(AbstractDetector): def detect(self): results = [] pragma = self.slither.pragma_directives - pragma = [''.join(p[1:]) for p in pragma] - pragma = [p.replace('solidity', '').replace('^', '') for p in pragma] - pragma = list(set(pragma)) - old_pragma = [p for p in pragma if p not in ['0.4.23', '0.4.24']] + versions = [p.version for p in pragma] + versions = [p.replace('solidity', '').replace('^', '') for p in versions] + versions = list(set(versions)) + old_pragma = [p for p in versions if p not in ['0.4.23', '0.4.24']] if old_pragma: info = "Old version of Solidity used in {}: {}".format(self.filename, old_pragma) self.log(info) - results.append({'vuln': 'OldPragma', 'pragma': old_pragma}) + source = [p.source_mapping for p in pragma] + results.append({'vuln': 'OldPragma', + 'pragma': old_pragma, + 'sourceMapping': source}) return results diff --git a/slither/detectors/examples/backdoor.py b/slither/detectors/examples/backdoor.py index dc1e857f6..90299bc40 100644 --- a/slither/detectors/examples/backdoor.py +++ b/slither/detectors/examples/backdoor.py @@ -15,12 +15,14 @@ class Backdoor(AbstractDetector): for contract in self.slither.contracts_derived: # Check if a function has 'backdoor' in its name - if any('backdoor' in f.name for f in contract.functions): - # Info to be printed - info = 'Backdoor function found in {}'.format(contract.name) - # Print the info - self.log(info) - # Add the result in ret - ret.append({'vuln': 'backdoor', 'contract': contract.name}) + for f in contract.functions: + if 'backdoor' in f.name: + # Info to be printed + info = 'Backdoor function found in {}.{}'.format(contract.name, f.name) + # Print the info + self.log(info) + # Add the result in ret + source = f.source_mapping + ret.append({'vuln': 'backdoor', 'contract': contract.name, 'sourceMapping' : source}) return ret diff --git a/slither/detectors/reentrancy/reentrancy.py b/slither/detectors/reentrancy/reentrancy.py index 754b6b3e0..22f0a9bd1 100644 --- a/slither/detectors/reentrancy/reentrancy.py +++ b/slither/detectors/reentrancy/reentrancy.py @@ -93,7 +93,7 @@ class Reentrancy(AbstractDetector): # we save the result wth (contract, func, calls) as key # calls are ordered finding_key = (node.function.contract.name, - node.function.name, + node.function.full_name, tuple(set(node.context[self.key]))) finding_vars = state_vars_written if finding_key not in self.result: @@ -124,19 +124,26 @@ class Reentrancy(AbstractDetector): results = [] for (contract, func, calls), varsWritten in self.result.items(): - varsWritten = list(set([str(x) for x in list(varsWritten)])) + varsWritten_str = list(set([str(x) for x in list(varsWritten)])) calls = list(set([str(x) for x in list(calls)])) info = 'Reentrancy in %s, Contract: %s, ' % (self.filename, contract) + \ 'Func: %s, Call: %s, ' % (func, calls) + \ - 'Vars Written:%s' % (str(varsWritten)) + 'Vars Written:%s' % (str(varsWritten_str)) self.log(info) + source = [v.source_mapping for v in varsWritten] + # The source mapping could be kept during the analysis + # So we sould not have to re-iterate over the contracts and functions + contract_instance = self.slither.get_contract_from_name(contract) + function_instance = contract_instance.get_function_from_signature(func) + source += [function_instance.source_mapping] + results.append({'vuln': 'Reentrancy', - # 'sourceMapping': sourceMapping, + 'sourceMapping': source, 'filename': self.filename, 'contract': contract, 'function_name': func, 'call': calls, - 'varsWritten': varsWritten}) + 'varsWritten': varsWritten_str}) return results diff --git a/slither/detectors/variables/uninitializedStateVarsDetection.py b/slither/detectors/variables/uninitializedStateVarsDetection.py index 4e9a5b076..c68e74eb2 100644 --- a/slither/detectors/variables/uninitializedStateVarsDetection.py +++ b/slither/detectors/variables/uninitializedStateVarsDetection.py @@ -64,8 +64,11 @@ class UninitializedStateVarsDetection(AbstractDetector): [str(f) for f in functions]) self.log(info) + source = [variable.source_mapping] + source += [f.source_mapping for f in functions] + results.append({'vuln': 'UninitializedStateVars', - 'sourceMapping': c.source_mapping, + 'sourceMapping': source, 'filename': self.filename, 'contract': c.name, 'functions': [str(f) for f in functions], diff --git a/slither/solcParsing/declarations/contractSolc04.py b/slither/solcParsing/declarations/contractSolc04.py index a37d8f052..5c926bcd2 100644 --- a/slither/solcParsing/declarations/contractSolc04.py +++ b/slither/solcParsing/declarations/contractSolc04.py @@ -129,7 +129,7 @@ class ContractSolc04(Contract): new_enum = Enum(name, canonicalName, values) new_enum.set_contract(self) - new_enum.set_offset(enum['src']) + new_enum.set_offset(enum['src'], self.slither) self._enums[canonicalName] = new_enum def _parse_struct(self, struct): @@ -145,7 +145,7 @@ class ContractSolc04(Contract): children = [] # empty struct st = StructureSolc(name, canonicalName, children) st.set_contract(self) - st.set_offset(struct['src']) + st.set_offset(struct['src'], self.slither) self._structures[name] = st def _analyze_struct(self, struct): @@ -181,7 +181,7 @@ class ContractSolc04(Contract): for varNotParsed in self._variablesNotParsed: var = StateVariableSolc(varNotParsed) - var.set_offset(varNotParsed['src']) + var.set_offset(varNotParsed['src'], self.slither) var.set_contract(self) self._variables[var.name] = var @@ -195,7 +195,7 @@ class ContractSolc04(Contract): modif = ModifierSolc(modifier) modif.set_contract(self) - modif.set_offset(modifier['src']) + modif.set_offset(modifier['src'], self.slither) self._modifiers_no_params.append(modif) def parse_modifiers(self): @@ -209,7 +209,7 @@ class ContractSolc04(Contract): def _parse_function(self, function): func = FunctionSolc(function) func.set_contract(self) - func.set_offset(function['src']) + func.set_offset(function['src'], self.slither) self._functions_no_params.append(func) def parse_functions(self): diff --git a/slither/solcParsing/declarations/functionSolc.py b/slither/solcParsing/declarations/functionSolc.py index 1c1a3226b..f2c256840 100644 --- a/slither/solcParsing/declarations/functionSolc.py +++ b/slither/solcParsing/declarations/functionSolc.py @@ -222,7 +222,7 @@ class FunctionSolc(Function): local_var = LocalVariableSolc(statement) #local_var = LocalVariableSolc(statement['children'][0], statement['children'][1::]) local_var.set_function(self) - local_var.set_offset(statement['src']) + local_var.set_offset(statement['src'], self.contract.slither) self._variables[local_var.name] = local_var #local_var.analyze(self) @@ -281,7 +281,7 @@ class FunctionSolc(Function): local_var = LocalVariableInitFromTupleSolc(statement, index) #local_var = LocalVariableSolc(statement['children'][0], statement['children'][1::]) local_var.set_function(self) - local_var.set_offset(statement['src']) + local_var.set_offset(statement['src'], self.contract.slither) self._variables[local_var.name] = local_var # local_var.analyze(self) @@ -464,7 +464,7 @@ class FunctionSolc(Function): local_var = LocalVariableSolc(param) local_var.set_function(self) - local_var.set_offset(param['src']) + local_var.set_offset(param['src'], self.contract.slither) local_var.analyze(self) self._variables[local_var.name] = local_var @@ -479,7 +479,7 @@ class FunctionSolc(Function): local_var = LocalVariableSolc(ret) local_var.set_function(self) - local_var.set_offset(ret['src']) + local_var.set_offset(ret['src'], self.contract.slither) local_var.analyze(self) self._variables[local_var.name] = local_var diff --git a/slither/solcParsing/declarations/structureSolc.py b/slither/solcParsing/declarations/structureSolc.py index 826f597a5..875ded46a 100644 --- a/slither/solcParsing/declarations/structureSolc.py +++ b/slither/solcParsing/declarations/structureSolc.py @@ -23,7 +23,7 @@ class StructureSolc(Structure): for elem_to_parse in self._elemsNotParsed: elem = StructureVariableSolc(elem_to_parse) elem.set_structure(self) - elem.set_offset(elem_to_parse['src']) + elem.set_offset(elem_to_parse['src'], self.contract.slither) elem.analyze(self.contract) diff --git a/slither/solcParsing/slitherSolc.py b/slither/solcParsing/slitherSolc.py index 029c135b5..07cac7395 100644 --- a/slither/solcParsing/slitherSolc.py +++ b/slither/solcParsing/slitherSolc.py @@ -6,7 +6,8 @@ logger = logging.getLogger("SlitherSolcParsing") from slither.solcParsing.declarations.contractSolc04 import ContractSolc04 from slither.core.slitherCore import Slither - +from slither.core.declarations.pragma_directive import Pragma +from slither.core.declarations.import_directive import Import class SlitherSolc(Slither): @@ -45,11 +46,17 @@ class SlitherSolc(Slither): assert contract_data['name'] in ['ContractDefinition', 'PragmaDirective', 'ImportDirective'] if contract_data['name'] == 'ContractDefinition': contract = ContractSolc04(self, contract_data) + if 'src' in contract_data: + contract.set_offset(contract_data['src'], self) self._contractsNotParsed.append(contract) elif contract_data['name'] == 'PragmaDirective': - self._pragma_directives.append(contract_data['attributes']["literals"]) + pragma = Pragma(contract_data['attributes']["literals"]) + pragma.set_offset(contract_data['src'], self) + self._pragma_directives.append(pragma) elif contract_data['name'] == 'ImportDirective': - self._import_directives.append(contract_data['attributes']["absolutePath"]) + import_directive = Import(contract_data['attributes']["absolutePath"]) + import_directive.set_offset(contract_data['src'], self) + self._import_directives.append(import_directive) return True return False diff --git a/slither/solcParsing/solidityTypes/typeParsing.py b/slither/solcParsing/solidityTypes/typeParsing.py index 07625c01e..07ced0986 100644 --- a/slither/solcParsing/solidityTypes/typeParsing.py +++ b/slither/solcParsing/solidityTypes/typeParsing.py @@ -171,13 +171,13 @@ def parse_type(t, caller_context): return_values_vars = [] for p in params['children']: var = FunctionTypeVariableSolc(p) - var.set_offset(p['src']) + var.set_offset(p['src'], caller_context.slithev) var.analyze(caller_context) params_vars.append(var) for p in return_values['children']: var = FunctionTypeVariableSolc(p) - var.set_offset(p['src']) + var.set_offset(p['src'], caller_context.slither) var.analyze(caller_context) return_values_vars.append(var) From f3b5d2e78e7976f8b08e1ce69c6ac04e6193c7f0 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 13 Sep 2018 13:44:04 +0100 Subject: [PATCH 039/308] Fix typo --- slither/solcParsing/solidityTypes/typeParsing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/solcParsing/solidityTypes/typeParsing.py b/slither/solcParsing/solidityTypes/typeParsing.py index 07ced0986..27224ec90 100644 --- a/slither/solcParsing/solidityTypes/typeParsing.py +++ b/slither/solcParsing/solidityTypes/typeParsing.py @@ -171,7 +171,7 @@ def parse_type(t, caller_context): return_values_vars = [] for p in params['children']: var = FunctionTypeVariableSolc(p) - var.set_offset(p['src'], caller_context.slithev) + var.set_offset(p['src'], caller_context.slither) var.analyze(caller_context) params_vars.append(var) for p in return_values['children']: From 533f94ddfa97d03b46fba063fc5066604a4cbad2 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 13 Sep 2018 14:11:12 +0100 Subject: [PATCH 040/308] Only use Impact for classification --- README.md | 20 ++++++++++---------- slither/detectors/reentrancy/reentrancy.py | 5 +++-- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index e8d0dbaef..60585ffcc 100644 --- a/README.md +++ b/README.md @@ -55,20 +55,20 @@ If Slither is applied on a directory, it will run on every `.sol` file of the di By default, all the checks are run. -Check | Purpose | Impact | Confidence | Prioritization ---- | --- | --- | --- | --- -`--detect-uninitialized`| Detect uninitialized variables | High | High | High -`--detect-pragma`| Detect if different pragma directives are used | Informational | High | Informational -`--detect-reentrancy`| Detect if different pragma directives are used | High | Medium | Medium -`--detect-solc-version`| Detect if an old version of Solidity is used (<0.4.23) | Informational | High | Informational +Check | Purpose | Impact +--- | --- | --- +`--detect-uninitialized`| Detect uninitialized variables | High +`--detect-pragma`| Detect if different pragma directives are used | Informational +`--detect-reentrancy`| Detect if different pragma directives are used | High +`--detect-solc-version`| Detect if an old version of Solidity is used (<0.4.23) | Informational A high prioritization check is likely to be a true positive with a severe impact. ### Exclude analyses -* `--exclude-informational`: Exclude informational prioritization analyses -* `--exclude-low`: Exclude low prioritization analyses -* `--exclude-medium`: Exclude medium prioritization impact analyses -* `--exclude-high`: Exclude high impact prioritization analyses +* `--exclude-informational`: Exclude informational impact analyses +* `--exclude-low`: Exclude low impact analyses +* `--exclude-medium`: Exclude medium impact analyses +* `--exclude-high`: Exclude high impact analyses * `--exclude-name` will exclude the detector `name` ## Configuration diff --git a/slither/detectors/reentrancy/reentrancy.py b/slither/detectors/reentrancy/reentrancy.py index 22f0a9bd1..8b7d64ca3 100644 --- a/slither/detectors/reentrancy/reentrancy.py +++ b/slither/detectors/reentrancy/reentrancy.py @@ -15,8 +15,9 @@ from slither.visitors.expression.exportValues import ExportValues class Reentrancy(AbstractDetector): ARGUMENT = 'reentrancy' HELP = 'Re-entrancy' - # Classified as medium, as the confience on the heuristic is not high - CLASSIFICATION = DetectorClassification.MEDIUM + # High impact + # Medium confidence + CLASSIFICATION = DetectorClassification.HIGH @staticmethod def _is_legit_call(call_name): From 6fb297b69eb11530af072eca9d8d87c13504744e Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 13 Sep 2018 14:41:15 +0100 Subject: [PATCH 041/308] Update README --- README.md | 53 ++++++++++++++++++++++++------------------------ docs/PRINTERS.md | 6 +++--- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 60585ffcc..589eb471e 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,46 @@ # Slither, the Solidity source analyzer [![Build Status](https://travis-ci.com/trailofbits/slither.svg?token=JEF97dFy1QsDCfQ2Wusd&branch=master)](https://travis-ci.com/trailofbits/slither) -Slither is a Solidity static analysis framework written in Python 3. It provides an API to easily manipulate Solidity code. In addition to exposing a Solidity contracts AST, Slither provides many APIs to quickly check local and state variable usage. +Slither is a Solidity static analysis framework written in Python 3. It provides an API to easily manipulate Solidity code, and integrates vulnerabilities detectors. +# Features With Slither you can: -- Detect vulnerabilities -- Speed up your understanding of code -- Build custom analyses to answer specific questions -- Quickly prototype a new static analysis techniques +- **Detect vulnerabilities** +- **Speed up your understanding** of code +- **Build custom analyses** to answer specific questions +- **Quickly prototype** a new static analysis techniques -## How to install +Slither can analyze contracts written with Solidity > 0.4. -Slither uses Python 3.6. +Some of Slither detectors are open-source, [contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. +# How to install -```bash -$ python setup.py install -``` +Slither uses Python 3.6. -You may also want solc, the Solidity compiler, which can be installed using homebrew: +## Using Pip -```bash -$ brew update -$ brew upgrade -$ brew tap ethereum/ethereum -$ brew install solidity -$ brew linkapps solidity +``` +$ pip install slither-analyzer ``` -or with aptitude: +## Using Gihtub ```bash -$ sudo add-apt-repository ppa:ethereum/ethereum -$ sudo apt-get update -$ sudo apt-get install solc +$ git clone https://github.com/trailofbits/slither.git & cd slither +$ python setup.py install ``` -## How to use +Slither requires [solc](https://github.com/ethereum/solidity/), the Solidity compiler. + +# How to use ``` $ slither file.sol ``` +For example: + ``` $ slither examples/bugs/uninitialized.sol [..] @@ -62,9 +61,7 @@ Check | Purpose | Impact `--detect-reentrancy`| Detect if different pragma directives are used | High `--detect-solc-version`| Detect if an old version of Solidity is used (<0.4.23) | Informational -A high prioritization check is likely to be a true positive with a severe impact. - -### Exclude analyses +## Exclude analyses * `--exclude-informational`: Exclude informational impact analyses * `--exclude-low`: Exclude low impact analyses * `--exclude-medium`: Exclude medium impact analyses @@ -86,7 +83,11 @@ A high prioritization check is likely to be a true positive with a severe impact For more information about printers, see the [Printers documentation](docs/PRINTERS.md) +## How to create analyses + +See the [API documentation](https://github.com/trailofbits/slither/wiki/API-examples), and the [detector documentation](https://github.com/trailofbits/slither/wiki/Adding-a-new-detector). + -## License +# License Slither is licensed and distributed under the AGPLv3 license. [Contact us](mailto:opensource@trailofbits.com) if you're looking for an exception to the terms. diff --git a/docs/PRINTERS.md b/docs/PRINTERS.md index b8b0cb880..749b3bdba 100644 --- a/docs/PRINTERS.md +++ b/docs/PRINTERS.md @@ -3,7 +3,7 @@ Slither allows printing contracts information through its printers. ## Quick Summary -`slither.py file.sol --print-quick-summary` +`slither file.sol --printer-quick-summary` Output a quick summary of the contract. Example: @@ -13,7 +13,7 @@ $ slither vulns/0x01293cd77f68341635814c35299ed30ae212789e.sol --printer-quick-s ## Summary -`slither.py file.sol --print-summary` +`slither file.sol --printer-summary` Output a summary of the contract showing for each function: - What are the visibility and the modifiers @@ -49,7 +49,7 @@ Inheritances:: [] Output a graph showing the inheritance interaction between the contracts. Example: ``` -$ slither examples/DAO.sol --print-inheritance +$ slither examples/DAO.sol --printer-inheritance [...] INFO:PrinterInheritance:Inheritance Graph: examples/DAO.sol.dot ``` From a0c366a955d8bb8065882855ebcc4b4a049d3a23 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 13 Sep 2018 18:02:53 +0100 Subject: [PATCH 042/308] Add uninitialized storage var detector --- README.md | 3 +- .../bugs/uninitialized_storage_pointer.sol | 12 ++++ scripts/travis_test.sh | 5 ++ slither/__main__.py | 10 +++- slither/core/declarations/function.py | 7 +++ slither/core/variables/localVariable.py | 45 ++++++++++++++- .../uninitialized_storage_variables.py | 56 +++++++++++++++++++ .../solcParsing/declarations/functionSolc.py | 8 +++ .../variables/localVariableSolc.py | 21 ++++++- .../variables/variableDeclarationSolc.py | 10 ++-- 10 files changed, 168 insertions(+), 9 deletions(-) create mode 100644 examples/bugs/uninitialized_storage_pointer.sol create mode 100644 slither/detectors/variables/uninitialized_storage_variables.py diff --git a/README.md b/README.md index 589eb471e..fbd5e0fd8 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,8 @@ By default, all the checks are run. Check | Purpose | Impact --- | --- | --- -`--detect-uninitialized`| Detect uninitialized variables | High +`--detect-uninitialized`| Detect uninitialized state variables | High +`--detect-uninitialized-storage`| Detect uninitialized storagevariables | High `--detect-pragma`| Detect if different pragma directives are used | Informational `--detect-reentrancy`| Detect if different pragma directives are used | High `--detect-solc-version`| Detect if an old version of Solidity is used (<0.4.23) | Informational diff --git a/examples/bugs/uninitialized_storage_pointer.sol b/examples/bugs/uninitialized_storage_pointer.sol new file mode 100644 index 000000000..8fe657d1d --- /dev/null +++ b/examples/bugs/uninitialized_storage_pointer.sol @@ -0,0 +1,12 @@ +contract Uninitialized{ + + struct St{ + uint a; + } + + function func() payable{ + St st; + St memory st2; + } + +} diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 2de418830..2764be2fc 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -25,4 +25,9 @@ if [ $? -ne 1 ]; then exit 1 fi +slither examples/bugs/uninitialized_storage_pointer.sol --disable-solc-warnings +if [ $? -ne 1 ]; then + exit 1 +fi + exit 0 diff --git a/slither/__main__.py b/slither/__main__.py index 0a7dae3a2..d0d06f327 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -70,8 +70,14 @@ def main(): from slither.detectors.attributes.constant_pragma import ConstantPragma from slither.detectors.attributes.old_solc import OldSolc from slither.detectors.reentrancy.reentrancy import Reentrancy - - detectors = [Backdoor, UninitializedStateVarsDetection, ConstantPragma, OldSolc, Reentrancy] + from slither.detectors.variables.uninitialized_storage_variables import UninitializedStorageVars + + detectors = [Backdoor, + UninitializedStateVarsDetection, + ConstantPragma, + OldSolc, + Reentrancy, + UninitializedStorageVars] from slither.printers.summary.printerSummary import PrinterSummary from slither.printers.summary.printerQuickSummary import PrinterQuickSummary diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index afec66f30..12b6d4ca3 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -158,6 +158,13 @@ class Function(ChildContract, SourceMapping): """ return list(self._variables.values()) + @property + def local_variables(self): + """ + Return all local variables (dont include paramters and return values) + """ + return list(set(self.variables) - set(self.returns) - set(self.parameters)) + def variables_as_dict(self): return self._variables diff --git a/slither/core/variables/localVariable.py b/slither/core/variables/localVariable.py index 53938aaa4..d4eb60de6 100644 --- a/slither/core/variables/localVariable.py +++ b/slither/core/variables/localVariable.py @@ -1,5 +1,48 @@ from .variable import Variable from slither.core.children.childFunction import ChildFunction +from slither.core.solidityTypes.userDefinedType import UserDefinedType +from slither.core.solidityTypes.arrayType import ArrayType -class LocalVariable(ChildFunction, Variable): pass +from slither.core.declarations.structure import Structure + +class LocalVariable(ChildFunction, Variable): + + def __init__(self): + super(LocalVariable, self).__init__() + self._location = None + + + def set_location(self, loc): + self._location = loc + + @property + def location(self): + ''' + Variable Location + Can be storage/memory or default + Returns: + (str) + ''' + return self._location + + @property + def is_storage(self): + """ + Return true if the variable is located in storage + See https://solidity.readthedocs.io/en/v0.4.24/types.html?highlight=storage%20location#data-location + Returns: + (bool) + """ + if self.location == 'memory': + return False + if self.location == 'storage': + return True + + if isinstance(self.type, ArrayType): + return True + + if isinstance(self.type, UserDefinedType): + return isinstance(self.type.type, Structure) + + return False diff --git a/slither/detectors/variables/uninitialized_storage_variables.py b/slither/detectors/variables/uninitialized_storage_variables.py new file mode 100644 index 000000000..f5c4091f2 --- /dev/null +++ b/slither/detectors/variables/uninitialized_storage_variables.py @@ -0,0 +1,56 @@ +""" + Module detecting state uninitialized variables + Recursively check the called functions + + The heuristic chekcs that: + - state variables are read or called + - the variables does not call push (avoid too many FP) + + Only analyze "leaf" contracts (contracts that are not inherited by another contract) +""" + +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification + +from slither.visitors.expression.findPush import FindPush + + +class UninitializedStorageVars(AbstractDetector): + """ + """ + + ARGUMENT = 'uninitialized-storage' + HELP = 'Uninitialized storage variables' + CLASSIFICATION = DetectorClassification.HIGH + + def detect(self): + """ Detect uninitialized state variables + + Recursively visit the calls + Returns: + dict: [contract name] = set(state variable uninitialized) + """ + results = [] + for contract in self.slither.contracts: + for function in contract.functions: + uninitialized_storage_variables = [v for v in function.variables if v.is_storage and v.uninitialized] + + if uninitialized_storage_variables: + vars_name = [v.name for v in uninitialized_storage_variables] + + info = "Uninitialized storage variables in %s, " % self.filename + \ + "Contract: %s, Function: %s, Variables %s" % (contract.name, + function.name, + vars_name) + self.log(info) + + source = [function.source_mapping] + source += [v.source_mapping for v in uninitialized_storage_variables] + + results.append({'vuln': 'UninitializedStorageVars', + 'sourceMapping': source, + 'filename': self.filename, + 'contract': contract.name, + 'function': function.name, + 'variables': vars_name}) + + return results diff --git a/slither/solcParsing/declarations/functionSolc.py b/slither/solcParsing/declarations/functionSolc.py index f2c256840..fa67091b6 100644 --- a/slither/solcParsing/declarations/functionSolc.py +++ b/slither/solcParsing/declarations/functionSolc.py @@ -467,6 +467,10 @@ class FunctionSolc(Function): local_var.set_offset(param['src'], self.contract.slither) local_var.analyze(self) + # see https://solidity.readthedocs.io/en/v0.4.24/types.html?highlight=storage%20location#data-location + if local_var.location == 'default': + local_var.set_location('memory') + self._variables[local_var.name] = local_var self._parameters.append(local_var) @@ -482,6 +486,10 @@ class FunctionSolc(Function): local_var.set_offset(ret['src'], self.contract.slither) local_var.analyze(self) + # see https://solidity.readthedocs.io/en/v0.4.24/types.html?highlight=storage%20location#data-location + if local_var.location == 'default': + local_var.set_location('memory') + self._variables[local_var.name] = local_var self._returns.append(local_var) diff --git a/slither/solcParsing/variables/localVariableSolc.py b/slither/solcParsing/variables/localVariableSolc.py index e983a86c9..8667a41e8 100644 --- a/slither/solcParsing/variables/localVariableSolc.py +++ b/slither/solcParsing/variables/localVariableSolc.py @@ -2,4 +2,23 @@ from .variableDeclarationSolc import VariableDeclarationSolc from slither.core.variables.localVariable import LocalVariable -class LocalVariableSolc(VariableDeclarationSolc, LocalVariable): pass +class LocalVariableSolc(VariableDeclarationSolc, LocalVariable): + + def _analyze_variable_attributes(self, attributes): + '''' + Variable Location + Can be storage/memory or default + ''' + if 'storageLocation' in attributes: + location = attributes['storageLocation'] + self._location = location + else: + if 'memory' in attributes['type']: + self._location = 'memory' + elif'storage' in attributes['type']: + self._location = 'storage' + else: + self._location = 'default' + + super(LocalVariableSolc, self)._analyze_variable_attributes(attributes) + diff --git a/slither/solcParsing/variables/variableDeclarationSolc.py b/slither/solcParsing/variables/variableDeclarationSolc.py index 78a6cd428..d8868e9a2 100644 --- a/slither/solcParsing/variables/variableDeclarationSolc.py +++ b/slither/solcParsing/variables/variableDeclarationSolc.py @@ -59,6 +59,11 @@ class VariableDeclarationSolc(Variable): def uninitialized(self): return not self._initialized + def _analyze_variable_attributes(self, attributes): + if 'visibility' in attributes: + self._visibility = attributes['visibility'] + else: + self._visibility = 'internal' def _init_from_declaration(self, var, init): assert len(var['children']) <= 2 @@ -75,10 +80,7 @@ class VariableDeclarationSolc(Variable): self._initial_expression = None self._type = None - if 'visibility' in attributes: - self._visibility = attributes['visibility'] - else: - self._visibility = 'internal' + self._analyze_variable_attributes(attributes) if not var['children']: # It happens on variable declared inside loop declaration From e1fedcacbf6cb944df760557f8110b64b0dd5dfa Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 13 Sep 2018 18:18:02 +0100 Subject: [PATCH 043/308] Speedup reentrancy detection through fixpoint --- slither/detectors/reentrancy/reentrancy.py | 27 +++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/slither/detectors/reentrancy/reentrancy.py b/slither/detectors/reentrancy/reentrancy.py index 8b7d64ca3..94f83db55 100644 --- a/slither/detectors/reentrancy/reentrancy.py +++ b/slither/detectors/reentrancy/reentrancy.py @@ -2,6 +2,7 @@ Re-entrancy detection Based on heuristics, it may lead to FP and FN + Iterate over all the nodes of the graph until reaching a fixpoint """ from slither.core.declarations.function import Function @@ -66,13 +67,28 @@ class Reentrancy(AbstractDetector): """ if node in visited: return + visited = visited + [node] # First we add the external calls executed in previous nodes node.context[self.key] = [] + + fathers_context = [] + for father in node.fathers: if self.key in father.context: - node.context[self.key] += father.context[self.key] + fathers_context += father.context[self.key] + + # Exclude path that dont bring further information + if node in self.visited_all_paths: + if all(f_c in self.visited_all_paths[node] for f_c in fathers_context): + return + else: + self.visited_all_paths[node] = [] + + self.visited_all_paths[node] = list(set(self.visited_all_paths[node] + fathers_context)) + + node.context[self.key] = fathers_context # Get all the new external calls for call in node.external_calls: @@ -88,6 +104,7 @@ class Reentrancy(AbstractDetector): if isinstance(internal_call, Function): state_vars_written += internal_call.all_state_variables_written() + # If a state variables is written, and there was an external call # We found a potential re-entrancy bug if state_vars_written and node.context[self.key]: @@ -119,6 +136,14 @@ class Reentrancy(AbstractDetector): """ """ self.result = {} + + # if a node was already visited by another path + # we will only explore it if the traversal brings + # new variables written + # This speedup the exploration through a light fixpoint + # Its particular useful on 'complex' functions with several loops and conditions + self.visited_all_paths = {} + for c in self.contracts: self.detect_reentrancy(c) From 26ab17459cf652e118e985684a4d31e60d3c0e46 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 13 Sep 2018 19:10:47 +0100 Subject: [PATCH 044/308] Improve uninitialized storage detector --- .../bugs/uninitialized_storage_pointer.sol | 4 +- .../uninitialized_storage_variables.py | 86 ++++++++++++++----- 2 files changed, 69 insertions(+), 21 deletions(-) diff --git a/examples/bugs/uninitialized_storage_pointer.sol b/examples/bugs/uninitialized_storage_pointer.sol index 8fe657d1d..c2cdf79a1 100644 --- a/examples/bugs/uninitialized_storage_pointer.sol +++ b/examples/bugs/uninitialized_storage_pointer.sol @@ -5,8 +5,10 @@ contract Uninitialized{ } function func() payable{ - St st; + St st; // non init, but never read so its fine St memory st2; + St st_bug; + st_bug.a; } } diff --git a/slither/detectors/variables/uninitialized_storage_variables.py b/slither/detectors/variables/uninitialized_storage_variables.py index f5c4091f2..2a2f12112 100644 --- a/slither/detectors/variables/uninitialized_storage_variables.py +++ b/slither/detectors/variables/uninitialized_storage_variables.py @@ -22,6 +22,46 @@ class UninitializedStorageVars(AbstractDetector): HELP = 'Uninitialized storage variables' CLASSIFICATION = DetectorClassification.HIGH + + key = "UNINITIALIZEDSTORAGE" + + def _detect_uninitialized(self, function, node, visited): + if node in visited: + return + + visited = visited + [node] + + fathers_context = [] + + for father in node.fathers: + if self.key in father.context: + fathers_context += father.context[self.key] + + # Exclude path that dont bring further information + if node in self.visited_all_paths: + if all(f_c in self.visited_all_paths[node] for f_c in fathers_context): + return + else: + self.visited_all_paths[node] = [] + + self.visited_all_paths[node] = list(set(self.visited_all_paths[node] + fathers_context)) + + if self.key in node.context: + fathers_context += node.context[self.key] + + variables_read = node.variables_read + for uninitialized_storage_variable in fathers_context: + if uninitialized_storage_variable in variables_read: + self.results.append((function, uninitialized_storage_variable)) + + # Only save the storage variables that are not yet written + uninitialized_storage_variables = list(set(fathers_context) - set(node.variables_written)) + node.context[self.key] = uninitialized_storage_variables + + for son in node.sons: + self._detect_uninitialized(function, son, visited) + + def detect(self): """ Detect uninitialized state variables @@ -30,27 +70,33 @@ class UninitializedStorageVars(AbstractDetector): dict: [contract name] = set(state variable uninitialized) """ results = [] + + self.results = [] + self.visited_all_paths = {} + for contract in self.slither.contracts: for function in contract.functions: - uninitialized_storage_variables = [v for v in function.variables if v.is_storage and v.uninitialized] - - if uninitialized_storage_variables: - vars_name = [v.name for v in uninitialized_storage_variables] - - info = "Uninitialized storage variables in %s, " % self.filename + \ - "Contract: %s, Function: %s, Variables %s" % (contract.name, - function.name, - vars_name) - self.log(info) - - source = [function.source_mapping] - source += [v.source_mapping for v in uninitialized_storage_variables] - - results.append({'vuln': 'UninitializedStorageVars', - 'sourceMapping': source, - 'filename': self.filename, - 'contract': contract.name, - 'function': function.name, - 'variables': vars_name}) + if function.is_implemented: + uninitialized_storage_variables = [v for v in function.local_variables if v.is_storage and v.uninitialized] + function.entry_point.context[self.key] = uninitialized_storage_variables + self._detect_uninitialized(function, function.entry_point, []) + + for(function, uninitialized_storage_variable) in self.results: + var_name = uninitialized_storage_variable.name + + info = "Uninitialized storage variables in %s, " % self.filename + \ + "Contract: %s, Function: %s, Variable %s" % (function.contract.name, + function.name, + var_name) + self.log(info) + + source = [function.source_mapping, uninitialized_storage_variable.source_mapping] + + results.append({'vuln': 'UninitializedStorageVars', + 'sourceMapping': source, + 'filename': self.filename, + 'contract': function.contract.name, + 'function': function.name, + 'variable': var_name}) return results From f45c514f185a62282ee02d9dbd1483016056d627 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 13 Sep 2018 19:12:38 +0100 Subject: [PATCH 045/308] Improve uninitialized storage detector documentation --- .../variables/uninitialized_storage_variables.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/slither/detectors/variables/uninitialized_storage_variables.py b/slither/detectors/variables/uninitialized_storage_variables.py index 2a2f12112..8fd0d5da3 100644 --- a/slither/detectors/variables/uninitialized_storage_variables.py +++ b/slither/detectors/variables/uninitialized_storage_variables.py @@ -1,12 +1,8 @@ """ - Module detecting state uninitialized variables - Recursively check the called functions + Module detecting state uninitialized storage variables - The heuristic chekcs that: - - state variables are read or called - - the variables does not call push (avoid too many FP) - - Only analyze "leaf" contracts (contracts that are not inherited by another contract) + Recursively explore the CFG to only report uninitialized storage variables that are + written before being read """ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification @@ -23,6 +19,7 @@ class UninitializedStorageVars(AbstractDetector): CLASSIFICATION = DetectorClassification.HIGH + # node.context[self.key] contains the uninitialized storage variables key = "UNINITIALIZEDSTORAGE" def _detect_uninitialized(self, function, node, visited): From ae255bfb601d4f60840a8f55906c9b72761a61f1 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 13 Sep 2018 19:18:26 +0100 Subject: [PATCH 046/308] Update README --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index fbd5e0fd8..f4e7326ee 100644 --- a/README.md +++ b/README.md @@ -54,13 +54,13 @@ If Slither is applied on a directory, it will run on every `.sol` file of the di By default, all the checks are run. -Check | Purpose | Impact ---- | --- | --- -`--detect-uninitialized`| Detect uninitialized state variables | High -`--detect-uninitialized-storage`| Detect uninitialized storagevariables | High -`--detect-pragma`| Detect if different pragma directives are used | Informational -`--detect-reentrancy`| Detect if different pragma directives are used | High -`--detect-solc-version`| Detect if an old version of Solidity is used (<0.4.23) | Informational +Check | Purpose | Impact | Confidence +--- | --- | --- | --- +`--detect-uninitialized`| Detect uninitialized state variables | High | High +`--detect-uninitialized-storage`| Detect uninitialized storage variables | High | High +`--detect-pragma`| Detect if different pragma directives are used | Informational | High +`--detect-reentrancy`| Detect if different pragma directives are used | High | Medium +`--detect-solc-version`| Detect if an old version of Solidity is used (<0.4.23) | Informational | High ## Exclude analyses * `--exclude-informational`: Exclude informational impact analyses From f7d13240f3a574719c89444e055d3612399091d4 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 14 Sep 2018 08:18:43 +0100 Subject: [PATCH 047/308] --detect-uninitialized -> --detect-uninitialized-state inheritances -> inheritance --- README.md | 2 +- slither/__main__.py | 2 +- slither/core/declarations/contract.py | 22 +++++++++---------- slither/core/slitherCore.py | 6 ++--- .../shadowing/shadowingFunctionsDetection.py | 2 +- ...on.py => uninitialized_state_variables.py} | 2 +- .../inheritance/printerInheritance.py | 4 ++-- .../printers/summary/printerQuickSummary.py | 2 +- slither/printers/summary/printerSummary.py | 4 ++-- .../declarations/contractSolc04.py | 16 +++++++------- .../expressions/expressionParsing.py | 6 ++--- slither/solcParsing/slitherSolc.py | 20 ++++++++--------- slither/utils/utils.py | 2 +- 13 files changed, 45 insertions(+), 45 deletions(-) rename slither/detectors/variables/{uninitializedStateVarsDetection.py => uninitialized_state_variables.py} (98%) diff --git a/README.md b/README.md index f4e7326ee..a77d9429f 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ By default, all the checks are run. Check | Purpose | Impact | Confidence --- | --- | --- | --- -`--detect-uninitialized`| Detect uninitialized state variables | High | High +`--detect-uninitialized-state`| Detect uninitialized state variables | High | High `--detect-uninitialized-storage`| Detect uninitialized storage variables | High | High `--detect-pragma`| Detect if different pragma directives are used | Informational | High `--detect-reentrancy`| Detect if different pragma directives are used | High | Medium diff --git a/slither/__main__.py b/slither/__main__.py index d0d06f327..0348b947b 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -66,7 +66,7 @@ def main(): NOTE: This contains just a few detectors and printers that we made public. """ from slither.detectors.examples.backdoor import Backdoor - from slither.detectors.variables.uninitializedStateVarsDetection import UninitializedStateVarsDetection + from slither.detectors.variables.uninitialized_state_variables import UninitializedStateVarsDetection from slither.detectors.attributes.constant_pragma import ConstantPragma from slither.detectors.attributes.old_solc import OldSolc from slither.detectors.reentrancy.reentrancy import Reentrancy diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index a0607c6c5..f866308b0 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -19,7 +19,7 @@ class Contract(ChildSlither, SourceMapping): self._name = None self._id = None - self._inheritances = [] + self._inheritance = [] self._enums = {} self._structures = {} @@ -52,21 +52,21 @@ class Contract(ChildSlither, SourceMapping): return self._id @property - def inheritances(self): + def inheritance(self): ''' - list(Contract): Inheritances list. Order: the first elem is the first father to be executed + list(Contract): Inheritance list. Order: the first elem is the first father to be executed ''' - return list(self._inheritances) + return list(self._inheritance) @property - def inheritances_reverse(self): + def inheritance_reverse(self): ''' - list(Contract): Inheritances list. Order: the last elem is the first father to be executed + list(Contract): Inheritance list. Order: the last elem is the first father to be executed ''' - return reversed(self._inheritances) + return reversed(self._inheritance) - def setInheritances(self, inheritances): - self._inheritances = inheritances + def setInheritance(self, inheritance): + self._inheritance = inheritance @property def structures(self): @@ -290,8 +290,8 @@ class Contract(ChildSlither, SourceMapping): """ Return the function summary Returns: - (str, list, list, list, list): (name, inheritances, variables, fuction summaries, modifier summaries) + (str, list, list, list, list): (name, inheritance, variables, fuction summaries, modifier summaries) """ func_summaries = [f.get_summary() for f in self.functions] modif_summaries = [f.get_summary() for f in self.modifiers] - return (self.name, [str(x) for x in self.inheritances], [str(x) for x in self.variables], func_summaries, modif_summaries) + return (self.name, [str(x) for x in self.inheritance], [str(x) for x in self.variables], func_summaries, modif_summaries) diff --git a/slither/core/slitherCore.py b/slither/core/slitherCore.py index b96a6a89a..4ae8ab058 100644 --- a/slither/core/slitherCore.py +++ b/slither/core/slitherCore.py @@ -29,9 +29,9 @@ class Slither: @property def contracts_derived(self): """list(Contract): List of contracts that are derived and not inherited.""" - inheritances = (x.inheritances for x in self.contracts) - inheritances = [item for sublist in inheritances for item in sublist] - return [c for c in self._contracts.values() if c not in inheritances] + inheritance = (x.inheritance for x in self.contracts) + inheritance = [item for sublist in inheritance for item in sublist] + return [c for c in self._contracts.values() if c not in inheritance] def contracts_as_dict(self): """list(dict(str: Contract): List of contracts as dict: name -> Contract.""" diff --git a/slither/detectors/shadowing/shadowingFunctionsDetection.py b/slither/detectors/shadowing/shadowingFunctionsDetection.py index e2535aaff..cd541b61f 100644 --- a/slither/detectors/shadowing/shadowingFunctionsDetection.py +++ b/slither/detectors/shadowing/shadowingFunctionsDetection.py @@ -22,7 +22,7 @@ class ShadowingFunctionsDetection(AbstractDetector): def detect_shadowing(self, contract): functions_declared = set([x.full_name for x in contract.functions]) ret = {} - for father in contract.inheritances: + for father in contract.inheritance: functions_declared_father = ([x.full_name for x in father.functions]) inter = functions_declared.intersection(functions_declared_father) if inter: diff --git a/slither/detectors/variables/uninitializedStateVarsDetection.py b/slither/detectors/variables/uninitialized_state_variables.py similarity index 98% rename from slither/detectors/variables/uninitializedStateVarsDetection.py rename to slither/detectors/variables/uninitialized_state_variables.py index c68e74eb2..721316c3c 100644 --- a/slither/detectors/variables/uninitializedStateVarsDetection.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -19,7 +19,7 @@ class UninitializedStateVarsDetection(AbstractDetector): Constant function detector """ - ARGUMENT = 'uninitialized' + ARGUMENT = 'uninitialized-state' HELP = 'Uninitialized state variables' CLASSIFICATION = DetectorClassification.HIGH diff --git a/slither/printers/inheritance/printerInheritance.py b/slither/printers/inheritance/printerInheritance.py index ba21ddd08..4867a3843 100644 --- a/slither/printers/inheritance/printerInheritance.py +++ b/slither/printers/inheritance/printerInheritance.py @@ -18,7 +18,7 @@ class PrinterInheritance(AbstractPrinter): def __init__(self, slither, logger): super(PrinterInheritance, self).__init__(slither, logger) - inheritance = [x.inheritances for x in slither.contracts] + inheritance = [x.inheritance for x in slither.contracts] self.inheritance = set([item for sublist in inheritance for item in sublist]) shadow = ShadowingFunctionsDetection(slither, None) @@ -60,7 +60,7 @@ class PrinterInheritance(AbstractPrinter): """ ret = '' # Add arrows - for i in contract.inheritances: + for i in contract.inheritance: ret += '%s -> %s;\n' % (contract.name, i) # Functions diff --git a/slither/printers/summary/printerQuickSummary.py b/slither/printers/summary/printerQuickSummary.py index 00a2fe58c..f51ea87a4 100644 --- a/slither/printers/summary/printerQuickSummary.py +++ b/slither/printers/summary/printerQuickSummary.py @@ -19,7 +19,7 @@ class PrinterQuickSummary(AbstractPrinter): txt = "" for c in self.contracts: - (name, _inheritances, _var, func_summaries, _modif_summaries) = c.get_summary() + (name, _inheritance, _var, func_summaries, _modif_summaries) = c.get_summary() txt += blue("\n+ Contract %s\n"%name) for (f_name, visi, _, _, _, _, _) in func_summaries: txt += " - " diff --git a/slither/printers/summary/printerSummary.py b/slither/printers/summary/printerSummary.py index 901827746..d34d3f152 100644 --- a/slither/printers/summary/printerSummary.py +++ b/slither/printers/summary/printerSummary.py @@ -27,10 +27,10 @@ class PrinterSummary(AbstractPrinter): """ for c in self.contracts: - (name, inheritances, var, func_summaries, modif_summaries) = c.get_summary() + (name, inheritance, var, func_summaries, modif_summaries) = c.get_summary() txt = "\nContract %s"%name txt += '\nContract vars: '+str(var) - txt += '\nInheritances:: '+str(inheritances) + txt += '\nInheritance:: '+str(inheritance) table = PrettyTable(["Function", "Visibility", "Modifiers", diff --git a/slither/solcParsing/declarations/contractSolc04.py b/slither/solcParsing/declarations/contractSolc04.py index 5c926bcd2..9706cd73e 100644 --- a/slither/solcParsing/declarations/contractSolc04.py +++ b/slither/solcParsing/declarations/contractSolc04.py @@ -39,7 +39,7 @@ class ContractSolc04(Contract): # Export info self._name = self._data['attributes']['name'] self._id = self._data['id'] - self._inheritances = [] + self._inheritance = [] self._parse_contract_info() self._parse_contract_items() @@ -89,7 +89,7 @@ class ContractSolc04(Contract): return def analyze_using_for(self): - for father in self.inheritances: + for father in self.inheritance: self._using_for.update(father.using_for) for using_for in self._usingForNotParsed: @@ -106,7 +106,7 @@ class ContractSolc04(Contract): def analyze_enums(self): - for father in self.inheritances: + for father in self.inheritance: self._enums.update(father.enums_as_dict()) for enum in self._enumsNotParsed: @@ -152,7 +152,7 @@ class ContractSolc04(Contract): struct.analyze() def parse_structs(self): - for father in self.inheritances_reverse: + for father in self.inheritance_reverse: self._structures.update(father.structures_as_dict()) for struct in self._structuresNotParsed: @@ -165,7 +165,7 @@ class ContractSolc04(Contract): def analyze_events(self): - for father in self.inheritances_reverse: + for father in self.inheritance_reverse: self._events.update(father.events_as_dict()) for event_to_parse in self._eventsNotParsed: @@ -176,7 +176,7 @@ class ContractSolc04(Contract): self._eventsNotParsed = None def parse_state_variables(self): - for father in self.inheritances_reverse: + for father in self.inheritance_reverse: self._variables.update(father.variables_as_dict()) for varNotParsed in self._variablesNotParsed: @@ -223,7 +223,7 @@ class ContractSolc04(Contract): return def analyze_params_modifiers(self): - for father in self.inheritances_reverse: + for father in self.inheritance_reverse: self._modifiers.update(father.modifiers_as_dict()) for modifier in self._modifiers_no_params: @@ -234,7 +234,7 @@ class ContractSolc04(Contract): return def analyze_params_functions(self): - for father in self.inheritances_reverse: + for father in self.inheritance_reverse: functions = {k:v for (k,v) in father.functions_as_dict().items()} #if not v.is_constructor} self._functions.update(functions) diff --git a/slither/solcParsing/expressions/expressionParsing.py b/slither/solcParsing/expressions/expressionParsing.py index eef81e695..5dcb634d4 100644 --- a/slither/solcParsing/expressions/expressionParsing.py +++ b/slither/solcParsing/expressions/expressionParsing.py @@ -316,12 +316,12 @@ def parse_expression(expression, caller_context): if str(member_expression) == 'super': super_name = parse_super_name(expression) if isinstance(caller_context, Contract): - inheritances = caller_context.inheritances + inheritance = caller_context.inheritance else: assert isinstance(caller_context, Function) - inheritances = caller_context.contract.inheritances + inheritance = caller_context.contract.inheritance var = None - for father in inheritances: + for father in inheritance: try: var = find_variable(super_name, father) break diff --git a/slither/solcParsing/slitherSolc.py b/slither/solcParsing/slitherSolc.py index 07cac7395..ecc2f809b 100644 --- a/slither/solcParsing/slitherSolc.py +++ b/slither/solcParsing/slitherSolc.py @@ -89,10 +89,10 @@ class SlitherSolc(Slither): self._contracts_by_id[contract.id] = contract self._contracts[contract.name] = contract - # Update of the inheritances + # Update of the inheritance for contract in self._contractsNotParsed: # remove the first elem in linearizedBaseContracts as it is the contract itself - contract.setInheritances([self._contracts_by_id[i] for i in contract.linearizedBaseContracts[1:]]) + contract.setInheritance([self._contracts_by_id[i] for i in contract.linearizedBaseContracts[1:]]) contracts_to_be_analyzed = self.contracts @@ -129,9 +129,9 @@ class SlitherSolc(Slither): contract = contracts_to_be_analyzed[0] contracts_to_be_analyzed = contracts_to_be_analyzed[1:] - all_father_analyzed = all(father.is_analyzed for father in contract.inheritances) + all_father_analyzed = all(father.is_analyzed for father in contract.inheritance) - if not contract.inheritances or all_father_analyzed: + if not contract.inheritance or all_father_analyzed: self._analyze_enums(contract) else: contracts_to_be_analyzed += [contract] @@ -149,9 +149,9 @@ class SlitherSolc(Slither): contract = contracts_to_be_analyzed[0] contracts_to_be_analyzed = contracts_to_be_analyzed[1:] - all_father_analyzed = all(father.is_analyzed for father in contract.inheritances) + all_father_analyzed = all(father.is_analyzed for father in contract.inheritance) - if not contract.inheritances or all_father_analyzed: + if not contract.inheritance or all_father_analyzed: self._parse_struct_var_modifiers_functions(contract) else: @@ -170,9 +170,9 @@ class SlitherSolc(Slither): contract = contracts_to_be_analyzed[0] contracts_to_be_analyzed = contracts_to_be_analyzed[1:] - all_father_analyzed = all(father.is_analyzed for father in contract.inheritances) + all_father_analyzed = all(father.is_analyzed for father in contract.inheritance) - if not contract.inheritances or all_father_analyzed: + if not contract.inheritance or all_father_analyzed: self._analyze_struct_events(contract) else: @@ -191,9 +191,9 @@ class SlitherSolc(Slither): contract = contracts_to_be_analyzed[0] contracts_to_be_analyzed = contracts_to_be_analyzed[1:] - all_father_analyzed = all(father.is_analyzed for father in contract.inheritances) + all_father_analyzed = all(father.is_analyzed for father in contract.inheritance) - if not contract.inheritances or all_father_analyzed: + if not contract.inheritance or all_father_analyzed: self._analyze_variables_modifiers_functions(contract) else: diff --git a/slither/utils/utils.py b/slither/utils/utils.py index 8f90ed8fb..34223d6f0 100644 --- a/slither/utils/utils.py +++ b/slither/utils/utils.py @@ -21,7 +21,7 @@ def find_call(call, contract, contracts): for f in contract.functions + contract.modifiers: if call == f.name: return f - for father in contract.inheritances: + for father in contract.inheritance: fatherContract = next((x for x in contracts if x.name == father), None) if fatherContract: for f in fatherContract.functions: From 4d797bb4b7de5aa199b2f88f290ddf4990cc0cd7 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 14 Sep 2018 10:09:41 +0100 Subject: [PATCH 048/308] API changes: - Remove slither.utils.utils - Follow pep8 for module name - Shorter module name Move examples/bugs to tests/ (close #7) Udpdate examples/scripts and integrate them in travis --- examples/scripts/functions_called.py | 9 ++- examples/scripts/functions_writing.py | 7 +- examples/scripts/variable_in_condition.py | 7 +- scripts/travis_test.sh | 30 ++++++-- slither/__main__.py | 6 +- slither/core/cfg/node.py | 77 +++++++++++++++++-- slither/core/cfg/nodeType.py | 63 --------------- .../{childContract.py => child_contract.py} | 0 .../{childEvent.py => child_event.py} | 0 .../{childFunction.py => child_function.py} | 0 .../{childSlither.py => child_slither.py} | 0 .../{childStructure.py => child_structure.py} | 0 slither/core/declarations/contract.py | 4 +- slither/core/declarations/enum.py | 4 +- slither/core/declarations/event.py | 4 +- slither/core/declarations/function.py | 14 ++-- slither/core/declarations/import_directive.py | 2 +- slither/core/declarations/pragma_directive.py | 2 +- ...dityVariables.py => solidity_variables.py} | 0 slither/core/declarations/structure.py | 4 +- ...ntOperation.py => assignment_operation.py} | 2 +- ...binaryOperation.py => binary_operation.py} | 2 +- .../{callExpression.py => call_expression.py} | 0 ...xpression.py => conditional_expression.py} | 0 ....py => elementary_type_name_expression.py} | 2 +- slither/core/expressions/expression.py | 2 +- ...expressionTyped.py => expression_typed.py} | 0 slither/core/expressions/identifier.py | 2 +- .../{indexAccess.py => index_access.py} | 4 +- .../{memberAccess.py => member_access.py} | 4 +- .../expressions/{newArray.py => new_array.py} | 2 +- .../{newContract.py => new_contract.py} | 0 ...ementaryType.py => new_elementary_type.py} | 2 +- ...Expression.py => super_call_expression.py} | 2 +- ...superIdentifier.py => super_identifier.py} | 2 +- ...tupleExpression.py => tuple_expression.py} | 0 .../{typeConversion.py => type_conversion.py} | 4 +- .../{unaryOperation.py => unary_operation.py} | 4 +- .../core/{slitherCore.py => slither_core.py} | 0 slither/core/solidityTypes/type.py | 3 - .../__init__.py | 0 .../array_type.py} | 2 +- .../elementary_type.py} | 2 +- .../function_type.py} | 4 +- .../mapping_type.py} | 2 +- slither/core/solidity_types/type.py | 3 + .../user_defined_type.py} | 2 +- .../__init__.py | 0 .../source_mapping.py} | 0 .../{eventVariable.py => event_variable.py} | 2 +- ...eVariable.py => function_type_variable.py} | 0 .../{localVariable.py => local_variable.py} | 6 +- ...e.py => local_variable_init_from_tuple.py} | 2 +- .../{stateVariable.py => state_variable.py} | 2 +- ...ctureVariable.py => structure_variable.py} | 2 +- slither/core/variables/variable.py | 2 +- slither/detectors/reentrancy/reentrancy.py | 6 +- ...onsDetection.py => shadowing_functions.py} | 0 .../uninitialized_state_variables.py | 2 +- .../uninitialized_storage_variables.py | 2 +- .../{printerInheritance.py => inheritance.py} | 2 +- ...rinterQuickSummary.py => quick_summary.py} | 0 .../summary/{printerSummary.py => summary.py} | 0 slither/slither.py | 2 +- .../variables/eventVariableSolc.py | 5 -- .../variables/functionTypeVariableSolc.py | 5 -- .../variables/stateVariableSolc.py | 5 -- .../variables/structureVariableSolc.py | 5 -- .../{solcParsing => solc_parsing}/__init__.py | 0 .../cfg/__init__.py | 0 .../nodeSolc.py => solc_parsing/cfg/node.py} | 14 ++-- .../declarations/__init__.py | 0 .../declarations/contract.py} | 12 +-- .../declarations/event.py} | 2 +- .../declarations/function.py} | 14 ++-- .../declarations/modifier.py} | 4 +- .../declarations/structure.py} | 2 +- .../expressions/__init__.py | 0 .../expressions/expression_parsing.py} | 42 +++++----- .../slitherSolc.py | 4 +- .../solidity_types}/__init__.py | 0 .../solidity_types/type_parsing.py} | 16 ++-- .../variables/__init__.py | 0 .../solc_parsing/variables/event_variable.py | 5 ++ .../variables/function_type_variable.py | 5 ++ .../variables/local_variable.py} | 4 +- .../local_variable_init_from_tuple.py} | 4 +- .../solc_parsing/variables/state_variable.py | 5 ++ .../variables/structure_variable.py | 5 ++ .../variables/variable_declaration.py} | 6 +- slither/utils/utils.py | 61 --------------- .../{exportValues.py => export_values.py} | 2 +- slither/visitors/expression/expression.py | 26 +++---- ...essionPrinter.py => expression_printer.py} | 0 slither/visitors/expression/find_calls.py | 2 +- .../expression/{findPush.py => find_push.py} | 4 +- .../{leftValue.py => left_value.py} | 2 +- .../expression/{readVar.py => read_var.py} | 4 +- .../{rightValue.py => right_value.py} | 2 +- .../expression/{writeVar.py => write_var.py} | 6 +- {examples/bugs => tests}/backdoor.sol | 0 {examples/bugs => tests}/old_solc.sol | 0 {examples/bugs => tests}/old_solc.sol.json | 0 {examples/bugs => tests}/pragma.0.4.23.sol | 0 {examples/bugs => tests}/pragma.0.4.24.sol | 0 {examples/bugs => tests}/reentrancy.sol | 0 {examples/bugs => tests}/uninitialized.sol | 0 .../uninitialized_storage_pointer.sol | 0 108 files changed, 280 insertions(+), 308 deletions(-) delete mode 100644 slither/core/cfg/nodeType.py rename slither/core/children/{childContract.py => child_contract.py} (100%) rename slither/core/children/{childEvent.py => child_event.py} (100%) rename slither/core/children/{childFunction.py => child_function.py} (100%) rename slither/core/children/{childSlither.py => child_slither.py} (100%) rename slither/core/children/{childStructure.py => child_structure.py} (100%) rename slither/core/declarations/{solidityVariables.py => solidity_variables.py} (100%) rename slither/core/expressions/{assignmentOperation.py => assignment_operation.py} (98%) rename slither/core/expressions/{binaryOperation.py => binary_operation.py} (98%) rename slither/core/expressions/{callExpression.py => call_expression.py} (100%) rename slither/core/expressions/{conditionalExpression.py => conditional_expression.py} (100%) rename slither/core/expressions/{elementaryTypeNameExpression.py => elementary_type_name_expression.py} (90%) rename slither/core/expressions/{expressionTyped.py => expression_typed.py} (100%) rename slither/core/expressions/{indexAccess.py => index_access.py} (86%) rename slither/core/expressions/{memberAccess.py => member_access.py} (86%) rename slither/core/expressions/{newArray.py => new_array.py} (92%) rename slither/core/expressions/{newContract.py => new_contract.py} (100%) rename slither/core/expressions/{newElementaryType.py => new_elementary_type.py} (84%) rename slither/core/expressions/{superCallExpression.py => super_call_expression.py} (61%) rename slither/core/expressions/{superIdentifier.py => super_identifier.py} (69%) rename slither/core/expressions/{tupleExpression.py => tuple_expression.py} (100%) rename slither/core/expressions/{typeConversion.py => type_conversion.py} (81%) rename slither/core/expressions/{unaryOperation.py => unary_operation.py} (97%) rename slither/core/{slitherCore.py => slither_core.py} (100%) delete mode 100644 slither/core/solidityTypes/type.py rename slither/core/{solidityTypes => solidity_types}/__init__.py (100%) rename slither/core/{solidityTypes/arrayType.py => solidity_types/array_type.py} (94%) rename slither/core/{solidityTypes/elementaryType.py => solidity_types/elementary_type.py} (97%) rename slither/core/{solidityTypes/functionType.py => solidity_types/function_type.py} (93%) rename slither/core/{solidityTypes/mappingType.py => solidity_types/mapping_type.py} (93%) create mode 100644 slither/core/solidity_types/type.py rename slither/core/{solidityTypes/userDefinedType.py => solidity_types/user_defined_type.py} (94%) rename slither/core/{sourceMapping => source_mapping}/__init__.py (100%) rename slither/core/{sourceMapping/sourceMapping.py => source_mapping/source_mapping.py} (100%) rename slither/core/variables/{eventVariable.py => event_variable.py} (58%) rename slither/core/variables/{functionTypeVariable.py => function_type_variable.py} (100%) rename slither/core/variables/{localVariable.py => local_variable.py} (84%) rename slither/core/variables/{localVariableInitFromTuple.py => local_variable_init_from_tuple.py} (88%) rename slither/core/variables/{stateVariable.py => state_variable.py} (56%) rename slither/core/variables/{structureVariable.py => structure_variable.py} (57%) rename slither/detectors/shadowing/{shadowingFunctionsDetection.py => shadowing_functions.py} (100%) rename slither/printers/inheritance/{printerInheritance.py => inheritance.py} (98%) rename slither/printers/summary/{printerQuickSummary.py => quick_summary.py} (100%) rename slither/printers/summary/{printerSummary.py => summary.py} (100%) delete mode 100644 slither/solcParsing/variables/eventVariableSolc.py delete mode 100644 slither/solcParsing/variables/functionTypeVariableSolc.py delete mode 100644 slither/solcParsing/variables/stateVariableSolc.py delete mode 100644 slither/solcParsing/variables/structureVariableSolc.py rename slither/{solcParsing => solc_parsing}/__init__.py (100%) rename slither/{solcParsing => solc_parsing}/cfg/__init__.py (100%) rename slither/{solcParsing/cfg/nodeSolc.py => solc_parsing/cfg/node.py} (83%) rename slither/{solcParsing => solc_parsing}/declarations/__init__.py (100%) rename slither/{solcParsing/declarations/contractSolc04.py => solc_parsing/declarations/contract.py} (95%) rename slither/{solcParsing/declarations/eventSolc.py => solc_parsing/declarations/event.py} (90%) rename slither/{solcParsing/declarations/functionSolc.py => solc_parsing/declarations/function.py} (97%) rename slither/{solcParsing/declarations/modifierSolc.py => solc_parsing/declarations/modifier.py} (92%) rename slither/{solcParsing/declarations/structureSolc.py => solc_parsing/declarations/structure.py} (90%) rename slither/{solcParsing => solc_parsing}/expressions/__init__.py (100%) rename slither/{solcParsing/expressions/expressionParsing.py => solc_parsing/expressions/expression_parsing.py} (89%) rename slither/{solcParsing => solc_parsing}/slitherSolc.py (98%) rename slither/{solcParsing/solidityTypes => solc_parsing/solidity_types}/__init__.py (100%) rename slither/{solcParsing/solidityTypes/typeParsing.py => solc_parsing/solidity_types/type_parsing.py} (91%) rename slither/{solcParsing => solc_parsing}/variables/__init__.py (100%) create mode 100644 slither/solc_parsing/variables/event_variable.py create mode 100644 slither/solc_parsing/variables/function_type_variable.py rename slither/{solcParsing/variables/localVariableSolc.py => solc_parsing/variables/local_variable.py} (84%) rename slither/{solcParsing/variables/localVariableInitFromTupleSolc.py => solc_parsing/variables/local_variable_init_from_tuple.py} (60%) create mode 100644 slither/solc_parsing/variables/state_variable.py create mode 100644 slither/solc_parsing/variables/structure_variable.py rename slither/{solcParsing/variables/variableDeclarationSolc.py => solc_parsing/variables/variable_declaration.py} (93%) delete mode 100644 slither/utils/utils.py rename slither/visitors/expression/{exportValues.py => export_values.py} (97%) rename slither/visitors/expression/{expressionPrinter.py => expression_printer.py} (100%) rename slither/visitors/expression/{findPush.py => find_push.py} (95%) rename slither/visitors/expression/{leftValue.py => left_value.py} (97%) rename slither/visitors/expression/{readVar.py => read_var.py} (95%) rename slither/visitors/expression/{rightValue.py => right_value.py} (97%) rename slither/visitors/expression/{writeVar.py => write_var.py} (95%) rename {examples/bugs => tests}/backdoor.sol (100%) rename {examples/bugs => tests}/old_solc.sol (100%) rename {examples/bugs => tests}/old_solc.sol.json (100%) rename {examples/bugs => tests}/pragma.0.4.23.sol (100%) rename {examples/bugs => tests}/pragma.0.4.24.sol (100%) rename {examples/bugs => tests}/reentrancy.sol (100%) rename {examples/bugs => tests}/uninitialized.sol (100%) rename {examples/bugs => tests}/uninitialized_storage_pointer.sol (100%) diff --git a/examples/scripts/functions_called.py b/examples/scripts/functions_called.py index afa48b870..f3e63f003 100644 --- a/examples/scripts/functions_called.py +++ b/examples/scripts/functions_called.py @@ -1,7 +1,12 @@ +import sys from slither.slither import Slither +if len(sys.argv) != 2: + print('python.py function_called.py functions_called.sol') + exit(-1) + # Init slither -slither = Slither('functions_called.sol') +slither = Slither(sys.argv[1]) # Get the contract contract = slither.get_contract_from_name('Contract') @@ -9,7 +14,7 @@ contract = slither.get_contract_from_name('Contract') # Get the variable entry_point = contract.get_function_from_signature('entry_point()') -all_calls = entry_point.all_calls() +all_calls = entry_point.all_internal_calls() all_calls_formated = [f.contract.name + '.' + f.name for f in all_calls] diff --git a/examples/scripts/functions_writing.py b/examples/scripts/functions_writing.py index 04775e0b7..d3d9afcfb 100644 --- a/examples/scripts/functions_writing.py +++ b/examples/scripts/functions_writing.py @@ -1,7 +1,12 @@ +import sys from slither.slither import Slither +if len(sys.argv) != 2: + print('python.py function_writing.py functions_writing.sol') + exit(-1) + # Init slither -slither = Slither('functions_writing.sol') +slither = Slither(sys.argv[1]) # Get the contract contract = slither.get_contract_from_name('Contract') diff --git a/examples/scripts/variable_in_condition.py b/examples/scripts/variable_in_condition.py index 58e3f16a0..584a69c12 100644 --- a/examples/scripts/variable_in_condition.py +++ b/examples/scripts/variable_in_condition.py @@ -1,7 +1,12 @@ +import sys from slither.slither import Slither +if len(sys.argv) != 2: + print('python.py variable_in_condition.py variable_in_condition.sol') + exit(-1) + # Init slither -slither = Slither('variable_in_condition.sol') +slither = Slither(sys.argv[1]) # Get the contract contract = slither.get_contract_from_name('Contract') diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 2764be2fc..94de7b23c 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -1,33 +1,51 @@ #!/usr/bin/env bash -slither examples/bugs/uninitialized.sol --disable-solc-warnings +### Test Detectors + +slither tests/uninitialized.sol --disable-solc-warnings if [ $? -ne 1 ]; then exit 1 fi -slither examples/bugs/backdoor.sol --disable-solc-warnings +slither tests/backdoor.sol --disable-solc-warnings if [ $? -ne 1 ]; then exit 1 fi -slither examples/bugs/pragma.0.4.24.sol --disable-solc-warnings +slither tests/pragma.0.4.24.sol --disable-solc-warnings if [ $? -ne 1 ]; then exit 1 fi -slither examples/bugs/old_solc.sol.json --solc-ast +slither tests/old_solc.sol.json --solc-ast if [ $? -ne 1 ]; then exit 1 fi -slither examples/bugs/reentrancy.sol --disable-solc-warnings +slither tests/reentrancy.sol --disable-solc-warnings if [ $? -ne 1 ]; then exit 1 fi -slither examples/bugs/uninitialized_storage_pointer.sol --disable-solc-warnings +slither tests/uninitialized_storage_pointer.sol --disable-solc-warnings if [ $? -ne 1 ]; then exit 1 fi +### Test scripts + +python examples/scripts/functions_called.py examples/scripts/functions_called.sol +if [ $? -ne 0 ]; then + exit 1 +fi + +python examples/scripts/functions_writing.py examples/scripts/functions_writing.sol +if [ $? -ne 0 ]; then + exit 1 +fi + +python examples/scripts/variable_in_condition.py examples/scripts/variable_in_condition.sol +if [ $? -ne 0 ]; then + exit 1 +fi exit 0 diff --git a/slither/__main__.py b/slither/__main__.py index 0348b947b..068d7c9ce 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -79,9 +79,9 @@ def main(): Reentrancy, UninitializedStorageVars] - from slither.printers.summary.printerSummary import PrinterSummary - from slither.printers.summary.printerQuickSummary import PrinterQuickSummary - from slither.printers.inheritance.printerInheritance import PrinterInheritance + from slither.printers.summary.summary import PrinterSummary + from slither.printers.summary.quick_summary import PrinterQuickSummary + from slither.printers.inheritance.inheritance import PrinterInheritance from slither.printers.functions.authorization import PrinterWrittenVariablesAndAuthorization printers = [PrinterSummary, PrinterQuickSummary, PrinterInheritance, PrinterWrittenVariablesAndAuthorization] diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index f131ce6a3..72030f4e1 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -3,19 +3,82 @@ """ import logging -from slither.core.sourceMapping.sourceMapping import SourceMapping -from slither.core.cfg.nodeType import NodeType +from slither.core.source_mapping.source_mapping import SourceMapping from slither.core.variables.variable import Variable -from slither.visitors.expression.expressionPrinter import ExpressionPrinter -from slither.visitors.expression.readVar import ReadVar -from slither.visitors.expression.writeVar import WriteVar +from slither.visitors.expression.expression_printer import ExpressionPrinter +from slither.visitors.expression.read_var import ReadVar +from slither.visitors.expression.write_var import WriteVar -from slither.core.children.childFunction import ChildFunction +from slither.core.children.child_function import ChildFunction -from slither.core.declarations.solidityVariables import SolidityFunction +from slither.core.declarations.solidity_variables import SolidityFunction logger = logging.getLogger("Node") +class NodeType: + + ENTRYPOINT = 0x0 # no expression + + # Node with expression + + EXPRESSION = 0x10 # normal case + RETURN = 0x11 # RETURN may contain an expression + IF = 0x12 + VARIABLE = 0x13 # Declaration of variable + ASSEMBLY = 0x14 + IFLOOP = 0x15 + + # Below the nodes have no expression + # But are used to expression CFG structure + + # Absorbing node + THROW = 0x20 + + # Loop related nodes + BREAK = 0x31 + CONTINUE = 0x32 + + # Only modifier node + PLACEHOLDER = 0x40 + + # Merging nodes + # Unclear if they will be necessary + ENDIF = 0x50 + STARTLOOP = 0x51 + ENDLOOP = 0x52 + + @staticmethod + def str(t): + if t == 0x0: + return 'EntryPoint' + if t == 0x10: + return 'Expressions' + if t == 0x11: + return 'Return' + if t == 0x12: + return 'If' + if t == 0x13: + return 'New variable' + if t == 0x14: + return 'Inline Assembly' + if t == 0x15: + return 'IfLoop' + if t == 0x20: + return 'Throw' + if t == 0x31: + return 'Break' + if t == 0x32: + return 'Continue' + if t == 0x40: + return '_' + if t == 0x50: + return 'EndIf' + if t == 0x51: + return 'BeginLoop' + if t == 0x52: + return 'EndLoop' + return 'Unknown type {}'.format(hex(t)) + def link_nodes(n1, n2): n1.add_son(n2) n2.add_father(n1) diff --git a/slither/core/cfg/nodeType.py b/slither/core/cfg/nodeType.py deleted file mode 100644 index 0ba09db31..000000000 --- a/slither/core/cfg/nodeType.py +++ /dev/null @@ -1,63 +0,0 @@ -class NodeType: - - ENTRYPOINT = 0x0 # no expression - - # Node with expression - - EXPRESSION = 0x10 # normal case - RETURN = 0x11 # RETURN may contain an expression - IF = 0x12 - VARIABLE = 0x13 # Declaration of variable - ASSEMBLY = 0x14 - IFLOOP = 0x15 - - # Below the nodes have no expression - # But are used to expression CFG structure - - # Absorbing node - THROW = 0x20 - - # Loop related nodes - BREAK = 0x31 - CONTINUE = 0x32 - - # Only modifier node - PLACEHOLDER = 0x40 - - # Merging nodes - # Unclear if they will be necessary - ENDIF = 0x50 - STARTLOOP = 0x51 - ENDLOOP = 0x52 - - @staticmethod - def str(t): - if t == 0x0: - return 'EntryPoint' - if t == 0x10: - return 'Expressions' - if t == 0x11: - return 'Return' - if t == 0x12: - return 'If' - if t == 0x13: - return 'New variable' - if t == 0x14: - return 'Inline Assembly' - if t == 0x15: - return 'IfLoop' - if t == 0x20: - return 'Throw' - if t == 0x31: - return 'Break' - if t == 0x32: - return 'Continue' - if t == 0x40: - return '_' - if t == 0x50: - return 'EndIf' - if t == 0x51: - return 'BeginLoop' - if t == 0x52: - return 'EndLoop' - return 'Unknown type {}'.format(hex(t)) diff --git a/slither/core/children/childContract.py b/slither/core/children/child_contract.py similarity index 100% rename from slither/core/children/childContract.py rename to slither/core/children/child_contract.py diff --git a/slither/core/children/childEvent.py b/slither/core/children/child_event.py similarity index 100% rename from slither/core/children/childEvent.py rename to slither/core/children/child_event.py diff --git a/slither/core/children/childFunction.py b/slither/core/children/child_function.py similarity index 100% rename from slither/core/children/childFunction.py rename to slither/core/children/child_function.py diff --git a/slither/core/children/childSlither.py b/slither/core/children/child_slither.py similarity index 100% rename from slither/core/children/childSlither.py rename to slither/core/children/child_slither.py diff --git a/slither/core/children/childStructure.py b/slither/core/children/child_structure.py similarity index 100% rename from slither/core/children/childStructure.py rename to slither/core/children/child_structure.py diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index f866308b0..6faec40b8 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -2,8 +2,8 @@ Contract module """ import logging -from slither.core.children.childSlither import ChildSlither -from slither.core.sourceMapping.sourceMapping import SourceMapping +from slither.core.children.child_slither import ChildSlither +from slither.core.source_mapping.source_mapping import SourceMapping from slither.core.declarations.function import Function logger = logging.getLogger("Contract") diff --git a/slither/core/declarations/enum.py b/slither/core/declarations/enum.py index e112eef2e..c81910c2c 100644 --- a/slither/core/declarations/enum.py +++ b/slither/core/declarations/enum.py @@ -1,5 +1,5 @@ -from slither.core.sourceMapping.sourceMapping import SourceMapping -from slither.core.children.childContract import ChildContract +from slither.core.source_mapping.source_mapping import SourceMapping +from slither.core.children.child_contract import ChildContract class Enum(ChildContract, SourceMapping): def __init__(self, name, canonical_name, values): diff --git a/slither/core/declarations/event.py b/slither/core/declarations/event.py index d008c5b59..0974c4773 100644 --- a/slither/core/declarations/event.py +++ b/slither/core/declarations/event.py @@ -1,5 +1,5 @@ -from slither.core.children.childContract import ChildContract -from slither.core.sourceMapping.sourceMapping import SourceMapping +from slither.core.children.child_contract import ChildContract +from slither.core.source_mapping.source_mapping import SourceMapping class Event(ChildContract, SourceMapping): diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 12b6d4ca3..ae9852f4a 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -3,16 +3,16 @@ """ import logging from itertools import groupby -from slither.core.sourceMapping.sourceMapping import SourceMapping -from slither.core.children.childContract import ChildContract +from slither.core.source_mapping.source_mapping import SourceMapping +from slither.core.children.child_contract import ChildContract -from slither.core.variables.stateVariable import StateVariable +from slither.core.variables.state_variable import StateVariable from slither.core.expressions.identifier import Identifier -from slither.core.expressions.unaryOperation import UnaryOperation -from slither.core.expressions.memberAccess import MemberAccess -from slither.core.expressions.indexAccess import IndexAccess +from slither.core.expressions.unary_operation import UnaryOperation +from slither.core.expressions.member_access import MemberAccess +from slither.core.expressions.index_access import IndexAccess -from slither.core.declarations.solidityVariables import SolidityVariable, SolidityFunction +from slither.core.declarations.solidity_variables import SolidityVariable, SolidityFunction logger = logging.getLogger("Function") diff --git a/slither/core/declarations/import_directive.py b/slither/core/declarations/import_directive.py index 06e4c2b6e..069558257 100644 --- a/slither/core/declarations/import_directive.py +++ b/slither/core/declarations/import_directive.py @@ -1,4 +1,4 @@ -from slither.core.sourceMapping.sourceMapping import SourceMapping +from slither.core.source_mapping.source_mapping import SourceMapping class Import(SourceMapping): diff --git a/slither/core/declarations/pragma_directive.py b/slither/core/declarations/pragma_directive.py index aefc112cf..4a949cd0d 100644 --- a/slither/core/declarations/pragma_directive.py +++ b/slither/core/declarations/pragma_directive.py @@ -1,4 +1,4 @@ -from slither.core.sourceMapping.sourceMapping import SourceMapping +from slither.core.source_mapping.source_mapping import SourceMapping class Pragma(SourceMapping): diff --git a/slither/core/declarations/solidityVariables.py b/slither/core/declarations/solidity_variables.py similarity index 100% rename from slither/core/declarations/solidityVariables.py rename to slither/core/declarations/solidity_variables.py diff --git a/slither/core/declarations/structure.py b/slither/core/declarations/structure.py index 45721df8e..b11fb7e35 100644 --- a/slither/core/declarations/structure.py +++ b/slither/core/declarations/structure.py @@ -1,5 +1,5 @@ -from slither.core.sourceMapping.sourceMapping import SourceMapping -from slither.core.children.childContract import ChildContract +from slither.core.source_mapping.source_mapping import SourceMapping +from slither.core.children.child_contract import ChildContract from slither.core.variables.variable import Variable diff --git a/slither/core/expressions/assignmentOperation.py b/slither/core/expressions/assignment_operation.py similarity index 98% rename from slither/core/expressions/assignmentOperation.py rename to slither/core/expressions/assignment_operation.py index eb9fbc3ec..1e095fd1f 100644 --- a/slither/core/expressions/assignmentOperation.py +++ b/slither/core/expressions/assignment_operation.py @@ -1,5 +1,5 @@ import logging -from slither.core.expressions.expressionTyped import ExpressionTyped +from slither.core.expressions.expression_typed import ExpressionTyped from slither.core.expressions.expression import Expression diff --git a/slither/core/expressions/binaryOperation.py b/slither/core/expressions/binary_operation.py similarity index 98% rename from slither/core/expressions/binaryOperation.py rename to slither/core/expressions/binary_operation.py index 4a7c22aa3..3f2fb6d7b 100644 --- a/slither/core/expressions/binaryOperation.py +++ b/slither/core/expressions/binary_operation.py @@ -1,5 +1,5 @@ import logging -from slither.core.expressions.expressionTyped import ExpressionTyped +from slither.core.expressions.expression_typed import ExpressionTyped from slither.core.expressions.expression import Expression diff --git a/slither/core/expressions/callExpression.py b/slither/core/expressions/call_expression.py similarity index 100% rename from slither/core/expressions/callExpression.py rename to slither/core/expressions/call_expression.py diff --git a/slither/core/expressions/conditionalExpression.py b/slither/core/expressions/conditional_expression.py similarity index 100% rename from slither/core/expressions/conditionalExpression.py rename to slither/core/expressions/conditional_expression.py diff --git a/slither/core/expressions/elementaryTypeNameExpression.py b/slither/core/expressions/elementary_type_name_expression.py similarity index 90% rename from slither/core/expressions/elementaryTypeNameExpression.py rename to slither/core/expressions/elementary_type_name_expression.py index 70f73130f..cf8c07c17 100644 --- a/slither/core/expressions/elementaryTypeNameExpression.py +++ b/slither/core/expressions/elementary_type_name_expression.py @@ -2,7 +2,7 @@ This expression does nothing, if a contract used it, its probably a bug """ from slither.core.expressions.expression import Expression -from slither.core.solidityTypes.type import Type +from slither.core.solidity_types.type import Type class ElementaryTypeNameExpression(Expression): diff --git a/slither/core/expressions/expression.py b/slither/core/expressions/expression.py index 620239403..6e9bd7aba 100644 --- a/slither/core/expressions/expression.py +++ b/slither/core/expressions/expression.py @@ -1,4 +1,4 @@ -from slither.core.sourceMapping.sourceMapping import SourceMapping +from slither.core.source_mapping.source_mapping import SourceMapping class Expression( SourceMapping): diff --git a/slither/core/expressions/expressionTyped.py b/slither/core/expressions/expression_typed.py similarity index 100% rename from slither/core/expressions/expressionTyped.py rename to slither/core/expressions/expression_typed.py diff --git a/slither/core/expressions/identifier.py b/slither/core/expressions/identifier.py index 71e104ad7..cb92d8329 100644 --- a/slither/core/expressions/identifier.py +++ b/slither/core/expressions/identifier.py @@ -1,4 +1,4 @@ -from slither.core.expressions.expressionTyped import ExpressionTyped +from slither.core.expressions.expression_typed import ExpressionTyped class Identifier(ExpressionTyped): diff --git a/slither/core/expressions/indexAccess.py b/slither/core/expressions/index_access.py similarity index 86% rename from slither/core/expressions/indexAccess.py rename to slither/core/expressions/index_access.py index 909bca393..5e02bc6fa 100644 --- a/slither/core/expressions/indexAccess.py +++ b/slither/core/expressions/index_access.py @@ -1,5 +1,5 @@ -from slither.core.expressions.expressionTyped import ExpressionTyped -from slither.core.solidityTypes.type import Type +from slither.core.expressions.expression_typed import ExpressionTyped +from slither.core.solidity_types.type import Type class IndexAccess(ExpressionTyped): diff --git a/slither/core/expressions/memberAccess.py b/slither/core/expressions/member_access.py similarity index 86% rename from slither/core/expressions/memberAccess.py rename to slither/core/expressions/member_access.py index 01b72bbee..72d1238b4 100644 --- a/slither/core/expressions/memberAccess.py +++ b/slither/core/expressions/member_access.py @@ -1,6 +1,6 @@ from slither.core.expressions.expression import Expression -from slither.core.expressions.expressionTyped import ExpressionTyped -from slither.core.solidityTypes.type import Type +from slither.core.expressions.expression_typed import ExpressionTyped +from slither.core.solidity_types.type import Type class MemberAccess(ExpressionTyped): diff --git a/slither/core/expressions/newArray.py b/slither/core/expressions/new_array.py similarity index 92% rename from slither/core/expressions/newArray.py rename to slither/core/expressions/new_array.py index fecdaeee7..ec37f459a 100644 --- a/slither/core/expressions/newArray.py +++ b/slither/core/expressions/new_array.py @@ -1,6 +1,6 @@ import logging from .expression import Expression -from slither.core.solidityTypes.type import Type +from slither.core.solidity_types.type import Type logger = logging.getLogger("NewArray") diff --git a/slither/core/expressions/newContract.py b/slither/core/expressions/new_contract.py similarity index 100% rename from slither/core/expressions/newContract.py rename to slither/core/expressions/new_contract.py diff --git a/slither/core/expressions/newElementaryType.py b/slither/core/expressions/new_elementary_type.py similarity index 84% rename from slither/core/expressions/newElementaryType.py rename to slither/core/expressions/new_elementary_type.py index de9a3a7cb..b099bed9e 100644 --- a/slither/core/expressions/newElementaryType.py +++ b/slither/core/expressions/new_elementary_type.py @@ -1,5 +1,5 @@ from slither.core.expressions.expression import Expression -from slither.core.solidityTypes.elementaryType import ElementaryType +from slither.core.solidity_types.elementary_type import ElementaryType class NewElementaryType(Expression): diff --git a/slither/core/expressions/superCallExpression.py b/slither/core/expressions/super_call_expression.py similarity index 61% rename from slither/core/expressions/superCallExpression.py rename to slither/core/expressions/super_call_expression.py index e2500b34a..420e324f9 100644 --- a/slither/core/expressions/superCallExpression.py +++ b/slither/core/expressions/super_call_expression.py @@ -1,4 +1,4 @@ from slither.core.expressions.expression import Expression -from slither.core.expressions.callExpression import CallExpression +from slither.core.expressions.call_expression import CallExpression class SuperCallExpression(CallExpression): pass diff --git a/slither/core/expressions/superIdentifier.py b/slither/core/expressions/super_identifier.py similarity index 69% rename from slither/core/expressions/superIdentifier.py rename to slither/core/expressions/super_identifier.py index 2f39f467d..33299b9a9 100644 --- a/slither/core/expressions/superIdentifier.py +++ b/slither/core/expressions/super_identifier.py @@ -1,4 +1,4 @@ -from slither.core.expressions.expressionTyped import ExpressionTyped +from slither.core.expressions.expression_typed import ExpressionTyped from slither.core.expressions.identifier import Identifier class SuperIdentifier(Identifier): diff --git a/slither/core/expressions/tupleExpression.py b/slither/core/expressions/tuple_expression.py similarity index 100% rename from slither/core/expressions/tupleExpression.py rename to slither/core/expressions/tuple_expression.py diff --git a/slither/core/expressions/typeConversion.py b/slither/core/expressions/type_conversion.py similarity index 81% rename from slither/core/expressions/typeConversion.py rename to slither/core/expressions/type_conversion.py index 4b0e01bdf..7fe165754 100644 --- a/slither/core/expressions/typeConversion.py +++ b/slither/core/expressions/type_conversion.py @@ -1,6 +1,6 @@ -from slither.core.expressions.expressionTyped import ExpressionTyped +from slither.core.expressions.expression_typed import ExpressionTyped from slither.core.expressions.expression import Expression -from slither.core.solidityTypes.type import Type +from slither.core.solidity_types.type import Type class TypeConversion(ExpressionTyped): diff --git a/slither/core/expressions/unaryOperation.py b/slither/core/expressions/unary_operation.py similarity index 97% rename from slither/core/expressions/unaryOperation.py rename to slither/core/expressions/unary_operation.py index 549bc3e24..dc334b396 100644 --- a/slither/core/expressions/unaryOperation.py +++ b/slither/core/expressions/unary_operation.py @@ -1,7 +1,7 @@ import logging -from slither.core.expressions.expressionTyped import ExpressionTyped +from slither.core.expressions.expression_typed import ExpressionTyped from slither.core.expressions.expression import Expression -from slither.core.solidityTypes.type import Type +from slither.core.solidity_types.type import Type logger = logging.getLogger("UnaryOperation") diff --git a/slither/core/slitherCore.py b/slither/core/slither_core.py similarity index 100% rename from slither/core/slitherCore.py rename to slither/core/slither_core.py diff --git a/slither/core/solidityTypes/type.py b/slither/core/solidityTypes/type.py deleted file mode 100644 index a7daac91f..000000000 --- a/slither/core/solidityTypes/type.py +++ /dev/null @@ -1,3 +0,0 @@ -from slither.core.sourceMapping.sourceMapping import SourceMapping - -class Type(SourceMapping): pass diff --git a/slither/core/solidityTypes/__init__.py b/slither/core/solidity_types/__init__.py similarity index 100% rename from slither/core/solidityTypes/__init__.py rename to slither/core/solidity_types/__init__.py diff --git a/slither/core/solidityTypes/arrayType.py b/slither/core/solidity_types/array_type.py similarity index 94% rename from slither/core/solidityTypes/arrayType.py rename to slither/core/solidity_types/array_type.py index f76cd364a..e6ce14abc 100644 --- a/slither/core/solidityTypes/arrayType.py +++ b/slither/core/solidity_types/array_type.py @@ -1,4 +1,4 @@ -from slither.core.solidityTypes.type import Type +from slither.core.solidity_types.type import Type from slither.core.expressions.expression import Expression class ArrayType(Type): diff --git a/slither/core/solidityTypes/elementaryType.py b/slither/core/solidity_types/elementary_type.py similarity index 97% rename from slither/core/solidityTypes/elementaryType.py rename to slither/core/solidity_types/elementary_type.py index 06955541c..99435a6f5 100644 --- a/slither/core/solidityTypes/elementaryType.py +++ b/slither/core/solidity_types/elementary_type.py @@ -1,6 +1,6 @@ import itertools -from slither.core.solidityTypes.type import Type +from slither.core.solidity_types.type import Type # see https://solidity.readthedocs.io/en/v0.4.24/miscellaneous.html?highlight=grammar diff --git a/slither/core/solidityTypes/functionType.py b/slither/core/solidity_types/function_type.py similarity index 93% rename from slither/core/solidityTypes/functionType.py rename to slither/core/solidity_types/function_type.py index af4421122..ce853c065 100644 --- a/slither/core/solidityTypes/functionType.py +++ b/slither/core/solidity_types/function_type.py @@ -1,5 +1,5 @@ -from slither.core.solidityTypes.type import Type -from slither.core.variables.functionTypeVariable import FunctionTypeVariable +from slither.core.solidity_types.type import Type +from slither.core.variables.function_type_variable import FunctionTypeVariable from slither.core.expressions.expression import Expression class FunctionType(Type): diff --git a/slither/core/solidityTypes/mappingType.py b/slither/core/solidity_types/mapping_type.py similarity index 93% rename from slither/core/solidityTypes/mappingType.py rename to slither/core/solidity_types/mapping_type.py index fb7604267..1c016c8c6 100644 --- a/slither/core/solidityTypes/mappingType.py +++ b/slither/core/solidity_types/mapping_type.py @@ -1,4 +1,4 @@ -from slither.core.solidityTypes.type import Type +from slither.core.solidity_types.type import Type class MappingType(Type): diff --git a/slither/core/solidity_types/type.py b/slither/core/solidity_types/type.py new file mode 100644 index 000000000..1c2794bec --- /dev/null +++ b/slither/core/solidity_types/type.py @@ -0,0 +1,3 @@ +from slither.core.source_mapping.source_mapping import SourceMapping + +class Type(SourceMapping): pass diff --git a/slither/core/solidityTypes/userDefinedType.py b/slither/core/solidity_types/user_defined_type.py similarity index 94% rename from slither/core/solidityTypes/userDefinedType.py rename to slither/core/solidity_types/user_defined_type.py index b97fcf014..d33fae1fe 100644 --- a/slither/core/solidityTypes/userDefinedType.py +++ b/slither/core/solidity_types/user_defined_type.py @@ -1,4 +1,4 @@ -from slither.core.solidityTypes.type import Type +from slither.core.solidity_types.type import Type from slither.core.declarations.structure import Structure from slither.core.declarations.enum import Enum diff --git a/slither/core/sourceMapping/__init__.py b/slither/core/source_mapping/__init__.py similarity index 100% rename from slither/core/sourceMapping/__init__.py rename to slither/core/source_mapping/__init__.py diff --git a/slither/core/sourceMapping/sourceMapping.py b/slither/core/source_mapping/source_mapping.py similarity index 100% rename from slither/core/sourceMapping/sourceMapping.py rename to slither/core/source_mapping/source_mapping.py diff --git a/slither/core/variables/eventVariable.py b/slither/core/variables/event_variable.py similarity index 58% rename from slither/core/variables/eventVariable.py rename to slither/core/variables/event_variable.py index 26daced04..a6ac7c0a3 100644 --- a/slither/core/variables/eventVariable.py +++ b/slither/core/variables/event_variable.py @@ -1,5 +1,5 @@ from .variable import Variable -from slither.core.children.childEvent import ChildEvent +from slither.core.children.child_event import ChildEvent class EventVariable(ChildEvent, Variable): pass diff --git a/slither/core/variables/functionTypeVariable.py b/slither/core/variables/function_type_variable.py similarity index 100% rename from slither/core/variables/functionTypeVariable.py rename to slither/core/variables/function_type_variable.py diff --git a/slither/core/variables/localVariable.py b/slither/core/variables/local_variable.py similarity index 84% rename from slither/core/variables/localVariable.py rename to slither/core/variables/local_variable.py index d4eb60de6..2ac3baad2 100644 --- a/slither/core/variables/localVariable.py +++ b/slither/core/variables/local_variable.py @@ -1,7 +1,7 @@ from .variable import Variable -from slither.core.children.childFunction import ChildFunction -from slither.core.solidityTypes.userDefinedType import UserDefinedType -from slither.core.solidityTypes.arrayType import ArrayType +from slither.core.children.child_function import ChildFunction +from slither.core.solidity_types.user_defined_type import UserDefinedType +from slither.core.solidity_types.array_type import ArrayType from slither.core.declarations.structure import Structure diff --git a/slither/core/variables/localVariableInitFromTuple.py b/slither/core/variables/local_variable_init_from_tuple.py similarity index 88% rename from slither/core/variables/localVariableInitFromTuple.py rename to slither/core/variables/local_variable_init_from_tuple.py index d8a314915..09ca7a361 100644 --- a/slither/core/variables/localVariableInitFromTuple.py +++ b/slither/core/variables/local_variable_init_from_tuple.py @@ -1,4 +1,4 @@ -from slither.core.variables.localVariable import LocalVariable +from slither.core.variables.local_variable import LocalVariable class LocalVariableInitFromTuple(LocalVariable): """ diff --git a/slither/core/variables/stateVariable.py b/slither/core/variables/state_variable.py similarity index 56% rename from slither/core/variables/stateVariable.py rename to slither/core/variables/state_variable.py index c5e7f711b..1f241e799 100644 --- a/slither/core/variables/stateVariable.py +++ b/slither/core/variables/state_variable.py @@ -1,4 +1,4 @@ from .variable import Variable -from slither.core.children.childContract import ChildContract +from slither.core.children.child_contract import ChildContract class StateVariable(ChildContract, Variable): pass diff --git a/slither/core/variables/structureVariable.py b/slither/core/variables/structure_variable.py similarity index 57% rename from slither/core/variables/structureVariable.py rename to slither/core/variables/structure_variable.py index 5f4920e2b..1c0c188e1 100644 --- a/slither/core/variables/structureVariable.py +++ b/slither/core/variables/structure_variable.py @@ -1,5 +1,5 @@ from .variable import Variable -from slither.core.children.childStructure import ChildStructure +from slither.core.children.child_structure import ChildStructure class StructureVariable(ChildStructure, Variable): pass diff --git a/slither/core/variables/variable.py b/slither/core/variables/variable.py index e45bb4522..ecdb07a35 100644 --- a/slither/core/variables/variable.py +++ b/slither/core/variables/variable.py @@ -2,7 +2,7 @@ Variable module """ -from slither.core.sourceMapping.sourceMapping import SourceMapping +from slither.core.source_mapping.source_mapping import SourceMapping class Variable(SourceMapping): diff --git a/slither/detectors/reentrancy/reentrancy.py b/slither/detectors/reentrancy/reentrancy.py index 94f83db55..214820dbb 100644 --- a/slither/detectors/reentrancy/reentrancy.py +++ b/slither/detectors/reentrancy/reentrancy.py @@ -6,11 +6,11 @@ """ from slither.core.declarations.function import Function -from slither.core.declarations.solidityVariables import SolidityFunction -from slither.core.expressions.unaryOperation import UnaryOperation, UnaryOperationType +from slither.core.declarations.solidity_variables import SolidityFunction +from slither.core.expressions.unary_operation import UnaryOperation, UnaryOperationType from slither.core.cfg.node import NodeType from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.visitors.expression.exportValues import ExportValues +from slither.visitors.expression.export_values import ExportValues class Reentrancy(AbstractDetector): diff --git a/slither/detectors/shadowing/shadowingFunctionsDetection.py b/slither/detectors/shadowing/shadowing_functions.py similarity index 100% rename from slither/detectors/shadowing/shadowingFunctionsDetection.py rename to slither/detectors/shadowing/shadowing_functions.py diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index 721316c3c..e70b9bc6e 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -11,7 +11,7 @@ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.visitors.expression.findPush import FindPush +from slither.visitors.expression.find_push import FindPush class UninitializedStateVarsDetection(AbstractDetector): diff --git a/slither/detectors/variables/uninitialized_storage_variables.py b/slither/detectors/variables/uninitialized_storage_variables.py index 8fd0d5da3..4ad74600d 100644 --- a/slither/detectors/variables/uninitialized_storage_variables.py +++ b/slither/detectors/variables/uninitialized_storage_variables.py @@ -7,7 +7,7 @@ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.visitors.expression.findPush import FindPush +from slither.visitors.expression.find_push import FindPush class UninitializedStorageVars(AbstractDetector): diff --git a/slither/printers/inheritance/printerInheritance.py b/slither/printers/inheritance/inheritance.py similarity index 98% rename from slither/printers/inheritance/printerInheritance.py rename to slither/printers/inheritance/inheritance.py index 4867a3843..27f55f21c 100644 --- a/slither/printers/inheritance/printerInheritance.py +++ b/slither/printers/inheritance/inheritance.py @@ -7,7 +7,7 @@ """ from slither.core.declarations.contract import Contract -from slither.detectors.shadowing.shadowingFunctionsDetection import ShadowingFunctionsDetection +from slither.detectors.shadowing.shadowing_functions import ShadowingFunctionsDetection from slither.printers.abstract_printer import AbstractPrinter diff --git a/slither/printers/summary/printerQuickSummary.py b/slither/printers/summary/quick_summary.py similarity index 100% rename from slither/printers/summary/printerQuickSummary.py rename to slither/printers/summary/quick_summary.py diff --git a/slither/printers/summary/printerSummary.py b/slither/printers/summary/summary.py similarity index 100% rename from slither/printers/summary/printerSummary.py rename to slither/printers/summary/summary.py diff --git a/slither/slither.py b/slither/slither.py index 6e4d67b59..30276b93d 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -5,7 +5,7 @@ import sys from slither.detectors.abstract_detector import AbstractDetector from slither.printers.abstract_printer import AbstractPrinter -from .solcParsing.slitherSolc import SlitherSolc +from .solc_parsing.slitherSolc import SlitherSolc from .utils.colors import red logger = logging.getLogger("Slither") diff --git a/slither/solcParsing/variables/eventVariableSolc.py b/slither/solcParsing/variables/eventVariableSolc.py deleted file mode 100644 index b38f6b282..000000000 --- a/slither/solcParsing/variables/eventVariableSolc.py +++ /dev/null @@ -1,5 +0,0 @@ - -from .variableDeclarationSolc import VariableDeclarationSolc -from slither.core.variables.eventVariable import EventVariable - -class EventVariableSolc(VariableDeclarationSolc, EventVariable): pass diff --git a/slither/solcParsing/variables/functionTypeVariableSolc.py b/slither/solcParsing/variables/functionTypeVariableSolc.py deleted file mode 100644 index 9ba8f46e0..000000000 --- a/slither/solcParsing/variables/functionTypeVariableSolc.py +++ /dev/null @@ -1,5 +0,0 @@ - -from slither.solcParsing.variables.variableDeclarationSolc import VariableDeclarationSolc -from slither.core.variables.functionTypeVariable import FunctionTypeVariable - -class FunctionTypeVariableSolc(VariableDeclarationSolc, FunctionTypeVariable): pass diff --git a/slither/solcParsing/variables/stateVariableSolc.py b/slither/solcParsing/variables/stateVariableSolc.py deleted file mode 100644 index 64db8f619..000000000 --- a/slither/solcParsing/variables/stateVariableSolc.py +++ /dev/null @@ -1,5 +0,0 @@ - -from .variableDeclarationSolc import VariableDeclarationSolc -from slither.core.variables.stateVariable import StateVariable - -class StateVariableSolc(VariableDeclarationSolc, StateVariable): pass diff --git a/slither/solcParsing/variables/structureVariableSolc.py b/slither/solcParsing/variables/structureVariableSolc.py deleted file mode 100644 index b7b03b06c..000000000 --- a/slither/solcParsing/variables/structureVariableSolc.py +++ /dev/null @@ -1,5 +0,0 @@ - -from .variableDeclarationSolc import VariableDeclarationSolc -from slither.core.variables.structureVariable import StructureVariable - -class StructureVariableSolc(VariableDeclarationSolc, StructureVariable): pass diff --git a/slither/solcParsing/__init__.py b/slither/solc_parsing/__init__.py similarity index 100% rename from slither/solcParsing/__init__.py rename to slither/solc_parsing/__init__.py diff --git a/slither/solcParsing/cfg/__init__.py b/slither/solc_parsing/cfg/__init__.py similarity index 100% rename from slither/solcParsing/cfg/__init__.py rename to slither/solc_parsing/cfg/__init__.py diff --git a/slither/solcParsing/cfg/nodeSolc.py b/slither/solc_parsing/cfg/node.py similarity index 83% rename from slither/solcParsing/cfg/nodeSolc.py rename to slither/solc_parsing/cfg/node.py index bad721d9b..965a0ca4f 100644 --- a/slither/solcParsing/cfg/nodeSolc.py +++ b/slither/solc_parsing/cfg/node.py @@ -1,15 +1,15 @@ from slither.core.cfg.node import Node -from slither.core.cfg.nodeType import NodeType -from slither.solcParsing.expressions.expressionParsing import parse_expression -from slither.visitors.expression.readVar import ReadVar -from slither.visitors.expression.writeVar import WriteVar +from slither.core.cfg.node import NodeType +from slither.solc_parsing.expressions.expression_parsing import parse_expression +from slither.visitors.expression.read_var import ReadVar +from slither.visitors.expression.write_var import WriteVar from slither.visitors.expression.find_calls import FindCalls -from slither.visitors.expression.exportValues import ExportValues -from slither.core.declarations.solidityVariables import SolidityVariable, SolidityFunction +from slither.visitors.expression.export_values import ExportValues +from slither.core.declarations.solidity_variables import SolidityVariable, SolidityFunction from slither.core.declarations.function import Function -from slither.core.variables.stateVariable import StateVariable +from slither.core.variables.state_variable import StateVariable from slither.core.expressions.identifier import Identifier diff --git a/slither/solcParsing/declarations/__init__.py b/slither/solc_parsing/declarations/__init__.py similarity index 100% rename from slither/solcParsing/declarations/__init__.py rename to slither/solc_parsing/declarations/__init__.py diff --git a/slither/solcParsing/declarations/contractSolc04.py b/slither/solc_parsing/declarations/contract.py similarity index 95% rename from slither/solcParsing/declarations/contractSolc04.py rename to slither/solc_parsing/declarations/contract.py index 9706cd73e..4aa605e9c 100644 --- a/slither/solcParsing/declarations/contractSolc04.py +++ b/slither/solc_parsing/declarations/contract.py @@ -3,14 +3,14 @@ import logging from slither.core.declarations.contract import Contract from slither.core.declarations.enum import Enum -from slither.solcParsing.declarations.structureSolc import StructureSolc -from slither.solcParsing.declarations.eventSolc import EventSolc -from slither.solcParsing.declarations.modifierSolc import ModifierSolc -from slither.solcParsing.declarations.functionSolc import FunctionSolc +from slither.solc_parsing.declarations.structure import StructureSolc +from slither.solc_parsing.declarations.event import EventSolc +from slither.solc_parsing.declarations.modifier import ModifierSolc +from slither.solc_parsing.declarations.function import FunctionSolc -from slither.solcParsing.variables.stateVariableSolc import StateVariableSolc +from slither.solc_parsing.variables.state_variable import StateVariableSolc -from slither.solcParsing.solidityTypes.typeParsing import parse_type +from slither.solc_parsing.solidity_types.type_parsing import parse_type logger = logging.getLogger("ContractSolcParsing") diff --git a/slither/solcParsing/declarations/eventSolc.py b/slither/solc_parsing/declarations/event.py similarity index 90% rename from slither/solcParsing/declarations/eventSolc.py rename to slither/solc_parsing/declarations/event.py index 647e673d5..777a114ad 100644 --- a/slither/solcParsing/declarations/eventSolc.py +++ b/slither/solc_parsing/declarations/event.py @@ -1,7 +1,7 @@ """ Event module """ -from slither.solcParsing.variables.eventVariableSolc import EventVariableSolc +from slither.solc_parsing.variables.event_variable import EventVariableSolc from slither.core.declarations.event import Event class EventSolc(Event): diff --git a/slither/solcParsing/declarations/functionSolc.py b/slither/solc_parsing/declarations/function.py similarity index 97% rename from slither/solcParsing/declarations/functionSolc.py rename to slither/solc_parsing/declarations/function.py index fa67091b6..cea6e6cdc 100644 --- a/slither/solcParsing/declarations/functionSolc.py +++ b/slither/solc_parsing/declarations/function.py @@ -3,17 +3,17 @@ """ import logging from slither.core.declarations.function import Function -from slither.solcParsing.cfg.nodeSolc import NodeSolc -from slither.core.cfg.nodeType import NodeType +from slither.solc_parsing.cfg.node import NodeSolc +from slither.core.cfg.node import NodeType from slither.core.cfg.node import link_nodes -from slither.solcParsing.variables.localVariableSolc import LocalVariableSolc -from slither.solcParsing.variables.localVariableInitFromTupleSolc import LocalVariableInitFromTupleSolc -from slither.solcParsing.variables.variableDeclarationSolc import MultipleVariablesDeclaration +from slither.solc_parsing.variables.local_variable import LocalVariableSolc +from slither.solc_parsing.variables.local_variable_init_from_tuple import LocalVariableInitFromTupleSolc +from slither.solc_parsing.variables.variable_declaration import MultipleVariablesDeclaration -from slither.solcParsing.expressions.expressionParsing import parse_expression +from slither.solc_parsing.expressions.expression_parsing import parse_expression -from slither.visitors.expression.exportValues import ExportValues +from slither.visitors.expression.export_values import ExportValues logger = logging.getLogger("FunctionSolc") class FunctionSolc(Function): diff --git a/slither/solcParsing/declarations/modifierSolc.py b/slither/solc_parsing/declarations/modifier.py similarity index 92% rename from slither/solcParsing/declarations/modifierSolc.py rename to slither/solc_parsing/declarations/modifier.py index 09b57851a..cd7e7d756 100644 --- a/slither/solcParsing/declarations/modifierSolc.py +++ b/slither/solc_parsing/declarations/modifier.py @@ -2,9 +2,9 @@ Event module """ from slither.core.declarations.modifier import Modifier -from slither.solcParsing.declarations.functionSolc import FunctionSolc +from slither.solc_parsing.declarations.function import FunctionSolc -from slither.core.cfg.nodeType import NodeType +from slither.core.cfg.node import NodeType from slither.core.cfg.node import link_nodes class ModifierSolc(Modifier, FunctionSolc): diff --git a/slither/solcParsing/declarations/structureSolc.py b/slither/solc_parsing/declarations/structure.py similarity index 90% rename from slither/solcParsing/declarations/structureSolc.py rename to slither/solc_parsing/declarations/structure.py index 875ded46a..0026f451e 100644 --- a/slither/solcParsing/declarations/structureSolc.py +++ b/slither/solc_parsing/declarations/structure.py @@ -1,7 +1,7 @@ """ Structure module """ -from slither.solcParsing.variables.structureVariableSolc import StructureVariableSolc +from slither.solc_parsing.variables.structure_variable import StructureVariableSolc from slither.core.declarations.structure import Structure class StructureSolc(Structure): diff --git a/slither/solcParsing/expressions/__init__.py b/slither/solc_parsing/expressions/__init__.py similarity index 100% rename from slither/solcParsing/expressions/__init__.py rename to slither/solc_parsing/expressions/__init__.py diff --git a/slither/solcParsing/expressions/expressionParsing.py b/slither/solc_parsing/expressions/expression_parsing.py similarity index 89% rename from slither/solcParsing/expressions/expressionParsing.py rename to slither/solc_parsing/expressions/expression_parsing.py index 5dcb634d4..df48d02e0 100644 --- a/slither/solcParsing/expressions/expressionParsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -1,33 +1,33 @@ import logging import re -from slither.core.expressions.unaryOperation import UnaryOperation, UnaryOperationType -from slither.core.expressions.binaryOperation import BinaryOperation, BinaryOperationType +from slither.core.expressions.unary_operation import UnaryOperation, UnaryOperationType +from slither.core.expressions.binary_operation import BinaryOperation, BinaryOperationType from slither.core.expressions.literal import Literal from slither.core.expressions.identifier import Identifier -from slither.core.expressions.superIdentifier import SuperIdentifier -from slither.core.expressions.indexAccess import IndexAccess -from slither.core.expressions.memberAccess import MemberAccess -from slither.core.expressions.tupleExpression import TupleExpression -from slither.core.expressions.conditionalExpression import ConditionalExpression -from slither.core.expressions.assignmentOperation import AssignmentOperation, AssignmentOperationType -from slither.core.expressions.typeConversion import TypeConversion -from slither.core.expressions.callExpression import CallExpression -from slither.core.expressions.superCallExpression import SuperCallExpression -from slither.core.expressions.newArray import NewArray -from slither.core.expressions.newContract import NewContract -from slither.core.expressions.newElementaryType import NewElementaryType -from slither.core.expressions.elementaryTypeNameExpression import ElementaryTypeNameExpression - -from slither.solcParsing.solidityTypes.typeParsing import parse_type, UnknownType +from slither.core.expressions.super_identifier import SuperIdentifier +from slither.core.expressions.index_access import IndexAccess +from slither.core.expressions.member_access import MemberAccess +from slither.core.expressions.tuple_expression import TupleExpression +from slither.core.expressions.conditional_expression import ConditionalExpression +from slither.core.expressions.assignment_operation import AssignmentOperation, AssignmentOperationType +from slither.core.expressions.type_conversion import TypeConversion +from slither.core.expressions.call_expression import CallExpression +from slither.core.expressions.super_call_expression import SuperCallExpression +from slither.core.expressions.new_array import NewArray +from slither.core.expressions.new_contract import NewContract +from slither.core.expressions.new_elementary_type import NewElementaryType +from slither.core.expressions.elementary_type_name_expression import ElementaryTypeNameExpression + +from slither.solc_parsing.solidity_types.type_parsing import parse_type, UnknownType from slither.core.declarations.contract import Contract from slither.core.declarations.function import Function -from slither.core.declarations.solidityVariables import SOLIDITY_VARIABLES, SOLIDITY_FUNCTIONS, SOLIDITY_VARIABLES_COMPOSED -from slither.core.declarations.solidityVariables import SolidityVariable, SolidityFunction, SolidityVariableComposed, solidity_function_signature +from slither.core.declarations.solidity_variables import SOLIDITY_VARIABLES, SOLIDITY_FUNCTIONS, SOLIDITY_VARIABLES_COMPOSED +from slither.core.declarations.solidity_variables import SolidityVariable, SolidityFunction, SolidityVariableComposed, solidity_function_signature -from slither.core.solidityTypes.elementaryType import ElementaryType -from slither.core.solidityTypes.functionType import FunctionType +from slither.core.solidity_types.elementary_type import ElementaryType +from slither.core.solidity_types.function_type import FunctionType logger = logging.getLogger("ExpressionParsing") diff --git a/slither/solcParsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py similarity index 98% rename from slither/solcParsing/slitherSolc.py rename to slither/solc_parsing/slitherSolc.py index ecc2f809b..2c5ce28c4 100644 --- a/slither/solcParsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -4,8 +4,8 @@ import logging logger = logging.getLogger("SlitherSolcParsing") -from slither.solcParsing.declarations.contractSolc04 import ContractSolc04 -from slither.core.slitherCore import Slither +from slither.solc_parsing.declarations.contract import ContractSolc04 +from slither.core.slither_core import Slither from slither.core.declarations.pragma_directive import Pragma from slither.core.declarations.import_directive import Import diff --git a/slither/solcParsing/solidityTypes/__init__.py b/slither/solc_parsing/solidity_types/__init__.py similarity index 100% rename from slither/solcParsing/solidityTypes/__init__.py rename to slither/solc_parsing/solidity_types/__init__.py diff --git a/slither/solcParsing/solidityTypes/typeParsing.py b/slither/solc_parsing/solidity_types/type_parsing.py similarity index 91% rename from slither/solcParsing/solidityTypes/typeParsing.py rename to slither/solc_parsing/solidity_types/type_parsing.py index 27224ec90..2bcb27380 100644 --- a/slither/solcParsing/solidityTypes/typeParsing.py +++ b/slither/solc_parsing/solidity_types/type_parsing.py @@ -1,12 +1,12 @@ import logging -from slither.core.solidityTypes.elementaryType import ElementaryType, ElementaryTypeName -from slither.core.solidityTypes.userDefinedType import UserDefinedType -from slither.core.solidityTypes.arrayType import ArrayType -from slither.core.solidityTypes.mappingType import MappingType -from slither.core.solidityTypes.functionType import FunctionType +from slither.core.solidity_types.elementary_type import ElementaryType, ElementaryTypeName +from slither.core.solidity_types.user_defined_type import UserDefinedType +from slither.core.solidity_types.array_type import ArrayType +from slither.core.solidity_types.mapping_type import MappingType +from slither.core.solidity_types.function_type import FunctionType -from slither.core.variables.functionTypeVariable import FunctionTypeVariable +from slither.core.variables.function_type_variable import FunctionTypeVariable from slither.core.declarations.contract import Contract from slither.core.declarations.function import Function @@ -116,8 +116,8 @@ def _find_from_type_name(name, contract, contracts, structures, enums): def parse_type(t, caller_context): # local import to avoid circular dependency - from slither.solcParsing.expressions.expressionParsing import parse_expression - from slither.solcParsing.variables.functionTypeVariableSolc import FunctionTypeVariableSolc + from slither.solc_parsing.expressions.expression_parsing import parse_expression + from slither.solc_parsing.variables.function_type_variable import FunctionTypeVariableSolc if isinstance(caller_context, Contract): contract = caller_context diff --git a/slither/solcParsing/variables/__init__.py b/slither/solc_parsing/variables/__init__.py similarity index 100% rename from slither/solcParsing/variables/__init__.py rename to slither/solc_parsing/variables/__init__.py diff --git a/slither/solc_parsing/variables/event_variable.py b/slither/solc_parsing/variables/event_variable.py new file mode 100644 index 000000000..794f19fe1 --- /dev/null +++ b/slither/solc_parsing/variables/event_variable.py @@ -0,0 +1,5 @@ + +from .variable_declaration import VariableDeclarationSolc +from slither.core.variables.event_variable import EventVariable + +class EventVariableSolc(VariableDeclarationSolc, EventVariable): pass diff --git a/slither/solc_parsing/variables/function_type_variable.py b/slither/solc_parsing/variables/function_type_variable.py new file mode 100644 index 000000000..50af5145c --- /dev/null +++ b/slither/solc_parsing/variables/function_type_variable.py @@ -0,0 +1,5 @@ + +from slither.solc_parsing.variables.variable_declaration import VariableDeclarationSolc +from slither.core.variables.function_type_variable import FunctionTypeVariable + +class FunctionTypeVariableSolc(VariableDeclarationSolc, FunctionTypeVariable): pass diff --git a/slither/solcParsing/variables/localVariableSolc.py b/slither/solc_parsing/variables/local_variable.py similarity index 84% rename from slither/solcParsing/variables/localVariableSolc.py rename to slither/solc_parsing/variables/local_variable.py index 8667a41e8..1df53261b 100644 --- a/slither/solcParsing/variables/localVariableSolc.py +++ b/slither/solc_parsing/variables/local_variable.py @@ -1,6 +1,6 @@ -from .variableDeclarationSolc import VariableDeclarationSolc -from slither.core.variables.localVariable import LocalVariable +from .variable_declaration import VariableDeclarationSolc +from slither.core.variables.local_variable import LocalVariable class LocalVariableSolc(VariableDeclarationSolc, LocalVariable): diff --git a/slither/solcParsing/variables/localVariableInitFromTupleSolc.py b/slither/solc_parsing/variables/local_variable_init_from_tuple.py similarity index 60% rename from slither/solcParsing/variables/localVariableInitFromTupleSolc.py rename to slither/solc_parsing/variables/local_variable_init_from_tuple.py index 5a73c4ee7..f219c3847 100644 --- a/slither/solcParsing/variables/localVariableInitFromTupleSolc.py +++ b/slither/solc_parsing/variables/local_variable_init_from_tuple.py @@ -1,6 +1,6 @@ -from .variableDeclarationSolc import VariableDeclarationSolc -from slither.core.variables.localVariableInitFromTuple import LocalVariableInitFromTuple +from .variable_declaration import VariableDeclarationSolc +from slither.core.variables.local_variable_init_from_tuple import LocalVariableInitFromTuple class LocalVariableInitFromTupleSolc(VariableDeclarationSolc, LocalVariableInitFromTuple): diff --git a/slither/solc_parsing/variables/state_variable.py b/slither/solc_parsing/variables/state_variable.py new file mode 100644 index 000000000..242f441ca --- /dev/null +++ b/slither/solc_parsing/variables/state_variable.py @@ -0,0 +1,5 @@ + +from .variable_declaration import VariableDeclarationSolc +from slither.core.variables.state_variable import StateVariable + +class StateVariableSolc(VariableDeclarationSolc, StateVariable): pass diff --git a/slither/solc_parsing/variables/structure_variable.py b/slither/solc_parsing/variables/structure_variable.py new file mode 100644 index 000000000..f0823f67d --- /dev/null +++ b/slither/solc_parsing/variables/structure_variable.py @@ -0,0 +1,5 @@ + +from .variable_declaration import VariableDeclarationSolc +from slither.core.variables.structure_variable import StructureVariable + +class StructureVariableSolc(VariableDeclarationSolc, StructureVariable): pass diff --git a/slither/solcParsing/variables/variableDeclarationSolc.py b/slither/solc_parsing/variables/variable_declaration.py similarity index 93% rename from slither/solcParsing/variables/variableDeclarationSolc.py rename to slither/solc_parsing/variables/variable_declaration.py index d8868e9a2..3e9601a6a 100644 --- a/slither/solcParsing/variables/variableDeclarationSolc.py +++ b/slither/solc_parsing/variables/variable_declaration.py @@ -1,11 +1,11 @@ import logging -from slither.solcParsing.expressions.expressionParsing import parse_expression +from slither.solc_parsing.expressions.expression_parsing import parse_expression from slither.core.variables.variable import Variable -from slither.solcParsing.solidityTypes.typeParsing import parse_type, UnknownType +from slither.solc_parsing.solidity_types.type_parsing import parse_type, UnknownType -from slither.core.solidityTypes.elementaryType import ElementaryType, NonElementaryType +from slither.core.solidity_types.elementary_type import ElementaryType, NonElementaryType logger = logging.getLogger("VariableDeclarationSolcParsing") diff --git a/slither/utils/utils.py b/slither/utils/utils.py deleted file mode 100644 index 34223d6f0..000000000 --- a/slither/utils/utils.py +++ /dev/null @@ -1,61 +0,0 @@ -""" - Utils module -""" -import re - -def find_call(call, contract, contracts): - """ Find call in the contract - - Do not respect c3 lineralization - Args: - call: call the find - contract: current contract - contracts: list of contracts - Return: - function: returns the function called (or None if the funciton was not found) -""" - if '.call.value' in str(call): - return None - if '.call.gas.value' in str(call): - return None - for f in contract.functions + contract.modifiers: - if call == f.name: - return f - for father in contract.inheritance: - fatherContract = next((x for x in contracts if x.name == father), None) - if fatherContract: - for f in fatherContract.functions: - if call == f.name: - return f - call_found = find_call(call, fatherContract, contracts) - if call_found: - return call_found - return None - -def convert_offset(offset, sourceUnits): - ''' - Convert a text offset to a real offset - see https://solidity.readthedocs.io/en/develop/miscellaneous.html#source-mappings - - To handle solc <0.3.6: - - If the matching is not found, returns an empty dict - - If the matching is found, but the filename is not knwon, return only start/length - Args: - offset (str): format: '..:..:..' - sourceUnits (dict): map int -> filename - Returns: - (dict): {'start':0, 'length':0, 'filename': 'file.sol'} - ''' - position = re.findall('([0-9]*):([0-9]*):([-]?[0-9]*)', offset) - if len(position) != 1: - return {} - - s, l, f = position[0] - s = int(s) - l = int(l) - f = int(f) - - if f not in sourceUnits: - return {'start':s, 'length':l} - filename = sourceUnits[f] - return {'start':s, 'length':l, 'filename': filename} diff --git a/slither/visitors/expression/exportValues.py b/slither/visitors/expression/export_values.py similarity index 97% rename from slither/visitors/expression/exportValues.py rename to slither/visitors/expression/export_values.py index 18ed9ed67..15afb4dfa 100644 --- a/slither/visitors/expression/exportValues.py +++ b/slither/visitors/expression/export_values.py @@ -1,7 +1,7 @@ from slither.visitors.expression.expression import ExpressionVisitor -from slither.core.expressions.assignmentOperation import AssignmentOperationType +from slither.core.expressions.assignment_operation import AssignmentOperationType from slither.core.variables.variable import Variable diff --git a/slither/visitors/expression/expression.py b/slither/visitors/expression/expression.py index b0486ba5c..de3453ef7 100644 --- a/slither/visitors/expression/expression.py +++ b/slither/visitors/expression/expression.py @@ -1,20 +1,20 @@ import logging -from slither.core.expressions.assignmentOperation import AssignmentOperation -from slither.core.expressions.binaryOperation import BinaryOperation -from slither.core.expressions.callExpression import CallExpression -from slither.core.expressions.conditionalExpression import ConditionalExpression -from slither.core.expressions.elementaryTypeNameExpression import ElementaryTypeNameExpression +from slither.core.expressions.assignment_operation import AssignmentOperation +from slither.core.expressions.binary_operation import BinaryOperation +from slither.core.expressions.call_expression import CallExpression +from slither.core.expressions.conditional_expression import ConditionalExpression +from slither.core.expressions.elementary_type_name_expression import ElementaryTypeNameExpression from slither.core.expressions.identifier import Identifier -from slither.core.expressions.indexAccess import IndexAccess +from slither.core.expressions.index_access import IndexAccess from slither.core.expressions.literal import Literal -from slither.core.expressions.memberAccess import MemberAccess -from slither.core.expressions.newArray import NewArray -from slither.core.expressions.newContract import NewContract -from slither.core.expressions.newElementaryType import NewElementaryType -from slither.core.expressions.tupleExpression import TupleExpression -from slither.core.expressions.typeConversion import TypeConversion -from slither.core.expressions.unaryOperation import UnaryOperation +from slither.core.expressions.member_access import MemberAccess +from slither.core.expressions.new_array import NewArray +from slither.core.expressions.new_contract import NewContract +from slither.core.expressions.new_elementary_type import NewElementaryType +from slither.core.expressions.tuple_expression import TupleExpression +from slither.core.expressions.type_conversion import TypeConversion +from slither.core.expressions.unary_operation import UnaryOperation logger = logging.getLogger("ExpressionVisitor") diff --git a/slither/visitors/expression/expressionPrinter.py b/slither/visitors/expression/expression_printer.py similarity index 100% rename from slither/visitors/expression/expressionPrinter.py rename to slither/visitors/expression/expression_printer.py diff --git a/slither/visitors/expression/find_calls.py b/slither/visitors/expression/find_calls.py index 4f89ef481..6af072329 100644 --- a/slither/visitors/expression/find_calls.py +++ b/slither/visitors/expression/find_calls.py @@ -1,7 +1,7 @@ from slither.visitors.expression.expression import ExpressionVisitor -from slither.core.expressions.assignmentOperation import AssignmentOperationType +from slither.core.expressions.assignment_operation import AssignmentOperationType from slither.core.variables.variable import Variable diff --git a/slither/visitors/expression/findPush.py b/slither/visitors/expression/find_push.py similarity index 95% rename from slither/visitors/expression/findPush.py rename to slither/visitors/expression/find_push.py index 9194b3e4e..76e0d7a56 100644 --- a/slither/visitors/expression/findPush.py +++ b/slither/visitors/expression/find_push.py @@ -1,8 +1,8 @@ from slither.visitors.expression.expression import ExpressionVisitor from slither.core.expressions.identifier import Identifier -from slither.core.expressions.indexAccess import IndexAccess +from slither.core.expressions.index_access import IndexAccess -from slither.visitors.expression.rightValue import RightValue +from slither.visitors.expression.right_value import RightValue key = 'FindPush' diff --git a/slither/visitors/expression/leftValue.py b/slither/visitors/expression/left_value.py similarity index 97% rename from slither/visitors/expression/leftValue.py rename to slither/visitors/expression/left_value.py index cf679859a..c23c3c06c 100644 --- a/slither/visitors/expression/leftValue.py +++ b/slither/visitors/expression/left_value.py @@ -2,7 +2,7 @@ from slither.visitors.expression.expression import ExpressionVisitor -from slither.core.expressions.assignmentOperation import AssignmentOperationType +from slither.core.expressions.assignment_operation import AssignmentOperationType from slither.core.variables.variable import Variable diff --git a/slither/visitors/expression/readVar.py b/slither/visitors/expression/read_var.py similarity index 95% rename from slither/visitors/expression/readVar.py rename to slither/visitors/expression/read_var.py index 4a10eece7..ae4882d84 100644 --- a/slither/visitors/expression/readVar.py +++ b/slither/visitors/expression/read_var.py @@ -1,10 +1,10 @@ from slither.visitors.expression.expression import ExpressionVisitor -from slither.core.expressions.assignmentOperation import AssignmentOperationType +from slither.core.expressions.assignment_operation import AssignmentOperationType from slither.core.variables.variable import Variable -from slither.core.declarations.solidityVariables import SolidityVariable +from slither.core.declarations.solidity_variables import SolidityVariable key = 'ReadVar' diff --git a/slither/visitors/expression/rightValue.py b/slither/visitors/expression/right_value.py similarity index 97% rename from slither/visitors/expression/rightValue.py rename to slither/visitors/expression/right_value.py index 5f34e45db..718ed392a 100644 --- a/slither/visitors/expression/rightValue.py +++ b/slither/visitors/expression/right_value.py @@ -5,7 +5,7 @@ from slither.visitors.expression.expression import ExpressionVisitor -from slither.core.expressions.assignmentOperation import AssignmentOperationType +from slither.core.expressions.assignment_operation import AssignmentOperationType from slither.core.expressions.expression import Expression from slither.core.variables.variable import Variable diff --git a/slither/visitors/expression/writeVar.py b/slither/visitors/expression/write_var.py similarity index 95% rename from slither/visitors/expression/writeVar.py rename to slither/visitors/expression/write_var.py index 5edb3c3b7..0368b4ad1 100644 --- a/slither/visitors/expression/writeVar.py +++ b/slither/visitors/expression/write_var.py @@ -1,13 +1,13 @@ from slither.visitors.expression.expression import ExpressionVisitor -from slither.core.expressions.assignmentOperation import AssignmentOperation +from slither.core.expressions.assignment_operation import AssignmentOperation from slither.core.variables.variable import Variable -from slither.core.expressions.memberAccess import MemberAccess +from slither.core.expressions.member_access import MemberAccess -from slither.core.expressions.indexAccess import IndexAccess +from slither.core.expressions.index_access import IndexAccess key = 'WriteVar' diff --git a/examples/bugs/backdoor.sol b/tests/backdoor.sol similarity index 100% rename from examples/bugs/backdoor.sol rename to tests/backdoor.sol diff --git a/examples/bugs/old_solc.sol b/tests/old_solc.sol similarity index 100% rename from examples/bugs/old_solc.sol rename to tests/old_solc.sol diff --git a/examples/bugs/old_solc.sol.json b/tests/old_solc.sol.json similarity index 100% rename from examples/bugs/old_solc.sol.json rename to tests/old_solc.sol.json diff --git a/examples/bugs/pragma.0.4.23.sol b/tests/pragma.0.4.23.sol similarity index 100% rename from examples/bugs/pragma.0.4.23.sol rename to tests/pragma.0.4.23.sol diff --git a/examples/bugs/pragma.0.4.24.sol b/tests/pragma.0.4.24.sol similarity index 100% rename from examples/bugs/pragma.0.4.24.sol rename to tests/pragma.0.4.24.sol diff --git a/examples/bugs/reentrancy.sol b/tests/reentrancy.sol similarity index 100% rename from examples/bugs/reentrancy.sol rename to tests/reentrancy.sol diff --git a/examples/bugs/uninitialized.sol b/tests/uninitialized.sol similarity index 100% rename from examples/bugs/uninitialized.sol rename to tests/uninitialized.sol diff --git a/examples/bugs/uninitialized_storage_pointer.sol b/tests/uninitialized_storage_pointer.sol similarity index 100% rename from examples/bugs/uninitialized_storage_pointer.sol rename to tests/uninitialized_storage_pointer.sol From 14643f5ecaea7b93787a00fe107517544a2a3a09 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 14 Sep 2018 11:19:54 +0100 Subject: [PATCH 049/308] Update README example Clean code in solidity_variables --- README.md | 4 ++-- slither/core/declarations/solidity_variables.py | 13 ++++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a77d9429f..651519b89 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,9 @@ $ slither file.sol For example: ``` -$ slither examples/bugs/uninitialized.sol +$ slither tests/uninitialized.sol [..] -INFO:Detectors:Uninitialized state variables in examples/bugs/uninitialized.sol, Contract: Uninitialized, Vars: destination, Used in ['transfer'] +INFO:Detectors:Uninitialized state variables in tests/uninitialized.sol, Contract: Uninitialized, Vars: destination, Used in ['transfer'] [..] ``` diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py index 81c840dec..c60a9f14d 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -3,7 +3,18 @@ SOLIDITY_VARIABLES = ["block", "msg", "now", "tx", "this", "super", 'abi'] -SOLIDITY_VARIABLES_COMPOSED = ["block.coinbase", "block.difficulty", "block.gaslimit", "block.number", "block.timestamp", "msg.data", "msg.gas", "msg.sender", "msg.sig", "msg.value", "tx.gasprice", "tx.origin"] +SOLIDITY_VARIABLES_COMPOSED = ["block.coinbase", + "block.difficulty", + "block.gaslimit", + "block.number", + "block.timestamp", + "msg.data", + "msg.gas", + "msg.sender", + "msg.sig", + "msg.value", + "tx.gasprice", + "tx.origin"] SOLIDITY_FUNCTIONS = {"gasleft()":['uint256'], From 5930e4ca6bca16c51a59a12af420982ca672c904 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 14 Sep 2018 11:54:37 +0100 Subject: [PATCH 050/308] Add plugin skeleton --- plugin_example/README.md | 19 +++++++++++++++++++ plugin_example/setup.py | 17 +++++++++++++++++ plugin_example/slither_my_plugin/__init__.py | 8 ++++++++ .../slither_my_plugin/detectors/example.py | 18 ++++++++++++++++++ 4 files changed, 62 insertions(+) create mode 100644 plugin_example/README.md create mode 100644 plugin_example/setup.py create mode 100644 plugin_example/slither_my_plugin/__init__.py create mode 100644 plugin_example/slither_my_plugin/detectors/example.py diff --git a/plugin_example/README.md b/plugin_example/README.md new file mode 100644 index 000000000..fc711f5d1 --- /dev/null +++ b/plugin_example/README.md @@ -0,0 +1,19 @@ +# Slither, Plugin Example + +This repo contains an example of plugin for Slither. + +See the [detector documentation](https://github.com/trailofbits/slither/wiki/Adding-a-new-detector). + +## Architecture + +- `setup.py`: Contain the plugin information +- `slither_my_plugin/__init__.py`: Contain `make_plugin()`. The function must return the list of new detectors and printers +- `slither_my_plugin/detectors/example.py`: Detector plugin skeleton. + +Once these files are updated with your plugin, you can install it: +``` +python setup.py develop +``` + +We recommend to use a Python virtual environment (for example: [virtualenvwrapper](https://virtualenvwrapper.readthedocs.io/en/latest/)). + diff --git a/plugin_example/setup.py b/plugin_example/setup.py new file mode 100644 index 000000000..2890e224f --- /dev/null +++ b/plugin_example/setup.py @@ -0,0 +1,17 @@ +from setuptools import setup, find_packages + +setup( + name='slither-my-plugins', + description='This is an example of detectors and printers to Slither.', + url='https://github.com/trailofbits/slither-plugins', + author='Trail of Bits', + version='0.0', + packages=find_packages(), + python_requires='>=3.6', + install_requires=[ + 'slither-analyzer==0.1' + ], + entry_points={ + 'slither_analyzer.plugin': 'slither my-plugin=slither_my_plugin:make_plugin', + } +) diff --git a/plugin_example/slither_my_plugin/__init__.py b/plugin_example/slither_my_plugin/__init__.py new file mode 100644 index 000000000..eabdb147e --- /dev/null +++ b/plugin_example/slither_my_plugin/__init__.py @@ -0,0 +1,8 @@ +from slither_my_plugin.detectors.example import Example + + +def make_plugin(): + plugin_detectors = [Example] + plugin_printers = [] + + return plugin_detectors, plugin_printers diff --git a/plugin_example/slither_my_plugin/detectors/example.py b/plugin_example/slither_my_plugin/detectors/example.py new file mode 100644 index 000000000..20e249e28 --- /dev/null +++ b/plugin_example/slither_my_plugin/detectors/example.py @@ -0,0 +1,18 @@ + +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification + + +class Example(AbstractDetector): + """ + Documentation + """ + + ARGUMENT = 'mydetector' # slither will launch the detector with slither.py --mydetector + HELP = 'Help printed by slither' + CLASSIFICATION = DetectorClassification.HIGH + + def detect(self): + + self.logger('Nothing to detect!') + + return [] From e6ed6a43c188753ca49d3212a621d94847eb1b14 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 14 Sep 2018 13:43:55 +0100 Subject: [PATCH 051/308] Update documentation --- docs/PRINTERS.md | 85 ------- docs/imgs/DAO.svg | 221 ------------------ docs/imgs/quick-summary.png | Bin 83396 -> 0 bytes examples/printers/inheritances.sol.dot | 4 +- examples/printers/inheritances.sol.png | Bin 0 -> 37297 bytes examples/printers/quick_summary.sol | 13 ++ examples/printers/quick_summary.sol.png | Bin 0 -> 12694 bytes .../uninitialized_state_variables.py | 4 +- tests/uninitialized.sol | 6 +- 9 files changed, 20 insertions(+), 313 deletions(-) delete mode 100644 docs/PRINTERS.md delete mode 100644 docs/imgs/DAO.svg delete mode 100644 docs/imgs/quick-summary.png create mode 100644 examples/printers/inheritances.sol.png create mode 100644 examples/printers/quick_summary.sol create mode 100644 examples/printers/quick_summary.sol.png diff --git a/docs/PRINTERS.md b/docs/PRINTERS.md deleted file mode 100644 index 749b3bdba..000000000 --- a/docs/PRINTERS.md +++ /dev/null @@ -1,85 +0,0 @@ -# Slither Printers - -Slither allows printing contracts information through its printers. - -## Quick Summary -`slither file.sol --printer-quick-summary` - -Output a quick summary of the contract. -Example: -``` -$ slither vulns/0x01293cd77f68341635814c35299ed30ae212789e.sol --printer-quick-summary -``` - - -## Summary -`slither file.sol --printer-summary` - -Output a summary of the contract showing for each function: -- What are the visibility and the modifiers -- What are the state variables read or written -- What are the calls - -Example: -``` -$ slither examples/bugs/backdoor.sol --printer-summary -``` -``` -[...] - -Contract C -Contract vars: [] -Inheritances:: [] - -+-----------------+------------+-----------+----------------+-------+---------------------------+----------------+ -| Function | Visibility | Modifiers | Read | Write | Internal Calls | External Calls | -+-----------------+------------+-----------+----------------+-------+---------------------------+----------------+ -| i_am_a_backdoor | public | [] | ['msg.sender'] | [] | ['selfdestruct(address)'] | [] | -+-----------------+------------+-----------+----------------+-------+---------------------------+----------------+ - -+-----------+------------+------+-------+----------------+----------------+ -| Modifiers | Visibility | Read | Write | Internal Calls | External Calls | -+-----------+------------+------+-------+----------------+----------------+ -+-----------+------------+------+-------+----------------+----------------+ -``` - -## Inheritance Graph -`slither file.sol --printer-inheritance` - -Output a graph showing the inheritance interaction between the contracts. -Example: -``` -$ slither examples/DAO.sol --printer-inheritance -[...] -INFO:PrinterInheritance:Inheritance Graph: examples/DAO.sol.dot -``` - -The output format is [dot](https://www.graphviz.org/) and can be converted to svg using: -``` -dot examples/DAO.sol.dot -Tsvg -o examples/DAO.svg -``` - -Functions in orange override a parent's functions. If a variable points to another contract, the contract type is written in blue. - - - - -## Variables written and authorization -`slither file.sol --printer-vars-and-auth` - -Print the variables written and the check on `msg.sender` of each function. -``` -... -INFO:Printers: -Contract MyNewBank -+----------+-------------------------+--------------------------+ -| Function | State variables written | Conditions on msg.sender | -+----------+-------------------------+--------------------------+ -| kill | [] | ['msg.sender != owner'] | -| withdraw | [] | ['msg.sender != owner'] | -| init | [u'owner'] | [] | -| owned | [u'owner'] | [] | -| fallback | [u'deposits'] | [] | -+----------+-------------------------+--------------------------+ -``` - diff --git a/docs/imgs/DAO.svg b/docs/imgs/DAO.svg deleted file mode 100644 index 47ea043bb..000000000 --- a/docs/imgs/DAO.svg +++ /dev/null @@ -1,221 +0,0 @@ - - - - - - -%3 - - -TokenInterface - -TokenInterface -Functions: -    balanceOf -    transfer -    transferFrom -    approve -    allowance -Public Variables: -    totalSupply -Private Variables: -    balances -    allowed - - -Token - -Token -Functions: -    balanceOf -    transfer -    transferFrom -    approve -    allowance -Modifiers: -    noEther - - -Token->TokenInterface - - - - -ManagedAccountInterface - -ManagedAccountInterface -Functions: -    payOut -Public Variables: -    owner -    payOwnerOnly -    accumulatedInput - - -ManagedAccount - -ManagedAccount -Functions: -    fallback -    payOut - - -ManagedAccount->ManagedAccountInterface - - - - -TokenCreationInterface - -TokenCreationInterface -Functions: -    createTokenProxy -    refund -    divisor -Public Variables: -    closingTime -    minTokensToCreate -    isFueled -    privateCreation -    extraBalance - (ManagedAccount) -Private Variables: -    weiGiven - - -TokenCreation - -TokenCreation -Functions: -    createTokenProxy -    refund -    divisor - - -TokenCreation->Token - - - - -TokenCreation->TokenCreationInterface - - - - -DAOInterface - -DAOInterface -Functions: -    fallback -    receiveEther -    newProposal -    checkProposalCode -    vote -    executeProposal -    splitDAO -    newContract -    changeAllowedRecipients -    changeProposalDeposit -    retrieveDAOReward -    getMyReward -    withdrawRewardFor -    transferWithoutReward -    transferFromWithoutReward -    halveMinQuorum -    numberOfProposals -    getNewDAOAddress -    isBlocked -    unblockMe -Modifiers: -    onlyTokenholders -Public Variables: -    proposals -    minQuorumDivisor -    lastTimeMinQuorumMet -    curator -    allowedRecipients -    rewardToken -    totalRewardToken -    rewardAccount - (ManagedAccount) -    DAOrewardAccount - (ManagedAccount) -    DAOpaidOut -    paidOut -    blocked -    proposalDeposit -    daoCreator - (DAO_Creator) -Private Variables: -    creationGracePeriod -    minProposalDebatePeriod -    minSplitDebatePeriod -    splitExecutionPeriod -    quorumHalvingPeriod -    executeProposalPeriod -    maxDepositDivisor -    sumOfProposalDeposits - - -DAO - -DAO -Functions: -    fallback -    receiveEther -    newProposal -    checkProposalCode -    vote -    executeProposal -    closeProposal -    splitDAO -    newContract -    retrieveDAOReward -    getMyReward -    withdrawRewardFor -    transfer -    transferWithoutReward -    transferFrom -    transferFromWithoutReward -    transferPaidOut -    changeProposalDeposit -    changeAllowedRecipients -    isRecipientAllowed -    actualBalance -    minQuorum -    halveMinQuorum -    createNewDAO -    numberOfProposals -    getNewDAOAddress -    isBlocked -    unblockMe -Modifiers: -    onlyTokenholders - - -DAO->Token - - - - -DAO->TokenCreation - - - - -DAO->DAOInterface - - - - -DAO_Creator - -DAO_Creator -Functions: -    createDAO - - - diff --git a/docs/imgs/quick-summary.png b/docs/imgs/quick-summary.png deleted file mode 100644 index ed978e1924c63df56bea0b30bcd49884a21073f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 83396 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 diff --git a/examples/printers/inheritances.sol.dot b/examples/printers/inheritances.sol.dot index 8a22220e0..8ed2c3345 100644 --- a/examples/printers/inheritances.sol.dot +++ b/examples/printers/inheritances.sol.dot @@ -1,7 +1,7 @@ digraph{ +Contract1[shape="box"label=<
Contract1
Public Functions:
myfunc()
Private Variables:
myvar
>]; +Contract2[shape="box"label=<
Contract2
Public Functions:
myfunc2()
Private Functions:
privatefunc()
Public Variables:
myvar2
>]; Contract3 -> Contract2; Contract3 -> Contract1; Contract3[shape="box"label=<
Contract3
Public Functions:
myfunc()
Public Variables:
myvar2
Private Variables:
myvar
>]; -Contract2[shape="box"label=<
Contract2
Public Functions:
myfunc2()
Private Functions:
privatefunc()
Public Variables:
myvar2
>]; -Contract1[shape="box"label=<
Contract1
Public Functions:
myfunc()
Private Variables:
myvar
>]; } \ No newline at end of file diff --git a/examples/printers/inheritances.sol.png b/examples/printers/inheritances.sol.png new file mode 100644 index 0000000000000000000000000000000000000000..bed1f2a80faad04748fe8dea75f9ca0ecf8a0cd4 GIT binary patch literal 37297 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 z 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ⅆl{J!*E_r$cI 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 literal 0 HcmV?d00001 diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index e70b9bc6e..1e2b00458 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -58,8 +58,8 @@ class UninitializedStateVarsDetection(AbstractDetector): for c in self.slither.contracts_derived: ret = self.detect_uninitialized(c) for variable, functions in ret: - info = "Uninitialized state variables in %s, " % self.filename + \ - "Contract: %s, Vars: %s, Used in %s" % (c.name, + info = "Uninitialized state variable in %s, " % self.filename + \ + "Contract: %s, Variable: %s, Used in %s" % (c.name, str(variable), [str(f) for f in functions]) self.log(info) diff --git a/tests/uninitialized.sol b/tests/uninitialized.sol index 89126a4f3..ff3f1addc 100644 --- a/tests/uninitialized.sol +++ b/tests/uninitialized.sol @@ -1,10 +1,10 @@ -contract Uninitialized{ +pragma solidity ^0.4.24; +contract Uninitialized{ address destination; - function transfer() payable{ - + function transfer() payable public{ destination.transfer(msg.value); } From 65cb37907cb047f34230a1a6dd3199b04ce506fd Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 14 Sep 2018 13:50:03 +0100 Subject: [PATCH 052/308] Update image documentation --- examples/printers/quick_summary.sol.png | Bin 12694 -> 21977 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/examples/printers/quick_summary.sol.png b/examples/printers/quick_summary.sol.png index 7f1ef7775334c1090c7b0545bfd29393ed5af1ba..45d2143a7ace7103f98d710d05fa499379f4a5f1 100644 GIT binary patch literal 21977 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 O9&R-~)oPWuk^cu2F`ZTb literal 12694 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 From 456aad2422aa29b817ec477590cba8ce6bbe5963 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Fri, 14 Sep 2018 14:01:59 +0100 Subject: [PATCH 053/308] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 651519b89..026086095 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Check | Purpose | Impact | Confidence * `--printer-inheritance`: Print the inheritance graph * `--printer-vars-and-auth`: Print the variables written and the check on `msg.sender` of each function. -For more information about printers, see the [Printers documentation](docs/PRINTERS.md) +For more information about printers, see the [Printers documentation](https://github.com/trailofbits/slither/wiki/Printer-documentation) ## How to create analyses From 2aea762600ebdb46798f53c7dd65ac375783c5dc Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 14 Sep 2018 15:44:55 +0100 Subject: [PATCH 054/308] Open source tx.origin detector --- README.md | 1 + scripts/travis_test.sh | 5 +++++ slither/__main__.py | 4 +++- tests/tx_origin.sol | 26 ++++++++++++++++++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/tx_origin.sol diff --git a/README.md b/README.md index 026086095..89e6fc11e 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ Check | Purpose | Impact | Confidence `--detect-pragma`| Detect if different pragma directives are used | Informational | High `--detect-reentrancy`| Detect if different pragma directives are used | High | Medium `--detect-solc-version`| Detect if an old version of Solidity is used (<0.4.23) | Informational | High +`--detect-tx-origin`| Detect dangerous usage of `tx.origin` | Medium | Medium ## Exclude analyses * `--exclude-informational`: Exclude informational impact analyses diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 94de7b23c..a7392418a 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -32,6 +32,11 @@ if [ $? -ne 1 ]; then exit 1 fi +slither tests/tx_origin.sol --disable-solc-warnings +if [ $? -ne 2 ]; then + exit 1 +fi + ### Test scripts python examples/scripts/functions_called.py examples/scripts/functions_called.sol diff --git a/slither/__main__.py b/slither/__main__.py index 068d7c9ce..c4cf321d1 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -71,13 +71,15 @@ def main(): from slither.detectors.attributes.old_solc import OldSolc from slither.detectors.reentrancy.reentrancy import Reentrancy from slither.detectors.variables.uninitialized_storage_variables import UninitializedStorageVars + from slither.detectors.statements.tx_origin import TxOrigin detectors = [Backdoor, UninitializedStateVarsDetection, ConstantPragma, OldSolc, Reentrancy, - UninitializedStorageVars] + UninitializedStorageVars, + TxOrigin] from slither.printers.summary.summary import PrinterSummary from slither.printers.summary.quick_summary import PrinterQuickSummary diff --git a/tests/tx_origin.sol b/tests/tx_origin.sol new file mode 100644 index 000000000..93bb5c757 --- /dev/null +++ b/tests/tx_origin.sol @@ -0,0 +1,26 @@ +pragma solidity ^0.4.24; + +contract TxOrigin { + + address owner; + + constructor() { owner = msg.sender; } + + function bug0() { + require(tx.origin == owner); + } + + function bug2() { + if (tx.origin != owner) { + revert(); + } + } + + function legit0(){ + require(tx.origin == msg.sender); + } + + function legit1(){ + tx.origin.transfer(this.balance); + } +} From 2d36296e0390d217361fb432b7fb43ca4a4dc024 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 14 Sep 2018 15:51:46 +0100 Subject: [PATCH 055/308] Add missing files --- slither/detectors/statements/__init__.py | 0 slither/detectors/statements/tx_origin.py | 64 +++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 slither/detectors/statements/__init__.py create mode 100644 slither/detectors/statements/tx_origin.py diff --git a/slither/detectors/statements/__init__.py b/slither/detectors/statements/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/detectors/statements/tx_origin.py b/slither/detectors/statements/tx_origin.py new file mode 100644 index 000000000..2e3b498c6 --- /dev/null +++ b/slither/detectors/statements/tx_origin.py @@ -0,0 +1,64 @@ +""" +Module detecting usage of `tx.origin` in a conditional node +""" + +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification + +class TxOrigin(AbstractDetector): + """ + Detect usage of tx.origin in a conditional node + """ + + ARGUMENT = 'tx-origin' + HELP = 'tx.origin usage' + CLASSIFICATION = DetectorClassification.MEDIUM + + @staticmethod + def _contains_incorrect_tx_origin_use(node): + """ + Check if the node read tx.origin and dont read msg.sender + Avoid the FP due to (msg.sender == tx.origin) + Returns: + (bool) + """ + solidity_var_read = node.solidity_variables_read + if solidity_var_read: + return any(v.name == 'tx.origin' for v in solidity_var_read) and\ + all(v.name != 'msg.sender' for v in solidity_var_read) + return False + + def detect_tx_origin(self, contract): + ret = [] + for f in contract.functions: + + nodes = f.nodes + condtional_nodes = [n for n in nodes if n.contains_if() or + n.contains_require_or_assert()] + bad_tx_nodes = [n for n in condtional_nodes if + self._contains_incorrect_tx_origin_use(n)] + if bad_tx_nodes: + ret.append((f, bad_tx_nodes)) + return ret + + def detect(self): + """ Detect the functions that use tx.origin in a conditional node + """ + results = [] + for c in self.contracts: + values = self.detect_tx_origin(c) + for func, nodes in values: + func_name = func.name + info = "tx.origin in %s, Contract: %s, Function: %s" % (self.filename, + c.name, + func_name) + self.log(info) + + sourceMapping = [n.source_mapping for n in nodes] + + results.append({'vuln': 'TxOrigin', + 'sourceMapping': sourceMapping, + 'filename': self.filename, + 'contract': c.name, + 'function_name': func_name}) + + return results From ab301facbae3fcad6420534bbe333438297cd582 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Fri, 14 Sep 2018 16:27:14 +0100 Subject: [PATCH 056/308] Update README.md --- README.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 89e6fc11e..d0c3323c6 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ Slither is a Solidity static analysis framework written in Python 3. It provides # Features With Slither you can: -- **Detect vulnerabilities** -- **Speed up your understanding** of code -- **Build custom analyses** to answer specific questions -- **Quickly prototype** a new static analysis techniques +- **Detect vulnerabilities**. +- **Speed up your understanding** of code. +- **Build custom analyses** to answer specific questions. +- **Quickly prototype** a new static analysis techniques. Slither can analyze contracts written with Solidity > 0.4. @@ -64,23 +64,23 @@ Check | Purpose | Impact | Confidence `--detect-tx-origin`| Detect dangerous usage of `tx.origin` | Medium | Medium ## Exclude analyses -* `--exclude-informational`: Exclude informational impact analyses -* `--exclude-low`: Exclude low impact analyses -* `--exclude-medium`: Exclude medium impact analyses -* `--exclude-high`: Exclude high impact analyses -* `--exclude-name` will exclude the detector `name` +* `--exclude-informational`: Exclude informational impact analyses. +* `--exclude-low`: Exclude low impact analyses. +* `--exclude-medium`: Exclude medium impact analyses. +* `--exclude-high`: Exclude high impact analyses. +* `--exclude-name` will exclude the detector `name`. ## Configuration -* `--solc SOLC`: Path to `solc` (default 'solc') +* `--solc SOLC`: Path to `solc` (default 'solc'). * `--solc-args SOLC_ARGS`: Add custom solc arguments. `SOLC_ARGS` can contain multiple arguments. -* `--disable-solc-warnings`: Do not print solc warnings -* `--solc-ast`: Use the solc AST file as input (`solc file.sol --ast-json > file.ast.json`) -* `--json FILE`: Export results as JSON +* `--disable-solc-warnings`: Do not print solc warnings. +* `--solc-ast`: Use the solc AST file as input (`solc file.sol --ast-json > file.ast.json`). +* `--json FILE`: Export results as JSON. ## Printers -* `--printer-summary`: Print a summary of the contracts -* `--printer-quick-summary`: Print a quick summary of the contracts -* `--printer-inheritance`: Print the inheritance graph +* `--printer-summary`: Print a summary of the contracts. +* `--printer-quick-summary`: Print a quick summary of the contracts. +* `--printer-inheritance`: Print the inheritance graph. * `--printer-vars-and-auth`: Print the variables written and the check on `msg.sender` of each function. For more information about printers, see the [Printers documentation](https://github.com/trailofbits/slither/wiki/Printer-documentation) From 46419ff12d2df7068fc5a555e56a5c2093b0167c Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 17 Sep 2018 12:21:46 +0100 Subject: [PATCH 057/308] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d0c3323c6..4b5761ed6 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,13 @@ Some of Slither detectors are open-source, [contact us](https://www.trailofbits. # How to install Slither uses Python 3.6. - + ## Using Gihtub ```bash @@ -58,10 +58,10 @@ Check | Purpose | Impact | Confidence --- | --- | --- | --- `--detect-uninitialized-state`| Detect uninitialized state variables | High | High `--detect-uninitialized-storage`| Detect uninitialized storage variables | High | High -`--detect-pragma`| Detect if different pragma directives are used | Informational | High `--detect-reentrancy`| Detect if different pragma directives are used | High | Medium -`--detect-solc-version`| Detect if an old version of Solidity is used (<0.4.23) | Informational | High `--detect-tx-origin`| Detect dangerous usage of `tx.origin` | Medium | Medium +`--detect-pragma`| Detect if different pragma directives are used | Informational | High +`--detect-solc-version`| Detect if an old version of Solidity is used (<0.4.23) | Informational | High ## Exclude analyses * `--exclude-informational`: Exclude informational impact analyses. From 13efd858edb71cd09976e2fe7cbfbc61382c7c73 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 19 Sep 2018 15:17:33 +0100 Subject: [PATCH 058/308] WIP: split ternary expression to multiples expersssion --- examples/scripts/export_to_dot.py | 17 ++++ slither/core/cfg/node.py | 7 ++ slither/core/expressions/binary_operation.py | 2 +- slither/core/variables/variable.py | 8 ++ slither/solc_parsing/cfg/node.py | 2 +- slither/solc_parsing/declarations/function.py | 67 ++++++++++++- .../expressions/expression_parsing.py | 2 + slither/solc_parsing/slitherSolc.py | 1 + slither/utils/expression_manipulations.py | 98 +++++++++++++++++++ slither/visitors/expression/expression.py | 2 +- .../visitors/expression/has_conditional.py | 13 +++ 11 files changed, 214 insertions(+), 5 deletions(-) create mode 100644 examples/scripts/export_to_dot.py create mode 100644 slither/utils/expression_manipulations.py create mode 100644 slither/visitors/expression/has_conditional.py diff --git a/examples/scripts/export_to_dot.py b/examples/scripts/export_to_dot.py new file mode 100644 index 000000000..e3e5440c5 --- /dev/null +++ b/examples/scripts/export_to_dot.py @@ -0,0 +1,17 @@ +import sys +from slither.slither import Slither + + +if len(sys.argv) != 4: + print('python.py function_called.py functions_called.sol Contract function()') + exit(-1) + +# Init slither +slither = Slither(sys.argv[1]) + +contract = slither.get_contract_from_name(sys.argv[2]) +test = contract.get_function_from_signature(sys.argv[3]) + +test.cfg_to_dot('/tmp/test.dot') + + diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index 72030f4e1..4a799af8c 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -265,6 +265,13 @@ class Node(SourceMapping, ChildFunction): """ self._fathers = [x for x in self._fathers if x.node_id != father.node_id] + def remove_son(self, son): + """ Remove the son node. Do nothing if the node is not a son + + Args: + fathers: list of fathers to add + """ + self._sons = [x for x in self._sons if x.node_id != son.node_id] def add_son(self, son): """ Add a son node diff --git a/slither/core/expressions/binary_operation.py b/slither/core/expressions/binary_operation.py index 3f2fb6d7b..26b8370d6 100644 --- a/slither/core/expressions/binary_operation.py +++ b/slither/core/expressions/binary_operation.py @@ -123,7 +123,7 @@ class BinaryOperation(ExpressionTyped): self._type = expression_type @property - def get_expression(self): + def expressions(self): return self._expressions @property diff --git a/slither/core/variables/variable.py b/slither/core/variables/variable.py index ecdb07a35..b80e56453 100644 --- a/slither/core/variables/variable.py +++ b/slither/core/variables/variable.py @@ -24,6 +24,14 @@ class Variable(SourceMapping): def expression(self): """ Expression: Expression of the node (if initialized) + Initial expression may be different than the expression of the node + where the variable is declared, if its used ternary operator + Ex: uint a = b?1:2 + The expression associated to a is uint a = b?1:2 + But two nodes are created, + one where uint a = 1, + and one where uint a = 2 + """ return self._initial_expression diff --git a/slither/solc_parsing/cfg/node.py b/slither/solc_parsing/cfg/node.py index 965a0ca4f..45c381c0a 100644 --- a/slither/solc_parsing/cfg/node.py +++ b/slither/solc_parsing/cfg/node.py @@ -24,7 +24,7 @@ class NodeSolc(Node): self._unparsed_expression = expression def analyze_expressions(self, caller_context): - if self.type == NodeType.VARIABLE: + if self.type == NodeType.VARIABLE and not self._expression: self._expression = self.variable_declaration.expression if self._unparsed_expression: expression = parse_expression(self._unparsed_expression, caller_context) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index cea6e6cdc..b6f5113c9 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -14,6 +14,11 @@ from slither.solc_parsing.variables.variable_declaration import MultipleVariable from slither.solc_parsing.expressions.expression_parsing import parse_expression from slither.visitors.expression.export_values import ExportValues +from slither.visitors.expression.has_conditional import HasConditional + +from slither.utils.expression_manipulations import SplitTernaryExpression + + logger = logging.getLogger("FunctionSolc") class FunctionSolc(Function): @@ -28,6 +33,7 @@ class FunctionSolc(Function): self._functionNotParsed = function self._params_was_analyzed = False self._content_was_analyzed = False + self._counter_nodes = 0 def _analyze_attributes(self): attributes = self._functionNotParsed['attributes'] @@ -63,8 +69,9 @@ class FunctionSolc(Function): if 'payable' in attributes: self._payable = attributes['payable'] - def _new_node(self, expression): - node = NodeSolc(expression, len(self.nodes)) + def _new_node(self, node_type): + node = NodeSolc(node_type, self._counter_nodes) + self._counter_nodes += 1 node.set_function(self) self._nodes.append(node) return node @@ -455,6 +462,7 @@ class FunctionSolc(Function): if node.type in [NodeType.CONTINUE]: self._fix_continue_node(node) + def _parse_params(self, params): assert params['name'] == 'ParameterList' @@ -543,5 +551,60 @@ class FunctionSolc(Function): for node in self.nodes: node.analyze_expressions(self) + ternary_found = True + while ternary_found: + ternary_found = False + for node in self.nodes: + has_cond = HasConditional(node.expression) + if has_cond.result(): + print('Expression to split {}'.format(node.expression)) + st = SplitTernaryExpression(node.expression) + condition = st.condition + assert condition + true_expr = st.true_expression + false_expr = st.false_expression + print('\tCondition {}'.format(condition)) + print('\ttrue {}'.format(true_expr)) + print('\tfalse {}'.format(false_expr)) + self.split_ternary_node(node, condition, true_expr, false_expr) + ternary_found = True + break + self._analyze_read_write() self._analyze_calls() + + + def split_ternary_node(self, node, condition, true_expr, false_expr): + condition_node = self._new_node(NodeType.IF) + condition_node.add_expression(condition) + condition_node.analyze_expressions(self) + + true_node = self._new_node(node.type) + true_node.add_expression(true_expr) + true_node.analyze_expressions(self) + + false_node = self._new_node(node.type) + false_node.add_expression(false_expr) + false_node.analyze_expressions(self) + + endif_node = self._new_node(NodeType.ENDIF) + + for father in node.fathers: + father.remove_son(node) + father.add_son(condition_node) + condition_node.add_father(father) + + for son in node.sons: + son.remove_father(node) + son.add_father(endif_node) + endif_node.add_son(son) + + link_nodes(condition_node, true_node) + link_nodes(condition_node, false_node) + + link_nodes(true_node, endif_node) + link_nodes(false_node, endif_node) + + self._nodes = [n for n in self._nodes if n.node_id != node.node_id] + + diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index df48d02e0..adaa59dd0 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -29,6 +29,7 @@ from slither.core.declarations.solidity_variables import SolidityVariable, Solid from slither.core.solidity_types.elementary_type import ElementaryType from slither.core.solidity_types.function_type import FunctionType + logger = logging.getLogger("ExpressionParsing") class VariableNotFound(Exception): pass @@ -260,6 +261,7 @@ def parse_expression(expression, caller_context): then_expression = parse_expression(children[1], caller_context) else_expression = parse_expression(children[2], caller_context) conditional = ConditionalExpression(if_expression, then_expression, else_expression) + #print(conditional) return conditional elif name == 'Assignment': diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index 2c5ce28c4..ed71cd5a9 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -17,6 +17,7 @@ class SlitherSolc(Slither): self._contractsNotParsed = [] self._contracts_by_id = {} self._analyzed = False + print(filename) def _parse_contracts_from_json(self, json_data): first = json_data.find('{') diff --git a/slither/utils/expression_manipulations.py b/slither/utils/expression_manipulations.py new file mode 100644 index 000000000..2804235ac --- /dev/null +++ b/slither/utils/expression_manipulations.py @@ -0,0 +1,98 @@ +""" + We use protected member, to avoid having setter in the expression + as they should be immutable +""" +import copy +from slither.core.expressions.assignment_operation import AssignmentOperation +from slither.core.expressions.binary_operation import BinaryOperation +from slither.core.expressions.call_expression import CallExpression +from slither.core.expressions.conditional_expression import ConditionalExpression +from slither.core.expressions.elementary_type_name_expression import ElementaryTypeNameExpression +from slither.core.expressions.identifier import Identifier +from slither.core.expressions.index_access import IndexAccess +from slither.core.expressions.literal import Literal +from slither.core.expressions.member_access import MemberAccess +from slither.core.expressions.new_array import NewArray +from slither.core.expressions.new_contract import NewContract +from slither.core.expressions.new_elementary_type import NewElementaryType +from slither.core.expressions.tuple_expression import TupleExpression +from slither.core.expressions.type_conversion import TypeConversion +from slither.core.expressions.unary_operation import UnaryOperation + +def f_expressions(e, x): + e._expressions.append(x) + +def f_call(e, x): + e._arguments.append(x) + +def f_expression(e, x): + e._expression = x + +class SplitTernaryExpression(object): + + def __init__(self, expression): + + # print(expression) + + if isinstance(expression, ConditionalExpression): + self.true_expression = copy.copy(expression.then_expression) + self.false_expression = copy.copy(expression.else_expression) + self.condition = copy.copy(expression.if_expression) + else: + self.true_expression = copy.copy(expression) + self.false_expression = copy.copy(expression) + self.condition = None + self.copy_expression(expression, self.true_expression, self.false_expression) + + def apply_copy(self, next_expr, true_expression, false_expression, f): + + if isinstance(next_expr, ConditionalExpression): + f(true_expression, copy.copy(next_expr.then_expression)) + f(false_expression, copy.copy(next_expr.else_expression)) + self.condition = copy.copy(next_expr.if_expression) + return False + else: + f(true_expression, copy.copy(next_expr)) + f(false_expression, copy.copy(next_expr)) + return True + + def copy_expression(self, expression, true_expression, false_expression): + if self.condition: + return + + if isinstance(expression, ConditionalExpression): + raise Exception('Nested ternary operator not handled') + + if isinstance(expression, (Literal, Identifier, IndexAccess, MemberAccess)): + return None + + elif isinstance(expression, (AssignmentOperation, BinaryOperation, TupleExpression)): + true_expression._expressions = [] + false_expression._expressions = [] + + for next_expr in expression.expressions: + if self.apply_copy(next_expr, true_expression, false_expression, f_expressions): + # always on last arguments added + self.copy_expression(next_expr, true_expression.expressions[-1], false_expression.expressions[-1]) + + elif isinstance(expression, CallExpression): + next_expr = expression.called + true_expression._called = copy.copy(next_expr) + false_expression._called = copy.copy(next_expr) + + true_expression._arguments = [] + false_expression._arguments = [] + + for next_expr in expression.arguments: + if self.apply_copy(next_expr, true_expression, false_expression, f_call): + # always on last arguments added + self.copy_expression(next_expr, true_expression.arguments[-1], false_expression.arguments[-1]) + + elif isinstance(expression, TypeConversion): + next_expr = expression.expression + if self.apply_copy(next_expr, true_expression, false_expression, f_expression): + self.copy_expression(expression.expression, true_expression.expression, false_expression.expression) + + else: + raise Exception('Ternary operation not handled {}'.format(type(expression))) + diff --git a/slither/visitors/expression/expression.py b/slither/visitors/expression/expression.py index de3453ef7..fe8eeb821 100644 --- a/slither/visitors/expression/expression.py +++ b/slither/visitors/expression/expression.py @@ -22,8 +22,8 @@ class ExpressionVisitor: def __init__(self, expression): self._expression = expression - self._visit_expression(self.expression) self._result = None + self._visit_expression(self.expression) def result(self): return self._result diff --git a/slither/visitors/expression/has_conditional.py b/slither/visitors/expression/has_conditional.py new file mode 100644 index 000000000..5378e4d98 --- /dev/null +++ b/slither/visitors/expression/has_conditional.py @@ -0,0 +1,13 @@ + +from slither.visitors.expression.expression import ExpressionVisitor + +class HasConditional(ExpressionVisitor): + + def result(self): + # == True, to convert None to false + return self._result is True + + def _post_conditional_expression(self, expression): +# if self._result is True: +# raise('Slither does not support nested ternary operator') + self._result = True From b9f5340843ebc7ccefbe900cb50fa6146430c386 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Thu, 20 Sep 2018 10:03:01 -0400 Subject: [PATCH 059/308] Update README.md --- README.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 4b5761ed6..a5551e6aa 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Slither uses Python 3.6. $ pip install slither-analyzer ``` --> -## Using Gihtub +## Using Github ```bash $ git clone https://github.com/trailofbits/slither.git & cd slither @@ -63,19 +63,13 @@ Check | Purpose | Impact | Confidence `--detect-pragma`| Detect if different pragma directives are used | Informational | High `--detect-solc-version`| Detect if an old version of Solidity is used (<0.4.23) | Informational | High -## Exclude analyses -* `--exclude-informational`: Exclude informational impact analyses. -* `--exclude-low`: Exclude low impact analyses. -* `--exclude-medium`: Exclude medium impact analyses. -* `--exclude-high`: Exclude high impact analyses. -* `--exclude-name` will exclude the detector `name`. - ## Configuration * `--solc SOLC`: Path to `solc` (default 'solc'). * `--solc-args SOLC_ARGS`: Add custom solc arguments. `SOLC_ARGS` can contain multiple arguments. * `--disable-solc-warnings`: Do not print solc warnings. * `--solc-ast`: Use the solc AST file as input (`solc file.sol --ast-json > file.ast.json`). * `--json FILE`: Export results as JSON. +* `--exclude-name` will exclude the detector `name`. ## Printers * `--printer-summary`: Print a summary of the contracts. @@ -83,7 +77,7 @@ Check | Purpose | Impact | Confidence * `--printer-inheritance`: Print the inheritance graph. * `--printer-vars-and-auth`: Print the variables written and the check on `msg.sender` of each function. -For more information about printers, see the [Printers documentation](https://github.com/trailofbits/slither/wiki/Printer-documentation) +For more information about printers, see the [Printers documentation](https://github.com/trailofbits/slither/wiki/Printer-documentation). ## How to create analyses From 5e092685ccd561988d0002c3f5f7bbe02a735976 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Thu, 20 Sep 2018 10:10:00 -0400 Subject: [PATCH 060/308] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a5551e6aa..9651bc5ec 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,9 @@ Slither uses Python 3.6. ``` $ pip install slither-analyzer ``` + +or --> -## Using Github ```bash $ git clone https://github.com/trailofbits/slither.git & cd slither From 4d24862b361dc4c2fde133cd0052cbbe68ddbaea Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Thu, 20 Sep 2018 10:26:25 -0400 Subject: [PATCH 061/308] Update README.md --- README.md | 94 +++++++++++++++++++++++++++---------------------------- 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 9651bc5ec..a627d6972 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ # Slither, the Solidity source analyzer [![Build Status](https://travis-ci.com/trailofbits/slither.svg?token=JEF97dFy1QsDCfQ2Wusd&branch=master)](https://travis-ci.com/trailofbits/slither) -Slither is a Solidity static analysis framework written in Python 3. It provides an API to easily manipulate Solidity code, and integrates vulnerabilities detectors. +Slither is a Solidity static analysis framework written in Python 3. It runs a suite of vulnerability detectors, prints visual information about contract details, and provides API to easily write custom analyses. + +## Features -# Features With Slither you can: - **Detect vulnerabilities**. - **Speed up your understanding** of code. @@ -12,35 +13,7 @@ With Slither you can: Slither can analyze contracts written with Solidity > 0.4. -Some of Slither detectors are open-source, [contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. - -# How to install - -Slither uses Python 3.6. - - -```bash -$ git clone https://github.com/trailofbits/slither.git & cd slither -$ python setup.py install -``` - -Slither requires [solc](https://github.com/ethereum/solidity/), the Solidity compiler. - -# How to use - -``` -$ slither file.sol -``` - -For example: +## Usage ``` $ slither tests/uninitialized.sol @@ -49,7 +22,23 @@ INFO:Detectors:Uninitialized state variables in tests/uninitialized.sol, Contrac [..] ``` -If Slither is applied on a directory, it will run on every `.sol` file of the directory. +If Slither is run on a directory, it will run on every `.sol` file of the directory. All vulnerability checks are run by default. + +### Configuration + +* `--solc SOLC`: Path to `solc` (default 'solc'). +* `--solc-args SOLC_ARGS`: Add custom solc arguments. `SOLC_ARGS` can contain multiple arguments. +* `--disable-solc-warnings`: Do not print solc warnings. +* `--solc-ast`: Use the solc AST file as input (`solc file.sol --ast-json > file.ast.json`). +* `--json FILE`: Export results as JSON. +* `--exclude-name` will exclude the detector `name`. + +### Printers + +* `--printer-summary`: Print a summary of the contracts. +* `--printer-quick-summary`: Print a quick summary of the contracts. +* `--printer-inheritance`: Print the inheritance graph. +* `--printer-vars-and-auth`: Print the variables written and the check on `msg.sender` of each function. ## Checks available @@ -64,27 +53,36 @@ Check | Purpose | Impact | Confidence `--detect-pragma`| Detect if different pragma directives are used | Informational | High `--detect-solc-version`| Detect if an old version of Solidity is used (<0.4.23) | Informational | High -## Configuration -* `--solc SOLC`: Path to `solc` (default 'solc'). -* `--solc-args SOLC_ARGS`: Add custom solc arguments. `SOLC_ARGS` can contain multiple arguments. -* `--disable-solc-warnings`: Do not print solc warnings. -* `--solc-ast`: Use the solc AST file as input (`solc file.sol --ast-json > file.ast.json`). -* `--json FILE`: Export results as JSON. -* `--exclude-name` will exclude the detector `name`. +[Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. -## Printers -* `--printer-summary`: Print a summary of the contracts. -* `--printer-quick-summary`: Print a quick summary of the contracts. -* `--printer-inheritance`: Print the inheritance graph. -* `--printer-vars-and-auth`: Print the variables written and the check on `msg.sender` of each function. +## How to install + +Slither requires Python 3.6+ and [solc](https://github.com/ethereum/solidity/), the Solidity compiler. + + +```bash +$ git clone https://github.com/trailofbits/slither.git & cd slither +$ python setup.py install +``` + +## Getting Help -For more information about printers, see the [Printers documentation](https://github.com/trailofbits/slither/wiki/Printer-documentation). +Feel free to stop by our [Slack channel](https://empirehacking.slack.com/messages/C7KKY517H/) for help on using or extending Slither. -## How to create analyses +* The [Printer documentation](https://github.com/trailofbits/slither/wiki/Printer-documentation) describes the information Slither is capable of visualizing for each contract. -See the [API documentation](https://github.com/trailofbits/slither/wiki/API-examples), and the [detector documentation](https://github.com/trailofbits/slither/wiki/Adding-a-new-detector). +* The [Detector documentation](https://github.com/trailofbits/slither/wiki/Adding-a-new-detector) describes how to write a new vulnerability analyses. +* The [API documentation](https://github.com/trailofbits/slither/wiki/API-examples) describes the methods and objects available for custom analyses. -# License +## License Slither is licensed and distributed under the AGPLv3 license. [Contact us](mailto:opensource@trailofbits.com) if you're looking for an exception to the terms. From 9f17dedd9b7d34eb5f2644a4d916cc28700fd375 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Thu, 20 Sep 2018 10:27:26 -0400 Subject: [PATCH 062/308] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a627d6972..f82d44448 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Slither, the Solidity source analyzer [![Build Status](https://travis-ci.com/trailofbits/slither.svg?token=JEF97dFy1QsDCfQ2Wusd&branch=master)](https://travis-ci.com/trailofbits/slither) +[![Slack Status](https://empireslacking.herokuapp.com/badge.svg)](https://empireslacking.herokuapp.com) Slither is a Solidity static analysis framework written in Python 3. It runs a suite of vulnerability detectors, prints visual information about contract details, and provides API to easily write custom analyses. From 2700853ee340dbcaba3b20e68f16fe0c2a907c46 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Thu, 20 Sep 2018 10:27:41 -0400 Subject: [PATCH 063/308] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f82d44448..5345bfdbb 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Status](https://travis-ci.com/trailofbits/slither.svg?token=JEF97dFy1QsDCfQ2Wusd&branch=master)](https://travis-ci.com/trailofbits/slither) [![Slack Status](https://empireslacking.herokuapp.com/badge.svg)](https://empireslacking.herokuapp.com) -Slither is a Solidity static analysis framework written in Python 3. It runs a suite of vulnerability detectors, prints visual information about contract details, and provides API to easily write custom analyses. +Slither is a Solidity static analysis framework written in Python 3. It runs a suite of vulnerability detectors, prints visual information about contract details, and provides an API to easily write custom analyses. ## Features From b538e52b55a2d9861ccc0962f1282aef7ace50bf Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Thu, 20 Sep 2018 10:31:48 -0400 Subject: [PATCH 064/308] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5345bfdbb..a2b4f978d 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ $ python setup.py install ## Getting Help -Feel free to stop by our [Slack channel](https://empirehacking.slack.com/messages/C7KKY517H/) for help on using or extending Slither. +Feel free to stop by our [Slack channel](https://empirehacking.slack.com/messages/C7KKY517H/) for help using or extending Slither. * The [Printer documentation](https://github.com/trailofbits/slither/wiki/Printer-documentation) describes the information Slither is capable of visualizing for each contract. From f1f08de4159e03c63aed072b4c17acc795bba465 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Thu, 20 Sep 2018 10:34:57 -0400 Subject: [PATCH 065/308] Update README.md --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index a2b4f978d..b794da3c8 100644 --- a/README.md +++ b/README.md @@ -27,19 +27,19 @@ If Slither is run on a directory, it will run on every `.sol` file of the direct ### Configuration -* `--solc SOLC`: Path to `solc` (default 'solc'). -* `--solc-args SOLC_ARGS`: Add custom solc arguments. `SOLC_ARGS` can contain multiple arguments. -* `--disable-solc-warnings`: Do not print solc warnings. -* `--solc-ast`: Use the solc AST file as input (`solc file.sol --ast-json > file.ast.json`). -* `--json FILE`: Export results as JSON. -* `--exclude-name` will exclude the detector `name`. +* `--solc SOLC`: Path to `solc` (default 'solc') +* `--solc-args SOLC_ARGS`: Add custom solc arguments. `SOLC_ARGS` can contain multiple arguments +* `--disable-solc-warnings`: Do not print solc warnings +* `--solc-ast`: Use the solc AST file as input (`solc file.sol --ast-json > file.ast.json`) +* `--json FILE`: Export results as JSON +* `--exclude-name`: Excludes the detector `name` from analysis ### Printers -* `--printer-summary`: Print a summary of the contracts. -* `--printer-quick-summary`: Print a quick summary of the contracts. -* `--printer-inheritance`: Print the inheritance graph. -* `--printer-vars-and-auth`: Print the variables written and the check on `msg.sender` of each function. +* `--printer-summary`: Print a summary of the contracts +* `--printer-quick-summary`: Print a quick summary of the contracts +* `--printer-inheritance`: Print the inheritance graph +* `--printer-vars-and-auth`: Print the variables written and the check on `msg.sender` of each function ## Checks available From 64dbdd17a24850881e7859a35bf01fe40810512f Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Thu, 20 Sep 2018 15:06:58 -0400 Subject: [PATCH 066/308] better features list --- README.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b794da3c8..8e6445e80 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,20 @@ [![Build Status](https://travis-ci.com/trailofbits/slither.svg?token=JEF97dFy1QsDCfQ2Wusd&branch=master)](https://travis-ci.com/trailofbits/slither) [![Slack Status](https://empireslacking.herokuapp.com/badge.svg)](https://empireslacking.herokuapp.com) -Slither is a Solidity static analysis framework written in Python 3. It runs a suite of vulnerability detectors, prints visual information about contract details, and provides an API to easily write custom analyses. +Slither is a Solidity static analysis framework written in Python 3. It runs a suite of vulnerability detectors, prints visual information about contract details, and provides an API to easily write custom analyses. Slither enables developers to find vulnerabilities, enhance their code comphrehension, and quickly prototype custom analyses. ## Features -With Slither you can: -- **Detect vulnerabilities**. -- **Speed up your understanding** of code. -- **Build custom analyses** to answer specific questions. -- **Quickly prototype** a new static analysis techniques. +* Detects vulnerable Solidity code with low false positives + * Detection of most major smart contract vulnerabilities + * Detection of poor coding practices +* Reports exact line number in source code where the error condition occurs +* Easy integration into continuous integration pipelines +* Four built-in 'printers' quickly report crucial contract information +* Detector API to write custom analyses in Python +* Ability to analyze contracts written with Solidity > 0.4 -Slither can analyze contracts written with Solidity > 0.4. +Support for advanced value- and taint-tracking is [coming soon](https://github.com/trailofbits/slither/issues/6)! ## Usage From 92e3578f160fdf6fb958c5627a1ec63464536789 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Fri, 21 Sep 2018 11:39:02 -0400 Subject: [PATCH 067/308] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e6445e80..ef1145742 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Slither is a Solidity static analysis framework written in Python 3. It runs a s * Detects vulnerable Solidity code with low false positives * Detection of most major smart contract vulnerabilities * Detection of poor coding practices -* Reports exact line number in source code where the error condition occurs +* Identifies where the error condition occurs in the source code * Easy integration into continuous integration pipelines * Four built-in 'printers' quickly report crucial contract information * Detector API to write custom analyses in Python From a15af9699328ea3135f88ee02fe91498631dd272 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 24 Sep 2018 17:32:45 +0100 Subject: [PATCH 068/308] Add SlithIR representation (WIP) Add taint example API changes: - Better handling of tuple - Add context to slither - Change node str representation - Add ChildNode - Add block.blockhash in SolidityVariable - Add __eq__ , __hash__ in SolidityVariable - Add full_name in solidityVariableComposed - Update of the expression in a VARIABLE Node to have the assignement - Remove ENDIF not never reached --- examples/scripts/convert_to_ir.py | 31 ++ examples/scripts/export_to_dot.py | 13 +- examples/scripts/taint_mapping.py | 88 ++++++ slither/core/cfg/node.py | 45 ++- slither/core/children/child_node.py | 12 + slither/core/declarations/function.py | 21 ++ .../core/declarations/solidity_variables.py | 17 + slither/core/expressions/new_array.py | 5 +- slither/core/expressions/tuple_expression.py | 4 +- slither/core/expressions/unary_operation.py | 4 + slither/core/slither_core.py | 5 +- slither/slithir/__init__.py | 0 slither/slithir/convert.py | 297 ++++++++++++++++++ slither/slithir/operations/__init__.py | 0 slither/slithir/operations/assignment.py | 115 +++++++ slither/slithir/operations/binary.py | 150 +++++++++ slither/slithir/operations/call.py | 17 + slither/slithir/operations/delete.py | 27 ++ slither/slithir/operations/event_call.py | 21 ++ slither/slithir/operations/high_level_call.py | 93 ++++++ slither/slithir/operations/index.py | 36 +++ slither/slithir/operations/init_array.py | 22 ++ slither/slithir/operations/internal_call.py | 38 +++ slither/slithir/operations/library_call.py | 27 ++ slither/slithir/operations/low_level_call.py | 82 +++++ slither/slithir/operations/lvalue.py | 15 + slither/slithir/operations/member.py | 37 +++ slither/slithir/operations/new_array.py | 29 ++ slither/slithir/operations/new_contract.py | 30 ++ .../slithir/operations/new_elementary_type.py | 27 ++ slither/slithir/operations/new_structure.py | 28 ++ slither/slithir/operations/operation.py | 8 + slither/slithir/operations/push.py | 26 ++ slither/slithir/operations/push_array.py | 27 ++ slither/slithir/operations/solidity_call.py | 38 +++ slither/slithir/operations/type_conversion.py | 33 ++ slither/slithir/operations/unary.py | 56 ++++ slither/slithir/operations/unpack.py | 31 ++ slither/slithir/tmp_operations/__init__.py | 0 slither/slithir/tmp_operations/argument.py | 47 +++ slither/slithir/tmp_operations/tmp_call.py | 65 ++++ .../slithir/tmp_operations/tmp_new_array.py | 27 ++ .../tmp_operations/tmp_new_contract.py | 20 ++ .../tmp_operations/tmp_new_elementary_type.py | 21 ++ slither/slithir/utils/__init__.py | 0 slither/slithir/utils/utils.py | 16 + slither/slithir/variables/__init__.py | 0 slither/slithir/variables/call.py | 19 ++ slither/slithir/variables/constant.py | 30 ++ slither/slithir/variables/reference.py | 27 ++ slither/slithir/variables/temporary.py | 27 ++ slither/slithir/variables/tuple.py | 26 ++ slither/solc_parsing/cfg/node.py | 11 +- slither/solc_parsing/declarations/function.py | 61 +++- .../expressions/expression_parsing.py | 26 ++ .../local_variable_init_from_tuple.py | 1 + .../variables/variable_declaration.py | 2 +- slither/visitors/slithir/__init__.py | 0 .../visitors/slithir/expression_to_slithir.py | 231 ++++++++++++++ tests/taint_mapping.sol | 18 ++ weird_tests/parsing/slithir.sol | 76 +++++ 61 files changed, 2268 insertions(+), 38 deletions(-) create mode 100644 examples/scripts/convert_to_ir.py create mode 100644 examples/scripts/taint_mapping.py create mode 100644 slither/core/children/child_node.py create mode 100644 slither/slithir/__init__.py create mode 100644 slither/slithir/convert.py create mode 100644 slither/slithir/operations/__init__.py create mode 100644 slither/slithir/operations/assignment.py create mode 100644 slither/slithir/operations/binary.py create mode 100644 slither/slithir/operations/call.py create mode 100644 slither/slithir/operations/delete.py create mode 100644 slither/slithir/operations/event_call.py create mode 100644 slither/slithir/operations/high_level_call.py create mode 100644 slither/slithir/operations/index.py create mode 100644 slither/slithir/operations/init_array.py create mode 100644 slither/slithir/operations/internal_call.py create mode 100644 slither/slithir/operations/library_call.py create mode 100644 slither/slithir/operations/low_level_call.py create mode 100644 slither/slithir/operations/lvalue.py create mode 100644 slither/slithir/operations/member.py create mode 100644 slither/slithir/operations/new_array.py create mode 100644 slither/slithir/operations/new_contract.py create mode 100644 slither/slithir/operations/new_elementary_type.py create mode 100644 slither/slithir/operations/new_structure.py create mode 100644 slither/slithir/operations/operation.py create mode 100644 slither/slithir/operations/push.py create mode 100644 slither/slithir/operations/push_array.py create mode 100644 slither/slithir/operations/solidity_call.py create mode 100644 slither/slithir/operations/type_conversion.py create mode 100644 slither/slithir/operations/unary.py create mode 100644 slither/slithir/operations/unpack.py create mode 100644 slither/slithir/tmp_operations/__init__.py create mode 100644 slither/slithir/tmp_operations/argument.py create mode 100644 slither/slithir/tmp_operations/tmp_call.py create mode 100644 slither/slithir/tmp_operations/tmp_new_array.py create mode 100644 slither/slithir/tmp_operations/tmp_new_contract.py create mode 100644 slither/slithir/tmp_operations/tmp_new_elementary_type.py create mode 100644 slither/slithir/utils/__init__.py create mode 100644 slither/slithir/utils/utils.py create mode 100644 slither/slithir/variables/__init__.py create mode 100644 slither/slithir/variables/call.py create mode 100644 slither/slithir/variables/constant.py create mode 100644 slither/slithir/variables/reference.py create mode 100644 slither/slithir/variables/temporary.py create mode 100644 slither/slithir/variables/tuple.py create mode 100644 slither/visitors/slithir/__init__.py create mode 100644 slither/visitors/slithir/expression_to_slithir.py create mode 100644 tests/taint_mapping.sol create mode 100644 weird_tests/parsing/slithir.sol diff --git a/examples/scripts/convert_to_ir.py b/examples/scripts/convert_to_ir.py new file mode 100644 index 000000000..fe71f1bd1 --- /dev/null +++ b/examples/scripts/convert_to_ir.py @@ -0,0 +1,31 @@ +import sys +from slither.slither import Slither +from slither.slithir.convert import convert_expression + + +if len(sys.argv) != 4: + print('python.py function_called.py functions_called.sol Contract function()') + exit(-1) + +# Init slither +slither = Slither(sys.argv[1]) + +# Get the contract +contract = slither.get_contract_from_name(sys.argv[2]) + +# Get the variable +test = contract.get_function_from_signature(sys.argv[3]) +#test = contract.get_function_from_signature('two()') + +nodes = test.nodes + +for node in nodes: + if node.expression: + print('Expression:\n\t{}'.format(node.expression)) + irs = convert_expression(node.expression) + print('IR expressions:') + for ir in irs: + print('\t{}'.format(ir)) + print() + + diff --git a/examples/scripts/export_to_dot.py b/examples/scripts/export_to_dot.py index e3e5440c5..268437d1c 100644 --- a/examples/scripts/export_to_dot.py +++ b/examples/scripts/export_to_dot.py @@ -2,16 +2,17 @@ import sys from slither.slither import Slither -if len(sys.argv) != 4: - print('python.py function_called.py functions_called.sol Contract function()') +if len(sys.argv) != 2: + print('python.py function_called.py') exit(-1) # Init slither slither = Slither(sys.argv[1]) -contract = slither.get_contract_from_name(sys.argv[2]) -test = contract.get_function_from_signature(sys.argv[3]) - -test.cfg_to_dot('/tmp/test.dot') +for contract in slither.contracts: + for function in contract.functions: + filename = "{}-{}-{}.dot".format(sys.argv[1], contract.name, function.full_name) + print('Export {}'.format(filename)) + function.slithir_cfg_to_dot(filename) diff --git a/examples/scripts/taint_mapping.py b/examples/scripts/taint_mapping.py new file mode 100644 index 000000000..2c3b6df9b --- /dev/null +++ b/examples/scripts/taint_mapping.py @@ -0,0 +1,88 @@ +import sys +from slither.slither import Slither + +from slither.core.declarations.solidity_variables import SolidityVariableComposed + +from slither.core.variables.state_variable import StateVariable + +#from slither.slithir.operations.lvalue import OperationWithLValue +from slither.slithir.operations.high_level_call import HighLevelCall +from slither.slithir.operations.member import Member +from slither.slithir.operations.index import Index +from slither.slithir.operations.assignment import Assignment + +from slither.slithir.variables.temporary import TemporaryVariable +from slither.slithir.variables.reference import ReferenceVariable + +def visit_node(node, visited): + if node in visited: + return + + visited += [node] + taints = node.function.slither.context[KEY] + + refs = {} + for ir in node.irs: + if isinstance(ir, Index): + refs[ir.lvalue] = ir.variable_left + + if isinstance(ir, Index): + read = [ir.variable_left] + else: + read = ir.read + print(ir) + print('Refs {}'.format(refs)) + print('Read {}'.format([str(x) for x in ir.read])) + print('Before {}'.format([str(x) for x in taints])) + if any(var_read in taints for var_read in read): + taints += [ir.lvalue] + lvalue = ir.lvalue + while isinstance(lvalue, ReferenceVariable): + taints += [refs[lvalue]] + lvalue = refs[lvalue] + + print('After {}'.format([str(x) for x in taints])) + print() + + taints = [v for v in taints if not isinstance(v, (TemporaryVariable, ReferenceVariable))] + + node.function.slither.context[KEY] = list(set(taints)) + + for son in node.sons: + visit_node(son, visited) + +def check_call(func, taints): + for node in func.nodes: + for ir in node.irs: + if isinstance(ir, HighLevelCall): + if ir.destination in taints: + print('Call to tainted address found in {}'.format(function.name)) + +if __name__ == "__main__": + if len(sys.argv) != 2: + print('python.py taint.py taint.sol') + exit(-1) + + # Init slither + slither = Slither(sys.argv[1]) + + initial_taint = [SolidityVariableComposed('msg.sender')] + initial_taint += [SolidityVariableComposed('msg.value')] + + KEY = 'TAINT' + + prev_taints = [] + slither.context[KEY] = initial_taint + while(set(prev_taints) != set(slither.context[KEY])): + prev_taints = slither.context[KEY] + for contract in slither.contracts: + for function in contract.functions: + print('Function {}'.format(function.name)) + slither.context[KEY] = list(set(slither.context[KEY] + function.parameters)) + visit_node(function.entry_point, []) + print('All variables tainted : {}'.format([str(v) for v in slither.context[KEY]])) + + print('All state variables tainted : {}'.format([str(v) for v in prev_taints if isinstance(v, StateVariable)])) + + for function in contract.functions: + check_call(function, slither.context[KEY]) diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index 4a799af8c..6422f8b06 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -13,6 +13,9 @@ from slither.visitors.expression.write_var import WriteVar from slither.core.children.child_function import ChildFunction from slither.core.declarations.solidity_variables import SolidityFunction + +from slither.slithir.convert import convert_expression + logger = logging.getLogger("Node") class NodeType: @@ -47,36 +50,36 @@ class NodeType: STARTLOOP = 0x51 ENDLOOP = 0x52 - @staticmethod +# @staticmethod def str(t): if t == 0x0: - return 'EntryPoint' + return 'ENTRY_POINT' if t == 0x10: - return 'Expressions' + return 'EXPRESSION' if t == 0x11: - return 'Return' + return 'RETURN' if t == 0x12: - return 'If' + return 'IF' if t == 0x13: - return 'New variable' + return 'NEW VARIABLE' if t == 0x14: - return 'Inline Assembly' + return 'INLINE ASM' if t == 0x15: - return 'IfLoop' + return 'IF_LOOP' if t == 0x20: - return 'Throw' + return 'THROW' if t == 0x31: - return 'Break' + return 'BREAK' if t == 0x32: - return 'Continue' + return 'CONTINUE' if t == 0x40: return '_' if t == 0x50: - return 'EndIf' + return 'END_IF' if t == 0x51: - return 'BeginLoop' + return 'BEGIN_LOOP' if t == 0x52: - return 'EndLoop' + return 'END_LOOP' return 'Unknown type {}'.format(hex(t)) def link_nodes(n1, n2): @@ -101,6 +104,7 @@ class Node(SourceMapping, ChildFunction): self._vars_read = [] self._internal_calls = [] self._external_calls = [] + self._irs = [] self._state_vars_written = [] self._state_vars_read = [] @@ -298,3 +302,16 @@ class Node(SourceMapping, ChildFunction): """ return list(self._sons) + @property + def irs(self): + """ Returns the slithIR representation + + return + list(slithIR.Operation) + """ + return self._irs + + def slithir_generation(self): + if self.expression: + expression = self.expression + self._irs = convert_expression(expression) diff --git a/slither/core/children/child_node.py b/slither/core/children/child_node.py new file mode 100644 index 000000000..747b7d285 --- /dev/null +++ b/slither/core/children/child_node.py @@ -0,0 +1,12 @@ + +class ChildNode(object): + def __init__(self): + super(ChildNode, self).__init__() + self._node = None + + def set_node(self, node): + self._node = node + + @property + def node(self): + return self._node diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index ae9852f4a..c76f6334b 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -6,6 +6,7 @@ from itertools import groupby from slither.core.source_mapping.source_mapping import SourceMapping from slither.core.children.child_contract import ChildContract + from slither.core.variables.state_variable import StateVariable from slither.core.expressions.identifier import Identifier from slither.core.expressions.unary_operation import UnaryOperation @@ -536,6 +537,26 @@ class Function(ChildContract, SourceMapping): f.write("}\n") + def slithir_cfg_to_dot(self, filename): + """ + Export the function to a dot file + Args: + filename (str) + """ + from slither.core.cfg.node import NodeType + with open(filename, 'w') as f: + f.write('digraph{\n') + for node in self.nodes: + label = 'Node Type: {}\n'.format(NodeType.str(node.type)) + if node.expression: + label += '\nEXPRESSION:\n{}\n'.format(node.expression) + label += '\nIRs:\n' + '\n'.join([str(ir) for ir in node.irs]) + f.write('{}[label="{}"];\n'.format(node.node_id, label)) + for son in node.sons: + f.write('{}->{};\n'.format(node.node_id, son.node_id)) + + f.write("}\n") + def get_summary(self): """ Return the function summary diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py index c60a9f14d..033a8e11e 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -8,6 +8,7 @@ SOLIDITY_VARIABLES_COMPOSED = ["block.coinbase", "block.gaslimit", "block.number", "block.timestamp", + "block.blockhash", # alias for blockhash. It's a call "msg.data", "msg.gas", "msg.sender", @@ -63,6 +64,12 @@ class SolidityVariable: def __str__(self): return self._name + def __eq__(self, other): + return self.__class__ == other.__class__ and self.name == other.name + + def __hash__(self): + return hash(self.name) + class SolidityVariableComposed(SolidityVariable): def __init__(self, name): assert name in SOLIDITY_VARIABLES_COMPOSED @@ -85,5 +92,15 @@ class SolidityFunction: def name(self): return self._name + @property + def full_name(self): + return self.name + def __str__(self): return self._name + + def __eq__(self, other): + return self.__class__ == other.__class__ and self.name == other.name + + def __hash__(self): + return hash(self.name) diff --git a/slither/core/expressions/new_array.py b/slither/core/expressions/new_array.py index ec37f459a..9c59a75a9 100644 --- a/slither/core/expressions/new_array.py +++ b/slither/core/expressions/new_array.py @@ -1,9 +1,6 @@ -import logging -from .expression import Expression +from slither.core.expressions.expression import Expression from slither.core.solidity_types.type import Type -logger = logging.getLogger("NewArray") - class NewArray(Expression): # note: dont conserve the size of the array if provided diff --git a/slither/core/expressions/tuple_expression.py b/slither/core/expressions/tuple_expression.py index 29df13e0c..90c7f91c0 100644 --- a/slither/core/expressions/tuple_expression.py +++ b/slither/core/expressions/tuple_expression.py @@ -12,6 +12,6 @@ class TupleExpression(Expression): return self._expressions def __str__(self): - expressions_str = [str(e) for e in self.expressions] - return '(' + ','.join(expressions_str) + ')' + expressions_str = [str(e) for e in self.expressions] + return '(' + ','.join(expressions_str) + ')' diff --git a/slither/core/expressions/unary_operation.py b/slither/core/expressions/unary_operation.py index dc334b396..3416cd854 100644 --- a/slither/core/expressions/unary_operation.py +++ b/slither/core/expressions/unary_operation.py @@ -101,6 +101,10 @@ class UnaryOperation(ExpressionTyped): def type_str(self): return UnaryOperationType.str(self._type) + @property + def type(self): + return self._type + @property def is_prefix(self): return UnaryOperationType.is_prefix(self._type) diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index 4ae8ab058..ba8342237 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -2,14 +2,15 @@ Main module """ import os +from slither.core.context.context import Context - -class Slither: +class Slither(Context): """ Slither static analyzer """ def __init__(self): + super(Slither, self).__init__() self._contracts = {} self._filename = None self._source_units = {} diff --git a/slither/slithir/__init__.py b/slither/slithir/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py new file mode 100644 index 000000000..e11e544a4 --- /dev/null +++ b/slither/slithir/convert.py @@ -0,0 +1,297 @@ +from slither.visitors.slithir.expression_to_slithir import ExpressionToSlithIR +from slither.slithir.operations.assignment import Assignment +from slither.slithir.operations.member import Member +from slither.slithir.operations.lvalue import OperationWithLValue + +from slither.slithir.operations.binary import BinaryOperation, BinaryOperationType +from slither.slithir.operations.high_level_call import HighLevelCall +from slither.slithir.operations.low_level_call import LowLevelCall +from slither.slithir.operations.solidity_call import SolidityCall +from slither.slithir.operations.library_call import LibraryCall +from slither.slithir.operations.new_elementary_type import NewElementaryType +from slither.slithir.operations.new_contract import NewContract +from slither.slithir.operations.new_structure import NewStructure +from slither.slithir.operations.new_array import NewArray +from slither.slithir.operations.event_call import EventCall +from slither.slithir.operations.push import Push +from slither.slithir.operations.push_array import PushArray + +from slither.slithir.tmp_operations.tmp_call import TmpCall +from slither.slithir.tmp_operations.tmp_new_elementary_type import TmpNewElementaryType +from slither.slithir.tmp_operations.tmp_new_contract import TmpNewContract +from slither.slithir.tmp_operations.tmp_new_array import TmpNewArray +from slither.slithir.tmp_operations.tmp_new_structure import TmpNewStructure +from slither.slithir.tmp_operations.argument import ArgumentType, Argument + +from slither.slithir.operations.call import Call + +from slither.slithir.variables.constant import Constant +from slither.slithir.variables.temporary import TemporaryVariable +from slither.slithir.variables.reference import ReferenceVariable +from slither.slithir.variables.tuple import TupleVariable + +from slither.core.variables.variable import Variable +from slither.core.declarations.solidity_variables import SolidityFunction, SolidityVariableComposed +from slither.core.declarations.event import Event +from slither.core.declarations.structure import Structure +from slither.core.declarations.contract import Contract + +from slither.core.expressions.literal import Literal + +def is_value(ins): + if isinstance(ins, TmpCall): + if isinstance(ins.ori, Member): + if ins.ori.variable_right == 'value': + return True + return False + +def is_gas(ins): + if isinstance(ins, TmpCall): + if isinstance(ins.ori, Member): + if ins.ori.variable_right == 'gas': + return True + return False + +def transform_calls(result): + was_changed = True + + calls = [] + + while was_changed: + # We loop until we do not find any call to value or gas + was_changed = False + + # Find all the assignments + assigments = {} + for i in result: + if isinstance(i, OperationWithLValue): + assigments[i.lvalue.name] = i + if isinstance(i, TmpCall): + if isinstance(i.called, Variable): + ins_ori = assigments[i.called.name] + i.set_ori(ins_ori) + + to_remove = [] + variable_to_replace = {} + + # Replace call to value, gas to an argument of the real call + for idx in range(len(result)): + ins = result[idx] + if is_value(ins): + was_changed = True + result[idx-1].set_type(ArgumentType.VALUE) + result[idx-1].call_id = ins.ori.variable_left.name + calls.append(ins.ori.variable_left) + to_remove.append(ins) + variable_to_replace[ins.lvalue.name] = ins.ori.variable_left + elif is_gas(ins): + was_changed = True + result[idx-1].set_type(ArgumentType.GAS) + result[idx-1].call_id = ins.ori.variable_left.name + calls.append(ins.ori.variable_left) + to_remove.append(ins) + variable_to_replace[ins.lvalue.name] = ins.ori.variable_left + + # Remove the call to value/gas instruction + result = [i for i in result if not i in to_remove] + + # update the real call + for ins in result: + if isinstance(ins, TmpCall): + # use of while if there redirections + while ins.called.name in variable_to_replace: + was_changed = True + ins.call_id = variable_to_replace[ins.called.name].name + calls.append(ins.called) + ins.called = variable_to_replace[ins.called.name] + if isinstance(ins, Argument): + while ins.call_id in variable_to_replace: + was_changed = True + ins.call_id = variable_to_replace[ins.call_id].name + + calls = list(set([str(c) for c in calls])) + idx = 0 + calls_d = {} + for call in calls: + calls_d[str(call)] = idx + idx = idx+1 + + for idx in range(len(result)): + ins = result[idx] + if isinstance(ins, TmpCall): + r = extract_tmp_call(ins) + if r: + result[idx] = r + return result + +def apply_ir_heuristics(result): + """ + Apply a set of heuristic to improve slithIR + """ + + result = transform_calls(result) + + result = remove_unused(result) + + # Move the arguments operation to the call + result = merge_call_parameters(result) + + # Remove temporary + result = remove_temporary(result) + + + reset_variable_number(result) + + return result + +def reset_variable_number(result): + """ + Reset the number associated to slithIR variables + """ + variables = [] + for ins in result: + variables += ins.read + if isinstance(ins, OperationWithLValue): + variables += [ins.lvalue] + + tmp_variables = [v for v in variables if isinstance(v, TemporaryVariable)] + for idx in range(len(tmp_variables)): + tmp_variables[idx].index = idx + ref_variables = [v for v in variables if isinstance(v, ReferenceVariable)] + for idx in range(len(ref_variables)): + ref_variables[idx].index = idx + tuple_variables = [v for v in variables if isinstance(v, TupleVariable)] + for idx in range(len(tuple_variables)): + tuple_variables[idx].index = idx + + +def merge_call_parameters(result): + + calls_value = {} + calls_gas = {} + + call_data = [] + + for ins in result: + if isinstance(ins, Argument): + if ins.get_type() in [ArgumentType.GAS]: + assert not ins.call_id in calls_gas + calls_gas[ins.call_id] = ins.argument + elif ins.get_type() in [ArgumentType.VALUE]: + assert not ins.call_id in calls_value + calls_value[ins.call_id] = ins.argument + else: + assert ins.get_type() == ArgumentType.CALL + call_data.append(ins.argument) + + if isinstance(ins, HighLevelCall): + if ins.call_id in calls_value: + ins.call_value = calls_value[ins.call_id] + if ins.call_id in calls_gas: + ins.call_gas = calls_gas[ins.call_id] + + if isinstance(ins, Call): + ins.arguments = call_data + call_data = [] + return result + +def remove_temporary(result): + result = [ins for ins in result if not isinstance(ins, (Argument, + TmpNewElementaryType, + TmpNewContract, + TmpNewArray, + TmpNewStructure))] + + return result + +def remove_unused(result): + + removed = True + while removed: + removed = False + + to_keep = [] + to_remove = [] + + for ins in result: + to_keep += [str(x) for x in ins.read] + + for ins in result: + if isinstance(ins, Member): + if not ins.lvalue.name in to_keep: + to_remove.append(ins) + removed = True + + result = [i for i in result if not i in to_remove] + return result + + +def replace_calls(result): + ''' + replace call to push to a Push Operation + Replace to call 'call' 'delegatecall', 'callcode' to an LowLevelCall + ''' + for idx in range(len(result)): + ins = result[idx] + if isinstance(ins, HighLevelCall): + if ins.function_name == 'push': + assert len(ins.arguments) == 1 + if isinstance(ins.arguments[0], list): + result[idx] = PushArray(ins.destination, ins.arguments[0]) + else: + result[idx] = Push(ins.destination, ins.arguments[0]) + if ins.function_name in ['call', 'delegatecall', 'callcode']: + result[idx] = LowLevelCall(ins.destination, ins.function_name, ins.nbr_arguments, ins.lvalue, ins.type_call) + + +def extract_tmp_call(ins): + assert isinstance(ins, TmpCall) + if isinstance(ins.ori, Member): + if isinstance(ins.ori.variable_left, Contract): + libcall = LibraryCall(ins.ori.variable_left, ins.ori.variable_right, ins.nbr_arguments, ins.lvalue, ins.type_call) + libcall.call_id = ins.call_id + return libcall + else: + msgcall = HighLevelCall(ins.ori.variable_left, ins.ori.variable_right, ins.nbr_arguments, ins.lvalue, ins.type_call) + msgcall.call_id = ins.call_id + return msgcall + if isinstance(ins.ori, TmpCall): + r = extract_tmp_call(ins.ori) + return r + if isinstance(ins.called, SolidityVariableComposed): + # block.blockhash is the only variable composed which is a call + assert str(ins.called) == 'block.blockhash' + ins.called = SolidityFunction('blockhash(uint256)') + + if isinstance(ins.called, SolidityFunction): + return SolidityCall(ins.called, ins.nbr_arguments, ins.lvalue, ins.type_call) + + if isinstance(ins.ori, TmpNewElementaryType): + return NewElementaryType(ins.ori.type, ins.lvalue) + + if isinstance(ins.ori, TmpNewContract): + return NewContract(Constant(ins.ori.contract_name), ins.lvalue) + + if isinstance(ins.ori, TmpNewArray): + return NewArray(ins.ori.depth, ins.ori.array_type, ins.lvalue) + + if isinstance(ins.called, Structure): + return NewStructure(ins.called, ins.lvalue) + + if isinstance(ins.called, Event): + return EventCall(ins.called.name) + + raise Exception('Not extracted {} {}'.format(type(ins.called), ins)) + +def convert_expression(expression): + # handle standlone expression + # such as return true; + if isinstance(expression, Literal): + print(expression.value) + return [Constant(expression.value)] + visitor = ExpressionToSlithIR(expression) + result = visitor.result() + + result = apply_ir_heuristics(result) + + return result diff --git a/slither/slithir/operations/__init__.py b/slither/slithir/operations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/slithir/operations/assignment.py b/slither/slithir/operations/assignment.py new file mode 100644 index 000000000..70e95620a --- /dev/null +++ b/slither/slithir/operations/assignment.py @@ -0,0 +1,115 @@ +import logging + +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.variables.variable import Variable +from slither.core.declarations.function import Function +from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue + +logger = logging.getLogger("AssignmentOperationIR") + +class AssignmentOperationType(object): + ASSIGN = 0 # = + ASSIGN_OR = 1 # |= + ASSIGN_CARET = 2 # ^= + ASSIGN_AND = 3 # &= + ASSIGN_LEFT_SHIFT = 4 # <<= + ASSIGN_RIGHT_SHIFT = 5 # >>= + ASSIGN_ADDITION = 6 # += + ASSIGN_SUBSTRACTION = 7 # -= + ASSIGN_MULTIPLICATION = 8 # *= + ASSIGN_DIVISION = 9 # /= + ASSIGN_MODULO = 10 # %= + + @staticmethod + def get_type(operation_type): + if operation_type == '=': + return AssignmentOperationType.ASSIGN + if operation_type == '|=': + return AssignmentOperationType.ASSIGN_OR + if operation_type == '^=': + return AssignmentOperationType.ASSIGN_CARET + if operation_type == '&=': + return AssignmentOperationType.ASSIGN_AND + if operation_type == '<<=': + return AssignmentOperationType.ASSIGN_LEFT_SHIFT + if operation_type == '>>=': + return AssignmentOperationType.ASSIGN_RIGHT_SHIFT + if operation_type == '+=': + return AssignmentOperationType.ASSIGN_ADDITION + if operation_type == '-=': + return AssignmentOperationType.ASSIGN_SUBSTRACTION + if operation_type == '*=': + return AssignmentOperationType.ASSIGN_MULTIPLICATION + if operation_type == '/=': + return AssignmentOperationType.ASSIGN_DIVISION + if operation_type == '%=': + return AssignmentOperationType.ASSIGN_MODULO + + logger.error('get_type: Unknown operation type {})'.format(operation_type)) + exit(-1) + + @staticmethod + def str(operation_type): + if operation_type == AssignmentOperationType.ASSIGN: + return '=' + if operation_type == AssignmentOperationType.ASSIGN_OR: + return '|=' + if operation_type == AssignmentOperationType.ASSIGN_CARET: + return '^=' + if operation_type == AssignmentOperationType.ASSIGN_AND: + return '&=' + if operation_type == AssignmentOperationType.ASSIGN_LEFT_SHIFT: + return '<<=' + if operation_type == AssignmentOperationType.ASSIGN_RIGHT_SHIFT: + return '>>=' + if operation_type == AssignmentOperationType.ASSIGN_ADDITION: + return '+=' + if operation_type == AssignmentOperationType.ASSIGN_SUBSTRACTION: + return '-=' + if operation_type == AssignmentOperationType.ASSIGN_MULTIPLICATION: + return '*=' + if operation_type == AssignmentOperationType.ASSIGN_DIVISION: + return '/=' + if operation_type == AssignmentOperationType.ASSIGN_MODULO: + return '%=' + + logger.error('str: Unknown operation type {})'.format(operation_type)) + exit(-1) + +class Assignment(OperationWithLValue): + + def __init__(self, left_variable, right_variable, variable_type, variable_return_type): + #print(type(right_variable)) + #print(type(left_variable)) + assert is_valid_lvalue(left_variable) + assert is_valid_rvalue(right_variable) or\ + (isinstance(right_variable, Function) and variable_type == AssignmentOperationType.ASSIGN) + super(Assignment, self).__init__() + self._variables = [left_variable, right_variable] + self._lvalue = left_variable + self._rvalue = right_variable + self._type = variable_type + self._variable_return_type = variable_return_type + + @property + def variables(self): + return list(self._variables) + + @property + def read(self): + return list(self.variables) + + @property + def variable_return_type(self): + return self._variable_return_type + + @property + def rvalue(self): + return self._rvalue + + @property + def type_str(self): + return AssignmentOperationType.str(self._type) + + def __str__(self): + return '{} {} {}'.format(self.lvalue, self.type_str, self.rvalue) diff --git a/slither/slithir/operations/binary.py b/slither/slithir/operations/binary.py new file mode 100644 index 000000000..b9ed01f59 --- /dev/null +++ b/slither/slithir/operations/binary.py @@ -0,0 +1,150 @@ +import logging +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.variables.variable import Variable +from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue + +logger = logging.getLogger("BinaryOperationIR") + +class BinaryOperationType(object): + POWER = 0 # ** + MULTIPLICATION = 1 # * + DIVISION = 2 # / + MODULO = 3 # % + ADDITION = 4 # + + SUBSTRACTION = 5 # - + LEFT_SHIFT = 6 # << + RIGHT_SHIT = 7 # >>> + AND = 8 # & + CARET = 9 # ^ + OR = 10 # | + LESS = 11 # < + GREATER = 12 # > + LESS_EQUAL = 13 # <= + GREATER_EQUAL = 14 # >= + EQUAL = 15 # == + NOT_EQUAL = 16 # != + ANDAND = 17 # && + OROR = 18 # || + + + @staticmethod + def get_type(operation_type): + if operation_type == '**': + return BinaryOperationType.POWER + if operation_type == '*': + return BinaryOperationType.MULTIPLICATION + if operation_type == '/': + return BinaryOperationType.DIVISION + if operation_type == '%': + return BinaryOperationType.MODULO + if operation_type == '+': + return BinaryOperationType.ADDITION + if operation_type == '-': + return BinaryOperationType.SUBSTRACTION + if operation_type == '<<': + return BinaryOperationType.LEFT_SHIFT + if operation_type == '>>': + return BinaryOperationType.RIGHT_SHIT + if operation_type == '&': + return BinaryOperationType.AND + if operation_type == '^': + return BinaryOperationType.CARET + if operation_type == '|': + return BinaryOperationType.OR + if operation_type == '<': + return BinaryOperationType.LESS + if operation_type == '>': + return BinaryOperationType.GREATER + if operation_type == '<=': + return BinaryOperationType.LESS_EQUAL + if operation_type == '>=': + return BinaryOperationType.GREATER_EQUAL + if operation_type == '==': + return BinaryOperationType.EQUAL + if operation_type == '!=': + return BinaryOperationType.NOT_EQUAL + if operation_type == '&&': + return BinaryOperationType.ANDAND + if operation_type == '||': + return BinaryOperationType.OROR + + logger.error('get_type: Unknown operation type {})'.format(operation_type)) + exit(-1) + + @staticmethod + def str(operation_type): + if operation_type == BinaryOperationType.POWER: + return '**' + if operation_type == BinaryOperationType.MULTIPLICATION: + return '*' + if operation_type == BinaryOperationType.DIVISION: + return '/' + if operation_type == BinaryOperationType.MODULO: + return '%' + if operation_type == BinaryOperationType.ADDITION: + return '+' + if operation_type == BinaryOperationType.SUBSTRACTION: + return '-' + if operation_type == BinaryOperationType.LEFT_SHIFT: + return '<<' + if operation_type == BinaryOperationType.RIGHT_SHIT: + return '>>' + if operation_type == BinaryOperationType.AND: + return '&' + if operation_type == BinaryOperationType.CARET: + return '^' + if operation_type == BinaryOperationType.OR: + return '|' + if operation_type == BinaryOperationType.LESS: + return '<' + if operation_type == BinaryOperationType.GREATER: + return '>' + if operation_type == BinaryOperationType.LESS_EQUAL: + return '<=' + if operation_type == BinaryOperationType.GREATER_EQUAL: + return '>=' + if operation_type == BinaryOperationType.EQUAL: + return '==' + if operation_type == BinaryOperationType.NOT_EQUAL: + return '!=' + if operation_type == BinaryOperationType.ANDAND: + return '&&' + if operation_type == BinaryOperationType.OROR: + return '||' + logger.error('str: Unknown operation type {})'.format(operation_type)) + exit(-1) + +class BinaryOperation(OperationWithLValue): + + def __init__(self, result, left_variable, right_variable, operation_type): + assert is_valid_rvalue(left_variable) +# print(right_variable) + assert is_valid_rvalue(right_variable) + assert is_valid_lvalue(result) + super(BinaryOperation, self).__init__() + self._variables = [left_variable, right_variable] + self._type = operation_type + self._lvalue = result + + @property + def read(self): + return [self.variable_left, self.variable_right] + + @property + def get_variable(self): + return self._variables + + @property + def variable_left(self): + return self._variables[0] + + @property + def variable_right(self): + return self._variables[1] + + @property + def type_str(self): + return BinaryOperationType.str(self._type) + + def __str__(self): + return str(self.lvalue)+ ' = ' + str(self.variable_left) + ' ' + self.type_str + ' ' + str(self.variable_right) diff --git a/slither/slithir/operations/call.py b/slither/slithir/operations/call.py new file mode 100644 index 000000000..25d929c92 --- /dev/null +++ b/slither/slithir/operations/call.py @@ -0,0 +1,17 @@ + +from slither.slithir.operations.operation import Operation + +class Call(Operation): + + def __init__(self): + super(Call, self).__init__() + self._arguments = [] + + @property + def arguments(self): + return self._arguments + + @arguments.setter + def arguments(self, v): + self._arguments = v + diff --git a/slither/slithir/operations/delete.py b/slither/slithir/operations/delete.py new file mode 100644 index 000000000..02f10809f --- /dev/null +++ b/slither/slithir/operations/delete.py @@ -0,0 +1,27 @@ +import logging +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.variables.variable import Variable + +from slither.slithir.utils.utils import is_valid_lvalue +class Delete(OperationWithLValue): + """ + Delete has a lvalue, as it has for effect to change the value + of its operand + """ + + def __init__(self, variable): + assert is_valid_lvalue(variable) + super(Delete, self).__init__() + self._variable = variable + self._lvalue = variable + + @property + def read(self): + return [self.variable] + + @property + def variable(self): + return self._variable + + def __str__(self): + return "{} = delete {} ".format(self.lvalue, self.variable) diff --git a/slither/slithir/operations/event_call.py b/slither/slithir/operations/event_call.py new file mode 100644 index 000000000..acffe5970 --- /dev/null +++ b/slither/slithir/operations/event_call.py @@ -0,0 +1,21 @@ + +from slither.slithir.operations.call import Call +from slither.core.variables.variable import Variable + +class EventCall(Call): + def __init__(self, name): + super(EventCall, self).__init__() + self._name = name + # todo add instance of the Event + + @property + def name(self): + return self._name + + @property + def read(self): + return list(self.arguments) + + def __str__(self): + args = [str(a) for a in self.arguments] + return 'Emit {}({})'.format(self.name, '.'.join(args)) diff --git a/slither/slithir/operations/high_level_call.py b/slither/slithir/operations/high_level_call.py new file mode 100644 index 000000000..a77bb785b --- /dev/null +++ b/slither/slithir/operations/high_level_call.py @@ -0,0 +1,93 @@ +from slither.slithir.operations.call import Call +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.variables.variable import Variable +from slither.core.declarations.solidity_variables import SolidityVariable + +from slither.slithir.utils.utils import is_valid_lvalue +from slither.slithir.variables.constant import Constant + +class HighLevelCall(Call, OperationWithLValue): + """ + High level message call + """ + + def __init__(self, destination, function_name, nbr_arguments, result, type_call): + assert isinstance(function_name, Constant) + assert is_valid_lvalue(result) + self._check_destination(destination) + super(HighLevelCall, self).__init__() + self._destination = destination + self._function_name = function_name + self._nbr_arguments = nbr_arguments + self._type_call = type_call + self._lvalue = result + self._callid = None # only used if gas/value != 0 + + self._call_value = None + self._call_gas = None + + # Development function, to be removed once the code is stable + # It is ovveride by LbraryCall + def _check_destination(self, destination): + assert isinstance(destination, (Variable, SolidityVariable)) + + @property + def call_id(self): + return self._callid + + @call_id.setter + def call_id(self, c): + self._callid = c + + @property + def call_value(self): + return self._call_value + + @call_value.setter + def call_value(self, v): + self._call_value = v + + @property + def call_gas(self): + return self._call_gas + + @call_gas.setter + def call_gas(self, v): + self._call_gas = v + + @property + def read(self): + return [self.destination] + + @property + def destination(self): + return self._destination + + @property + def function_name(self): + return self._function_name + + @property + def nbr_arguments(self): + return self._nbr_arguments + + @property + def type_call(self): + return self._type_call + + def __str__(self): + value = '' + gas = '' + if self.call_value: + value = 'value:{}'.format(self.call_value) + if self.call_gas: + gas = 'gas:{}'.format(self.call_gas) + arguments = [] + if self.arguments: + arguments = self.arguments + return str(self.lvalue) +' = HIGH_LEVEL_CALL dest:{} function:{} arguments:{} {} {}'.format(self.destination, self.function_name, [str(x) for x in arguments], value, gas) +# if self.call_id: +# call_id = '(id ({}))'.format(self.call_id) +# return str(self.lvalue) +' = EXTERNALCALL dest:{} function:{} (#arg {}) {}'.format(self.destination, self.function_name, self.nbr_arguments) + + diff --git a/slither/slithir/operations/index.py b/slither/slithir/operations/index.py new file mode 100644 index 000000000..f63651fe8 --- /dev/null +++ b/slither/slithir/operations/index.py @@ -0,0 +1,36 @@ +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.variables.variable import Variable +from slither.slithir.variables.reference import ReferenceVariable + +from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue + + +class Index(OperationWithLValue): + + def __init__(self, result, left_variable, right_variable, index_type): + super(Index, self).__init__() + assert is_valid_lvalue(left_variable) + assert is_valid_rvalue(right_variable) + assert isinstance(result, ReferenceVariable) + self._variables = [left_variable, right_variable] + self._type = index_type + self._lvalue = result + + @property + def read(self): + return list(self.variables) + + @property + def variables(self): + return self._variables + + @property + def variable_left(self): + return self._variables[0] + + @property + def variable_right(self): + return self._variables[1] + + def __str__(self): + return "{} -> {}[{}]".format(self.lvalue, self.variable_left, self.variable_right) diff --git a/slither/slithir/operations/init_array.py b/slither/slithir/operations/init_array.py new file mode 100644 index 000000000..8df153dd5 --- /dev/null +++ b/slither/slithir/operations/init_array.py @@ -0,0 +1,22 @@ +import logging +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.variables.variable import Variable +from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue + +class InitArray(OperationWithLValue): + + def __init__(self, init_values, lvalue): + assert all(is_valid_rvalue(v) for v in init_values) + self._init_values = init_values + self._lvalue = lvalue + + @property + def read(self): + return list(self.init_values) + + @property + def init_values(self): + return list(self._init_values) + + def __str__(self): + return "{} = {}".format(self.lvalue, [str(x) for x in self.init_values]) diff --git a/slither/slithir/operations/internal_call.py b/slither/slithir/operations/internal_call.py new file mode 100644 index 000000000..ed69f9cf9 --- /dev/null +++ b/slither/slithir/operations/internal_call.py @@ -0,0 +1,38 @@ +from slither.core.declarations.function import Function +from slither.slithir.operations.call import Call +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.variables.variable import Variable + + +class InternalCall(Call, OperationWithLValue): + + def __init__(self, function, nbr_arguments, result, type_call): + assert isinstance(function, Function) + super(InternalCall, self).__init__() + self._function = function + self._nbr_arguments = nbr_arguments + self._type_call = type_call + self._lvalue = result + + @property + def read(self): + return [] + + @property + def function(self): + return self._function + + @property + def nbr_arguments(self): + return self._nbr_arguments + + @property + def type_call(self): + return self._type_call + + def __str__(self): + args = [str(a) for a in self.arguments] + return str(self.lvalue) +' = INTERNAL_CALL {}.{} ({}) '.format(self.function.contract.name, self.function.full_name, ','.join(args)) + # return str(self.lvalue) +' = INTERNALCALL {} (arg {})'.format(self.function, + # self.nbr_arguments) + diff --git a/slither/slithir/operations/library_call.py b/slither/slithir/operations/library_call.py new file mode 100644 index 000000000..27bc59f6f --- /dev/null +++ b/slither/slithir/operations/library_call.py @@ -0,0 +1,27 @@ +from slither.slithir.operations.high_level_call import HighLevelCall +from slither.core.declarations.contract import Contract + + +# TODO: use the usefor declaration + +class LibraryCall(HighLevelCall): + """ + High level message call + """ + # Development function, to be removed once the code is stable + def _check_destination(self, destination): + assert isinstance(destination, (Contract)) + + def __str__(self): + gas = '' + if self.call_gas: + gas = 'gas:{}'.format(self.call_gas) + arguments = [] + if self.arguments: + arguments = self.arguments + return str(self.lvalue) +' = LIBRARY_CALL dest:{} function:{} arguments:{} {}'.format(self.destination, self.function_name, [str(x) for x in arguments], gas) +# if self.call_id: +# call_id = '(id ({}))'.format(self.call_id) +# return str(self.lvalue) +' = EXTERNALCALL dest:{} function:{} (#arg {}) {}'.format(self.destination, self.function_name, self.nbr_arguments) + + diff --git a/slither/slithir/operations/low_level_call.py b/slither/slithir/operations/low_level_call.py new file mode 100644 index 000000000..f883db279 --- /dev/null +++ b/slither/slithir/operations/low_level_call.py @@ -0,0 +1,82 @@ +from slither.slithir.operations.call import Call +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.variables.variable import Variable +from slither.core.declarations.solidity_variables import SolidityVariableComposed + +from slither.slithir.variables.constant import Constant + +class LowLevelCall(Call, OperationWithLValue): + """ + High level message call + """ + + def __init__(self, destination, function_name, nbr_arguments, result, type_call): + assert isinstance(destination, (Variable, SolidityVariableComposed)) + assert isinstance(function_name, Constant) + super(LowLevelCall, self).__init__() + self._destination = destination + self._function_name = function_name + self._nbr_arguments = nbr_arguments + self._type_call = type_call + self._lvalue = result + self._callid = None # only used if gas/value != 0 + + self._call_value = None + self._call_gas = None + + @property + def call_id(self): + return self._callid + + @call_id.setter + def call_id(self, c): + self._callid = c + + @property + def call_value(self): + return self._call_value + + @call_value.setter + def call_value(self, v): + self._call_value = v + + @property + def call_gas(self): + return self._call_gas + + @call_gas.setter + def call_gas(self, v): + self._call_gas = v + + @property + def read(self): + return [self.destination] + + @property + def destination(self): + return self._destination + + @property + def function_name(self): + return self._function_name + + @property + def nbr_arguments(self): + return self._nbr_arguments + + @property + def type_call(self): + return self._type_call + + def __str__(self): + value = '' + gas = '' + if self.call_value: + value = 'value:{}'.format(self.call_value) + if self.call_gas: + gas = 'gas:{}'.format(self.call_gas) + arguments = [] + if self.arguments: + arguments = self.arguments + return str(self.lvalue) +' = LOW_LEVEL_CALL dest:{} function:{} arguments:{} {} {}'.format(self.destination, self.function_name, [str(x) for x in arguments], value, gas) + diff --git a/slither/slithir/operations/lvalue.py b/slither/slithir/operations/lvalue.py new file mode 100644 index 000000000..ccea68249 --- /dev/null +++ b/slither/slithir/operations/lvalue.py @@ -0,0 +1,15 @@ +from slither.slithir.operations.operation import Operation + +class OperationWithLValue(Operation): + ''' + Operation with a lvalue + ''' + + def __init__(self): + super(OperationWithLValue, self).__init__() + + self._lvalue = None + + @property + def lvalue(self): + return self._lvalue diff --git a/slither/slithir/operations/member.py b/slither/slithir/operations/member.py new file mode 100644 index 000000000..909be69ad --- /dev/null +++ b/slither/slithir/operations/member.py @@ -0,0 +1,37 @@ +from slither.core.expressions.expression import Expression +from slither.core.expressions.expression_typed import ExpressionTyped +from slither.core.solidity_types.type import Type +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue +from slither.slithir.variables.reference import ReferenceVariable +from slither.slithir.variables.constant import Constant + +from slither.core.declarations.contract import Contract +from slither.core.declarations.enum import Enum + +class Member(OperationWithLValue): + + def __init__(self, variable_left, variable_right, result): + assert is_valid_rvalue(variable_left) or isinstance(variable_left, (Contract, Enum)) + assert isinstance(variable_right, Constant) + assert isinstance(result, ReferenceVariable) + super(Member, self).__init__() + self._variable_left = variable_left + self._variable_right = variable_right + self._lvalue = result + + @property + def read(self): + return [self.variable_left, self.variable_right] + + @property + def variable_left(self): + return self._variable_left + + @property + def variable_right(self): + return self._variable_right + + def __str__(self): + return str(self.lvalue) + ' -> ' + str(self.variable_left) + '.' + str(self.variable_right) + diff --git a/slither/slithir/operations/new_array.py b/slither/slithir/operations/new_array.py new file mode 100644 index 000000000..91383b00c --- /dev/null +++ b/slither/slithir/operations/new_array.py @@ -0,0 +1,29 @@ +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.slithir.operations.call import Call +from slither.core.solidity_types.type import Type + +class NewArray(Call, OperationWithLValue): + + def __init__(self, depth, array_type, lvalue): + super(NewArray, self).__init__() + assert isinstance(array_type, Type) + self._depth = depth + self._array_type = array_type + + self._lvalue = lvalue + + @property + def array_type(self): + return self._array_type + + @property + def read(self): + return list(self.arguments) + + @property + def depth(self): + return self._depth + + def __str__(self): + args = [str(a) for a in self.arguments] + return '{} = new {}{}({})'.format(self.lvalue, self.array_type, '[]'*self.depth, ','.join(args)) diff --git a/slither/slithir/operations/new_contract.py b/slither/slithir/operations/new_contract.py new file mode 100644 index 000000000..1e0452180 --- /dev/null +++ b/slither/slithir/operations/new_contract.py @@ -0,0 +1,30 @@ +from slither.slithir.operations.call import Call +from slither.slithir.operations.lvalue import OperationWithLValue + +from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue + +from slither.core.declarations.contract import Contract +from slither.slithir.variables.constant import Constant + +class NewContract(Call, OperationWithLValue): + + def __init__(self, contract_name, lvalue): + print(contract_name) + print(type(contract_name)) + assert isinstance(contract_name, Constant) + assert is_valid_lvalue(lvalue) + super(NewContract, self).__init__() + self._contract_name = contract_name + # todo create analyze to add the contract instance + self._lvalue = lvalue + + @property + def contract_name(self): + return self._contract_name + + @property + def read(self): + return list(self.arguments) + def __str__(self): + args = [str(a) for a in self.arguments] + return '{} = new {}({})'.format(self.lvalue, self.contract_name, ','.join(args)) diff --git a/slither/slithir/operations/new_elementary_type.py b/slither/slithir/operations/new_elementary_type.py new file mode 100644 index 000000000..90714abb6 --- /dev/null +++ b/slither/slithir/operations/new_elementary_type.py @@ -0,0 +1,27 @@ +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.slithir.operations.call import Call +from slither.core.solidity_types.elementary_type import ElementaryType + +from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue + +class NewElementaryType(Call, OperationWithLValue): + + def __init__(self, new_type, lvalue): + assert isinstance(new_type, ElementaryType) + assert is_valid_lvalue(lvalue) + super(NewElementaryType, self).__init__() + self._type = new_type + self._lvalue = lvalue + + @property + def type(self): + return self._type + + @property + def read(self): + return list(self.arguments) + + def __str__(self): + args = [str(a) for a in self.arguments] + + return '{} = new {}({})'.format(self.lvalue, self._type, ','.join(args)) diff --git a/slither/slithir/operations/new_structure.py b/slither/slithir/operations/new_structure.py new file mode 100644 index 000000000..cfb93e531 --- /dev/null +++ b/slither/slithir/operations/new_structure.py @@ -0,0 +1,28 @@ +from slither.slithir.operations.call import Call +from slither.slithir.operations.lvalue import OperationWithLValue + +from slither.slithir.utils.utils import is_valid_lvalue + +from slither.core.declarations.structure import Structure + +class NewStructure(Call, OperationWithLValue): + + def __init__(self, structure_name, lvalue): + super(NewStructure, self).__init__() + assert isinstance(structure_name, Structure) + assert is_valid_lvalue(lvalue) + self._structure_name = structure_name + # todo create analyze to add the contract instance + self._lvalue = lvalue + + @property + def read(self): + return list(self.arguments) + + @property + def structure_name(self): + return self._structure_name + + def __str__(self): + args = [str(a) for a in self.arguments] + return '{} = new {}({})'.format(self.lvalue, self.structure_name, ','.join(args)) diff --git a/slither/slithir/operations/operation.py b/slither/slithir/operations/operation.py new file mode 100644 index 000000000..01b5a72f8 --- /dev/null +++ b/slither/slithir/operations/operation.py @@ -0,0 +1,8 @@ +class Operation(object): + + @property + def read(self): + """ + Must be ovveriden + """ + raise Exception('Not overrided {}'.format(type(self))) diff --git a/slither/slithir/operations/push.py b/slither/slithir/operations/push.py new file mode 100644 index 000000000..19d34aa7f --- /dev/null +++ b/slither/slithir/operations/push.py @@ -0,0 +1,26 @@ +import logging +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.variables.variable import Variable +from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue + +class Push(OperationWithLValue): + + def __init__(self, array, value): + assert is_valid_rvalue(value) + assert is_valid_lvalue(array) + self._value = value + self._lvalue = array + + @property + def read(self): + return [self._value] + + @property + def array(self): + return self._lvalue + + def value(self): + return self._value + + def __str__(self): + return "PUSH {} in {}".format(self.value, self.lvalue) diff --git a/slither/slithir/operations/push_array.py b/slither/slithir/operations/push_array.py new file mode 100644 index 000000000..6d1b1152a --- /dev/null +++ b/slither/slithir/operations/push_array.py @@ -0,0 +1,27 @@ +import logging +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.variables.variable import Variable +from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue + +class PushArray(OperationWithLValue): + + def __init__(self, array, values): + assert all(is_valid_rvalue(value) for value in values) + assert is_valid_lvalue(array) + self._values = values + self._lvalue = array + + @property + def read(self): + return list(self._values) + + @property + def array(self): + return self._lvalue + + @property + def values(self): + return list(self._values) + + def __str__(self): + return "PUSH_ARRAY {} in {}".format([str(x) for x in self.values], self.lvalue) diff --git a/slither/slithir/operations/solidity_call.py b/slither/slithir/operations/solidity_call.py new file mode 100644 index 000000000..3e791ce0c --- /dev/null +++ b/slither/slithir/operations/solidity_call.py @@ -0,0 +1,38 @@ +from slither.core.declarations.solidity_variables import SolidityFunction +from slither.slithir.operations.call import Call +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.variables.variable import Variable + + +class SolidityCall(Call, OperationWithLValue): + + def __init__(self, function, nbr_arguments, result, type_call): + assert isinstance(function, SolidityFunction) + super(SolidityCall, self).__init__() + self._function = function + self._nbr_arguments = nbr_arguments + self._type_call = type_call + self._lvalue = result + + @property + def read(self): + return [] + + @property + def function(self): + return self._function + + @property + def nbr_arguments(self): + return self._nbr_arguments + + @property + def type_call(self): + return self._type_call + + def __str__(self): + args = [str(a) for a in self.arguments] + return str(self.lvalue) +' = SOLIDITY_CALL {}({})'.format(self.function.full_name, '.'.join(args)) + # return str(self.lvalue) +' = INTERNALCALL {} (arg {})'.format(self.function, + # self.nbr_arguments) + diff --git a/slither/slithir/operations/type_conversion.py b/slither/slithir/operations/type_conversion.py new file mode 100644 index 000000000..661f38e1a --- /dev/null +++ b/slither/slithir/operations/type_conversion.py @@ -0,0 +1,33 @@ +from slither.slithir.operations.lvalue import OperationWithLValue + +from slither.core.variables.variable import Variable + +from slither.core.solidity_types.type import Type +from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue + +class TypeConversion(OperationWithLValue): + + def __init__(self, result, variable, variable_type): + assert is_valid_rvalue(variable) + assert is_valid_lvalue(result) + assert isinstance(variable_type, Type) + + self._variable = variable + self._type = variable_type + self._lvalue = result + + + @property + def variable(self): + return self._variable + + @property + def type(self): + return self._type + + @property + def read(self): + return [self.variable] + + def __str__(self): + return str(self.lvalue) +' = CONVERT {} to {}'.format(self.variable, self.type) diff --git a/slither/slithir/operations/unary.py b/slither/slithir/operations/unary.py new file mode 100644 index 000000000..68e15bdbb --- /dev/null +++ b/slither/slithir/operations/unary.py @@ -0,0 +1,56 @@ +import logging +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.variables.variable import Variable + +from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue + +logger = logging.getLogger("BinaryOperationIR") + +class UnaryOperationType: + BANG = 0 # ! + TILD = 1 # ~ + + @staticmethod + def get_type(operation_type, isprefix): + if isprefix: + if operation_type == '!': + return UnaryOperationType.BANG + if operation_type == '~': + return UnaryOperationType.TILD + logger.error('get_type: Unknown operation type {}'.format(operation_type)) + exit(-1) + + @staticmethod + def str(operation_type): + if operation_type == UnaryOperationType.BANG: + return '!' + if operation_type == UnaryOperationType.TILD: + return '~' + + logger.error('str: Unknown operation type {}'.format(operation_type)) + exit(-1) + +class UnaryOperation(OperationWithLValue): + + def __init__(self, result, variable, operation_type): + assert is_valid_rvalue(variable) + assert is_valid_lvalue(result) + super(UnaryOperation, self).__init__() + self._variable = variable + self._type = operation_type + self._lvalue = result + + @property + def read(self): + return [self.variable] + + @property + def variable(self): + return self._variable + + @property + def type_str(self): + return UnaryOperationType.str(self._type) + + def __str__(self): + return "{} = {} {} ".format(self.lvalue, self.type_str, self.variable) diff --git a/slither/slithir/operations/unpack.py b/slither/slithir/operations/unpack.py new file mode 100644 index 000000000..ec9c86385 --- /dev/null +++ b/slither/slithir/operations/unpack.py @@ -0,0 +1,31 @@ +import logging +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.slithir.variables.tuple import TupleVariable + +from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue + +class Unpack(OperationWithLValue): + + def __init__(self, result, tuple_var, idx): + assert is_valid_lvalue(result) + assert isinstance(tuple_var, TupleVariable) + assert isinstance(idx, int) + super(Unpack, self).__init__() + self._tuple = tuple_var + self._idx = idx + self._lvalue = result + + @property + def read(self): + return [self.tuple] + + @property + def tuple(self): + return self._tuple + + @property + def index(self): + return self._idx + + def __str__(self): + return "{} = UNPACK {} index: {} ".format(self.lvalue, self.tuple, self.index) diff --git a/slither/slithir/tmp_operations/__init__.py b/slither/slithir/tmp_operations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/slithir/tmp_operations/argument.py b/slither/slithir/tmp_operations/argument.py new file mode 100644 index 000000000..4c04924c2 --- /dev/null +++ b/slither/slithir/tmp_operations/argument.py @@ -0,0 +1,47 @@ +from enum import Enum +from slither.slithir.operations.operation import Operation + +class ArgumentType(Enum): + CALL = 0 + VALUE = 1 + GAS = 2 + DATA = 3 + +class Argument(Operation): + + def __init__(self, argument): + super(Argument, self).__init__() + self._argument = argument + self._type = ArgumentType.CALL + self._callid = None + + + @property + def argument(self): + return self._argument + + @property + def call_id(self): + return self._callid + + @call_id.setter + def call_id(self, c): + self._callid = c + + @property + def read(self): + return [self.argument] + + def set_type(self, t): + assert isinstance(t, ArgumentType) + self._type = t + + def get_type(self): + return self._type + + def __str__(self): + call_id = 'none' + if self.call_id: + call_id = '(id ({}))'.format(self.call_id) + return 'ARG_{} {} {}'.format(self._type.name, str(self._argument), call_id) + diff --git a/slither/slithir/tmp_operations/tmp_call.py b/slither/slithir/tmp_operations/tmp_call.py new file mode 100644 index 000000000..0d7fda1e9 --- /dev/null +++ b/slither/slithir/tmp_operations/tmp_call.py @@ -0,0 +1,65 @@ +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.variables.variable import Variable +from slither.core.declarations.solidity_variables import SolidityVariableComposed, SolidityFunction +from slither.core.declarations.structure import Structure +from slither.core.declarations.event import Event + + +class TmpCall(OperationWithLValue): + + def __init__(self, called, nbr_arguments, result, type_call): + assert isinstance(called, (Variable, + SolidityVariableComposed, + SolidityFunction, + Structure, + Event)) + super(TmpCall, self).__init__() + self._called = called + self._nbr_arguments = nbr_arguments + self._type_call = type_call + self._lvalue = result + self._ori = None # + self._callid = None + + @property + def call_id(self): + return self._callid + + @property + def read(self): + return [self.called] + + @call_id.setter + def call_id(self, c): + self._callid = c + + @property + def called(self): + return self._called + + @property + def read(self): + return [self.called] + + @called.setter + def called(self, c): + self._called = c + + @property + def nbr_arguments(self): + return self._nbr_arguments + + @property + def type_call(self): + return self._type_call + + @property + def ori(self): + return self._ori + + def set_ori(self, ori): + self._ori = ori + + def __str__(self): + return str(self.lvalue) +' = TMPCALL{} '.format(self.nbr_arguments)+ str(self._called) + diff --git a/slither/slithir/tmp_operations/tmp_new_array.py b/slither/slithir/tmp_operations/tmp_new_array.py new file mode 100644 index 000000000..e4deb4fe7 --- /dev/null +++ b/slither/slithir/tmp_operations/tmp_new_array.py @@ -0,0 +1,27 @@ +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.solidity_types.type import Type + +class TmpNewArray(OperationWithLValue): + + def __init__(self, depth, array_type, lvalue): + super(TmpNewArray, self).__init__() + assert isinstance(array_type, Type) + self._depth = depth + self._array_type = array_type + self._lvalue = lvalue + + @property + def array_type(self): + return self._array_type + + @property + def read(self): + return [] + + @property + def depth(self): + return self._depth + + def __str__(self): + return '{} = new {}{}'.format(self.lvalue, self.array_type, '[]'*self._depth) + diff --git a/slither/slithir/tmp_operations/tmp_new_contract.py b/slither/slithir/tmp_operations/tmp_new_contract.py new file mode 100644 index 000000000..35521d068 --- /dev/null +++ b/slither/slithir/tmp_operations/tmp_new_contract.py @@ -0,0 +1,20 @@ +from slither.slithir.operations.lvalue import OperationWithLValue + +class TmpNewContract(OperationWithLValue): + + def __init__(self, contract_name, lvalue): + super(TmpNewContract, self).__init__() + self._contract_name = contract_name + self._lvalue = lvalue + + @property + def contract_name(self): + return self._contract_name + + @property + def read(self): + return [] + + def __str__(self): + return '{} = new {}'.format(self.lvalue, self.contract_name) + diff --git a/slither/slithir/tmp_operations/tmp_new_elementary_type.py b/slither/slithir/tmp_operations/tmp_new_elementary_type.py new file mode 100644 index 000000000..1fcb65334 --- /dev/null +++ b/slither/slithir/tmp_operations/tmp_new_elementary_type.py @@ -0,0 +1,21 @@ +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.solidity_types.elementary_type import ElementaryType + +class TmpNewElementaryType(OperationWithLValue): + + def __init__(self, new_type, lvalue): + assert isinstance(new_type, ElementaryType) + super(TmpNewElementaryType, self).__init__() + self._type = new_type + self._lvalue = lvalue + + @property + def read(self): + return [] + + @property + def type(self): + return self._type + + def __str__(self): + return '{} = new {}'.format(self.lvalue, self._type) diff --git a/slither/slithir/utils/__init__.py b/slither/slithir/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/slithir/utils/utils.py b/slither/slithir/utils/utils.py new file mode 100644 index 000000000..74ef1b2cf --- /dev/null +++ b/slither/slithir/utils/utils.py @@ -0,0 +1,16 @@ +from slither.core.variables.local_variable import LocalVariable +from slither.core.variables.state_variable import StateVariable + +from slither.core.declarations.solidity_variables import SolidityVariable + +from slither.slithir.variables.temporary import TemporaryVariable +from slither.slithir.variables.constant import Constant +from slither.slithir.variables.reference import ReferenceVariable +from slither.slithir.variables.tuple import TupleVariable + +def is_valid_rvalue(v): + return isinstance(v, (StateVariable, LocalVariable, TemporaryVariable, Constant, SolidityVariable, ReferenceVariable)) + +def is_valid_lvalue(v): + return isinstance(v, (StateVariable, LocalVariable, TemporaryVariable, ReferenceVariable, TupleVariable)) + diff --git a/slither/slithir/variables/__init__.py b/slither/slithir/variables/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/slithir/variables/call.py b/slither/slithir/variables/call.py new file mode 100644 index 000000000..e8624cc46 --- /dev/null +++ b/slither/slithir/variables/call.py @@ -0,0 +1,19 @@ + +from slither.core.variables.variable import Variable +from slither.core.children.child_node import ChildNode + +class TemporaryVariable(ChildNode, Variable): + + COUNTER = 0 + + def __init__(self): + super(TemporaryVariable, self).__init__() + self._index = TemporaryVariable.COUNTER + TemporaryVariable.COUNTER += 1 + + @property + def index(self): + return self._index + + def __str__(self): + return 'TMP_{}'.format(self.index) diff --git a/slither/slithir/variables/constant.py b/slither/slithir/variables/constant.py new file mode 100644 index 000000000..5ead63214 --- /dev/null +++ b/slither/slithir/variables/constant.py @@ -0,0 +1,30 @@ +from slither.core.variables.variable import Variable + +class Constant(Variable): + + def __init__(self, val): + assert isinstance(val, str) + if val.isdigit(): + self._type = 'uint256' + self._val = int(val) + else: + self._type = 'string' + self._val = val + + @property + def value(self): + ''' + Return the value. + If the expression was an hexadecimal delcared as hex'...' + return a str + Returns: + (str, int) + ''' + return self._val + + def __str__(self): + return str(self.value) + + + def __eq__(self, other): + return self.value == other diff --git a/slither/slithir/variables/reference.py b/slither/slithir/variables/reference.py new file mode 100644 index 000000000..18f33733e --- /dev/null +++ b/slither/slithir/variables/reference.py @@ -0,0 +1,27 @@ + +from slither.core.variables.variable import Variable +from slither.core.children.child_node import ChildNode + +class ReferenceVariable(ChildNode, Variable): + + COUNTER = 0 + + def __init__(self): + super(ReferenceVariable, self).__init__() + self._index = ReferenceVariable.COUNTER + ReferenceVariable.COUNTER += 1 + + @property + def index(self): + return self._index + + @index.setter + def index(self, idx): + self._index = idx + + @property + def name(self): + return 'REF_{}'.format(self.index) + + def __str__(self): + return self.name diff --git a/slither/slithir/variables/temporary.py b/slither/slithir/variables/temporary.py new file mode 100644 index 000000000..910ff51b1 --- /dev/null +++ b/slither/slithir/variables/temporary.py @@ -0,0 +1,27 @@ + +from slither.core.variables.variable import Variable +from slither.core.children.child_node import ChildNode + +class TemporaryVariable(ChildNode, Variable): + + COUNTER = 0 + + def __init__(self): + super(TemporaryVariable, self).__init__() + self._index = TemporaryVariable.COUNTER + TemporaryVariable.COUNTER += 1 + + @property + def index(self): + return self._index + + @index.setter + def index(self, idx): + self._index = idx + + @property + def name(self): + return 'TMP_{}'.format(self.index) + + def __str__(self): + return self.name diff --git a/slither/slithir/variables/tuple.py b/slither/slithir/variables/tuple.py new file mode 100644 index 000000000..7abe34a25 --- /dev/null +++ b/slither/slithir/variables/tuple.py @@ -0,0 +1,26 @@ + +from slither.core.variables.variable import Variable + +class TupleVariable(Variable): + + COUNTER = 0 + + def __init__(self): + super(TupleVariable, self).__init__() + self._index = TupleVariable.COUNTER + TupleVariable.COUNTER += 1 + + @property + def index(self): + return self._index + + @index.setter + def index(self, idx): + self._index = idx + + @property + def name(self): + return 'TUPLE_{}'.format(self.index) + + def __str__(self): + return self.name diff --git a/slither/solc_parsing/cfg/node.py b/slither/solc_parsing/cfg/node.py index 45c381c0a..f72e7aaa7 100644 --- a/slither/solc_parsing/cfg/node.py +++ b/slither/solc_parsing/cfg/node.py @@ -12,6 +12,7 @@ from slither.core.declarations.function import Function from slither.core.variables.state_variable import StateVariable from slither.core.expressions.identifier import Identifier +from slither.core.expressions.assignment_operation import AssignmentOperation, AssignmentOperationType class NodeSolc(Node): @@ -32,6 +33,15 @@ class NodeSolc(Node): self._unparsed_expression = None if self.expression: + + if self.type == NodeType.VARIABLE: + # Update the expression to be an assignement to the variable + #print(self.variable_declaration) + self._expression = AssignmentOperation(Identifier(self.variable_declaration), + self.expression, + AssignmentOperationType.ASSIGN, + self.variable_declaration.type) + expression = self.expression pp = ReadVar(expression) self._expression_vars_read = pp.result() @@ -57,4 +67,3 @@ class NodeSolc(Node): self._external_calls = [c for c in self.calls_as_expression if not isinstance(c.called, Identifier)] - diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index b6f5113c9..524688b29 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -3,6 +3,7 @@ """ import logging from slither.core.declarations.function import Function +from slither.core.cfg.node import NodeType from slither.solc_parsing.cfg.node import NodeSolc from slither.core.cfg.node import NodeType from slither.core.cfg.node import link_nodes @@ -240,7 +241,6 @@ class FunctionSolc(Function): return new_node except MultipleVariablesDeclaration: # Custom handling of var (a,b) = .. style declaration - # We split the variabledeclaration in multiple declarations count = 0 children = statement['children'] child = children[0] @@ -270,18 +270,48 @@ class FunctionSolc(Function): else: # If we have # var (a, b) = f() - # we can split in multiple declarations, keep the init value and use LocalVariableSolc - # We use LocalVariableInitFromTupleSolc class + # we can split in multiple declarations, without init + # Then we craft one expression that does not assignment assert tuple_vars['name'] in ['FunctionCall', 'Conditional'] + variables = [] for variable in variables_declaration: src = variable['src'] i= i+1 # Create a fake statement to be consistent new_statement = {'name':'VariableDefinitionStatement', 'src': src, - 'children':[variable, tuple_vars]} + 'children':[variable]} + variables.append(variable) new_node = self._parse_variable_definition_init_tuple(new_statement, i, new_node) + var_identifiers = [] + # craft of the expression doing the assignement + for v in variables: + identifier = { + 'name' : 'Identifier', + 'src': v['src'], + 'attributes': { + 'value': v['attributes']['name'], + 'type': v['attributes']['type']} + } + var_identifiers.append(identifier) + + expression = { + 'name' : 'Assignment', + 'src':statement['src'], + 'attributes': {'operator': '=', + 'type':'tuple()'}, + 'children': + [{'name': 'TupleExpression', + 'src': statement['src'], + 'children': var_identifiers}, + tuple_vars]} + node = new_node + new_node = self._new_node(NodeType.EXPRESSION) + new_node.add_unparsed_expression(expression) + link_nodes(node, new_node) + + return new_node def _parse_variable_definition_init_tuple(self, statement, index, node): @@ -396,6 +426,7 @@ class FunctionSolc(Function): self._is_empty = False self._parse_block(cfg, node) self._remove_incorrect_edges() + self._remove_alone_endif() def _find_end_loop(self, node, visited): if node in visited: @@ -462,6 +493,18 @@ class FunctionSolc(Function): if node.type in [NodeType.CONTINUE]: self._fix_continue_node(node) + def _remove_alone_endif(self): + """ + Can occur on: + if(..){ + return + } + else{ + return + } + + """ + self._nodes = [n for n in self.nodes if n.type != NodeType.ENDIF or n.sons or n.fathers] def _parse_params(self, params): @@ -557,21 +600,19 @@ class FunctionSolc(Function): for node in self.nodes: has_cond = HasConditional(node.expression) if has_cond.result(): - print('Expression to split {}'.format(node.expression)) st = SplitTernaryExpression(node.expression) condition = st.condition assert condition true_expr = st.true_expression false_expr = st.false_expression - print('\tCondition {}'.format(condition)) - print('\ttrue {}'.format(true_expr)) - print('\tfalse {}'.format(false_expr)) self.split_ternary_node(node, condition, true_expr, false_expr) ternary_found = True break self._analyze_read_write() self._analyze_calls() + for node in self.nodes: + node.slithir_generation() def split_ternary_node(self, node, condition, true_expr, false_expr): @@ -580,10 +621,14 @@ class FunctionSolc(Function): condition_node.analyze_expressions(self) true_node = self._new_node(node.type) + if node.type == NodeType.VARIABLE: + true_node.add_variable_declaration(node.variable_declaration) true_node.add_expression(true_expr) true_node.analyze_expressions(self) false_node = self._new_node(node.type) + if node.type == NodeType.VARIABLE: + false_node.add_variable_declaration(node.variable_declaration) false_node.add_expression(false_expr) false_node.analyze_expressions(self) diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index adaa59dd0..a2206f128 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -245,12 +245,33 @@ def parse_expression(expression, caller_context): return parse_call(expression, caller_context) elif name == 'TupleExpression': + """ + For expression like + (a,,c) = (1,2,3) + the AST provides only two children in the left side + We check the type provided (tuple(uint256,,uint256)) + To determine that there is an empty variable + Otherwhise we would not be able to determine that + a = 1, c = 3, and 2 is lost + + Note: this is only possible with Solidity >= 0.4.12 + """ if 'children' not in expression : attributes = expression['attributes'] components = attributes['components'] expressions = [parse_expression(c, caller_context) if c else None for c in components] else: expressions = [parse_expression(e, caller_context) for e in expression['children']] + # Add none for empty tuple items + if "attributes" in expression: + if "type" in expression['attributes']: + t = expression['attributes']['type'] + if ',,' in t or '(,' in t or ',)' in t: + t = t[len('tuple('):-1] + elems = t.split(',') + for idx in range(len(elems)): + if elems[idx] == '': + expressions.insert(idx, None) t = TupleExpression(expressions) return t @@ -280,6 +301,11 @@ def parse_expression(expression, caller_context): elif name == 'Literal': assert 'children' not in expression value = expression['attributes']['value'] + if value is None: + # for literal declared as hex + # see https://solidity.readthedocs.io/en/v0.4.25/types.html?highlight=hex#hexadecimal-literals + assert 'hexvalue' in expression['attributes'] + value = '0x'+expression['attributes']['hexvalue'] literal = Literal(value) return literal diff --git a/slither/solc_parsing/variables/local_variable_init_from_tuple.py b/slither/solc_parsing/variables/local_variable_init_from_tuple.py index f219c3847..e962e4c3c 100644 --- a/slither/solc_parsing/variables/local_variable_init_from_tuple.py +++ b/slither/solc_parsing/variables/local_variable_init_from_tuple.py @@ -8,3 +8,4 @@ class LocalVariableInitFromTupleSolc(VariableDeclarationSolc, LocalVariableInitF super(LocalVariableInitFromTupleSolc, self).__init__(var) self._tuple_index = index + diff --git a/slither/solc_parsing/variables/variable_declaration.py b/slither/solc_parsing/variables/variable_declaration.py index 3e9601a6a..7574136f1 100644 --- a/slither/solc_parsing/variables/variable_declaration.py +++ b/slither/solc_parsing/variables/variable_declaration.py @@ -96,7 +96,7 @@ class VariableDeclarationSolc(Variable): assert len(var['children']) <= 1 self._initialized = True self._initializedNotParsed = init - elif len(var['children']) == 1: + elif len(var['children']) in [0, 1]: self._initialized = False self._initializedNotParsed = [] else: diff --git a/slither/visitors/slithir/__init__.py b/slither/visitors/slithir/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py new file mode 100644 index 000000000..774de7ce9 --- /dev/null +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -0,0 +1,231 @@ + +from slither.visitors.expression.expression import ExpressionVisitor + +from slither.core.expressions.assignment_operation import AssignmentOperationType +from slither.core.declarations.function import Function +from slither.core.declarations.structure import Structure +from slither.core.expressions.unary_operation import UnaryOperationType +from slither.core.solidity_types.array_type import ArrayType + +from slither.slithir.operations.assignment import Assignment +from slither.slithir.operations.binary import BinaryOperation, BinaryOperationType +from slither.slithir.operations.unary import UnaryOperation +from slither.slithir.operations.index import Index +from slither.slithir.operations.internal_call import InternalCall +from slither.slithir.operations.member import Member +from slither.slithir.operations.type_conversion import TypeConversion +from slither.slithir.operations.delete import Delete +from slither.slithir.operations.unpack import Unpack +from slither.slithir.operations.init_array import InitArray + +from slither.slithir.tmp_operations.tmp_call import TmpCall +from slither.slithir.tmp_operations.tmp_new_elementary_type import TmpNewElementaryType +from slither.slithir.tmp_operations.tmp_new_contract import TmpNewContract +from slither.slithir.tmp_operations.tmp_new_array import TmpNewArray +from slither.slithir.tmp_operations.tmp_new_structure import TmpNewStructure +from slither.slithir.tmp_operations.argument import Argument + +from slither.slithir.variables.temporary import TemporaryVariable +from slither.slithir.variables.tuple import TupleVariable +from slither.slithir.variables.constant import Constant +from slither.slithir.variables.reference import ReferenceVariable + +key = 'expressionToSlithIR' + +def get(expression): + val = expression.context[key] + # we delete the item to reduce memory use + del expression.context[key] + return val + +def set_val(expression, val): + expression.context[key] = val + +class ExpressionToSlithIR(ExpressionVisitor): + + def __init__(self, expression): + self._expression = expression + self._result = [] + self._visit_expression(self.expression) + + def result(self): + return self._result + + def _post_assignement_operation(self, expression): + left = get(expression.expression_left) + right = get(expression.expression_right) + if isinstance(left, list): # tuple expression: + if isinstance(right, list): # unbox assigment + assert len(left) == len(right) + for idx in range(len(left)): + if not left[idx] is None: + operation = Assignment(left[idx], right[idx], expression.type, expression.expression_return_type) + self._result.append(operation) + set_val(expression, None) + else: + assert isinstance(right, TupleVariable) + for idx in range(len(left)): + if not left[idx] is None: + operation = Unpack(left[idx], right, idx) + self._result.append(operation) + set_val(expression, None) + else: + # Init of array, like + # uint8[2] var = [1,2]; + if isinstance(right, list): + operation = InitArray(right, left) + self._result.append(operation) + set_val(expression, left) + else: + operation = Assignment(left, right, expression.type, expression.expression_return_type) + self._result.append(operation) + # Return left to handle + # a = b = 1; + set_val(expression, left) + + def _post_binary_operation(self, expression): + left = get(expression.expression_left) + right = get(expression.expression_right) + val = TemporaryVariable() + + operation = BinaryOperation(val, left, right, expression.type) + self._result.append(operation) + set_val(expression, val) + + def _post_call_expression(self, expression): + called = get(expression.called) + args = [get(a) for a in expression.arguments if a] + for arg in args: + arg_ = Argument(arg) + self._result.append(arg_) + if isinstance(called, Function): + # internal call + + # If tuple + if expression.type_call.startswith('tuple(') and expression.type_call != 'tuple()': + val = TupleVariable() + else: + val = TemporaryVariable() + internal_call = InternalCall(called, len(args), val, expression.type_call) + self._result.append(internal_call) + set_val(expression, val) + else: + val = TemporaryVariable() + + # If tuple + if expression.type_call.startswith('tuple(') and expression.type_call != 'tuple()': + val = TupleVariable() + else: + val = TemporaryVariable() + + if isinstance(called, Structure) and False: + operation = TmpNewStructure(called, val) +# self._result.append(message_call) +# set_val(expression, val) + else: + message_call = TmpCall(called, len(args), val, expression.type_call) + self._result.append(message_call) + set_val(expression, val) + + def _post_conditional_expression(self, expression): + raise Exception('Ternary operator are not convertible to SlithIR {}'.format(expression)) + + def _post_elementary_type_name_expression(self, expression): + set_val(expression, expression.type) + + def _post_identifier(self, expression): + set_val(expression, expression.value) + + def _post_index_access(self, expression): + left = get(expression.expression_left) + right = get(expression.expression_right) + val = ReferenceVariable() + operation = Index(val, left, right, expression.type) + self._result.append(operation) + set_val(expression, val) + + def _post_literal(self, expression): + set_val(expression, Constant(expression.value)) + + def _post_member_access(self, expression): + expr = get(expression.expression) + val = ReferenceVariable() + member = Member(expr, Constant(expression.member_name), val) + self._result.append(member) + set_val(expression, val) + + def _post_new_array(self, expression): + val = TemporaryVariable() + operation = TmpNewArray(expression.depth, expression.array_type, val) + self._result.append(operation) + set_val(expression, val) + + def _post_new_contract(self, expression): + val = TemporaryVariable() + operation = TmpNewContract(expression.contract_name, val) + self._result.append(operation) + set_val(expression, val) + + def _post_new_elementary_type(self, expression): + # TODO unclear if this is ever used? + val = TemporaryVariable() + operation = TmpNewElementaryType(expression.type, val) + self._result.append(operation) + set_val(expression, val) + + def _post_tuple_expression(self, expression): + expressions = [get(e) if e else None for e in expression.expressions] + if len(expressions) == 1: + val = expressions[0] + else: + val = expressions + set_val(expression, val) + + def _post_type_conversion(self, expression): + expr = get(expression.expression) + val = TemporaryVariable() + operation = TypeConversion(val, expr, expression.type) + self._result.append(operation) + set_val(expression, val) + + def _post_unary_operation(self, expression): + value = get(expression.expression) + if expression.type in [UnaryOperationType.BANG, UnaryOperationType.TILD]: + lvalue = TemporaryVariable() + operation = UnaryOperation(lvalue, value, expression.type) + self._result.append(operation) + set_val(expression, lvalue) + elif expression.type in [UnaryOperationType.DELETE]: + operation = Delete(value) + self._result.append(operation) + set_val(expression, value) + elif expression.type in [UnaryOperationType.PLUSPLUS_PRE]: + operation = BinaryOperation(value, value, Constant("1"), BinaryOperationType.ADDITION) + self._result.append(operation) + set_val(expression, value) + elif expression.type in [UnaryOperationType.MINUSMINUS_PRE]: + operation = BinaryOperation(value, value, Constant("1"), BinaryOperationType.SUBSTRACTION) + self._result.append(operation) + set_val(expression, value) + elif expression.type in [UnaryOperationType.PLUSPLUS_POST]: + lvalue = TemporaryVariable() + operation = Assignment(lvalue, value, AssignmentOperationType.ASSIGN, value.type) + self._result.append(operation) + operation = BinaryOperation(value, value, Constant("1"), BinaryOperationType.ADDITION) + self._result.append(operation) + set_val(expression, lvalue) + elif expression.type in [UnaryOperationType.MINUSMINUS_POST]: + lvalue = TemporaryVariable() + operation = Assignment(lvalue, value, AssignmentOperationType.ASSIGN, value.type) + self._result.append(operation) + operation = BinaryOperation(value, value, Constant("1"), BinaryOperationType.SUBSTRACTION) + self._result.append(operation) + set_val(expression, lvalue) + elif expression.type in [UnaryOperationType.PLUS_PRE]: + set_val(expression, value) + elif expression.type in [UnaryOperationType.MINUS_PRE]: + set_val(expression, Constant("-"+str(value.value))) + else: + raise Exception('Unary operation to IR not supported {}'.format(expression)) + + diff --git a/tests/taint_mapping.sol b/tests/taint_mapping.sol new file mode 100644 index 000000000..c1a24ed38 --- /dev/null +++ b/tests/taint_mapping.sol @@ -0,0 +1,18 @@ +contract Test{ + + mapping(uint => mapping(uint => address)) authorized_destination; + + address destination; + + function init(){ + authorized_destination[0][0] = msg.sender; + } + + function setup(uint idx){ + destination = authorized_destination[0][0]; + } + + function withdraw(){ + destination.transfer(this.balance); + } +} diff --git a/weird_tests/parsing/slithir.sol b/weird_tests/parsing/slithir.sol new file mode 100644 index 000000000..00ebed3a5 --- /dev/null +++ b/weird_tests/parsing/slithir.sol @@ -0,0 +1,76 @@ +contract Test{ + + Test t; + struct St{ + address a; + Test t2; + } + + St st; + + bool ret; + + function one(){ + t.test(1); + t.test.value(0)(1); + t.test.value(test2())(1); + t.test.value(0).gas(1)(3); + } + function two(){ + + msg.sender.call.value(0)(bytes4(keccak256(('test(uint256)'))), 0); + + } + + + function three(){ + new bytes(0); + new bytes(test3(1, test2())); + new Test2(test2()); + } + function test(uint a) payable{ + uint b; + + a = a + 1; + + a = a + 1 + a; + + test(a); + + test(test2()); + + a = test2() + 1; + + t.test.value(0)(a); + + t.test(a); + st.t2.test(0); + + st.a.call(); + + + msg.sender.call.value(0)(bytes4(keccak256(('test(uint256)'))), 0); + ret = msg.sender.call.value(0)(); + + msg.sender.call.value(0).gas(0)(); + + //#(b,a) = (0,1); + + } + + function test2() returns(uint){ + + } + + + function test3(uint, uint) returns(uint){ + } +} + +contract Test2{ + + constructor(uint){ + + } + +} From 7952a804a82d218b02a6afbf9c350c0ecc46d7ad Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 24 Sep 2018 18:15:56 +0100 Subject: [PATCH 069/308] Refactor function.all_.. methods --- slither/core/declarations/function.py | 100 ++++++-------------------- 1 file changed, 21 insertions(+), 79 deletions(-) diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index ae9852f4a..c7d4be413 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -248,7 +248,8 @@ class Function(ChildContract, SourceMapping): @property def signature(self): """ - (str, list(str), list(str)): Function signature as (name, list parameters type, list return values type) + (str, list(str), list(str)): Function signature as + (name, list parameters type, list return values type) """ return self.name, [str(x.type) for x in self.parameters], [str(x.type) for x in self.returns] @@ -276,7 +277,7 @@ class Function(ChildContract, SourceMapping): return self.contract.slither def _filter_state_variables_written(self, expressions): - ret =[] + ret = [] for expression in expressions: if isinstance(expression, Identifier): ret.append(expression) @@ -357,12 +358,12 @@ class Function(ChildContract, SourceMapping): groupby(sorted(external_calls, key=lambda x: str(x)), lambda x: str(x))] self._external_calls = external_calls - def all_state_variables_read(self): - """ recursive version of variables_read - """ - variables = self.state_variables_read + + def _explore_functions(self, f_new_values): + values = f_new_values(self) explored = [self] - to_explore = [c for c in self.internal_calls if isinstance(c, Function) and c not in explored] + to_explore = [c for c in self.internal_calls if + isinstance(c, Function) and c not in explored] to_explore += [m for m in self.modifiers if m not in explored] while to_explore: @@ -371,98 +372,39 @@ class Function(ChildContract, SourceMapping): if f in explored: continue explored.append(f) - variables += f.state_variables_read + + values += f_new_values(f) + to_explore += [c for c in f.internal_calls if\ isinstance(c, Function) and c not in explored and c not in to_explore] to_explore += [m for m in f.modifiers if m not in explored and m not in to_explore] - return list(set(variables)) + return list(set(values)) + + def all_state_variables_read(self): + """ recursive version of variables_read + """ + return self._explore_functions(lambda x: x.state_variables_read) def all_solidity_variables_read(self): """ recursive version of solidity_read """ - variables = self.solidity_variables_read - explored = [self] - to_explore = [c for c in self.internal_calls if isinstance(c, Function) and c not in explored] - to_explore += [m for m in self.modifiers if m not in explored] - - while to_explore: - f = to_explore[0] - to_explore = to_explore[1:] - if f in explored: - continue - explored.append(f) - variables += f.solidity_variables_read - to_explore += [c for c in f.internal_calls if\ - isinstance(c, Function) and c not in explored and c not in to_explore] - to_explore += [m for m in f.modifiers if m not in explored and m not in to_explore] - - return list(set(variables)) + return self._explore_functions(lambda x: x.solidity_variables_read) def all_expressions(self): """ recursive version of variables_read """ - variables = self.expressions - explored = [self] - to_explore = [c for c in self.internal_calls if isinstance(c, Function) and c not in explored] - to_explore += [m for m in self.modifiers if m not in explored] - - while to_explore: - f = to_explore[0] - to_explore = to_explore[1:] - if f in explored: - continue - explored.append(f) - variables += f.expressions - to_explore += [c for c in f.internal_calls if\ - isinstance(c, Function) and c not in explored and c not in to_explore] - to_explore += [m for m in f.modifiers if m not in explored and m not in to_explore] - - return list(set(variables)) + return self._explore_functions(lambda x: x.expressions) def all_state_variables_written(self): """ recursive version of variables_written """ - variables = self.state_variables_written - explored = [self] - to_explore = [c for c in self.internal_calls if - isinstance(c, Function) and c not in explored] - to_explore += [m for m in self.modifiers if m not in explored] - - while to_explore: - f = to_explore[0] - to_explore = to_explore[1:] - if f in explored: - continue - explored.append(f) - variables += f.state_variables_written - to_explore += [c for c in f.internal_calls if\ - isinstance(c, Function) and c not in explored and c not in to_explore] - to_explore += [m for m in f.modifiers if m not in explored and m not in to_explore] - - return list(set(variables)) + return self._explore_functions(lambda x: x.state_variables_written) def all_internal_calls(self): """ recursive version of internal_calls """ - calls = self.internal_calls - explored = [self] - to_explore = [c for c in self.internal_calls if - isinstance(c, Function) and c not in explored] - to_explore += [m for m in self.modifiers if m not in explored] - - while to_explore: - f = to_explore[0] - to_explore = to_explore[1:] - if f in explored: - continue - explored.append(f) - calls += f.internal_calls - to_explore += [c for c in f.internal_calls if\ - isinstance(c, Function) and c not in explored and c not in to_explore] - to_explore += [m for m in f.modifiers if m not in explored and m not in to_explore] - - return list(set(calls)) + return self._explore_functions(lambda x: x.internal_calls) def is_reading(self, variable): """ From dda057728de384e941aed9436a0bd2c495d5667f Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 24 Sep 2018 19:12:50 +0100 Subject: [PATCH 070/308] Add taint as slither module Add is_unprotected to core.function --- slither/analyses/__init__.py | 0 slither/analyses/taint/__init__.py | 0 slither/analyses/taint/state_variables.py | 82 +++++++++++++++++++++++ slither/core/declarations/function.py | 20 ++++++ weird_tests/parsing/slithir.sol | 76 --------------------- 5 files changed, 102 insertions(+), 76 deletions(-) create mode 100644 slither/analyses/__init__.py create mode 100644 slither/analyses/taint/__init__.py create mode 100644 slither/analyses/taint/state_variables.py delete mode 100644 weird_tests/parsing/slithir.sol diff --git a/slither/analyses/__init__.py b/slither/analyses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/analyses/taint/__init__.py b/slither/analyses/taint/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/analyses/taint/state_variables.py b/slither/analyses/taint/state_variables.py new file mode 100644 index 000000000..a0035d16f --- /dev/null +++ b/slither/analyses/taint/state_variables.py @@ -0,0 +1,82 @@ +""" + Compute taint on state variables + + Do not propagate taint on protected function + Compute taint from function parameters, msg.sender and msg.value + Iterate until it finding a fixpoint + +""" +from slither.core.variables.state_variable import StateVariable +from slither.core.declarations.solidity_variables import SolidityVariableComposed + +from slither.slithir.operations.index import Index + +from slither.slithir.variables.temporary import TemporaryVariable +from slither.slithir.variables.reference import ReferenceVariable + +KEY = 'TAINT_STATE_VARIABLES' + +def _visit_node(node, visited): + if node in visited: + return + + visited += [node] + taints = node.function.slither.context[KEY] + + refs = {} + for ir in node.irs: + if isinstance(ir, Index): + refs[ir.lvalue] = ir.variable_left + + if isinstance(ir, Index): + read = [ir.variable_left] + else: + read = ir.read + if any(var_read in taints for var_read in read): + taints += [ir.lvalue] + lvalue = ir.lvalue + while isinstance(lvalue, ReferenceVariable): + taints += [refs[lvalue]] + lvalue = refs[lvalue] + + taints = [v for v in taints if not isinstance(v, (TemporaryVariable, ReferenceVariable))] + + node.function.slither.context[KEY] = list(set(taints)) + + for son in node.sons: + _visit_node(son, visited) + +def _run_taint(slither, initial_taint): + if KEY in slither.context: + return + + prev_taints = [] + slither.context[KEY] = initial_taint + # Loop until reaching a fixpoint + while(set(prev_taints) != set(slither.context[KEY])): + prev_taints = slither.context[KEY] + for contract in slither.contracts: + for function in contract.functions: + # Dont propagated taint on protected functions + if not function.is_protected(): + slither.context[KEY] = list(set(slither.context[KEY] + function.parameters)) + _visit_node(function.entry_point, []) + + slither.context[KEY] = [v for v in prev_taints if isinstance(v, StateVariable)] + +def get_taint(slither, initial_taint=None): + """ + Return the state variables tainted + Args: + slither: + initial_taint (List Variable) + Returns: + List(StateVariable) + """ + if initial_taint is None: + initial_taint = [SolidityVariableComposed('msg.sender')] + initial_taint += [SolidityVariableComposed('msg.value')] + + if KEY not in slither.context: + _run_taint(slither, initial_taint) + return slither.context[KEY] diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 5e0054dae..115d5a3ac 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -512,3 +512,23 @@ class Function(ChildContract, SourceMapping): [str(x) for x in self.state_variables_written], [str(x) for x in self.internal_calls], [str(x) for x in self.external_calls]) + + def is_protected(self): + """ + Determine if the function is protected using a check on msg.sender + + Returns + (bool) + """ + + from slither.core.cfg.node import NodeType + read_var_cond = [x.variables_read for x in self.nodes if x.type == NodeType.IF] + read_var_cond = [item for sublist in read_var_cond for item in sublist] + if 'msg.sender' in [x.name for x in read_var_cond]: + return True + + read_var_require = [n.variables_read for n in self.nodes if n.contains_require_or_assert()] + read_var_require = [item for sublist in read_var_require for item in sublist] + if 'msg.sender' in [x.name for x in read_var_require]: + return True + return False diff --git a/weird_tests/parsing/slithir.sol b/weird_tests/parsing/slithir.sol deleted file mode 100644 index 00ebed3a5..000000000 --- a/weird_tests/parsing/slithir.sol +++ /dev/null @@ -1,76 +0,0 @@ -contract Test{ - - Test t; - struct St{ - address a; - Test t2; - } - - St st; - - bool ret; - - function one(){ - t.test(1); - t.test.value(0)(1); - t.test.value(test2())(1); - t.test.value(0).gas(1)(3); - } - function two(){ - - msg.sender.call.value(0)(bytes4(keccak256(('test(uint256)'))), 0); - - } - - - function three(){ - new bytes(0); - new bytes(test3(1, test2())); - new Test2(test2()); - } - function test(uint a) payable{ - uint b; - - a = a + 1; - - a = a + 1 + a; - - test(a); - - test(test2()); - - a = test2() + 1; - - t.test.value(0)(a); - - t.test(a); - st.t2.test(0); - - st.a.call(); - - - msg.sender.call.value(0)(bytes4(keccak256(('test(uint256)'))), 0); - ret = msg.sender.call.value(0)(); - - msg.sender.call.value(0).gas(0)(); - - //#(b,a) = (0,1); - - } - - function test2() returns(uint){ - - } - - - function test3(uint, uint) returns(uint){ - } -} - -contract Test2{ - - constructor(uint){ - - } - -} From cad16a5d969fa4bf67a597cac9683e6540bbd0bc Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 24 Sep 2018 19:25:01 +0100 Subject: [PATCH 071/308] Add is_erc20 to core.contract --- slither/core/declarations/contract.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index 6faec40b8..6ae09b65b 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -286,6 +286,18 @@ class Contract(ChildSlither, SourceMapping): """ return next((e for e in self.enums if e.canonical_name == enum_name), None) + def is_erc20(self): + """ + Check if the contract is a erc20 token + Note: it does not check for correct return values + Returns: + bool + """ + full_names = [f.full_name for f in self.functions] + return 'transfer(address,uint256)' in full_names and\ + 'transferFrom(address,address,uint256)' in full_names and\ + 'approve(address,uint256)' in full_names + def get_summary(self): """ Return the function summary From c9bdaf19a9d23278d99e22864894797f9a9bbafa Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 24 Sep 2018 19:35:35 +0100 Subject: [PATCH 072/308] Remove print --- slither/solc_parsing/declarations/function.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index b6f5113c9..da354ea69 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -557,15 +557,11 @@ class FunctionSolc(Function): for node in self.nodes: has_cond = HasConditional(node.expression) if has_cond.result(): - print('Expression to split {}'.format(node.expression)) st = SplitTernaryExpression(node.expression) condition = st.condition assert condition true_expr = st.true_expression false_expr = st.false_expression - print('\tCondition {}'.format(condition)) - print('\ttrue {}'.format(true_expr)) - print('\tfalse {}'.format(false_expr)) self.split_ternary_node(node, condition, true_expr, false_expr) ternary_found = True break From 386e1023b1ba1ebbe8ce13d7f9d126e2cc7e181d Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 24 Sep 2018 19:42:37 +0100 Subject: [PATCH 073/308] Remove print --- slither/slithir/convert.py | 1 - slither/slithir/operations/binary.py | 1 - slither/slithir/operations/new_contract.py | 2 -- slither/solc_parsing/slitherSolc.py | 1 - 4 files changed, 5 deletions(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index e11e544a4..42ff156ed 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -287,7 +287,6 @@ def convert_expression(expression): # handle standlone expression # such as return true; if isinstance(expression, Literal): - print(expression.value) return [Constant(expression.value)] visitor = ExpressionToSlithIR(expression) result = visitor.result() diff --git a/slither/slithir/operations/binary.py b/slither/slithir/operations/binary.py index b9ed01f59..ce13ccd73 100644 --- a/slither/slithir/operations/binary.py +++ b/slither/slithir/operations/binary.py @@ -118,7 +118,6 @@ class BinaryOperation(OperationWithLValue): def __init__(self, result, left_variable, right_variable, operation_type): assert is_valid_rvalue(left_variable) -# print(right_variable) assert is_valid_rvalue(right_variable) assert is_valid_lvalue(result) super(BinaryOperation, self).__init__() diff --git a/slither/slithir/operations/new_contract.py b/slither/slithir/operations/new_contract.py index 1e0452180..fb24553f2 100644 --- a/slither/slithir/operations/new_contract.py +++ b/slither/slithir/operations/new_contract.py @@ -9,8 +9,6 @@ from slither.slithir.variables.constant import Constant class NewContract(Call, OperationWithLValue): def __init__(self, contract_name, lvalue): - print(contract_name) - print(type(contract_name)) assert isinstance(contract_name, Constant) assert is_valid_lvalue(lvalue) super(NewContract, self).__init__() diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index ed71cd5a9..2c5ce28c4 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -17,7 +17,6 @@ class SlitherSolc(Slither): self._contractsNotParsed = [] self._contracts_by_id = {} self._analyzed = False - print(filename) def _parse_contracts_from_json(self, json_data): first = json_data.find('{') From 040d2d374478a5f6ed225437bbc542fbc1c64b2f Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 24 Sep 2018 20:10:44 +0100 Subject: [PATCH 074/308] Add __eq__ , __hash__ in SolidityVariable --- slither/core/declarations/solidity_variables.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py index c60a9f14d..a4ba7a660 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -63,6 +63,13 @@ class SolidityVariable: def __str__(self): return self._name + def __eq__(self, other): + return self.__class__ == other.__class__ and self.name == other.name + + def __hash__(self): + return hash(self.name) + + class SolidityVariableComposed(SolidityVariable): def __init__(self, name): assert name in SOLIDITY_VARIABLES_COMPOSED @@ -87,3 +94,9 @@ class SolidityFunction: def __str__(self): return self._name + + def __eq__(self, other): + return self.__class__ == other.__class__ and self.name == other.name + + def __hash__(self): + return hash(self.name) From bacd12ec2cb272009d1302508685cd40c10bd817 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 25 Sep 2018 08:43:48 +0100 Subject: [PATCH 075/308] API changes: - get_functions_writing_variable -> get_functions_writing_to_variable - get_functions_reading_variable -> get_functions_reading_from_variable --- examples/scripts/functions_writing.py | 2 +- examples/scripts/variable_in_condition.py | 2 +- slither/core/declarations/contract.py | 4 ++-- slither/detectors/variables/uninitialized_state_variables.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/scripts/functions_writing.py b/examples/scripts/functions_writing.py index d3d9afcfb..e545c6f22 100644 --- a/examples/scripts/functions_writing.py +++ b/examples/scripts/functions_writing.py @@ -15,7 +15,7 @@ contract = slither.get_contract_from_name('Contract') var_a = contract.get_state_variable_from_name('a') # Get the functions writing the variable -functions_writing_a = contract.get_functions_writing_variable(var_a) +functions_writing_a = contract.get_functions_writing_to_variable(var_a) # Print the result print('The function writing "a" are {}'.format([f.name for f in functions_writing_a])) diff --git a/examples/scripts/variable_in_condition.py b/examples/scripts/variable_in_condition.py index 584a69c12..90463c25e 100644 --- a/examples/scripts/variable_in_condition.py +++ b/examples/scripts/variable_in_condition.py @@ -15,7 +15,7 @@ contract = slither.get_contract_from_name('Contract') var_a = contract.get_state_variable_from_name('a') # Get the functions reading the variable -functions_reading_a = contract.get_functions_reading_variable(var_a) +functions_reading_a = contract.get_functions_reading_from_variable(var_a) function_using_a_as_condition = [f for f in functions_reading_a if\ f.is_reading_in_conditional_node(var_a) or\ diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index 6ae09b65b..f863d22e8 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -163,13 +163,13 @@ class Contract(ChildSlither, SourceMapping): def __str__(self): return self.name - def get_functions_reading_variable(self, variable): + def get_functions_reading_from_variable(self, variable): ''' Return the functions reading the variable ''' return [f for f in self.functions if f.is_reading(variable)] - def get_functions_writing_variable(self, variable): + def get_functions_writing_to_variable(self, variable): ''' Return the functions writting the variable ''' diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index 1e2b00458..051117019 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -45,7 +45,7 @@ class UninitializedStateVarsDetection(AbstractDetector): v not in all_push and \ v.type not in contract.using_for])) # Note: does not handle using X for * - return [(v, contract.get_functions_reading_variable(v)) for v in uninitialized_vars] + return [(v, contract.get_functions_reading_from_variable(v)) for v in uninitialized_vars] def detect(self): """ Detect uninitialized state variables From 904e453c86d4aadb6ab20117f168175eacbcb750 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 25 Sep 2018 09:41:59 +0100 Subject: [PATCH 076/308] Remove PushArray operation Clean convert.py --- examples/scripts/taint_mapping.py | 14 ++-- slither/slithir/convert.py | 85 +++++++++++++----------- slither/slithir/operations/push.py | 3 +- slither/slithir/operations/push_array.py | 27 -------- 4 files changed, 54 insertions(+), 75 deletions(-) delete mode 100644 slither/slithir/operations/push_array.py diff --git a/examples/scripts/taint_mapping.py b/examples/scripts/taint_mapping.py index 2c3b6df9b..64e46b49a 100644 --- a/examples/scripts/taint_mapping.py +++ b/examples/scripts/taint_mapping.py @@ -1,18 +1,14 @@ import sys -from slither.slither import Slither - -from slither.core.declarations.solidity_variables import SolidityVariableComposed +from slither.core.declarations.solidity_variables import \ + SolidityVariableComposed from slither.core.variables.state_variable import StateVariable - -#from slither.slithir.operations.lvalue import OperationWithLValue +from slither.slither import Slither from slither.slithir.operations.high_level_call import HighLevelCall -from slither.slithir.operations.member import Member from slither.slithir.operations.index import Index -from slither.slithir.operations.assignment import Assignment - -from slither.slithir.variables.temporary import TemporaryVariable from slither.slithir.variables.reference import ReferenceVariable +from slither.slithir.variables.temporary import TemporaryVariable + def visit_node(node, visited): if node in visited: diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 42ff156ed..79fc79cc5 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -1,42 +1,37 @@ -from slither.visitors.slithir.expression_to_slithir import ExpressionToSlithIR -from slither.slithir.operations.assignment import Assignment -from slither.slithir.operations.member import Member -from slither.slithir.operations.lvalue import OperationWithLValue - -from slither.slithir.operations.binary import BinaryOperation, BinaryOperationType +from slither.core.declarations.contract import Contract +from slither.core.declarations.event import Event +from slither.core.declarations.solidity_variables import (SolidityFunction, + SolidityVariableComposed) +from slither.core.declarations.structure import Structure +from slither.core.expressions.literal import Literal +from slither.core.variables.variable import Variable +from slither.slithir.operations.call import Call +from slither.slithir.operations.event_call import EventCall from slither.slithir.operations.high_level_call import HighLevelCall -from slither.slithir.operations.low_level_call import LowLevelCall -from slither.slithir.operations.solidity_call import SolidityCall +from slither.slithir.operations.init_array import InitArray from slither.slithir.operations.library_call import LibraryCall -from slither.slithir.operations.new_elementary_type import NewElementaryType +from slither.slithir.operations.low_level_call import LowLevelCall +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.slithir.operations.member import Member +from slither.slithir.operations.new_array import NewArray from slither.slithir.operations.new_contract import NewContract +from slither.slithir.operations.new_elementary_type import NewElementaryType from slither.slithir.operations.new_structure import NewStructure -from slither.slithir.operations.new_array import NewArray -from slither.slithir.operations.event_call import EventCall from slither.slithir.operations.push import Push -from slither.slithir.operations.push_array import PushArray - +from slither.slithir.operations.solidity_call import SolidityCall +from slither.slithir.tmp_operations.argument import Argument, ArgumentType from slither.slithir.tmp_operations.tmp_call import TmpCall -from slither.slithir.tmp_operations.tmp_new_elementary_type import TmpNewElementaryType -from slither.slithir.tmp_operations.tmp_new_contract import TmpNewContract from slither.slithir.tmp_operations.tmp_new_array import TmpNewArray +from slither.slithir.tmp_operations.tmp_new_contract import TmpNewContract +from slither.slithir.tmp_operations.tmp_new_elementary_type import \ + TmpNewElementaryType from slither.slithir.tmp_operations.tmp_new_structure import TmpNewStructure -from slither.slithir.tmp_operations.argument import ArgumentType, Argument - -from slither.slithir.operations.call import Call - from slither.slithir.variables.constant import Constant -from slither.slithir.variables.temporary import TemporaryVariable from slither.slithir.variables.reference import ReferenceVariable +from slither.slithir.variables.temporary import TemporaryVariable from slither.slithir.variables.tuple import TupleVariable +from slither.visitors.slithir.expression_to_slithir import ExpressionToSlithIR -from slither.core.variables.variable import Variable -from slither.core.declarations.solidity_variables import SolidityFunction, SolidityVariableComposed -from slither.core.declarations.event import Event -from slither.core.declarations.structure import Structure -from slither.core.declarations.contract import Contract - -from slither.core.expressions.literal import Literal def is_value(ins): if isinstance(ins, TmpCall): @@ -139,6 +134,7 @@ def apply_ir_heuristics(result): # Remove temporary result = remove_temporary(result) + result = replace_calls(result) reset_variable_number(result) @@ -231,17 +227,30 @@ def replace_calls(result): replace call to push to a Push Operation Replace to call 'call' 'delegatecall', 'callcode' to an LowLevelCall ''' - for idx in range(len(result)): - ins = result[idx] - if isinstance(ins, HighLevelCall): - if ins.function_name == 'push': - assert len(ins.arguments) == 1 - if isinstance(ins.arguments[0], list): - result[idx] = PushArray(ins.destination, ins.arguments[0]) - else: - result[idx] = Push(ins.destination, ins.arguments[0]) - if ins.function_name in ['call', 'delegatecall', 'callcode']: - result[idx] = LowLevelCall(ins.destination, ins.function_name, ins.nbr_arguments, ins.lvalue, ins.type_call) + reset = True + while reset: + reset = False + for idx in range(len(result)): + ins = result[idx] + if isinstance(ins, HighLevelCall): + if ins.function_name == 'push': + assert len(ins.arguments) == 1 + if isinstance(ins.arguments[0], list): + val = TemporaryVariable() + operation = InitArray(ins.arguments[0], val) + result.insert(idx, operation) + result[idx+1] = Push(ins.destination, val) + reset = True + break + else: + result[idx] = Push(ins.destination, ins.arguments[0]) + if ins.function_name in ['call', 'delegatecall', 'callcode']: + result[idx] = LowLevelCall(ins.destination, + ins.function_name, + ins.nbr_arguments, + ins.lvalue, + ins.type_call) + return result def extract_tmp_call(ins): diff --git a/slither/slithir/operations/push.py b/slither/slithir/operations/push.py index 19d34aa7f..cde9bbf2c 100644 --- a/slither/slithir/operations/push.py +++ b/slither/slithir/operations/push.py @@ -19,8 +19,9 @@ class Push(OperationWithLValue): def array(self): return self._lvalue + @property def value(self): return self._value def __str__(self): - return "PUSH {} in {}".format(self.value, self.lvalue) + return "PUSH {} in {}".format(self.value, self.lvalue) diff --git a/slither/slithir/operations/push_array.py b/slither/slithir/operations/push_array.py deleted file mode 100644 index 6d1b1152a..000000000 --- a/slither/slithir/operations/push_array.py +++ /dev/null @@ -1,27 +0,0 @@ -import logging -from slither.slithir.operations.lvalue import OperationWithLValue -from slither.core.variables.variable import Variable -from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue - -class PushArray(OperationWithLValue): - - def __init__(self, array, values): - assert all(is_valid_rvalue(value) for value in values) - assert is_valid_lvalue(array) - self._values = values - self._lvalue = array - - @property - def read(self): - return list(self._values) - - @property - def array(self): - return self._lvalue - - @property - def values(self): - return list(self._values) - - def __str__(self): - return "PUSH_ARRAY {} in {}".format([str(x) for x in self.values], self.lvalue) From ab690d1f24ca2e1a33279491428c1621c7834e21 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 25 Sep 2018 10:00:22 +0100 Subject: [PATCH 077/308] Dont run detectors if a printer is called --- slither/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/__main__.py b/slither/__main__.py index c4cf321d1..6f12bfaa7 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -40,7 +40,7 @@ def process(filename, args, detector_classes, printer_classes): if printer_classes: slither.run_printers() # Currently printers does not return results - if detector_classes: + elif detector_classes: detector_results = slither.run_detectors() detector_results = [x for x in detector_results if x] # remove empty results detector_results = [item for sublist in detector_results for item in sublist] # flatten From c442ec056efd816dc5540fb674812cc70e05ab4b Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 25 Sep 2018 10:13:48 +0100 Subject: [PATCH 078/308] Add authorization printer example --- examples/printers/authorization.sol | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 examples/printers/authorization.sol diff --git a/examples/printers/authorization.sol b/examples/printers/authorization.sol new file mode 100644 index 000000000..a4b361754 --- /dev/null +++ b/examples/printers/authorization.sol @@ -0,0 +1,25 @@ +pragma solidity ^0.4.24; +contract Owner{ + + address owner; + + modifier onlyOwner(){ + require(msg.sender == owner); + _; + } + +} + +contract MyContract is Owner{ + + mapping(address => uint) balances; + + constructor() public{ + owner = msg.sender; + } + + function mint(uint value) onlyOwner public{ + balances[msg.sender] += value; + } + +} From a5db66067f8af5948800c83ea638a997bbaa4b42 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 25 Sep 2018 10:40:12 +0100 Subject: [PATCH 079/308] Fix incorrect multiple inheritance order --- slither/solc_parsing/declarations/contract.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index 4aa605e9c..c2db767eb 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -234,10 +234,15 @@ class ContractSolc04(Contract): return def analyze_params_functions(self): + # keep track of the contracts visited + # to prevent an ovveride due to multiple inheritance of the same contract + # A is B, C, D is C, --> the second C was already seen + contracts_visited = [] for father in self.inheritance_reverse: - functions = {k:v for (k,v) in father.functions_as_dict().items()} #if not v.is_constructor} + functions = {k:v for (k, v) in father.functions_as_dict().items() + if not v.contract in contracts_visited} + contracts_visited.append(father) self._functions.update(functions) - for function in self._functions_no_params: function.analyze_params() self._functions[function.full_name] = function From 374dfa72d58ee65d7f02134d78234f0a8d29d83e Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 25 Sep 2018 12:07:35 +0100 Subject: [PATCH 080/308] Improve call to library (not working with using A for *) --- slither/core/cfg/node.py | 2 +- slither/slithir/convert.py | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index 6422f8b06..8f6be4af6 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -314,4 +314,4 @@ class Node(SourceMapping, ChildFunction): def slithir_generation(self): if self.expression: expression = self.expression - self._irs = convert_expression(expression) + self._irs = convert_expression(expression, self) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 79fc79cc5..54333ada7 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -292,7 +292,29 @@ def extract_tmp_call(ins): raise Exception('Not extracted {} {}'.format(type(ins.called), ins)) -def convert_expression(expression): +def convert_libs(result, contract): + using_for = contract.using_for + for idx in range(len(result)): + ir = result[idx] + if isinstance(ir, HighLevelCall): + if ir.destination.type in using_for: + destination = using_for[ir.destination.type] + print(destination) + # destination is a UserDefinedType + destination = contract.slither.get_contract_from_name(str(destination)) + print(destination) + lib_call = LibraryCall(destination, + ir.function_name, + ir.nbr_arguments, + ir.lvalue, + ir.type_call) + lib_call.call_gas = ir.call_gas + lib_call.arguments = [ir.destination] + ir.arguments + result[idx] = lib_call + + return result + +def convert_expression(expression, node): # handle standlone expression # such as return true; if isinstance(expression, Literal): @@ -302,4 +324,6 @@ def convert_expression(expression): result = apply_ir_heuristics(result) + result = convert_libs(result, node.function.contract) + return result From 7df39d4b07734617d8e84389d1f102a6a0aadc7b Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 25 Sep 2018 12:10:06 +0100 Subject: [PATCH 081/308] Filter library to only Variable --- slither/slithir/convert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 54333ada7..4e62124bb 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -296,7 +296,7 @@ def convert_libs(result, contract): using_for = contract.using_for for idx in range(len(result)): ir = result[idx] - if isinstance(ir, HighLevelCall): + if isinstance(ir, HighLevelCall) and isinstance(ir.destination, Variable): if ir.destination.type in using_for: destination = using_for[ir.destination.type] print(destination) From d8ca53c6e960997e582a944ea15afc7e4ae14672 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 25 Sep 2018 12:12:21 +0100 Subject: [PATCH 082/308] Add TmpNewStructure module --- .../tmp_operations/tmp_new_structure.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 slither/slithir/tmp_operations/tmp_new_structure.py diff --git a/slither/slithir/tmp_operations/tmp_new_structure.py b/slither/slithir/tmp_operations/tmp_new_structure.py new file mode 100644 index 000000000..90f11d115 --- /dev/null +++ b/slither/slithir/tmp_operations/tmp_new_structure.py @@ -0,0 +1,20 @@ +from slither.slithir.operations.lvalue import OperationWithLValue + +class TmpNewStructure(OperationWithLValue): + + def __init__(self, contract_name, lvalue): + super(TmpNewStructure, self).__init__() + self._contract_name = contract_name + self._lvalue = lvalue + + @property + def contract_name(self): + return self._contract_name + + @property + def read(self): + return [] + + def __str__(self): + return '{} = tmpnew {}'.format(self.lvalue, self.contract_name) + From 43f6414d9ff4d3ed53f6dad91b0ba1288e6fcff8 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 25 Sep 2018 17:08:21 +0100 Subject: [PATCH 083/308] Open source unused state variable detector --- README.md | 1 + scripts/travis_test.sh | 6 +++ slither/__main__.py | 2 + .../variables/unused_state_variables.py | 48 +++++++++++++++++++ tests/unused_state.sol | 13 +++++ 5 files changed, 70 insertions(+) create mode 100644 slither/detectors/variables/unused_state_variables.py create mode 100644 tests/unused_state.sol diff --git a/README.md b/README.md index ef1145742..a8355d368 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ Check | Purpose | Impact | Confidence `--detect-tx-origin`| Detect dangerous usage of `tx.origin` | Medium | Medium `--detect-pragma`| Detect if different pragma directives are used | Informational | High `--detect-solc-version`| Detect if an old version of Solidity is used (<0.4.23) | Informational | High +`--detect-unused-state`| Detection of unused state variables | Informational | High [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index a7392418a..3eb95c396 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -37,6 +37,12 @@ if [ $? -ne 2 ]; then exit 1 fi +slither tests/unused_state.sol +if [ $? -ne 1 ]; then + exit 1 +fi + + ### Test scripts python examples/scripts/functions_called.py examples/scripts/functions_called.sol diff --git a/slither/__main__.py b/slither/__main__.py index 6f12bfaa7..f65e7f97c 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -71,6 +71,7 @@ def main(): from slither.detectors.attributes.old_solc import OldSolc from slither.detectors.reentrancy.reentrancy import Reentrancy from slither.detectors.variables.uninitialized_storage_variables import UninitializedStorageVars + from slither.detectors.variables.unused_state_variables import UnusedStateVars from slither.detectors.statements.tx_origin import TxOrigin detectors = [Backdoor, @@ -79,6 +80,7 @@ def main(): OldSolc, Reentrancy, UninitializedStorageVars, + UnusedStateVars, TxOrigin] from slither.printers.summary.summary import PrinterSummary diff --git a/slither/detectors/variables/unused_state_variables.py b/slither/detectors/variables/unused_state_variables.py new file mode 100644 index 000000000..d1e6777c1 --- /dev/null +++ b/slither/detectors/variables/unused_state_variables.py @@ -0,0 +1,48 @@ +""" +Module detecting unused state variables +""" + +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification + +class UnusedStateVars(AbstractDetector): + """ + Unused state variables detector + """ + + ARGUMENT = 'unused-state' + HELP = 'Unused state variables' + CLASSIFICATION = DetectorClassification.CODE_QUALITY + + def detect_unused(self, contract): + if contract.is_signature_only(): + return None + # Get all the variables read in all the functions and modifiers + variables_used = [x.state_variables_read + x.state_variables_written for x in + (contract.functions + contract.modifiers)] + # Flat list + variables_used = [item for sublist in variables_used for item in sublist] + # Return the variables unused that are not public + return [x for x in contract.variables if + x not in variables_used and x.visibility != 'public'] + + def detect(self): + """ Detect unused state variables + """ + results = [] + for c in self.slither.contracts_derived: + unusedVars = self.detect_unused(c) + if unusedVars: + unusedVarsName = [v.name for v in unusedVars] + info = "Unused state variables in %s, Contract: %s, Vars %s" % (self.filename, + c.name, + str(unusedVarsName)) + self.log(info) + + sourceMapping = [v.source_mapping for v in unusedVars] + + results.append({'vuln': 'unusedStateVars', + 'sourceMapping': sourceMapping, + 'filename': self.filename, + 'contract': c.name, + 'unusedVars': unusedVarsName}) + return results diff --git a/tests/unused_state.sol b/tests/unused_state.sol new file mode 100644 index 000000000..7d3e5875a --- /dev/null +++ b/tests/unused_state.sol @@ -0,0 +1,13 @@ +pragma solidity 0.4.24; + +contract A{ + address unused; + address used; +} + +contract B is A{ + + function () public{ + used = 0; + } +} From 3d7781aac505cd4f8c44d856901ec510037f2b28 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 25 Sep 2018 17:20:31 +0100 Subject: [PATCH 084/308] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a8355d368..424fe9dbf 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Check | Purpose | Impact | Confidence --- | --- | --- | --- `--detect-uninitialized-state`| Detect uninitialized state variables | High | High `--detect-uninitialized-storage`| Detect uninitialized storage variables | High | High -`--detect-reentrancy`| Detect if different pragma directives are used | High | Medium +`--detect-reentrancy`| Detect reentrancy bugs | High | Medium `--detect-tx-origin`| Detect dangerous usage of `tx.origin` | Medium | Medium `--detect-pragma`| Detect if different pragma directives are used | Informational | High `--detect-solc-version`| Detect if an old version of Solidity is used (<0.4.23) | Informational | High From f428e4cb4b6bfda0ffde29e3d02ee732537188e9 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 26 Sep 2018 10:55:00 +0100 Subject: [PATCH 085/308] AbstractDetector: - Replace Classification by Impact - Add Confidence - Replace CODEQUALITY by Informational Add hidden option to automatically generate markdown list of detectors Update Readme --- README.md | 6 +-- slither/__main__.py | 38 ++++++++++++++++++- slither/detectors/abstract_detector.py | 33 ++++++++++------ .../detectors/attributes/constant_pragma.py | 5 ++- slither/detectors/attributes/old_solc.py | 5 ++- slither/detectors/examples/backdoor.py | 3 +- slither/detectors/reentrancy/reentrancy.py | 7 ++-- .../shadowing/shadowing_functions.py | 5 +-- slither/detectors/statements/tx_origin.py | 5 ++- .../uninitialized_state_variables.py | 5 ++- .../uninitialized_storage_variables.py | 5 ++- .../variables/unused_state_variables.py | 5 ++- 12 files changed, 86 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 424fe9dbf..7c1d5cbcd 100644 --- a/README.md +++ b/README.md @@ -50,13 +50,13 @@ By default, all the checks are run. Check | Purpose | Impact | Confidence --- | --- | --- | --- +`--detect-reentrancy`| Detect reentrancy vulnerabilities | High | Medium `--detect-uninitialized-state`| Detect uninitialized state variables | High | High `--detect-uninitialized-storage`| Detect uninitialized storage variables | High | High -`--detect-reentrancy`| Detect reentrancy bugs | High | Medium `--detect-tx-origin`| Detect dangerous usage of `tx.origin` | Medium | Medium `--detect-pragma`| Detect if different pragma directives are used | Informational | High -`--detect-solc-version`| Detect if an old version of Solidity is used (<0.4.23) | Informational | High -`--detect-unused-state`| Detection of unused state variables | Informational | High +`--detect-solc-version`| Detect if an old version of Solidity used (<0.4.23) | Informational | High +`--detect-unused-state`| Detect unused state variables | Informational | High [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. diff --git a/slither/__main__.py b/slither/__main__.py index f65e7f97c..a8cd4e4fc 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -10,13 +10,37 @@ import traceback from pkg_resources import iter_entry_points -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.detectors.abstract_detector import (AbstractDetector, + DetectorClassification, + classification_txt) from slither.printers.abstract_printer import AbstractPrinter from slither.slither import Slither logging.basicConfig() logger = logging.getLogger("Slither") +def output_to_markdown(detector_classes): + """ + Pretty print of the detectors to README.md + """ + detectors_list = [] + for detector in detector_classes: + argument = detector.ARGUMENT + # dont show the backdoor example + if argument == 'backdoor': + continue + help_info = detector.HELP + impact = detector.IMPACT + confidence = classification_txt[detector.CONFIDENCE] + detectors_list.append((argument, help_info, impact, confidence)) + + # Sort by impact and name + detectors_list = sorted(detectors_list, key=lambda element: (element[2], element[0])) + for (argument, help_info, impact, confidence) in detectors_list: + print('`--detect-{}`| Detect {} | {} | {}'.format(argument, + help_info, + classification_txt[impact], + confidence)) def process(filename, args, detector_classes, printer_classes): """ @@ -116,6 +140,10 @@ def main_impl(all_detector_classes, all_printer_classes): """ args = parse_args(all_detector_classes, all_printer_classes) + if args.markdown: + output_to_markdown(all_detector_classes) + return + detector_classes = choose_detectors(args, all_detector_classes) printer_classes = choose_printers(args, all_printer_classes) @@ -248,8 +276,14 @@ def parse_args(detector_classes, printer_classes): dest="printers_to_run", const=printer_cls.ARGUMENT) + # debugger command parser.add_argument('--debug', - help='Debug mode', + help=argparse.SUPPRESS, + action="store_true", + default=False) + + parser.add_argument('--markdown', + help=argparse.SUPPRESS, action="store_true", default=False) diff --git a/slither/detectors/abstract_detector.py b/slither/detectors/abstract_detector.py index b05cdf9d0..83c857e45 100644 --- a/slither/detectors/abstract_detector.py +++ b/slither/detectors/abstract_detector.py @@ -9,26 +9,31 @@ class IncorrectDetectorInitialization(Exception): class DetectorClassification: - LOW = 0 + HIGH = 0 MEDIUM = 1 - HIGH = 2 - CODE_QUALITY = 3 + LOW = 2 + INFORMATIONAL = 3 classification_colors = { - DetectorClassification.CODE_QUALITY: green, + 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 - CLASSIFICATION = None - - HIDDEN_DETECTOR = False # yes if the detector should not be showed + IMPACT = None + CONFIDENCE = None def __init__(self, slither, logger): self.slither = slither @@ -45,11 +50,17 @@ class AbstractDetector(metaclass=abc.ABCMeta): if re.match('^[a-zA-Z0-9_-]*$', self.ARGUMENT) is None: raise IncorrectDetectorInitialization('ARGUMENT has illegal character') - if self.CLASSIFICATION not in [DetectorClassification.LOW, + if self.IMPACT not in [DetectorClassification.LOW, + DetectorClassification.MEDIUM, + DetectorClassification.HIGH, + DetectorClassification.INFORMATIONAL]: + raise IncorrectDetectorInitialization('IMPACT is not initialized') + + if self.CONFIDENCE not in [DetectorClassification.LOW, DetectorClassification.MEDIUM, DetectorClassification.HIGH, - DetectorClassification.CODE_QUALITY]: - raise IncorrectDetectorInitialization('CLASSIFICATION is not initialized') + DetectorClassification.INFORMATIONAL]: + raise IncorrectDetectorInitialization('CONFIDENCE is not initialized') def log(self, info): if self.logger: @@ -62,4 +73,4 @@ class AbstractDetector(metaclass=abc.ABCMeta): @property def color(self): - return classification_colors[self.CLASSIFICATION] + return classification_colors[self.IMPACT] diff --git a/slither/detectors/attributes/constant_pragma.py b/slither/detectors/attributes/constant_pragma.py index c68724045..51b6a2b9d 100644 --- a/slither/detectors/attributes/constant_pragma.py +++ b/slither/detectors/attributes/constant_pragma.py @@ -11,8 +11,9 @@ class ConstantPragma(AbstractDetector): """ ARGUMENT = 'pragma' - HELP = 'different pragma directives' - CLASSIFICATION = DetectorClassification.CODE_QUALITY + HELP = 'if different pragma directives are used' + IMPACT = DetectorClassification.INFORMATIONAL + CONFIDENCE = DetectorClassification.HIGH def detect(self): results = [] diff --git a/slither/detectors/attributes/old_solc.py b/slither/detectors/attributes/old_solc.py index c5220d42e..13f5f8393 100644 --- a/slither/detectors/attributes/old_solc.py +++ b/slither/detectors/attributes/old_solc.py @@ -12,8 +12,9 @@ class OldSolc(AbstractDetector): """ ARGUMENT = 'solc-version' - HELP = 'an old version of Solidity used (<0.4.23)' - CLASSIFICATION = DetectorClassification.CODE_QUALITY + HELP = 'if an old version of Solidity used (<0.4.23)' + IMPACT = DetectorClassification.INFORMATIONAL + CONFIDENCE = DetectorClassification.HIGH def detect(self): results = [] diff --git a/slither/detectors/examples/backdoor.py b/slither/detectors/examples/backdoor.py index 90299bc40..e3bf25cca 100644 --- a/slither/detectors/examples/backdoor.py +++ b/slither/detectors/examples/backdoor.py @@ -8,7 +8,8 @@ class Backdoor(AbstractDetector): ARGUMENT = 'backdoor' # slither will launch the detector with slither.py --mydetector HELP = 'function named backdoor (detector example)' - CLASSIFICATION = DetectorClassification.HIGH + IMPACT = DetectorClassification.HIGH + CONFIDENCE = DetectorClassification.HIGH def detect(self): ret = [] diff --git a/slither/detectors/reentrancy/reentrancy.py b/slither/detectors/reentrancy/reentrancy.py index 214820dbb..26357aa98 100644 --- a/slither/detectors/reentrancy/reentrancy.py +++ b/slither/detectors/reentrancy/reentrancy.py @@ -15,10 +15,9 @@ from slither.visitors.expression.export_values import ExportValues class Reentrancy(AbstractDetector): ARGUMENT = 'reentrancy' - HELP = 'Re-entrancy' - # High impact - # Medium confidence - CLASSIFICATION = DetectorClassification.HIGH + HELP = 'reentrancy vulnerabilities' + IMPACT = DetectorClassification.HIGH + CONFIDENCE = DetectorClassification.MEDIUM @staticmethod def _is_legit_call(call_name): diff --git a/slither/detectors/shadowing/shadowing_functions.py b/slither/detectors/shadowing/shadowing_functions.py index cd541b61f..60d9677df 100644 --- a/slither/detectors/shadowing/shadowing_functions.py +++ b/slither/detectors/shadowing/shadowing_functions.py @@ -15,9 +15,8 @@ class ShadowingFunctionsDetection(AbstractDetector): ARGUMENT = 'shadowing-function' HELP = 'Function Shadowing' - CLASSIFICATION = DetectorClassification.LOW - - HIDDEN_DETECTOR = True + IMPACT = DetectorClassification.LOW + CONFIDENCE = DetectorClassification.HIGH def detect_shadowing(self, contract): functions_declared = set([x.full_name for x in contract.functions]) diff --git a/slither/detectors/statements/tx_origin.py b/slither/detectors/statements/tx_origin.py index 2e3b498c6..9b02f008d 100644 --- a/slither/detectors/statements/tx_origin.py +++ b/slither/detectors/statements/tx_origin.py @@ -10,8 +10,9 @@ class TxOrigin(AbstractDetector): """ ARGUMENT = 'tx-origin' - HELP = 'tx.origin usage' - CLASSIFICATION = DetectorClassification.MEDIUM + HELP = 'dangerous usage of `tx.origin`' + IMPACT = DetectorClassification.MEDIUM + CONFIDENCE = DetectorClassification.MEDIUM @staticmethod def _contains_incorrect_tx_origin_use(node): diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index 051117019..cd9d48e39 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -20,8 +20,9 @@ class UninitializedStateVarsDetection(AbstractDetector): """ ARGUMENT = 'uninitialized-state' - HELP = 'Uninitialized state variables' - CLASSIFICATION = DetectorClassification.HIGH + HELP = 'uninitialized state variables' + IMPACT = DetectorClassification.HIGH + CONFIDENCE = DetectorClassification.HIGH def detect_uninitialized(self, contract): # get all the state variables read by all functions diff --git a/slither/detectors/variables/uninitialized_storage_variables.py b/slither/detectors/variables/uninitialized_storage_variables.py index 4ad74600d..70ffdc625 100644 --- a/slither/detectors/variables/uninitialized_storage_variables.py +++ b/slither/detectors/variables/uninitialized_storage_variables.py @@ -15,8 +15,9 @@ class UninitializedStorageVars(AbstractDetector): """ ARGUMENT = 'uninitialized-storage' - HELP = 'Uninitialized storage variables' - CLASSIFICATION = DetectorClassification.HIGH + HELP = 'uninitialized storage variables' + IMPACT = DetectorClassification.HIGH + CONFIDENCE = DetectorClassification.HIGH # node.context[self.key] contains the uninitialized storage variables diff --git a/slither/detectors/variables/unused_state_variables.py b/slither/detectors/variables/unused_state_variables.py index d1e6777c1..7079a7f3c 100644 --- a/slither/detectors/variables/unused_state_variables.py +++ b/slither/detectors/variables/unused_state_variables.py @@ -10,8 +10,9 @@ class UnusedStateVars(AbstractDetector): """ ARGUMENT = 'unused-state' - HELP = 'Unused state variables' - CLASSIFICATION = DetectorClassification.CODE_QUALITY + HELP = 'unused state variables' + IMPACT = DetectorClassification.INFORMATIONAL + CONFIDENCE = DetectorClassification.HIGH def detect_unused(self, contract): if contract.is_signature_only(): From a2e15902279c37b4c0bb3a3c759e6c7083aae070 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 26 Sep 2018 11:29:43 +0100 Subject: [PATCH 086/308] Improve error reporting for incorrect detector config --- slither/detectors/abstract_detector.py | 10 +++++----- slither/solc_parsing/slitherSolc.py | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/slither/detectors/abstract_detector.py b/slither/detectors/abstract_detector.py index 83c857e45..1eab711d1 100644 --- a/slither/detectors/abstract_detector.py +++ b/slither/detectors/abstract_detector.py @@ -42,25 +42,25 @@ class AbstractDetector(metaclass=abc.ABCMeta): self.logger = logger if not self.HELP: - raise IncorrectDetectorInitialization('HELP is not initialized') + raise IncorrectDetectorInitialization('HELP is not initialized {}'.format(self.__class__.__name__)) if not self.ARGUMENT: - raise IncorrectDetectorInitialization('ARGUMENT is not initialized') + raise IncorrectDetectorInitialization('ARGUMENT is not initialized {}'.format(self.__class__.__name__)) if re.match('^[a-zA-Z0-9_-]*$', self.ARGUMENT) is None: - raise IncorrectDetectorInitialization('ARGUMENT has illegal character') + 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') + 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') + raise IncorrectDetectorInitialization('CONFIDENCE is not initialized {}'.format(self.__class__.__name__)) def log(self, info): if self.logger: diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index ed71cd5a9..2c5ce28c4 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -17,7 +17,6 @@ class SlitherSolc(Slither): self._contractsNotParsed = [] self._contracts_by_id = {} self._analyzed = False - print(filename) def _parse_contracts_from_json(self, json_data): first = json_data.find('{') From 67907575492b199d98282c2f05bbd941c26f780c Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 26 Sep 2018 11:31:29 +0100 Subject: [PATCH 087/308] Update __main__.py with new classification system --- slither/__main__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/slither/__main__.py b/slither/__main__.py index a8cd4e4fc..4ebefb286 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -299,16 +299,16 @@ def choose_detectors(args, all_detector_classes): if args.exclude_informational: detectors_to_run = [d for d in detectors_to_run if - d.CLASSIFICATION != DetectorClassification.CODE_QUALITY] + d.IMPACT != DetectorClassification.INFORMATIONAL] if args.exclude_low: detectors_to_run = [d for d in detectors_to_run if - d.CLASSIFICATION != DetectorClassification.LOW] + d.IMPACT != DetectorClassification.LOW] if args.exclude_medium: detectors_to_run = [d for d in detectors_to_run if - d.CLASSIFICATION != DetectorClassification.MEDIUM] + d.IMPACT != DetectorClassification.MEDIUM] if args.exclude_high: detectors_to_run = [d for d in detectors_to_run if - d.CLASSIFICATION != DetectorClassification.HIGH] + d.IMPACT != DetectorClassification.HIGH] if args.detectors_to_exclude: detectors_to_run = [d for d in detectors_to_run if d.ARGUMENT not in args.detectors_to_exclude] From 0d1bbbebad52affcc8f6ee5855ab16e3b6bbbc74 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Wed, 26 Sep 2018 11:35:48 +0100 Subject: [PATCH 088/308] Update example.py Update plugin example --- plugin_example/slither_my_plugin/detectors/example.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin_example/slither_my_plugin/detectors/example.py b/plugin_example/slither_my_plugin/detectors/example.py index 20e249e28..be800abe8 100644 --- a/plugin_example/slither_my_plugin/detectors/example.py +++ b/plugin_example/slither_my_plugin/detectors/example.py @@ -9,7 +9,8 @@ class Example(AbstractDetector): ARGUMENT = 'mydetector' # slither will launch the detector with slither.py --mydetector HELP = 'Help printed by slither' - CLASSIFICATION = DetectorClassification.HIGH + IMPACT = DetectorClassification.HIGH + CONFIDENCE = DetectorClassification.HIGH def detect(self): From 773e39f1503f9dc86b26e536555ea530359e346e Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 26 Sep 2018 14:02:35 +0100 Subject: [PATCH 089/308] Use slithIR to determine if a function is unprotected Add slithIR to modifiers --- examples/scripts/export_to_dot.py | 2 +- slither/core/cfg/node.py | 11 +++- slither/core/declarations/function.py | 65 +++++++++++++------ slither/slithir/convert.py | 2 - slither/solc_parsing/declarations/modifier.py | 2 + 5 files changed, 59 insertions(+), 23 deletions(-) diff --git a/examples/scripts/export_to_dot.py b/examples/scripts/export_to_dot.py index 268437d1c..ef4e79779 100644 --- a/examples/scripts/export_to_dot.py +++ b/examples/scripts/export_to_dot.py @@ -10,7 +10,7 @@ if len(sys.argv) != 2: slither = Slither(sys.argv[1]) for contract in slither.contracts: - for function in contract.functions: + for function in contract.functions + contract.modifiers: filename = "{}-{}-{}.dot".format(sys.argv[1], contract.name, function.full_name) print('Export {}'.format(filename)) function.slithir_cfg_to_dot(filename) diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index 8f6be4af6..d174a3c71 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -229,12 +229,21 @@ class Node(SourceMapping, ChildFunction): def contains_if(self): """ - Check if the node is a conditional node + Check if the node is a IF node Returns: bool: True if the node is a conditional node (IF or IFLOOP) """ return self.type in [NodeType.IF, NodeType.IFLOOP] + def is_conditional(self): + """ + Check if the node is a conditional node + A conditional node is either a IF or a require/assert + Returns: + bool: True if the node is a conditional node + """ + return self.contains_if() or self.contains_require_or_assert() + def add_father(self, father): """ Add a father node diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 115d5a3ac..85c6d8489 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -3,17 +3,17 @@ """ import logging from itertools import groupby -from slither.core.source_mapping.source_mapping import SourceMapping -from slither.core.children.child_contract import ChildContract - -from slither.core.variables.state_variable import StateVariable +from slither.core.children.child_contract import ChildContract +from slither.core.declarations.solidity_variables import (SolidityFunction, + SolidityVariable, + SolidityVariableComposed) from slither.core.expressions.identifier import Identifier -from slither.core.expressions.unary_operation import UnaryOperation -from slither.core.expressions.member_access import MemberAccess from slither.core.expressions.index_access import IndexAccess - -from slither.core.declarations.solidity_variables import SolidityVariable, SolidityFunction +from slither.core.expressions.member_access import MemberAccess +from slither.core.expressions.unary_operation import UnaryOperation +from slither.core.source_mapping.source_mapping import SourceMapping +from slither.core.variables.state_variable import StateVariable logger = logging.getLogger("Function") @@ -407,6 +407,38 @@ class Function(ChildContract, SourceMapping): """ return self._explore_functions(lambda x: x.internal_calls) + def all_conditional_state_variables_read(self): + """ + Return the state variable used in a condition + + Over approximate and also return index access + It won't work if the variable is assigned to a temp variable + """ + def _explore_func(func): + ret = [n.state_variables_read for n in func.nodes if n.is_conditional()] + return [item for sublist in ret for item in sublist] + return self._explore_functions(lambda x: _explore_func(x)) + + def all_conditional_solidity_variables_read(self): + """ + Return the Soldiity variables directly used in a condtion + + Use of the IR to filter index access + Assumption: the solidity vars are used directly in the conditional node + It won't work if the variable is assigned to a temp variable + """ + from slither.slithir.operations.binary import BinaryOperation as BinaryOperationIR + def _solidity_variable_in_node(node): + ret = [] + for ir in node.irs: + if isinstance(ir, BinaryOperationIR): + ret += ir.read + return [var for var in ret if isinstance(var, SolidityVariable)] + def _explore_func(func, f): + ret = [f(n) for n in func.nodes if n.is_conditional()] + return [item for sublist in ret for item in sublist] + return self._explore_functions(lambda x: _explore_func(x, _solidity_variable_in_node)) + def is_reading(self, variable): """ Check if the function reads the variable @@ -517,18 +549,13 @@ class Function(ChildContract, SourceMapping): """ Determine if the function is protected using a check on msg.sender + Only detects if msg.sender is directly used in a condition + For example, it wont work for: + address a = msg.sender + require(a == owner) Returns (bool) """ - from slither.core.cfg.node import NodeType - read_var_cond = [x.variables_read for x in self.nodes if x.type == NodeType.IF] - read_var_cond = [item for sublist in read_var_cond for item in sublist] - if 'msg.sender' in [x.name for x in read_var_cond]: - return True - - read_var_require = [n.variables_read for n in self.nodes if n.contains_require_or_assert()] - read_var_require = [item for sublist in read_var_require for item in sublist] - if 'msg.sender' in [x.name for x in read_var_require]: - return True - return False + conditional_vars = self.all_conditional_solidity_variables_read() + return SolidityVariableComposed('msg.sender') in conditional_vars diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 4e62124bb..35c3c6355 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -299,10 +299,8 @@ def convert_libs(result, contract): if isinstance(ir, HighLevelCall) and isinstance(ir.destination, Variable): if ir.destination.type in using_for: destination = using_for[ir.destination.type] - print(destination) # destination is a UserDefinedType destination = contract.slither.get_contract_from_name(str(destination)) - print(destination) lib_call = LibraryCall(destination, ir.function_name, ir.nbr_arguments, diff --git a/slither/solc_parsing/declarations/modifier.py b/slither/solc_parsing/declarations/modifier.py index cd7e7d756..4797a6b74 100644 --- a/slither/solc_parsing/declarations/modifier.py +++ b/slither/solc_parsing/declarations/modifier.py @@ -50,6 +50,8 @@ class ModifierSolc(Modifier, FunctionSolc): self._analyze_read_write() self._analyze_calls() + for node in self.nodes: + node.slithir_generation() def _parse_statement(self, statement, node): name = statement['name'] From b486c8c1fb3f40f1380b44cbd81a6eb549625fa6 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 26 Sep 2018 17:24:36 +0100 Subject: [PATCH 090/308] Add Transfer and Send operation --- slither/core/declarations/function.py | 26 +++++++++++++-- slither/slithir/convert.py | 31 +++++++++++++---- slither/slithir/operations/internal_call.py | 2 +- slither/slithir/operations/send.py | 37 +++++++++++++++++++++ slither/slithir/operations/transfer.py | 35 +++++++++++++++++++ 5 files changed, 121 insertions(+), 10 deletions(-) create mode 100644 slither/slithir/operations/send.py create mode 100644 slither/slithir/operations/transfer.py diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 85c6d8489..23dc4f90d 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -427,11 +427,11 @@ class Function(ChildContract, SourceMapping): Assumption: the solidity vars are used directly in the conditional node It won't work if the variable is assigned to a temp variable """ - from slither.slithir.operations.binary import BinaryOperation as BinaryOperationIR + from slither.slithir.operations.binary import BinaryOperation def _solidity_variable_in_node(node): ret = [] for ir in node.irs: - if isinstance(ir, BinaryOperationIR): + if isinstance(ir, BinaryOperation): ret += ir.read return [var for var in ret if isinstance(var, SolidityVariable)] def _explore_func(func, f): @@ -439,6 +439,25 @@ class Function(ChildContract, SourceMapping): return [item for sublist in ret for item in sublist] return self._explore_functions(lambda x: _explore_func(x, _solidity_variable_in_node)) + def all_solidity_variables_used_as_args(self): + """ + Return the Soldiity variables directly used in a call + + Use of the IR to filter index access + Used to catch check(msg.sender) + """ + from slither.slithir.operations.internal_call import InternalCall + def _solidity_variable_in_node(node): + ret = [] + for ir in node.irs: + if isinstance(ir, InternalCall): + ret += ir.read + return [var for var in ret if isinstance(var, SolidityVariable)] + def _explore_func(func, f): + ret = [f(n) for n in func.nodes] + return [item for sublist in ret for item in sublist] + return self._explore_functions(lambda x: _explore_func(x, _solidity_variable_in_node)) + def is_reading(self, variable): """ Check if the function reads the variable @@ -558,4 +577,5 @@ class Function(ChildContract, SourceMapping): """ conditional_vars = self.all_conditional_solidity_variables_read() - return SolidityVariableComposed('msg.sender') in conditional_vars + args_vars = self.all_solidity_variables_used_as_args() + return SolidityVariableComposed('msg.sender') in conditional_vars + args_vars diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 35c3c6355..f4fc14427 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -4,6 +4,7 @@ from slither.core.declarations.solidity_variables import (SolidityFunction, SolidityVariableComposed) from slither.core.declarations.structure import Structure from slither.core.expressions.literal import Literal +from slither.core.solidity_types.elementary_type import ElementaryType from slither.core.variables.variable import Variable from slither.slithir.operations.call import Call from slither.slithir.operations.event_call import EventCall @@ -18,7 +19,9 @@ from slither.slithir.operations.new_contract import NewContract from slither.slithir.operations.new_elementary_type import NewElementaryType from slither.slithir.operations.new_structure import NewStructure from slither.slithir.operations.push import Push +from slither.slithir.operations.send import Send from slither.slithir.operations.solidity_call import SolidityCall +from slither.slithir.operations.transfer import Transfer from slither.slithir.tmp_operations.argument import Argument, ArgumentType from slither.slithir.tmp_operations.tmp_call import TmpCall from slither.slithir.tmp_operations.tmp_new_array import TmpNewArray @@ -228,6 +231,12 @@ def replace_calls(result): Replace to call 'call' 'delegatecall', 'callcode' to an LowLevelCall ''' reset = True + def is_address(v): + if not isinstance(v, Variable): + return False + if not isinstance(v.type, ElementaryType): + return False + return v.type.type == 'address' while reset: reset = False for idx in range(len(result)): @@ -244,12 +253,22 @@ def replace_calls(result): break else: result[idx] = Push(ins.destination, ins.arguments[0]) - if ins.function_name in ['call', 'delegatecall', 'callcode']: - result[idx] = LowLevelCall(ins.destination, - ins.function_name, - ins.nbr_arguments, - ins.lvalue, - ins.type_call) + if is_address(ins.destination): + if ins.function_name == 'transfer': + assert len(ins.arguments) == 1 + result[idx] = Transfer(ins.destination, ins.arguments[0]) + elif ins.function_name == 'send': + assert len(ins.arguments) == 1 + result[idx] = Send(ins.destination, ins.arguments[0], ins.lvalue) + else: + assert ins.function_name in ['call', 'delegatecall', 'callcode'] + result[idx] = LowLevelCall(ins.destination, + ins.function_name, + ins.nbr_arguments, + ins.lvalue, + ins.type_call) + result[idx].call_gas = ins.call_gas + result[idx].call_value = ins.call_value return result diff --git a/slither/slithir/operations/internal_call.py b/slither/slithir/operations/internal_call.py index ed69f9cf9..5cf50903c 100644 --- a/slither/slithir/operations/internal_call.py +++ b/slither/slithir/operations/internal_call.py @@ -16,7 +16,7 @@ class InternalCall(Call, OperationWithLValue): @property def read(self): - return [] + return list(self.arguments) @property def function(self): diff --git a/slither/slithir/operations/send.py b/slither/slithir/operations/send.py new file mode 100644 index 000000000..9a521e5e3 --- /dev/null +++ b/slither/slithir/operations/send.py @@ -0,0 +1,37 @@ +from slither.slithir.operations.call import Call +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.variables.variable import Variable +from slither.core.declarations.solidity_variables import SolidityVariable + +from slither.slithir.utils.utils import is_valid_lvalue +from slither.slithir.variables.constant import Constant + +class Send(Call, OperationWithLValue): + + def __init__(self, destination, value, result): + assert is_valid_lvalue(result) + assert isinstance(destination, (Variable, SolidityVariable)) + super(Send, self).__init__() + self._destination = destination + self._lvalue = result + + self._call_value = value + + @property + def call_value(self): + return self._call_value + + @property + def read(self): + return [self.destination] + + @property + def destination(self): + return self._destination + + + def __str__(self): + value = 'value:{}'.format(self.call_value) + return str(self.lvalue) +' = SEND dest:{} {}'.format(self.destination, value) +# + diff --git a/slither/slithir/operations/transfer.py b/slither/slithir/operations/transfer.py new file mode 100644 index 000000000..48187d210 --- /dev/null +++ b/slither/slithir/operations/transfer.py @@ -0,0 +1,35 @@ +from slither.slithir.operations.call import Call +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.variables.variable import Variable +from slither.core.declarations.solidity_variables import SolidityVariable + +from slither.slithir.utils.utils import is_valid_lvalue +from slither.slithir.variables.constant import Constant + +class Transfer(Call): + + def __init__(self, destination, value): + assert isinstance(destination, (Variable, SolidityVariable)) + self._destination = destination + super(Transfer, self).__init__() + + self._call_value = value + + + @property + def call_value(self): + return self._call_value + + @property + def read(self): + return [self.destination] + + @property + def destination(self): + return self._destination + + def __str__(self): + value = 'value:{}'.format(self.call_value) + return 'Transfer dest:{} {}'.format(self.destination, value) + + From 15b0f2d18ad9399bbabe5252f14e13c8e589132a Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 26 Sep 2018 17:37:00 +0100 Subject: [PATCH 091/308] Allow library on address --- slither/slithir/convert.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index f4fc14427..228bf773f 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -260,8 +260,8 @@ def replace_calls(result): elif ins.function_name == 'send': assert len(ins.arguments) == 1 result[idx] = Send(ins.destination, ins.arguments[0], ins.lvalue) - else: - assert ins.function_name in ['call', 'delegatecall', 'callcode'] + elif ins.function_name in ['call', 'delegatecall', 'callcode']: + # TODO: handle name collision result[idx] = LowLevelCall(ins.destination, ins.function_name, ins.nbr_arguments, @@ -269,6 +269,7 @@ def replace_calls(result): ins.type_call) result[idx].call_gas = ins.call_gas result[idx].call_value = ins.call_value + # other case are library on address return result From 75cb9c56fbb229ca0b6dfb3b380dc87403126012 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 26 Sep 2018 17:53:56 +0100 Subject: [PATCH 092/308] Allow multiple using_for on the same type --- slither/core/declarations/contract.py | 4 +++ slither/slithir/convert.py | 25 +++++++++++-------- slither/slithir/operations/library_call.py | 3 --- slither/solc_parsing/declarations/contract.py | 4 ++- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index f863d22e8..7be208bee 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -154,6 +154,10 @@ class Contract(ChildSlither, SourceMapping): return self._using_for def reverse_using_for(self, name): + ''' + Returns: + (list) + ''' return self._using_for[name] @property diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 228bf773f..aa68429e0 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -318,17 +318,20 @@ def convert_libs(result, contract): ir = result[idx] if isinstance(ir, HighLevelCall) and isinstance(ir.destination, Variable): if ir.destination.type in using_for: - destination = using_for[ir.destination.type] - # destination is a UserDefinedType - destination = contract.slither.get_contract_from_name(str(destination)) - lib_call = LibraryCall(destination, - ir.function_name, - ir.nbr_arguments, - ir.lvalue, - ir.type_call) - lib_call.call_gas = ir.call_gas - lib_call.arguments = [ir.destination] + ir.arguments - result[idx] = lib_call + for destination in using_for[ir.destination.type]: + # destination is a UserDefinedType + destination = contract.slither.get_contract_from_name(str(destination)) + if destination: + lib_call = LibraryCall(destination, + ir.function_name, + ir.nbr_arguments, + ir.lvalue, + ir.type_call) + lib_call.call_gas = ir.call_gas + lib_call.arguments = [ir.destination] + ir.arguments + result[idx] = lib_call + break + assert destination return result diff --git a/slither/slithir/operations/library_call.py b/slither/slithir/operations/library_call.py index 27bc59f6f..7bf0988f0 100644 --- a/slither/slithir/operations/library_call.py +++ b/slither/slithir/operations/library_call.py @@ -1,9 +1,6 @@ from slither.slithir.operations.high_level_call import HighLevelCall from slither.core.declarations.contract import Contract - -# TODO: use the usefor declaration - class LibraryCall(HighLevelCall): """ High level message call diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index c2db767eb..a59385f45 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -101,7 +101,9 @@ class ContractSolc04(Contract): else: new = parse_type(children[0], self) old = '*' - self._using_for[old] = new + if not old in self._using_for: + self.using_for[old] = [] + self._using_for[old].append(new) self._usingForNotParsed = [] def analyze_enums(self): From e426f1bc93b61edd6f224b03218b266a48843ff8 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 27 Sep 2018 11:05:50 +0100 Subject: [PATCH 093/308] Add specific_taint module Add Return/Condition operation Fix small bugs Change Substraction -> Subtraction --- slither/analyses/taint/specific_variable.py | 111 ++++++++++++++++++ .../core/declarations/solidity_variables.py | 22 +++- .../core/expressions/assignment_operation.py | 6 +- slither/core/expressions/binary_operation.py | 6 +- slither/slithir/convert.py | 18 ++- slither/slithir/operations/assignment.py | 6 +- slither/slithir/operations/binary.py | 6 +- slither/slithir/operations/condition.py | 23 ++++ .../slithir/operations/return_operation.py | 24 ++++ slither/slithir/operations/solidity_call.py | 4 +- slither/slithir/variables/constant.py | 1 + slither/solc_parsing/slitherSolc.py | 1 + .../visitors/slithir/expression_to_slithir.py | 4 +- 13 files changed, 211 insertions(+), 21 deletions(-) create mode 100644 slither/analyses/taint/specific_variable.py create mode 100644 slither/slithir/operations/condition.py create mode 100644 slither/slithir/operations/return_operation.py diff --git a/slither/analyses/taint/specific_variable.py b/slither/analyses/taint/specific_variable.py new file mode 100644 index 000000000..ffddfda13 --- /dev/null +++ b/slither/analyses/taint/specific_variable.py @@ -0,0 +1,111 @@ +""" + Compute taint from a specific variable + + Do not propagate taint on protected function + Propage to state variables + Iterate until it finding a fixpoint +""" +from slither.core.variables.variable import Variable +from slither.core.variables.state_variable import StateVariable +from slither.core.declarations.solidity_variables import SolidityVariable, SolidityVariableComposed + +from slither.slithir.operations.index import Index +from slither.slithir.operations.member import Member +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.slithir.operations.internal_call import InternalCall +from slither.slithir.variables.temporary import TemporaryVariable +from slither.slithir.variables.reference import ReferenceVariable + +def make_key(variable): + if isinstance(variable, Variable): + key = 'TAINT_{}{}{}'.format(variable.contract.name, + variable.name, + str(type(variable))) + else: + assert isinstance(variable, SolidityVariable) + key = 'TAINT_{}{}'.format(variable.name, + str(type(variable))) + return key + +def _visit_node(node, visited, key): + if node in visited: + return + + visited += [node] + taints = node.function.slither.context[key] + + refs = {} + for ir in node.irs: + if not isinstance(ir, OperationWithLValue): + continue + if isinstance(ir, (Index, Member)): + refs[ir.lvalue] = ir.variable_left + + if isinstance(ir, Index): + read = [ir.variable_left] + else: + read = ir.read +# print(ir) +# print('READ {}'.format([str(v) for v in read])) +# print('TAINT {}'.format([str(v) for v in taints])) +# print(any(var_read in taints for var_read in read)) +# print() + if any(is_tainted_from_key(var_read, key) or var_read in taints for var_read in read): + taints += [ir.lvalue] + ir.lvalue.context[key] = True + lvalue = ir.lvalue + while isinstance(lvalue, ReferenceVariable): + taints += [refs[lvalue]] + lvalue = refs[lvalue] + lvalue.context[key] = True + + taints = [v for v in taints if not isinstance(v, (TemporaryVariable, ReferenceVariable))] + + node.function.slither.context[key] = list(set(taints)) + + for son in node.sons: + _visit_node(son, visited, key) + +def run_taint(slither, taint): + + key = make_key(taint) + + prev_taints = [] + slither.context[key] = [taint] + # Loop until reaching a fixpoint + while(set(prev_taints) != set(slither.context[key])): + prev_taints = slither.context[key] + for contract in slither.contracts: + for function in contract.functions: + # Dont propagated taint on protected functions + if function.is_implemented and not function.is_protected(): + slither.context[key] = list(set(slither.context[key])) + _visit_node(function.entry_point, [], key) + + slither.context[key] = [v for v in prev_taints if isinstance(v, (StateVariable, SolidityVariable))] + +def is_tainted(variable, taint): + """ + Args: + variable (Variable) + taint (Variable): Root of the taint + """ + if not isinstance(variable, (Variable, SolidityVariable)): + return False + key = make_key(taint) + return key in variable.context and variable.context[key] + +def is_tainted_from_key(variable, key): + """ + Args: + variable (Variable) + key (str): key + """ + if not isinstance(variable, (Variable, SolidityVariable)): + return False + return key in variable.context and variable.context[key] + + +def get_state_variable_tainted(slither, taint): + key = make_key(taint) + return slither.context[key] diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py index 033a8e11e..a642e5c5e 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -1,5 +1,5 @@ # https://solidity.readthedocs.io/en/v0.4.24/units-and-global-variables.html - +from slither.core.context.context import Context SOLIDITY_VARIABLES = ["block", "msg", "now", "tx", "this", "super", 'abi'] @@ -51,12 +51,17 @@ def solidity_function_signature(name): """ return name+' returns({})'.format(','.join(SOLIDITY_FUNCTIONS[name])) -class SolidityVariable: +class SolidityVariable(Context): def __init__(self, name): - assert name in SOLIDITY_VARIABLES + super(SolidityVariable, self).__init__() + self._check_name(name) self._name = name + # dev function, will be removed once the code is stable + def _check_name(self, name): + assert name in SOLIDITY_VARIABLES + @property def name(self): return self._name @@ -72,8 +77,10 @@ class SolidityVariable: class SolidityVariableComposed(SolidityVariable): def __init__(self, name): + super(SolidityVariableComposed, self).__init__(name) + + def _check_name(self, name): assert name in SOLIDITY_VARIABLES_COMPOSED - self._name = name @property def name(self): @@ -82,6 +89,13 @@ class SolidityVariableComposed(SolidityVariable): def __str__(self): return self._name + def __eq__(self, other): + return self.__class__ == other.__class__ and self.name == other.name + + def __hash__(self): + return hash(self.name) + + class SolidityFunction: def __init__(self, name): diff --git a/slither/core/expressions/assignment_operation.py b/slither/core/expressions/assignment_operation.py index 1e095fd1f..fe7d524bf 100644 --- a/slither/core/expressions/assignment_operation.py +++ b/slither/core/expressions/assignment_operation.py @@ -13,7 +13,7 @@ class AssignmentOperationType: ASSIGN_LEFT_SHIFT = 4 # <<= ASSIGN_RIGHT_SHIFT = 5 # >>= ASSIGN_ADDITION = 6 # += - ASSIGN_SUBSTRACTION = 7 # -= + ASSIGN_SUBTRACTION = 7 # -= ASSIGN_MULTIPLICATION = 8 # *= ASSIGN_DIVISION = 9 # /= ASSIGN_MODULO = 10 # %= @@ -35,7 +35,7 @@ class AssignmentOperationType: if operation_type == '+=': return AssignmentOperationType.ASSIGN_ADDITION if operation_type == '-=': - return AssignmentOperationType.ASSIGN_SUBSTRACTION + return AssignmentOperationType.ASSIGN_SUBTRACTION if operation_type == '*=': return AssignmentOperationType.ASSIGN_MULTIPLICATION if operation_type == '/=': @@ -62,7 +62,7 @@ class AssignmentOperationType: return '>>=' if operation_type == AssignmentOperationType.ASSIGN_ADDITION: return '+=' - if operation_type == AssignmentOperationType.ASSIGN_SUBSTRACTION: + if operation_type == AssignmentOperationType.ASSIGN_SUBTRACTION: return '-=' if operation_type == AssignmentOperationType.ASSIGN_MULTIPLICATION: return '*=' diff --git a/slither/core/expressions/binary_operation.py b/slither/core/expressions/binary_operation.py index 26b8370d6..b404d88cc 100644 --- a/slither/core/expressions/binary_operation.py +++ b/slither/core/expressions/binary_operation.py @@ -11,7 +11,7 @@ class BinaryOperationType: DIVISION = 2 # / MODULO = 3 # % ADDITION = 4 # + - SUBSTRACTION = 5 # - + SUBTRACTION = 5 # - LEFT_SHIFT = 6 # << RIGHT_SHIT = 7 # >>> AND = 8 # & @@ -39,7 +39,7 @@ class BinaryOperationType: if operation_type == '+': return BinaryOperationType.ADDITION if operation_type == '-': - return BinaryOperationType.SUBSTRACTION + return BinaryOperationType.SUBTRACTION if operation_type == '<<': return BinaryOperationType.LEFT_SHIFT if operation_type == '>>': @@ -82,7 +82,7 @@ class BinaryOperationType: return '%' if operation_type == BinaryOperationType.ADDITION: return '+' - if operation_type == BinaryOperationType.SUBSTRACTION: + if operation_type == BinaryOperationType.SUBTRACTION: return '-' if operation_type == BinaryOperationType.LEFT_SHIFT: return '<<' diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index aa68429e0..1e7eb2789 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -22,6 +22,8 @@ from slither.slithir.operations.push import Push from slither.slithir.operations.send import Send from slither.slithir.operations.solidity_call import SolidityCall from slither.slithir.operations.transfer import Transfer +from slither.slithir.operations.return_operation import Return +from slither.slithir.operations.condition import Condition from slither.slithir.tmp_operations.argument import Argument, ArgumentType from slither.slithir.tmp_operations.tmp_call import TmpCall from slither.slithir.tmp_operations.tmp_new_array import TmpNewArray @@ -338,8 +340,10 @@ def convert_libs(result, contract): def convert_expression(expression, node): # handle standlone expression # such as return true; + from slither.core.cfg.node import NodeType if isinstance(expression, Literal): - return [Constant(expression.value)] + result = [Return(Constant(expression.value))] + return result visitor = ExpressionToSlithIR(expression) result = visitor.result() @@ -347,4 +351,16 @@ def convert_expression(expression, node): result = convert_libs(result, node.function.contract) + if result: +# print(expression) +# for ir in result: +# print(ir) + if node.type in [NodeType.IF, NodeType.IFLOOP]: + assert isinstance(result[-1], (OperationWithLValue)) + result.append(Condition(result[-1].lvalue)) + elif node.type == NodeType.RETURN: + assert isinstance(result[-1], (OperationWithLValue)) + result.append(Return(result[-1].lvalue)) + + return result diff --git a/slither/slithir/operations/assignment.py b/slither/slithir/operations/assignment.py index 70e95620a..d81a53d09 100644 --- a/slither/slithir/operations/assignment.py +++ b/slither/slithir/operations/assignment.py @@ -15,7 +15,7 @@ class AssignmentOperationType(object): ASSIGN_LEFT_SHIFT = 4 # <<= ASSIGN_RIGHT_SHIFT = 5 # >>= ASSIGN_ADDITION = 6 # += - ASSIGN_SUBSTRACTION = 7 # -= + ASSIGN_SUBTRACTION = 7 # -= ASSIGN_MULTIPLICATION = 8 # *= ASSIGN_DIVISION = 9 # /= ASSIGN_MODULO = 10 # %= @@ -37,7 +37,7 @@ class AssignmentOperationType(object): if operation_type == '+=': return AssignmentOperationType.ASSIGN_ADDITION if operation_type == '-=': - return AssignmentOperationType.ASSIGN_SUBSTRACTION + return AssignmentOperationType.ASSIGN_SUBTRACTION if operation_type == '*=': return AssignmentOperationType.ASSIGN_MULTIPLICATION if operation_type == '/=': @@ -64,7 +64,7 @@ class AssignmentOperationType(object): return '>>=' if operation_type == AssignmentOperationType.ASSIGN_ADDITION: return '+=' - if operation_type == AssignmentOperationType.ASSIGN_SUBSTRACTION: + if operation_type == AssignmentOperationType.ASSIGN_SUBTRACTION: return '-=' if operation_type == AssignmentOperationType.ASSIGN_MULTIPLICATION: return '*=' diff --git a/slither/slithir/operations/binary.py b/slither/slithir/operations/binary.py index ce13ccd73..cdfae664f 100644 --- a/slither/slithir/operations/binary.py +++ b/slither/slithir/operations/binary.py @@ -11,7 +11,7 @@ class BinaryOperationType(object): DIVISION = 2 # / MODULO = 3 # % ADDITION = 4 # + - SUBSTRACTION = 5 # - + SUBTRACTION = 5 # - LEFT_SHIFT = 6 # << RIGHT_SHIT = 7 # >>> AND = 8 # & @@ -40,7 +40,7 @@ class BinaryOperationType(object): if operation_type == '+': return BinaryOperationType.ADDITION if operation_type == '-': - return BinaryOperationType.SUBSTRACTION + return BinaryOperationType.SUBTRACTION if operation_type == '<<': return BinaryOperationType.LEFT_SHIFT if operation_type == '>>': @@ -83,7 +83,7 @@ class BinaryOperationType(object): return '%' if operation_type == BinaryOperationType.ADDITION: return '+' - if operation_type == BinaryOperationType.SUBSTRACTION: + if operation_type == BinaryOperationType.SUBTRACTION: return '-' if operation_type == BinaryOperationType.LEFT_SHIFT: return '<<' diff --git a/slither/slithir/operations/condition.py b/slither/slithir/operations/condition.py new file mode 100644 index 000000000..0de1467e5 --- /dev/null +++ b/slither/slithir/operations/condition.py @@ -0,0 +1,23 @@ +from slither.slithir.operations.operation import Operation + +from slither.slithir.utils.utils import is_valid_rvalue +class Condition(Operation): + """ + Condition + Only present as last operation in conditional node + """ + def __init__(self, value): + assert is_valid_rvalue(value) + super(Condition, self).__init__() + self._value = value + + @property + def read(self): + return [self.value] + + @property + def value(self): + return self._value + + def __str__(self): + return "CONDITION {}".format(self.value) diff --git a/slither/slithir/operations/return_operation.py b/slither/slithir/operations/return_operation.py new file mode 100644 index 000000000..5489087ea --- /dev/null +++ b/slither/slithir/operations/return_operation.py @@ -0,0 +1,24 @@ +from slither.slithir.operations.operation import Operation + +from slither.slithir.variables.tuple import TupleVariable +from slither.slithir.utils.utils import is_valid_rvalue +class Return(Operation): + """ + Return + Only present as last operation in RETURN node + """ + def __init__(self, value): + assert is_valid_rvalue(value) or isinstance(value, TupleVariable) + super(Return, self).__init__() + self._value = value + + @property + def read(self): + return [self.value] + + @property + def value(self): + return self._value + + def __str__(self): + return "RETURN {}".format(self.value) diff --git a/slither/slithir/operations/solidity_call.py b/slither/slithir/operations/solidity_call.py index 3e791ce0c..070a70b46 100644 --- a/slither/slithir/operations/solidity_call.py +++ b/slither/slithir/operations/solidity_call.py @@ -16,7 +16,7 @@ class SolidityCall(Call, OperationWithLValue): @property def read(self): - return [] + return list(self.arguments) @property def function(self): @@ -32,7 +32,7 @@ class SolidityCall(Call, OperationWithLValue): def __str__(self): args = [str(a) for a in self.arguments] - return str(self.lvalue) +' = SOLIDITY_CALL {}({})'.format(self.function.full_name, '.'.join(args)) + return str(self.lvalue) +' = SOLIDITY_CALL {}({})'.format(self.function.full_name, ','.join(args)) # return str(self.lvalue) +' = INTERNALCALL {} (arg {})'.format(self.function, # self.nbr_arguments) diff --git a/slither/slithir/variables/constant.py b/slither/slithir/variables/constant.py index 5ead63214..296dd7f25 100644 --- a/slither/slithir/variables/constant.py +++ b/slither/slithir/variables/constant.py @@ -3,6 +3,7 @@ from slither.core.variables.variable import Variable class Constant(Variable): def __init__(self, val): + super(Constant, self).__init__() assert isinstance(val, str) if val.isdigit(): self._type = 'uint256' diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index 2c5ce28c4..ed71cd5a9 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -17,6 +17,7 @@ class SlitherSolc(Slither): self._contractsNotParsed = [] self._contracts_by_id = {} self._analyzed = False + print(filename) def _parse_contracts_from_json(self, json_data): first = json_data.find('{') diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 774de7ce9..da95fe90a 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -204,7 +204,7 @@ class ExpressionToSlithIR(ExpressionVisitor): self._result.append(operation) set_val(expression, value) elif expression.type in [UnaryOperationType.MINUSMINUS_PRE]: - operation = BinaryOperation(value, value, Constant("1"), BinaryOperationType.SUBSTRACTION) + operation = BinaryOperation(value, value, Constant("1"), BinaryOperationType.SUBTRACTION) self._result.append(operation) set_val(expression, value) elif expression.type in [UnaryOperationType.PLUSPLUS_POST]: @@ -218,7 +218,7 @@ class ExpressionToSlithIR(ExpressionVisitor): lvalue = TemporaryVariable() operation = Assignment(lvalue, value, AssignmentOperationType.ASSIGN, value.type) self._result.append(operation) - operation = BinaryOperation(value, value, Constant("1"), BinaryOperationType.SUBSTRACTION) + operation = BinaryOperation(value, value, Constant("1"), BinaryOperationType.SUBTRACTION) self._result.append(operation) set_val(expression, lvalue) elif expression.type in [UnaryOperationType.PLUS_PRE]: From 8f579bdd829d06c64b425635d9c83718b10957a2 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 27 Sep 2018 15:29:44 +0100 Subject: [PATCH 094/308] Better handling of ternary on return statement --- slither/solc_parsing/declarations/function.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 524688b29..fa7dd8c61 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -608,6 +608,7 @@ class FunctionSolc(Function): self.split_ternary_node(node, condition, true_expr, false_expr) ternary_found = True break + self._remove_alone_endif() self._analyze_read_write() self._analyze_calls() @@ -647,8 +648,11 @@ class FunctionSolc(Function): link_nodes(condition_node, true_node) link_nodes(condition_node, false_node) - link_nodes(true_node, endif_node) - link_nodes(false_node, endif_node) + + if not true_node.type in [NodeType.THROW, NodeType.RETURN]: + link_nodes(true_node, endif_node) + if not false_node.type in [NodeType.THROW, NodeType.RETURN]: + link_nodes(false_node, endif_node) self._nodes = [n for n in self._nodes if n.node_id != node.node_id] From accb786037e135f9cfc4f54c0d7a47a7a676c441 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 27 Sep 2018 18:14:20 +0100 Subject: [PATCH 095/308] Fix incorrect CFG recovery of 'else if' Add are variable written analysis (WIP) Fix small bugs --- slither/analyses/write/__init__.py | 0 .../analyses/write/are_variables_written.py | 56 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 slither/analyses/write/__init__.py create mode 100644 slither/analyses/write/are_variables_written.py diff --git a/slither/analyses/write/__init__.py b/slither/analyses/write/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/analyses/write/are_variables_written.py b/slither/analyses/write/are_variables_written.py new file mode 100644 index 000000000..5505fc307 --- /dev/null +++ b/slither/analyses/write/are_variables_written.py @@ -0,0 +1,56 @@ +""" + Detect if all the given variables are written in all the paths of the function +""" +from slither.core.cfg.node import NodeType +from slither.core.declarations.solidity_variables import SolidityFunction +from slither.slithir.operations.index import Index +from slither.slithir.operations.member import Member +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.slithir.operations.solidity_call import SolidityCall +from slither.slithir.variables.reference import ReferenceVariable + +def _visit(node, visited, variables_written, variables_to_write): + + if node in visited: + return [] + + visited = visited + [node] + + refs = {} + for ir in node.irs: + if isinstance(ir, SolidityCall): + # TODO convert the revert to a THROW node + if ir.function in [SolidityFunction('revert(string)'), + SolidityFunction('revert()')]: + return [] + + if not isinstance(ir, OperationWithLValue): + continue + if isinstance(ir, (Index, Member)): + refs[ir.lvalue] = ir.variable_left + + variables_written = variables_written + [ir.lvalue] + lvalue = ir.lvalue + while isinstance(lvalue, ReferenceVariable): + variables_written = variables_written + [refs[lvalue]] + lvalue = refs[lvalue] + + ret = [] + if not node.sons and not node.type in [NodeType.THROW, NodeType.RETURN]: + ret += [v for v in variables_to_write if not v in variables_written] + + for son in node.sons: + ret += _visit(son, visited, variables_written, variables_to_write) + return ret + +def are_variables_written(function, variables_to_write): + """ + Return the list of variable that are not written at the end of the function + + Args: + function (Function) + variables_to_write (list Variable): variable that must be written + Returns: + list(Variable): List of variable that are not written (sublist of variables_to_write) + """ + return list(set(_visit(function.entry_point, [], [], variables_to_write))) From f7a0337c24cea5cd6eda9cdbef72d33b47fe873c Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 28 Sep 2018 18:57:44 +0100 Subject: [PATCH 096/308] Fix bugs (incorrect if then else parsing, incorrect - operator) --- slither/analyses/taint/specific_variable.py | 2 +- slither/slithir/convert.py | 9 ++++--- slither/solc_parsing/declarations/function.py | 25 ++++++++++++++++--- slither/solc_parsing/slitherSolc.py | 4 +-- .../visitors/slithir/expression_to_slithir.py | 5 +++- 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/slither/analyses/taint/specific_variable.py b/slither/analyses/taint/specific_variable.py index ffddfda13..707d8fece 100644 --- a/slither/analyses/taint/specific_variable.py +++ b/slither/analyses/taint/specific_variable.py @@ -31,7 +31,7 @@ def _visit_node(node, visited, key): if node in visited: return - visited += [node] + visited = visited + [node] taints = node.function.slither.context[key] refs = {} diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 1e7eb2789..9cf68b708 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -4,6 +4,7 @@ from slither.core.declarations.solidity_variables import (SolidityFunction, SolidityVariableComposed) from slither.core.declarations.structure import Structure from slither.core.expressions.literal import Literal +from slither.core.expressions.identifier import Identifier from slither.core.solidity_types.elementary_type import ElementaryType from slither.core.variables.variable import Variable from slither.slithir.operations.call import Call @@ -341,9 +342,12 @@ def convert_expression(expression, node): # handle standlone expression # such as return true; from slither.core.cfg.node import NodeType - if isinstance(expression, Literal): + if isinstance(expression, Literal) and node.type == NodeType.RETURN: result = [Return(Constant(expression.value))] return result + if isinstance(expression, Identifier) and node.type == NodeType.RETURN: + result = [Return(expression.value)] + return result visitor = ExpressionToSlithIR(expression) result = visitor.result() @@ -352,9 +356,6 @@ def convert_expression(expression, node): result = convert_libs(result, node.function.contract) if result: -# print(expression) -# for ir in result: -# print(ir) if node.type in [NodeType.IF, NodeType.IFLOOP]: assert isinstance(result[-1], (OperationWithLValue)) result.append(Condition(result[-1].lvalue)) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index fa7dd8c61..869da06d2 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -90,6 +90,7 @@ class FunctionSolc(Function): trueStatement = self._parse_statement(children[1], condition_node) + endIf_node = self._new_node(NodeType.ENDIF) link_nodes(trueStatement, endIf_node) @@ -503,9 +504,27 @@ class FunctionSolc(Function): return } + Iterate until a fix point to remove the ENDIF node + creates on the following pattern + if(){ + return + } + else if(){ + return + } """ - self._nodes = [n for n in self.nodes if n.type != NodeType.ENDIF or n.sons or n.fathers] - + prev_nodes = [] + while set(prev_nodes) != set(self.nodes): + prev_nodes = self.nodes + to_remove = [] + for node in self.nodes: + if node.type == NodeType.ENDIF and not node.fathers: + for son in node.sons: + son.remove_father(node) + node.set_sons([]) + to_remove.append(node) + self._nodes = [n for n in self.nodes if not n in to_remove] +# def _parse_params(self, params): assert params['name'] == 'ParameterList' @@ -650,7 +669,7 @@ class FunctionSolc(Function): if not true_node.type in [NodeType.THROW, NodeType.RETURN]: - link_nodes(true_node, endif_node) + link_nodes(true_node, endif_node) if not false_node.type in [NodeType.THROW, NodeType.RETURN]: link_nodes(false_node, endif_node) diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index ed71cd5a9..265162664 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -31,13 +31,13 @@ class SlitherSolc(Slither): if data_loaded['name'] == 'root': self._solc_version = '0.3' logger.error('solc <0.4 is not supported') - exit(-1) + return elif data_loaded['name'] == 'SourceUnit': self._solc_version = '0.4' self._parse_source_unit(data_loaded, filename) else: logger.error('solc version is not supported') - exit(-1) + return for contract_data in data_loaded['children']: # if self.solc_version == '0.3': diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index da95fe90a..a8ffff95b 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -224,7 +224,10 @@ class ExpressionToSlithIR(ExpressionVisitor): elif expression.type in [UnaryOperationType.PLUS_PRE]: set_val(expression, value) elif expression.type in [UnaryOperationType.MINUS_PRE]: - set_val(expression, Constant("-"+str(value.value))) + lvalue = TemporaryVariable() + operation = BinaryOperation(lvalue, Constant("0"), value, BinaryOperationType.SUBTRACTION) + self._result.append(operation) + set_val(expression, lvalue) else: raise Exception('Unary operation to IR not supported {}'.format(expression)) From 4bf5f47364e9561122548782540ef43dd3564ee3 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Fri, 28 Sep 2018 17:27:10 -0400 Subject: [PATCH 097/308] Dockerfile: Add initial Dockerfile --- Dockerfile | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..88d24606b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM alpine:3.6 + +LABEL name slither +LABEL src "https://github.com/trailofbits/slither" +LABEL creator trailofbits +LABEL dockerfile_maintenance trailofbits +LABEL desc "Static Analyzer for Solidity" + +# Mostly stolen from ethereum/solc. +RUN apk add --no-cache git python3 build-base cmake boost-dev \ +&& sed -i -E -e 's/include /include /' /usr/include/boost/asio/detail/socket_types.hpp \ +&& git clone --depth 1 --recursive -b release https://github.com/ethereum/solidity \ +&& cd /solidity && cmake -DCMAKE_BUILD_TYPE=Release -DTESTS=0 -DSTATIC_LINKING=1 \ +&& cd /solidity && make solc && install -s solc/solc /usr/bin \ +&& cd / && rm -rf solidity \ +&& rm -rf /var/cache/apk/* \ +&& git clone https://github.com/trailofbits/slither.git +WORKDIR slither +RUN python3 setup.py install +ENTRYPOINT ["slither"] +CMD ["tests/uninitialized.sol"] From 6216a9ccd72e3ea44bf0c0a9f0352cf3d810a251 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 1 Oct 2018 08:46:05 +0100 Subject: [PATCH 098/308] API change: contract.functions_all_called -> all_functions_called Use all_functions_called in unused variables detector --- slither/core/declarations/contract.py | 2 +- slither/detectors/variables/uninitialized_state_variables.py | 4 ++-- slither/detectors/variables/unused_state_variables.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index 7be208bee..e7b80817c 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -110,7 +110,7 @@ class Contract(ChildSlither, SourceMapping): return [f for f in self.functions if f.contract != self] @property - def functions_all_called(self): + def all_functions_called(self): ''' list(Function): List of functions reachable from the contract (include super) ''' diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index cd9d48e39..47691ba43 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -26,14 +26,14 @@ class UninitializedStateVarsDetection(AbstractDetector): def detect_uninitialized(self, contract): # get all the state variables read by all functions - var_read = [f.state_variables_read for f in contract.functions_all_called + contract.modifiers] + var_read = [f.state_variables_read for f in contract.all_functions_called + contract.modifiers] # flat list var_read = [item for sublist in var_read for item in sublist] # remove state variable that are initiliazed at contract construction var_read = [v for v in var_read if v.uninitialized] # get all the state variables written by the functions - var_written = [f.state_variables_written for f in contract.functions_all_called + contract.modifiers] + var_written = [f.state_variables_written for f in contract.all_functions_called + contract.modifiers] # flat list var_written = [item for sublist in var_written for item in sublist] diff --git a/slither/detectors/variables/unused_state_variables.py b/slither/detectors/variables/unused_state_variables.py index 7079a7f3c..b23ac8e57 100644 --- a/slither/detectors/variables/unused_state_variables.py +++ b/slither/detectors/variables/unused_state_variables.py @@ -19,7 +19,7 @@ class UnusedStateVars(AbstractDetector): return None # Get all the variables read in all the functions and modifiers variables_used = [x.state_variables_read + x.state_variables_written for x in - (contract.functions + contract.modifiers)] + (contract.all_functions_called + contract.modifiers)] # Flat list variables_used = [item for sublist in variables_used for item in sublist] # Return the variables unused that are not public From 64231df5aed05b5c0dce979a6c6def89b49cbfd7 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 1 Oct 2018 08:48:04 +0100 Subject: [PATCH 099/308] Add LockedEther detector --- README.md | 2 + slither/__main__.py | 2 + slither/detectors/attributes/locked_ether.py | 53 ++++++++++++++++++++ tests/locked_ether.sol | 24 +++++++++ 4 files changed, 81 insertions(+) create mode 100644 slither/detectors/attributes/locked_ether.py create mode 100644 tests/locked_ether.sol diff --git a/README.md b/README.md index 7c1d5cbcd..cccb6d3cd 100644 --- a/README.md +++ b/README.md @@ -53,11 +53,13 @@ Check | Purpose | Impact | Confidence `--detect-reentrancy`| Detect reentrancy vulnerabilities | High | Medium `--detect-uninitialized-state`| Detect uninitialized state variables | High | High `--detect-uninitialized-storage`| Detect uninitialized storage variables | High | High +`--detect-locked-ether`| Detect contracts with a payable function that do not send ether | Medium | High `--detect-tx-origin`| Detect dangerous usage of `tx.origin` | Medium | Medium `--detect-pragma`| Detect if different pragma directives are used | Informational | High `--detect-solc-version`| Detect if an old version of Solidity used (<0.4.23) | Informational | High `--detect-unused-state`| Detect unused state variables | Informational | High + [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. ## How to install diff --git a/slither/__main__.py b/slither/__main__.py index 4ebefb286..4311ba26b 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -93,6 +93,7 @@ def main(): from slither.detectors.variables.uninitialized_state_variables import UninitializedStateVarsDetection from slither.detectors.attributes.constant_pragma import ConstantPragma from slither.detectors.attributes.old_solc import OldSolc + from slither.detectors.attributes.locked_ether import LockedEther from slither.detectors.reentrancy.reentrancy import Reentrancy from slither.detectors.variables.uninitialized_storage_variables import UninitializedStorageVars from slither.detectors.variables.unused_state_variables import UnusedStateVars @@ -104,6 +105,7 @@ def main(): OldSolc, Reentrancy, UninitializedStorageVars, + LockedEther, UnusedStateVars, TxOrigin] diff --git a/slither/detectors/attributes/locked_ether.py b/slither/detectors/attributes/locked_ether.py new file mode 100644 index 000000000..c333ea67b --- /dev/null +++ b/slither/detectors/attributes/locked_ether.py @@ -0,0 +1,53 @@ +""" + Check if ether are locked in the contract +""" + +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.slithir.operations.send import Send +from slither.slithir.operations.transfer import Transfer +from slither.slithir.operations.high_level_call import HighLevelCall +from slither.slithir.operations.low_level_call import LowLevelCall + + +class LockedEther(AbstractDetector): + """ + """ + + ARGUMENT = 'locked-ether' + HELP = "contracts with a payable function that do not send ether" + IMPACT = DetectorClassification.MEDIUM + CONFIDENCE = DetectorClassification.HIGH + + @staticmethod + def do_no_send_ether(contract): + functions = contract.all_functions_called + for function in functions: + for node in function.nodes: + for ir in node.irs: + if isinstance(ir, (Send, Transfer, HighLevelCall, LowLevelCall)): + if ir.call_value and ir.call_value != 0: + return False + return True + + + def detect(self): + results = [] + + for contract in self.slither.contracts: + funcs_payable = [function for function in contract.functions if function.payable] + if funcs_payable: + if self.do_no_send_ether(contract): + txt = "Contract locked ether in {}, Contract {}, Functions {}" + info = txt.format(self.filename, + contract.name, + [f.name for f in funcs_payable]) + self.log(info) + + source = [f.source_mapping for f in funcs_payable] + + results.append({'vuln': 'LockedEther', + 'functions_payable' : [f.name for f in funcs_payable], + 'contract': contract.name, + 'sourceMapping': source}) + + return results diff --git a/tests/locked_ether.sol b/tests/locked_ether.sol new file mode 100644 index 000000000..4d16cda89 --- /dev/null +++ b/tests/locked_ether.sol @@ -0,0 +1,24 @@ +pragma solidity 0.4.24; +contract Locked{ + + function receive() payable public{ + require(msg.value > 0); + } + +} + +contract Send{ + address owner = msg.sender; + + function withdraw() public{ + owner.transfer(address(this).balance); + } +} + +contract Unlocked is Locked, Send{ + + function withdraw() public{ + super.withdraw(); + } + +} From c0ba9a7191dab2b5d3f107b6f583635ace6da379 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 1 Oct 2018 08:46:05 +0100 Subject: [PATCH 100/308] API change: contract.functions_all_called -> all_functions_called Use all_functions_called in unused variables detector --- slither/core/declarations/contract.py | 2 +- slither/detectors/variables/uninitialized_state_variables.py | 4 ++-- slither/detectors/variables/unused_state_variables.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index f863d22e8..337c17e03 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -110,7 +110,7 @@ class Contract(ChildSlither, SourceMapping): return [f for f in self.functions if f.contract != self] @property - def functions_all_called(self): + def all_functions_called(self): ''' list(Function): List of functions reachable from the contract (include super) ''' diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index cd9d48e39..47691ba43 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -26,14 +26,14 @@ class UninitializedStateVarsDetection(AbstractDetector): def detect_uninitialized(self, contract): # get all the state variables read by all functions - var_read = [f.state_variables_read for f in contract.functions_all_called + contract.modifiers] + var_read = [f.state_variables_read for f in contract.all_functions_called + contract.modifiers] # flat list var_read = [item for sublist in var_read for item in sublist] # remove state variable that are initiliazed at contract construction var_read = [v for v in var_read if v.uninitialized] # get all the state variables written by the functions - var_written = [f.state_variables_written for f in contract.functions_all_called + contract.modifiers] + var_written = [f.state_variables_written for f in contract.all_functions_called + contract.modifiers] # flat list var_written = [item for sublist in var_written for item in sublist] diff --git a/slither/detectors/variables/unused_state_variables.py b/slither/detectors/variables/unused_state_variables.py index 7079a7f3c..b23ac8e57 100644 --- a/slither/detectors/variables/unused_state_variables.py +++ b/slither/detectors/variables/unused_state_variables.py @@ -19,7 +19,7 @@ class UnusedStateVars(AbstractDetector): return None # Get all the variables read in all the functions and modifiers variables_used = [x.state_variables_read + x.state_variables_written for x in - (contract.functions + contract.modifiers)] + (contract.all_functions_called + contract.modifiers)] # Flat list variables_used = [item for sublist in variables_used for item in sublist] # Return the variables unused that are not public From 39860db4e9506cb0d148ec2d44303ba43a5cc1bd Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 1 Oct 2018 08:51:47 +0100 Subject: [PATCH 101/308] Update Travis tests --- scripts/travis_test.sh | 5 +++++ tests/uninitialized_storage_pointer.sol | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 3eb95c396..78e96ed65 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -42,6 +42,11 @@ if [ $? -ne 1 ]; then exit 1 fi +slither tests/locked_ether.sol +if [ $? -ne 1 ]; then + exit 1 +fi + ### Test scripts diff --git a/tests/uninitialized_storage_pointer.sol b/tests/uninitialized_storage_pointer.sol index c2cdf79a1..e494cfe68 100644 --- a/tests/uninitialized_storage_pointer.sol +++ b/tests/uninitialized_storage_pointer.sol @@ -4,7 +4,7 @@ contract Uninitialized{ uint a; } - function func() payable{ + function func() { St st; // non init, but never read so its fine St memory st2; St st_bug; From 05c9db7f58e354be7b3f1565344c780448912fb0 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 1 Oct 2018 09:28:41 +0100 Subject: [PATCH 102/308] Improve LockedEther detector Open source Suicidal detector --- slither/__main__.py | 2 + slither/detectors/attributes/locked_ether.py | 10 ++- slither/detectors/functions/suicidal.py | 72 ++++++++++++++++++++ 3 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 slither/detectors/functions/suicidal.py diff --git a/slither/__main__.py b/slither/__main__.py index 4311ba26b..df208651d 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -94,6 +94,7 @@ def main(): from slither.detectors.attributes.constant_pragma import ConstantPragma from slither.detectors.attributes.old_solc import OldSolc from slither.detectors.attributes.locked_ether import LockedEther + from slither.detectors.functions.suicidal import Suicidal from slither.detectors.reentrancy.reentrancy import Reentrancy from slither.detectors.variables.uninitialized_storage_variables import UninitializedStorageVars from slither.detectors.variables.unused_state_variables import UnusedStateVars @@ -106,6 +107,7 @@ def main(): Reentrancy, UninitializedStorageVars, LockedEther, + Suicidal, UnusedStateVars, TxOrigin] diff --git a/slither/detectors/attributes/locked_ether.py b/slither/detectors/attributes/locked_ether.py index c333ea67b..80850958c 100644 --- a/slither/detectors/attributes/locked_ether.py +++ b/slither/detectors/attributes/locked_ether.py @@ -22,18 +22,26 @@ class LockedEther(AbstractDetector): def do_no_send_ether(contract): functions = contract.all_functions_called for function in functions: + calls = [c.name for c in function.internal_calls] + if 'suicide(address)' in calls or 'selfdestruct(address)' in calls: + return False for node in function.nodes: for ir in node.irs: if isinstance(ir, (Send, Transfer, HighLevelCall, LowLevelCall)): if ir.call_value and ir.call_value != 0: return False + if isinstance(ir, (LowLevelCall)): + if ir.function_name in ['delegatecall', 'callcode']: + return False return True def detect(self): results = [] - for contract in self.slither.contracts: + for contract in self.slither.contracts_derived: + if contract.is_signature_only(): + continue funcs_payable = [function for function in contract.functions if function.payable] if funcs_payable: if self.do_no_send_ether(contract): diff --git a/slither/detectors/functions/suicidal.py b/slither/detectors/functions/suicidal.py new file mode 100644 index 000000000..6652cde9d --- /dev/null +++ b/slither/detectors/functions/suicidal.py @@ -0,0 +1,72 @@ +""" +Module detecting suicidal contract + +A suicidal contract is an unprotected function that calls selfdestruct +""" + +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification + +class Suicidal(AbstractDetector): + """ + Unprotected function detector + """ + + ARGUMENT = 'suicidal' + HELP = 'suicidal functions' + IMPACT = DetectorClassification.HIGH + CONFIDENCE = DetectorClassification.HIGH + + @staticmethod + def detect_suicidal_func(func): + """ Detect if the function is suicidal + + Detect the public functions calling suicide/selfdestruct without protection + Returns: + (bool): True if the function is suicidal + """ + + if func.is_constructor: + return False + + if func.visibility != 'public': + return False + + calls = [c.name for c in func.internal_calls] + if not ('suicide(address)' in calls or 'selfdestruct(address)' in calls): + return False + + if func.is_protected(): + return False + + return True + + def detect_suicidal(self, contract): + ret = [] + for f in [f for f in contract.functions if f.contract == contract]: + if self.detect_suicidal_func(f): + ret.append(f) + return ret + + def detect(self): + """ Detect the suicidal functions + """ + results = [] + for c in self.contracts: + functions = self.detect_suicidal(c) + for func in functions: + func_name = func.name + + txt = "Suicidal function in {} Contract: {}, Function: {}" + info = txt.format(self.filename, + c.name, + func_name) + + self.log(info) + + results.append({'vuln': 'SuicidalFunc', + 'sourceMapping': func.source_mapping, + 'filename': self.filename, + 'contract': c.name, + 'func': func_name}) + + return results From ca51fcf50245ccd4e0ef386f94dbc195494551cb Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 1 Oct 2018 09:32:26 +0100 Subject: [PATCH 103/308] Add missing __init__.py --- slither/detectors/functions/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 slither/detectors/functions/__init__.py diff --git a/slither/detectors/functions/__init__.py b/slither/detectors/functions/__init__.py new file mode 100644 index 000000000..e69de29bb From 8cc789e18a9dac829d5c0c5de7993cff2e74e250 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 1 Oct 2018 09:36:38 +0100 Subject: [PATCH 104/308] Update lockedEther testcase --- scripts/travis_test.sh | 3 ++- tests/locked_ether.sol | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 78e96ed65..71b29dabe 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -7,8 +7,9 @@ if [ $? -ne 1 ]; then exit 1 fi +# contains also the test for the suicidal detector slither tests/backdoor.sol --disable-solc-warnings -if [ $? -ne 1 ]; then +if [ $? -ne 2 ]; then exit 1 fi diff --git a/tests/locked_ether.sol b/tests/locked_ether.sol index 4d16cda89..1e9e57c7d 100644 --- a/tests/locked_ether.sol +++ b/tests/locked_ether.sol @@ -22,3 +22,5 @@ contract Unlocked is Locked, Send{ } } + +contract OnlyLocked is Locked{ } From 4e8f4cf6f4c2902e92762d092ac9a5bc7025b25b Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 1 Oct 2018 09:43:57 +0100 Subject: [PATCH 105/308] Update README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index cccb6d3cd..1646e5021 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ By default, all the checks are run. Check | Purpose | Impact | Confidence --- | --- | --- | --- `--detect-reentrancy`| Detect reentrancy vulnerabilities | High | Medium +`--detect-suicidal`| Detect suicidal functions | High | High `--detect-uninitialized-state`| Detect uninitialized state variables | High | High `--detect-uninitialized-storage`| Detect uninitialized storage variables | High | High `--detect-locked-ether`| Detect contracts with a payable function that do not send ether | Medium | High @@ -60,6 +61,7 @@ Check | Purpose | Impact | Confidence `--detect-unused-state`| Detect unused state variables | Informational | High + [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. ## How to install From dbc33e5c68ea3b8f3cf31829ccc9c3ba2771f517 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Mon, 1 Oct 2018 11:02:30 -0400 Subject: [PATCH 106/308] Note which detectors can from MAIAN --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1646e5021..200333ea9 100644 --- a/README.md +++ b/README.md @@ -51,17 +51,15 @@ By default, all the checks are run. Check | Purpose | Impact | Confidence --- | --- | --- | --- `--detect-reentrancy`| Detect reentrancy vulnerabilities | High | Medium -`--detect-suicidal`| Detect suicidal functions | High | High +`--detect-suicidal`| Detect suicidal functions (from MAIAN) | High | High `--detect-uninitialized-state`| Detect uninitialized state variables | High | High `--detect-uninitialized-storage`| Detect uninitialized storage variables | High | High -`--detect-locked-ether`| Detect contracts with a payable function that do not send ether | Medium | High +`--detect-locked-ether`| Detect contracts with payable functions that do not send ether (from MAIAN) | Medium | High `--detect-tx-origin`| Detect dangerous usage of `tx.origin` | Medium | Medium `--detect-pragma`| Detect if different pragma directives are used | Informational | High `--detect-solc-version`| Detect if an old version of Solidity used (<0.4.23) | Informational | High `--detect-unused-state`| Detect unused state variables | Informational | High - - [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. ## How to install From 2dc475e2757ec8d2110e1dc743757ac35c641307 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Mon, 1 Oct 2018 11:08:42 -0400 Subject: [PATCH 107/308] Update README.md These detectors use different heuristics than MAIAN and the results are not exactly the same. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 200333ea9..62d191c8e 100644 --- a/README.md +++ b/README.md @@ -51,10 +51,10 @@ By default, all the checks are run. Check | Purpose | Impact | Confidence --- | --- | --- | --- `--detect-reentrancy`| Detect reentrancy vulnerabilities | High | Medium -`--detect-suicidal`| Detect suicidal functions (from MAIAN) | High | High +`--detect-suicidal`| Detect suicidal functions | High | High `--detect-uninitialized-state`| Detect uninitialized state variables | High | High `--detect-uninitialized-storage`| Detect uninitialized storage variables | High | High -`--detect-locked-ether`| Detect contracts with payable functions that do not send ether (from MAIAN) | Medium | High +`--detect-locked-ether`| Detect contracts with payable functions that do not send ether | Medium | High `--detect-tx-origin`| Detect dangerous usage of `tx.origin` | Medium | Medium `--detect-pragma`| Detect if different pragma directives are used | Informational | High `--detect-solc-version`| Detect if an old version of Solidity used (<0.4.23) | Informational | High From 9eb9114fa169e53784867b0898588d578a25dc29 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 3 Oct 2018 20:10:07 +0100 Subject: [PATCH 108/308] Improve taint Add this.balance to solidity variable --- slither/__main__.py | 2 ++ slither/analyses/taint/specific_variable.py | 2 +- slither/analyses/taint/state_variables.py | 26 +++++++++++++------ .../core/declarations/solidity_variables.py | 3 ++- slither/slithir/convert.py | 3 +++ slither/slithir/operations/operation.py | 3 ++- 6 files changed, 28 insertions(+), 11 deletions(-) diff --git a/slither/__main__.py b/slither/__main__.py index df208651d..ded120ea0 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -94,6 +94,7 @@ def main(): from slither.detectors.attributes.constant_pragma import ConstantPragma from slither.detectors.attributes.old_solc import OldSolc from slither.detectors.attributes.locked_ether import LockedEther + from slither.detectors.functions.arbitrary_send import ArbitrarySend from slither.detectors.functions.suicidal import Suicidal from slither.detectors.reentrancy.reentrancy import Reentrancy from slither.detectors.variables.uninitialized_storage_variables import UninitializedStorageVars @@ -107,6 +108,7 @@ def main(): Reentrancy, UninitializedStorageVars, LockedEther, + ArbitrarySend, Suicidal, UnusedStateVars, TxOrigin] diff --git a/slither/analyses/taint/specific_variable.py b/slither/analyses/taint/specific_variable.py index 707d8fece..52bb0f29a 100644 --- a/slither/analyses/taint/specific_variable.py +++ b/slither/analyses/taint/specific_variable.py @@ -50,7 +50,7 @@ def _visit_node(node, visited, key): # print('TAINT {}'.format([str(v) for v in taints])) # print(any(var_read in taints for var_read in read)) # print() - if any(is_tainted_from_key(var_read, key) or var_read in taints for var_read in read): + if isinstance(ir, OperationWithLValue) and any(is_tainted_from_key(var_read, key) or var_read in taints for var_read in read): taints += [ir.lvalue] ir.lvalue.context[key] = True lvalue = ir.lvalue diff --git a/slither/analyses/taint/state_variables.py b/slither/analyses/taint/state_variables.py index a0035d16f..c0aad6be0 100644 --- a/slither/analyses/taint/state_variables.py +++ b/slither/analyses/taint/state_variables.py @@ -9,7 +9,10 @@ from slither.core.variables.state_variable import StateVariable from slither.core.declarations.solidity_variables import SolidityVariableComposed +from slither.slithir.operations.lvalue import OperationWithLValue + from slither.slithir.operations.index import Index +from slither.slithir.operations.member import Member from slither.slithir.variables.temporary import TemporaryVariable from slither.slithir.variables.reference import ReferenceVariable @@ -24,15 +27,16 @@ def _visit_node(node, visited): taints = node.function.slither.context[KEY] refs = {} + for ir in node.irs: - if isinstance(ir, Index): + if isinstance(ir, (Index, Member)): refs[ir.lvalue] = ir.variable_left if isinstance(ir, Index): read = [ir.variable_left] else: read = ir.read - if any(var_read in taints for var_read in read): + if isinstance(ir, OperationWithLValue) and any(var_read in taints for var_read in read): taints += [ir.lvalue] lvalue = ir.lvalue while isinstance(lvalue, ReferenceVariable): @@ -46,6 +50,7 @@ def _visit_node(node, visited): for son in node.sons: _visit_node(son, visited) + def _run_taint(slither, initial_taint): if KEY in slither.context: return @@ -57,6 +62,8 @@ def _run_taint(slither, initial_taint): prev_taints = slither.context[KEY] for contract in slither.contracts: for function in contract.functions: + if not function.is_implemented: + continue # Dont propagated taint on protected functions if not function.is_protected(): slither.context[KEY] = list(set(slither.context[KEY] + function.parameters)) @@ -64,6 +71,14 @@ def _run_taint(slither, initial_taint): slither.context[KEY] = [v for v in prev_taints if isinstance(v, StateVariable)] +def run_taint(slither, initial_taint=None): + if initial_taint is None: + initial_taint = [SolidityVariableComposed('msg.sender')] + initial_taint += [SolidityVariableComposed('msg.value')] + + if KEY not in slither.context: + _run_taint(slither, initial_taint) + def get_taint(slither, initial_taint=None): """ Return the state variables tainted @@ -73,10 +88,5 @@ def get_taint(slither, initial_taint=None): Returns: List(StateVariable) """ - if initial_taint is None: - initial_taint = [SolidityVariableComposed('msg.sender')] - initial_taint += [SolidityVariableComposed('msg.value')] - - if KEY not in slither.context: - _run_taint(slither, initial_taint) + run_taint(slither, initial_taint) return slither.context[KEY] diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py index a642e5c5e..85eaec4ae 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -15,7 +15,8 @@ SOLIDITY_VARIABLES_COMPOSED = ["block.coinbase", "msg.sig", "msg.value", "tx.gasprice", - "tx.origin"] + "tx.origin", + "this.balance"] SOLIDITY_FUNCTIONS = {"gasleft()":['uint256'], diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 9cf68b708..4709c6059 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -235,6 +235,9 @@ def replace_calls(result): ''' reset = True def is_address(v): + if v in [SolidityVariableComposed('msg.sender'), + SolidityVariableComposed('tx.origin')]: + return True if not isinstance(v, Variable): return False if not isinstance(v.type, ElementaryType): diff --git a/slither/slithir/operations/operation.py b/slither/slithir/operations/operation.py index 01b5a72f8..5a88b85b7 100644 --- a/slither/slithir/operations/operation.py +++ b/slither/slithir/operations/operation.py @@ -1,4 +1,5 @@ -class Operation(object): +from slither.core.context.context import Context +class Operation(Context): @property def read(self): From 381d97b86882c240117c01876ea121a3d1367dc2 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 3 Oct 2018 20:20:38 +0100 Subject: [PATCH 109/308] Open source ArbitrarySend detector (WIP) --- slither/analyses/taint/call.py | 66 +++++++++++ slither/detectors/functions/arbitrary_send.py | 105 ++++++++++++++++++ tests/arbitrary_send.sol | 31 ++++++ 3 files changed, 202 insertions(+) create mode 100644 slither/analyses/taint/call.py create mode 100644 slither/detectors/functions/arbitrary_send.py create mode 100644 tests/arbitrary_send.sol diff --git a/slither/analyses/taint/call.py b/slither/analyses/taint/call.py new file mode 100644 index 000000000..34c3d4f7f --- /dev/null +++ b/slither/analyses/taint/call.py @@ -0,0 +1,66 @@ +""" + Compute taint on state call + + use taint on state_variable + + an call ir with a taint set to yes means tainted destination +""" +from slither.analyses.taint.state_variables import get_taint as get_taint_state +from slither.core.declarations.solidity_variables import \ + SolidityVariableComposed +from slither.core.variables.state_variable import StateVariable +from slither.slithir.operations.high_level_call import HighLevelCall +from slither.slithir.operations.index import Index +from slither.slithir.operations.low_level_call import LowLevelCall +from slither.slithir.operations.send import Send +from slither.slithir.operations.transfer import Transfer +from slither.slithir.variables.reference import ReferenceVariable + +from slither.slithir.operations.member import Member +from slither.slithir.operations.lvalue import OperationWithLValue +KEY = 'TAINT_CALL_DESTINATION' + +def _visit_node(node, visited, taints): + if node in visited: + return + + visited += [node] + + refs = {} + for ir in node.irs: + if isinstance(ir, (Index, Member)): + refs[ir.lvalue] = ir.variable_left + + if isinstance(ir, Index): + read = [ir.variable_left] + else: + read = ir.read + if isinstance(ir, OperationWithLValue) and any(var_read in taints for var_read in read): + taints += [ir.lvalue] + lvalue = ir.lvalue + while isinstance(lvalue, ReferenceVariable): + taints += [refs[lvalue]] + lvalue = refs[lvalue] + if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)): + if ir.destination in taints: + ir.context[KEY] = True + + for son in node.sons: + _visit_node(son, visited, taints) + +def _run_taint(slither, initial_taint): + if KEY in slither.context: + return + for contract in slither.contracts: + for function in contract.functions: + if not function.is_implemented: + continue + _visit_node(function.entry_point, [], initial_taint + function.parameters) + +def run_taint(slither): + initial_taint = get_taint_state(slither) + initial_taint += [SolidityVariableComposed('msg.sender')] + + if KEY not in slither.context: + _run_taint(slither, initial_taint) + diff --git a/slither/detectors/functions/arbitrary_send.py b/slither/detectors/functions/arbitrary_send.py new file mode 100644 index 000000000..48a5a4fe7 --- /dev/null +++ b/slither/detectors/functions/arbitrary_send.py @@ -0,0 +1,105 @@ +""" + Module detecting send to arbitrary address + + To avoid FP, it does not report: + - If msg.sender is used as index (withdraw situation) + - If the function is protected + - If the value sent is msg.value (repay situation) + + TODO: dont report if the value is tainted by msg.value +""" + +from slither.analyses.taint.call import KEY, run_taint +from slither.core.declarations.solidity_variables import (SolidityFunction, + SolidityVariableComposed) +from slither.detectors.abstract_detector import (AbstractDetector, + DetectorClassification) +from slither.slithir.operations.high_level_call import HighLevelCall +from slither.slithir.operations.index import Index +from slither.slithir.operations.low_level_call import LowLevelCall +from slither.slithir.operations.send import Send +from slither.slithir.operations.solidity_call import SolidityCall +from slither.slithir.operations.transfer import Transfer + + +class ArbitrarySend(AbstractDetector): + """ + """ + + ARGUMENT = 'arbitrary-send' + HELP = 'function sending ethers to arbitrary destination' + IMPACT = DetectorClassification.HIGH + CONFIDENCE = DetectorClassification.HIGH + + @staticmethod + def arbitrary_send(func): + """ + """ + if func.is_protected(): + return [] + + ret = [] + for node in func.nodes: + for ir in node.irs: + if isinstance(ir, SolidityCall): + if ir.function == SolidityFunction('ecrecover(bytes32,uint8,bytes32,bytes32)'): + return False + if isinstance(ir, Index): + if ir.variable_right == SolidityVariableComposed('msg.sender'): + return False + if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)): + if ir.call_value is None: + continue + if ir.call_value == SolidityVariableComposed('msg.value'): + continue + + if KEY in ir.context: + if ir.context[KEY]: + ret.append(node) + return ret + + + def detect_arbitrary_send(self, contract): + """ + Detect arbitrary send + Args: + contract (Contract) + Returns: + list((Function), (list (Node))) + """ + ret = [] + for f in [f for f in contract.functions if f.contract == contract]: + nodes = self.arbitrary_send(f) + if nodes: + ret.append((f, nodes)) + return ret + + def detect(self): + """ + """ + run_taint(self.slither) + results = [] + for c in self.contracts: + arbitrary_send = self.detect_arbitrary_send(c) + for (func, nodes) in arbitrary_send: + func_name = func.name + calls_str = [str(node.expression) for node in nodes] + + txt = "Arbitrary send in {} Contract: {}, Function: {}, Calls: {}" + info = txt.format(self.filename, + c.name, + func_name, + calls_str) + + self.log(info) + + source_mapping = [node.source_mapping for node in nodes] + + results.append({'vuln': 'SuicidalFunc', + 'sourceMapping': source_mapping, + 'filename': self.filename, + 'contract': c.name, + 'func': func_name, + 'calls': calls_str}) + + return results diff --git a/tests/arbitrary_send.sol b/tests/arbitrary_send.sol new file mode 100644 index 000000000..096f6fc47 --- /dev/null +++ b/tests/arbitrary_send.sol @@ -0,0 +1,31 @@ +contract Test{ + + address destination; + + mapping (address => uint) balances; + + constructor(){ + balances[msg.sender] = 0; + } + + function direct(){ + msg.sender.send(this.balance); + } + + function init(){ + destination = msg.sender; + } + + function indirect(){ + destination.send(this.balance); + } + + function repay() payable{ + msg.sender.transfer(msg.value); + } + + function withdraw(){ + msg.sender.send(balances[msg.sender]); + } + +} From 37e32e1c6d51a1ff00579ec1a160f05fab77bb8c Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 3 Oct 2018 20:32:07 +0100 Subject: [PATCH 110/308] Clean code --- slither/analyses/taint/{call.py => calls.py} | 5 ++--- slither/analyses/taint/specific_variable.py | 12 +++--------- slither/analyses/taint/state_variables.py | 10 ++++------ slither/detectors/functions/arbitrary_send.py | 2 +- 4 files changed, 10 insertions(+), 19 deletions(-) rename slither/analyses/taint/{call.py => calls.py} (97%) diff --git a/slither/analyses/taint/call.py b/slither/analyses/taint/calls.py similarity index 97% rename from slither/analyses/taint/call.py rename to slither/analyses/taint/calls.py index 34c3d4f7f..2a5ad78f5 100644 --- a/slither/analyses/taint/call.py +++ b/slither/analyses/taint/calls.py @@ -8,16 +8,15 @@ from slither.analyses.taint.state_variables import get_taint as get_taint_state from slither.core.declarations.solidity_variables import \ SolidityVariableComposed -from slither.core.variables.state_variable import StateVariable from slither.slithir.operations.high_level_call import HighLevelCall from slither.slithir.operations.index import Index from slither.slithir.operations.low_level_call import LowLevelCall +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.slithir.operations.member import Member from slither.slithir.operations.send import Send from slither.slithir.operations.transfer import Transfer from slither.slithir.variables.reference import ReferenceVariable -from slither.slithir.operations.member import Member -from slither.slithir.operations.lvalue import OperationWithLValue KEY = 'TAINT_CALL_DESTINATION' def _visit_node(node, visited, taints): diff --git a/slither/analyses/taint/specific_variable.py b/slither/analyses/taint/specific_variable.py index 52bb0f29a..4e9f93fe7 100644 --- a/slither/analyses/taint/specific_variable.py +++ b/slither/analyses/taint/specific_variable.py @@ -5,16 +5,15 @@ Propage to state variables Iterate until it finding a fixpoint """ +from slither.core.declarations.solidity_variables import SolidityVariable from slither.core.variables.variable import Variable from slither.core.variables.state_variable import StateVariable -from slither.core.declarations.solidity_variables import SolidityVariable, SolidityVariableComposed - from slither.slithir.operations.index import Index from slither.slithir.operations.member import Member from slither.slithir.operations.lvalue import OperationWithLValue -from slither.slithir.operations.internal_call import InternalCall -from slither.slithir.variables.temporary import TemporaryVariable from slither.slithir.variables.reference import ReferenceVariable +from slither.slithir.variables.temporary import TemporaryVariable + def make_key(variable): if isinstance(variable, Variable): @@ -45,11 +44,6 @@ def _visit_node(node, visited, key): read = [ir.variable_left] else: read = ir.read -# print(ir) -# print('READ {}'.format([str(v) for v in read])) -# print('TAINT {}'.format([str(v) for v in taints])) -# print(any(var_read in taints for var_read in read)) -# print() if isinstance(ir, OperationWithLValue) and any(is_tainted_from_key(var_read, key) or var_read in taints for var_read in read): taints += [ir.lvalue] ir.lvalue.context[key] = True diff --git a/slither/analyses/taint/state_variables.py b/slither/analyses/taint/state_variables.py index c0aad6be0..4a719a711 100644 --- a/slither/analyses/taint/state_variables.py +++ b/slither/analyses/taint/state_variables.py @@ -6,16 +6,14 @@ Iterate until it finding a fixpoint """ +from slither.core.declarations.solidity_variables import \ + SolidityVariableComposed from slither.core.variables.state_variable import StateVariable -from slither.core.declarations.solidity_variables import SolidityVariableComposed - -from slither.slithir.operations.lvalue import OperationWithLValue - from slither.slithir.operations.index import Index +from slither.slithir.operations.lvalue import OperationWithLValue from slither.slithir.operations.member import Member - -from slither.slithir.variables.temporary import TemporaryVariable from slither.slithir.variables.reference import ReferenceVariable +from slither.slithir.variables.temporary import TemporaryVariable KEY = 'TAINT_STATE_VARIABLES' diff --git a/slither/detectors/functions/arbitrary_send.py b/slither/detectors/functions/arbitrary_send.py index 48a5a4fe7..8556448b6 100644 --- a/slither/detectors/functions/arbitrary_send.py +++ b/slither/detectors/functions/arbitrary_send.py @@ -9,7 +9,7 @@ TODO: dont report if the value is tainted by msg.value """ -from slither.analyses.taint.call import KEY, run_taint +from slither.analyses.taint.calls import KEY, run_taint from slither.core.declarations.solidity_variables import (SolidityFunction, SolidityVariableComposed) from slither.detectors.abstract_detector import (AbstractDetector, From 88b37c2b3c264597cbede1d5c0de6bd7d153a224 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 3 Oct 2018 20:37:10 +0100 Subject: [PATCH 111/308] Update README Enable travis on arbitrary send testcase --- README.md | 2 +- scripts/travis_test.sh | 6 ++++++ slither/detectors/functions/arbitrary_send.py | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1646e5021..79adeace7 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ By default, all the checks are run. Check | Purpose | Impact | Confidence --- | --- | --- | --- +`--detect-arbitrary-send`| Detect functions sending ethers to an arbitrary destination | High | High `--detect-reentrancy`| Detect reentrancy vulnerabilities | High | Medium `--detect-suicidal`| Detect suicidal functions | High | High `--detect-uninitialized-state`| Detect uninitialized state variables | High | High @@ -61,7 +62,6 @@ Check | Purpose | Impact | Confidence `--detect-unused-state`| Detect unused state variables | Informational | High - [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. ## How to install diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 71b29dabe..45c005d6f 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -48,6 +48,12 @@ if [ $? -ne 1 ]; then exit 1 fi +slither tests/arbitrary_send.sol --disable-solc-warnings +if [ $? -ne 2 ]; then + exit 1 +fi + + ### Test scripts diff --git a/slither/detectors/functions/arbitrary_send.py b/slither/detectors/functions/arbitrary_send.py index 8556448b6..4ae79df43 100644 --- a/slither/detectors/functions/arbitrary_send.py +++ b/slither/detectors/functions/arbitrary_send.py @@ -27,7 +27,7 @@ class ArbitrarySend(AbstractDetector): """ ARGUMENT = 'arbitrary-send' - HELP = 'function sending ethers to arbitrary destination' + HELP = 'functions sending ethers to an arbitrary destination' IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.HIGH From 473e6dba2f96c4fa6732050f0db9f4fae92f7a6a Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 3 Oct 2018 20:41:29 +0100 Subject: [PATCH 112/308] Reduce arbitrary send confidence until larger testing --- README.md | 2 +- slither/detectors/functions/arbitrary_send.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dd5b03bbc..d2ded9a68 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ By default, all the checks are run. Check | Purpose | Impact | Confidence --- | --- | --- | --- -`--detect-arbitrary-send`| Detect functions sending ethers to an arbitrary destination | High | High +`--detect-arbitrary-send`| Detect functions sending ethers to an arbitrary destination | High | Medium `--detect-reentrancy`| Detect reentrancy vulnerabilities | High | Medium `--detect-suicidal`| Detect suicidal functions | High | High `--detect-uninitialized-state`| Detect uninitialized state variables | High | High diff --git a/slither/detectors/functions/arbitrary_send.py b/slither/detectors/functions/arbitrary_send.py index 4ae79df43..87d16b850 100644 --- a/slither/detectors/functions/arbitrary_send.py +++ b/slither/detectors/functions/arbitrary_send.py @@ -29,7 +29,7 @@ class ArbitrarySend(AbstractDetector): ARGUMENT = 'arbitrary-send' HELP = 'functions sending ethers to an arbitrary destination' IMPACT = DetectorClassification.HIGH - CONFIDENCE = DetectorClassification.HIGH + CONFIDENCE = DetectorClassification.MEDIUM @staticmethod def arbitrary_send(func): From c4b55e128cde4dbea1eba92b895b6284e9465acd Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 4 Oct 2018 09:44:07 -0700 Subject: [PATCH 113/308] Clean import architecture --- slither/analyses/taint/calls.py | 15 ++-- slither/analyses/taint/specific_variable.py | 9 +- slither/analyses/taint/state_variables.py | 7 +- .../analyses/write/are_variables_written.py | 11 ++- slither/core/declarations/__init__.py | 9 ++ slither/core/declarations/function.py | 4 +- slither/core/expressions/__init__.py | 16 ++++ slither/detectors/attributes/locked_ether.py | 9 +- slither/detectors/functions/arbitrary_send.py | 8 +- slither/detectors/reentrancy/reentrancy.py | 8 +- slither/slithir/convert.py | 41 +++------ slither/slithir/operations/__init__.py | 27 ++++++ slither/slithir/operations/assignment.py | 50 +++++------ slither/slithir/operations/binary.py | 84 +++++++++---------- slither/slithir/operations/unary.py | 16 ++-- slither/slithir/variables/__init__.py | 4 + slither/slithir/variables/call.py | 19 ----- .../visitors/slithir/expression_to_slithir.py | 54 +++++------- 18 files changed, 190 insertions(+), 201 deletions(-) delete mode 100644 slither/slithir/variables/call.py diff --git a/slither/analyses/taint/calls.py b/slither/analyses/taint/calls.py index 2a5ad78f5..b9747283a 100644 --- a/slither/analyses/taint/calls.py +++ b/slither/analyses/taint/calls.py @@ -6,16 +6,11 @@ an call ir with a taint set to yes means tainted destination """ from slither.analyses.taint.state_variables import get_taint as get_taint_state -from slither.core.declarations.solidity_variables import \ - SolidityVariableComposed -from slither.slithir.operations.high_level_call import HighLevelCall -from slither.slithir.operations.index import Index -from slither.slithir.operations.low_level_call import LowLevelCall -from slither.slithir.operations.lvalue import OperationWithLValue -from slither.slithir.operations.member import Member -from slither.slithir.operations.send import Send -from slither.slithir.operations.transfer import Transfer -from slither.slithir.variables.reference import ReferenceVariable +from slither.core.declarations import SolidityVariableComposed +from slither.slithir.operations import (HighLevelCall, Index, LowLevelCall, + Member, OperationWithLValue, Send, + Transfer) +from slither.slithir.variables import ReferenceVariable KEY = 'TAINT_CALL_DESTINATION' diff --git a/slither/analyses/taint/specific_variable.py b/slither/analyses/taint/specific_variable.py index 4e9f93fe7..bb322a992 100644 --- a/slither/analyses/taint/specific_variable.py +++ b/slither/analyses/taint/specific_variable.py @@ -6,13 +6,10 @@ Iterate until it finding a fixpoint """ from slither.core.declarations.solidity_variables import SolidityVariable -from slither.core.variables.variable import Variable from slither.core.variables.state_variable import StateVariable -from slither.slithir.operations.index import Index -from slither.slithir.operations.member import Member -from slither.slithir.operations.lvalue import OperationWithLValue -from slither.slithir.variables.reference import ReferenceVariable -from slither.slithir.variables.temporary import TemporaryVariable +from slither.core.variables.variable import Variable +from slither.slithir.operations import Index, Member, OperationWithLValue +from slither.slithir.variables import ReferenceVariable, TemporaryVariable def make_key(variable): diff --git a/slither/analyses/taint/state_variables.py b/slither/analyses/taint/state_variables.py index 4a719a711..8e955112b 100644 --- a/slither/analyses/taint/state_variables.py +++ b/slither/analyses/taint/state_variables.py @@ -9,11 +9,8 @@ from slither.core.declarations.solidity_variables import \ SolidityVariableComposed from slither.core.variables.state_variable import StateVariable -from slither.slithir.operations.index import Index -from slither.slithir.operations.lvalue import OperationWithLValue -from slither.slithir.operations.member import Member -from slither.slithir.variables.reference import ReferenceVariable -from slither.slithir.variables.temporary import TemporaryVariable +from slither.slithir.operations import Index, Member, OperationWithLValue +from slither.slithir.variables import ReferenceVariable, TemporaryVariable KEY = 'TAINT_STATE_VARIABLES' diff --git a/slither/analyses/write/are_variables_written.py b/slither/analyses/write/are_variables_written.py index 5505fc307..f76ca4874 100644 --- a/slither/analyses/write/are_variables_written.py +++ b/slither/analyses/write/are_variables_written.py @@ -2,12 +2,11 @@ Detect if all the given variables are written in all the paths of the function """ from slither.core.cfg.node import NodeType -from slither.core.declarations.solidity_variables import SolidityFunction -from slither.slithir.operations.index import Index -from slither.slithir.operations.member import Member -from slither.slithir.operations.lvalue import OperationWithLValue -from slither.slithir.operations.solidity_call import SolidityCall -from slither.slithir.variables.reference import ReferenceVariable +from slither.core.declarations import SolidityFunction +from slither.slithir.operations import (Index, Member, OperationWithLValue, + SolidityCall) +from slither.slithir.variables import ReferenceVariable + def _visit(node, visited, variables_written, variables_to_write): diff --git a/slither/core/declarations/__init__.py b/slither/core/declarations/__init__.py index e69de29bb..b6cddc787 100644 --- a/slither/core/declarations/__init__.py +++ b/slither/core/declarations/__init__.py @@ -0,0 +1,9 @@ +from .contract import Contract +from .enum import Enum +from .event import Event +from .function import Function +from .import_directive import Import +from .modifier import Modifier +from .pragma_directive import Pragma +from .solidity_variables import SolidityVariable, SolidityVariableComposed, SolidityFunction +from .structure import Structure diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 23dc4f90d..f8d19112b 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -427,11 +427,11 @@ class Function(ChildContract, SourceMapping): Assumption: the solidity vars are used directly in the conditional node It won't work if the variable is assigned to a temp variable """ - from slither.slithir.operations.binary import BinaryOperation + from slither.slithir.operations.binary import Binary def _solidity_variable_in_node(node): ret = [] for ir in node.irs: - if isinstance(ir, BinaryOperation): + if isinstance(ir, Binary): ret += ir.read return [var for var in ret if isinstance(var, SolidityVariable)] def _explore_func(func, f): diff --git a/slither/core/expressions/__init__.py b/slither/core/expressions/__init__.py index e69de29bb..23690aca0 100644 --- a/slither/core/expressions/__init__.py +++ b/slither/core/expressions/__init__.py @@ -0,0 +1,16 @@ +from .assignment_operation import AssignmentOperation, AssignmentOperationType +from .binary_operation import BinaryOperation, BinaryOperationType +from .call_expression import CallExpression +from .conditional_expression import ConditionalExpression +from .elementary_type_name_expression import ElementaryTypeNameExpression +from .identifier import Identifier +from .index_access import IndexAccess +from .literal import Literal +from .new_array import NewArray +from .new_contract import NewContract +from .new_elementary_type import NewElementaryType +from .super_call_expression import SuperCallExpression +from .super_identifier import SuperIdentifier +from .tuple_expression import TupleExpression +from .type_conversion import TypeConversion +from .unary_operation import UnaryOperation, UnaryOperationType diff --git a/slither/detectors/attributes/locked_ether.py b/slither/detectors/attributes/locked_ether.py index 80850958c..eb84a402b 100644 --- a/slither/detectors/attributes/locked_ether.py +++ b/slither/detectors/attributes/locked_ether.py @@ -2,11 +2,10 @@ Check if ether are locked in the contract """ -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.slithir.operations.send import Send -from slither.slithir.operations.transfer import Transfer -from slither.slithir.operations.high_level_call import HighLevelCall -from slither.slithir.operations.low_level_call import LowLevelCall +from slither.detectors.abstract_detector import (AbstractDetector, + DetectorClassification) +from slither.slithir.operations import (HighLevelCall, LowLevelCall, Send, + Transfer) class LockedEther(AbstractDetector): diff --git a/slither/detectors/functions/arbitrary_send.py b/slither/detectors/functions/arbitrary_send.py index 87d16b850..47d960283 100644 --- a/slither/detectors/functions/arbitrary_send.py +++ b/slither/detectors/functions/arbitrary_send.py @@ -14,12 +14,8 @@ from slither.core.declarations.solidity_variables import (SolidityFunction, SolidityVariableComposed) from slither.detectors.abstract_detector import (AbstractDetector, DetectorClassification) -from slither.slithir.operations.high_level_call import HighLevelCall -from slither.slithir.operations.index import Index -from slither.slithir.operations.low_level_call import LowLevelCall -from slither.slithir.operations.send import Send -from slither.slithir.operations.solidity_call import SolidityCall -from slither.slithir.operations.transfer import Transfer +from slither.slithir.operations import (HighLevelCall, Index, LowLevelCall, + Send, SolidityCall, Transfer) class ArbitrarySend(AbstractDetector): diff --git a/slither/detectors/reentrancy/reentrancy.py b/slither/detectors/reentrancy/reentrancy.py index 26357aa98..0eacca53b 100644 --- a/slither/detectors/reentrancy/reentrancy.py +++ b/slither/detectors/reentrancy/reentrancy.py @@ -5,11 +5,11 @@ Iterate over all the nodes of the graph until reaching a fixpoint """ -from slither.core.declarations.function import Function -from slither.core.declarations.solidity_variables import SolidityFunction -from slither.core.expressions.unary_operation import UnaryOperation, UnaryOperationType from slither.core.cfg.node import NodeType -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.core.declarations import Function, SolidityFunction +from slither.core.expressions import UnaryOperation, UnaryOperationType +from slither.detectors.abstract_detector import (AbstractDetector, + DetectorClassification) from slither.visitors.expression.export_values import ExportValues diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 4709c6059..b78dcb709 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -1,30 +1,15 @@ -from slither.core.declarations.contract import Contract -from slither.core.declarations.event import Event -from slither.core.declarations.solidity_variables import (SolidityFunction, - SolidityVariableComposed) -from slither.core.declarations.structure import Structure -from slither.core.expressions.literal import Literal -from slither.core.expressions.identifier import Identifier +from slither.core.declarations import (Contract, Event, SolidityFunction, + SolidityVariableComposed, Structure) +from slither.core.expressions import Identifier, Literal from slither.core.solidity_types.elementary_type import ElementaryType from slither.core.variables.variable import Variable -from slither.slithir.operations.call import Call -from slither.slithir.operations.event_call import EventCall -from slither.slithir.operations.high_level_call import HighLevelCall -from slither.slithir.operations.init_array import InitArray -from slither.slithir.operations.library_call import LibraryCall -from slither.slithir.operations.low_level_call import LowLevelCall -from slither.slithir.operations.lvalue import OperationWithLValue -from slither.slithir.operations.member import Member -from slither.slithir.operations.new_array import NewArray -from slither.slithir.operations.new_contract import NewContract -from slither.slithir.operations.new_elementary_type import NewElementaryType -from slither.slithir.operations.new_structure import NewStructure -from slither.slithir.operations.push import Push -from slither.slithir.operations.send import Send -from slither.slithir.operations.solidity_call import SolidityCall -from slither.slithir.operations.transfer import Transfer -from slither.slithir.operations.return_operation import Return -from slither.slithir.operations.condition import Condition +from slither.slithir.operations import (Call, Condition, EventCall, + HighLevelCall, InitArray, LibraryCall, + LowLevelCall, Member, NewArray, + NewContract, NewElementaryType, + NewStructure, OperationWithLValue, + Push, Return, Send, SolidityCall, + Transfer) from slither.slithir.tmp_operations.argument import Argument, ArgumentType from slither.slithir.tmp_operations.tmp_call import TmpCall from slither.slithir.tmp_operations.tmp_new_array import TmpNewArray @@ -32,10 +17,8 @@ from slither.slithir.tmp_operations.tmp_new_contract import TmpNewContract from slither.slithir.tmp_operations.tmp_new_elementary_type import \ TmpNewElementaryType from slither.slithir.tmp_operations.tmp_new_structure import TmpNewStructure -from slither.slithir.variables.constant import Constant -from slither.slithir.variables.reference import ReferenceVariable -from slither.slithir.variables.temporary import TemporaryVariable -from slither.slithir.variables.tuple import TupleVariable +from slither.slithir.variables import (Constant, ReferenceVariable, + TemporaryVariable, TupleVariable) from slither.visitors.slithir.expression_to_slithir import ExpressionToSlithIR diff --git a/slither/slithir/operations/__init__.py b/slither/slithir/operations/__init__.py index e69de29bb..e4f4a459b 100644 --- a/slither/slithir/operations/__init__.py +++ b/slither/slithir/operations/__init__.py @@ -0,0 +1,27 @@ +from .assignment import Assignment, AssignmentType +from .binary import Binary, BinaryType +from .call import Call +from .condition import Condition +from .delete import Delete +from .event_call import EventCall +from .high_level_call import HighLevelCall +from .index import Index +from .init_array import InitArray +from .internal_call import InternalCall +from .library_call import LibraryCall +from .low_level_call import LowLevelCall +from .lvalue import OperationWithLValue +from .member import Member +from .new_array import NewArray +from .new_elementary_type import NewElementaryType +from .new_contract import NewContract +from .new_structure import NewStructure +from .operation import Operation +from .push import Push +from .return_operation import Return +from .send import Send +from .solidity_call import SolidityCall +from .transfer import Transfer +from .type_conversion import TypeConversion +from .unary import Unary, UnaryType +from .unpack import Unpack diff --git a/slither/slithir/operations/assignment.py b/slither/slithir/operations/assignment.py index d81a53d09..a2d35579d 100644 --- a/slither/slithir/operations/assignment.py +++ b/slither/slithir/operations/assignment.py @@ -7,7 +7,7 @@ from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue logger = logging.getLogger("AssignmentOperationIR") -class AssignmentOperationType(object): +class AssignmentType(object): ASSIGN = 0 # = ASSIGN_OR = 1 # |= ASSIGN_CARET = 2 # ^= @@ -23,54 +23,54 @@ class AssignmentOperationType(object): @staticmethod def get_type(operation_type): if operation_type == '=': - return AssignmentOperationType.ASSIGN + return AssignmentType.ASSIGN if operation_type == '|=': - return AssignmentOperationType.ASSIGN_OR + return AssignmentType.ASSIGN_OR if operation_type == '^=': - return AssignmentOperationType.ASSIGN_CARET + return AssignmentType.ASSIGN_CARET if operation_type == '&=': - return AssignmentOperationType.ASSIGN_AND + return AssignmentType.ASSIGN_AND if operation_type == '<<=': - return AssignmentOperationType.ASSIGN_LEFT_SHIFT + return AssignmentType.ASSIGN_LEFT_SHIFT if operation_type == '>>=': - return AssignmentOperationType.ASSIGN_RIGHT_SHIFT + return AssignmentType.ASSIGN_RIGHT_SHIFT if operation_type == '+=': - return AssignmentOperationType.ASSIGN_ADDITION + return AssignmentType.ASSIGN_ADDITION if operation_type == '-=': - return AssignmentOperationType.ASSIGN_SUBTRACTION + return AssignmentType.ASSIGN_SUBTRACTION if operation_type == '*=': - return AssignmentOperationType.ASSIGN_MULTIPLICATION + return AssignmentType.ASSIGN_MULTIPLICATION if operation_type == '/=': - return AssignmentOperationType.ASSIGN_DIVISION + return AssignmentType.ASSIGN_DIVISION if operation_type == '%=': - return AssignmentOperationType.ASSIGN_MODULO + return AssignmentType.ASSIGN_MODULO logger.error('get_type: Unknown operation type {})'.format(operation_type)) exit(-1) @staticmethod def str(operation_type): - if operation_type == AssignmentOperationType.ASSIGN: + if operation_type == AssignmentType.ASSIGN: return '=' - if operation_type == AssignmentOperationType.ASSIGN_OR: + if operation_type == AssignmentType.ASSIGN_OR: return '|=' - if operation_type == AssignmentOperationType.ASSIGN_CARET: + if operation_type == AssignmentType.ASSIGN_CARET: return '^=' - if operation_type == AssignmentOperationType.ASSIGN_AND: + if operation_type == AssignmentType.ASSIGN_AND: return '&=' - if operation_type == AssignmentOperationType.ASSIGN_LEFT_SHIFT: + if operation_type == AssignmentType.ASSIGN_LEFT_SHIFT: return '<<=' - if operation_type == AssignmentOperationType.ASSIGN_RIGHT_SHIFT: + if operation_type == AssignmentType.ASSIGN_RIGHT_SHIFT: return '>>=' - if operation_type == AssignmentOperationType.ASSIGN_ADDITION: + if operation_type == AssignmentType.ASSIGN_ADDITION: return '+=' - if operation_type == AssignmentOperationType.ASSIGN_SUBTRACTION: + if operation_type == AssignmentType.ASSIGN_SUBTRACTION: return '-=' - if operation_type == AssignmentOperationType.ASSIGN_MULTIPLICATION: + if operation_type == AssignmentType.ASSIGN_MULTIPLICATION: return '*=' - if operation_type == AssignmentOperationType.ASSIGN_DIVISION: + if operation_type == AssignmentType.ASSIGN_DIVISION: return '/=' - if operation_type == AssignmentOperationType.ASSIGN_MODULO: + if operation_type == AssignmentType.ASSIGN_MODULO: return '%=' logger.error('str: Unknown operation type {})'.format(operation_type)) @@ -83,7 +83,7 @@ class Assignment(OperationWithLValue): #print(type(left_variable)) assert is_valid_lvalue(left_variable) assert is_valid_rvalue(right_variable) or\ - (isinstance(right_variable, Function) and variable_type == AssignmentOperationType.ASSIGN) + (isinstance(right_variable, Function) and variable_type == AssignmentType.ASSIGN) super(Assignment, self).__init__() self._variables = [left_variable, right_variable] self._lvalue = left_variable @@ -109,7 +109,7 @@ class Assignment(OperationWithLValue): @property def type_str(self): - return AssignmentOperationType.str(self._type) + return AssignmentType.str(self._type) def __str__(self): return '{} {} {}'.format(self.lvalue, self.type_str, self.rvalue) diff --git a/slither/slithir/operations/binary.py b/slither/slithir/operations/binary.py index cdfae664f..0da8ad60e 100644 --- a/slither/slithir/operations/binary.py +++ b/slither/slithir/operations/binary.py @@ -5,7 +5,7 @@ from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue logger = logging.getLogger("BinaryOperationIR") -class BinaryOperationType(object): +class BinaryType(object): POWER = 0 # ** MULTIPLICATION = 1 # * DIVISION = 2 # / @@ -30,97 +30,97 @@ class BinaryOperationType(object): @staticmethod def get_type(operation_type): if operation_type == '**': - return BinaryOperationType.POWER + return BinaryType.POWER if operation_type == '*': - return BinaryOperationType.MULTIPLICATION + return BinaryType.MULTIPLICATION if operation_type == '/': - return BinaryOperationType.DIVISION + return BinaryType.DIVISION if operation_type == '%': - return BinaryOperationType.MODULO + return BinaryType.MODULO if operation_type == '+': - return BinaryOperationType.ADDITION + return BinaryType.ADDITION if operation_type == '-': - return BinaryOperationType.SUBTRACTION + return BinaryType.SUBTRACTION if operation_type == '<<': - return BinaryOperationType.LEFT_SHIFT + return BinaryType.LEFT_SHIFT if operation_type == '>>': - return BinaryOperationType.RIGHT_SHIT + return BinaryType.RIGHT_SHIT if operation_type == '&': - return BinaryOperationType.AND + return BinaryType.AND if operation_type == '^': - return BinaryOperationType.CARET + return BinaryType.CARET if operation_type == '|': - return BinaryOperationType.OR + return BinaryType.OR if operation_type == '<': - return BinaryOperationType.LESS + return BinaryType.LESS if operation_type == '>': - return BinaryOperationType.GREATER + return BinaryType.GREATER if operation_type == '<=': - return BinaryOperationType.LESS_EQUAL + return BinaryType.LESS_EQUAL if operation_type == '>=': - return BinaryOperationType.GREATER_EQUAL + return BinaryType.GREATER_EQUAL if operation_type == '==': - return BinaryOperationType.EQUAL + return BinaryType.EQUAL if operation_type == '!=': - return BinaryOperationType.NOT_EQUAL + return BinaryType.NOT_EQUAL if operation_type == '&&': - return BinaryOperationType.ANDAND + return BinaryType.ANDAND if operation_type == '||': - return BinaryOperationType.OROR + return BinaryType.OROR logger.error('get_type: Unknown operation type {})'.format(operation_type)) exit(-1) @staticmethod def str(operation_type): - if operation_type == BinaryOperationType.POWER: + if operation_type == BinaryType.POWER: return '**' - if operation_type == BinaryOperationType.MULTIPLICATION: + if operation_type == BinaryType.MULTIPLICATION: return '*' - if operation_type == BinaryOperationType.DIVISION: + if operation_type == BinaryType.DIVISION: return '/' - if operation_type == BinaryOperationType.MODULO: + if operation_type == BinaryType.MODULO: return '%' - if operation_type == BinaryOperationType.ADDITION: + if operation_type == BinaryType.ADDITION: return '+' - if operation_type == BinaryOperationType.SUBTRACTION: + if operation_type == BinaryType.SUBTRACTION: return '-' - if operation_type == BinaryOperationType.LEFT_SHIFT: + if operation_type == BinaryType.LEFT_SHIFT: return '<<' - if operation_type == BinaryOperationType.RIGHT_SHIT: + if operation_type == BinaryType.RIGHT_SHIT: return '>>' - if operation_type == BinaryOperationType.AND: + if operation_type == BinaryType.AND: return '&' - if operation_type == BinaryOperationType.CARET: + if operation_type == BinaryType.CARET: return '^' - if operation_type == BinaryOperationType.OR: + if operation_type == BinaryType.OR: return '|' - if operation_type == BinaryOperationType.LESS: + if operation_type == BinaryType.LESS: return '<' - if operation_type == BinaryOperationType.GREATER: + if operation_type == BinaryType.GREATER: return '>' - if operation_type == BinaryOperationType.LESS_EQUAL: + if operation_type == BinaryType.LESS_EQUAL: return '<=' - if operation_type == BinaryOperationType.GREATER_EQUAL: + if operation_type == BinaryType.GREATER_EQUAL: return '>=' - if operation_type == BinaryOperationType.EQUAL: + if operation_type == BinaryType.EQUAL: return '==' - if operation_type == BinaryOperationType.NOT_EQUAL: + if operation_type == BinaryType.NOT_EQUAL: return '!=' - if operation_type == BinaryOperationType.ANDAND: + if operation_type == BinaryType.ANDAND: return '&&' - if operation_type == BinaryOperationType.OROR: + if operation_type == BinaryType.OROR: return '||' logger.error('str: Unknown operation type {})'.format(operation_type)) exit(-1) -class BinaryOperation(OperationWithLValue): +class Binary(OperationWithLValue): def __init__(self, result, left_variable, right_variable, operation_type): assert is_valid_rvalue(left_variable) assert is_valid_rvalue(right_variable) assert is_valid_lvalue(result) - super(BinaryOperation, self).__init__() + super(Binary, self).__init__() self._variables = [left_variable, right_variable] self._type = operation_type self._lvalue = result @@ -143,7 +143,7 @@ class BinaryOperation(OperationWithLValue): @property def type_str(self): - return BinaryOperationType.str(self._type) + return BinaryType.str(self._type) def __str__(self): return str(self.lvalue)+ ' = ' + str(self.variable_left) + ' ' + self.type_str + ' ' + str(self.variable_right) diff --git a/slither/slithir/operations/unary.py b/slither/slithir/operations/unary.py index 68e15bdbb..edcb1c92d 100644 --- a/slither/slithir/operations/unary.py +++ b/slither/slithir/operations/unary.py @@ -6,7 +6,7 @@ from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue logger = logging.getLogger("BinaryOperationIR") -class UnaryOperationType: +class UnaryType: BANG = 0 # ! TILD = 1 # ~ @@ -14,28 +14,28 @@ class UnaryOperationType: def get_type(operation_type, isprefix): if isprefix: if operation_type == '!': - return UnaryOperationType.BANG + return UnaryType.BANG if operation_type == '~': - return UnaryOperationType.TILD + return UnaryType.TILD logger.error('get_type: Unknown operation type {}'.format(operation_type)) exit(-1) @staticmethod def str(operation_type): - if operation_type == UnaryOperationType.BANG: + if operation_type == UnaryType.BANG: return '!' - if operation_type == UnaryOperationType.TILD: + if operation_type == UnaryType.TILD: return '~' logger.error('str: Unknown operation type {}'.format(operation_type)) exit(-1) -class UnaryOperation(OperationWithLValue): +class Unary(OperationWithLValue): def __init__(self, result, variable, operation_type): assert is_valid_rvalue(variable) assert is_valid_lvalue(result) - super(UnaryOperation, self).__init__() + super(Unary, self).__init__() self._variable = variable self._type = operation_type self._lvalue = result @@ -50,7 +50,7 @@ class UnaryOperation(OperationWithLValue): @property def type_str(self): - return UnaryOperationType.str(self._type) + return UnaryType.str(self._type) def __str__(self): return "{} = {} {} ".format(self.lvalue, self.type_str, self.variable) diff --git a/slither/slithir/variables/__init__.py b/slither/slithir/variables/__init__.py index e69de29bb..ad005c9be 100644 --- a/slither/slithir/variables/__init__.py +++ b/slither/slithir/variables/__init__.py @@ -0,0 +1,4 @@ +from .constant import Constant +from .reference import ReferenceVariable +from .temporary import TemporaryVariable +from .tuple import TupleVariable diff --git a/slither/slithir/variables/call.py b/slither/slithir/variables/call.py deleted file mode 100644 index e8624cc46..000000000 --- a/slither/slithir/variables/call.py +++ /dev/null @@ -1,19 +0,0 @@ - -from slither.core.variables.variable import Variable -from slither.core.children.child_node import ChildNode - -class TemporaryVariable(ChildNode, Variable): - - COUNTER = 0 - - def __init__(self): - super(TemporaryVariable, self).__init__() - self._index = TemporaryVariable.COUNTER - TemporaryVariable.COUNTER += 1 - - @property - def index(self): - return self._index - - def __str__(self): - return 'TMP_{}'.format(self.index) diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index a8ffff95b..8c7f715dc 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -1,34 +1,21 @@ -from slither.visitors.expression.expression import ExpressionVisitor - -from slither.core.expressions.assignment_operation import AssignmentOperationType -from slither.core.declarations.function import Function -from slither.core.declarations.structure import Structure -from slither.core.expressions.unary_operation import UnaryOperationType +from slither.core.declarations import Function, Structure +from slither.core.expressions import (AssignmentOperationType, + UnaryOperationType) from slither.core.solidity_types.array_type import ArrayType - -from slither.slithir.operations.assignment import Assignment -from slither.slithir.operations.binary import BinaryOperation, BinaryOperationType -from slither.slithir.operations.unary import UnaryOperation -from slither.slithir.operations.index import Index -from slither.slithir.operations.internal_call import InternalCall -from slither.slithir.operations.member import Member -from slither.slithir.operations.type_conversion import TypeConversion -from slither.slithir.operations.delete import Delete -from slither.slithir.operations.unpack import Unpack -from slither.slithir.operations.init_array import InitArray - +from slither.slithir.operations import (Assignment, Binary, BinaryType, Delete, + Index, InitArray, InternalCall, Member, + TypeConversion, Unary, Unpack) +from slither.slithir.tmp_operations.argument import Argument from slither.slithir.tmp_operations.tmp_call import TmpCall -from slither.slithir.tmp_operations.tmp_new_elementary_type import TmpNewElementaryType -from slither.slithir.tmp_operations.tmp_new_contract import TmpNewContract from slither.slithir.tmp_operations.tmp_new_array import TmpNewArray +from slither.slithir.tmp_operations.tmp_new_contract import TmpNewContract +from slither.slithir.tmp_operations.tmp_new_elementary_type import \ + TmpNewElementaryType from slither.slithir.tmp_operations.tmp_new_structure import TmpNewStructure -from slither.slithir.tmp_operations.argument import Argument - -from slither.slithir.variables.temporary import TemporaryVariable -from slither.slithir.variables.tuple import TupleVariable -from slither.slithir.variables.constant import Constant -from slither.slithir.variables.reference import ReferenceVariable +from slither.slithir.variables import (Constant, ReferenceVariable, + TemporaryVariable, TupleVariable) +from slither.visitors.expression.expression import ExpressionVisitor key = 'expressionToSlithIR' @@ -88,7 +75,7 @@ class ExpressionToSlithIR(ExpressionVisitor): right = get(expression.expression_right) val = TemporaryVariable() - operation = BinaryOperation(val, left, right, expression.type) + operation = Binary(val, left, right, expression.type) self._result.append(operation) set_val(expression, val) @@ -192,7 +179,7 @@ class ExpressionToSlithIR(ExpressionVisitor): value = get(expression.expression) if expression.type in [UnaryOperationType.BANG, UnaryOperationType.TILD]: lvalue = TemporaryVariable() - operation = UnaryOperation(lvalue, value, expression.type) + operation = Unary(lvalue, value, expression.type) self._result.append(operation) set_val(expression, lvalue) elif expression.type in [UnaryOperationType.DELETE]: @@ -200,35 +187,34 @@ class ExpressionToSlithIR(ExpressionVisitor): self._result.append(operation) set_val(expression, value) elif expression.type in [UnaryOperationType.PLUSPLUS_PRE]: - operation = BinaryOperation(value, value, Constant("1"), BinaryOperationType.ADDITION) + operation = Binary(value, value, Constant("1"), BinaryType.ADDITION) self._result.append(operation) set_val(expression, value) elif expression.type in [UnaryOperationType.MINUSMINUS_PRE]: - operation = BinaryOperation(value, value, Constant("1"), BinaryOperationType.SUBTRACTION) + operation = Binary(value, value, Constant("1"), BinaryType.SUBTRACTION) self._result.append(operation) set_val(expression, value) elif expression.type in [UnaryOperationType.PLUSPLUS_POST]: lvalue = TemporaryVariable() operation = Assignment(lvalue, value, AssignmentOperationType.ASSIGN, value.type) self._result.append(operation) - operation = BinaryOperation(value, value, Constant("1"), BinaryOperationType.ADDITION) + operation = Binary(value, value, Constant("1"), BinaryType.ADDITION) self._result.append(operation) set_val(expression, lvalue) elif expression.type in [UnaryOperationType.MINUSMINUS_POST]: lvalue = TemporaryVariable() operation = Assignment(lvalue, value, AssignmentOperationType.ASSIGN, value.type) self._result.append(operation) - operation = BinaryOperation(value, value, Constant("1"), BinaryOperationType.SUBTRACTION) + operation = Binary(value, value, Constant("1"), BinaryType.SUBTRACTION) self._result.append(operation) set_val(expression, lvalue) elif expression.type in [UnaryOperationType.PLUS_PRE]: set_val(expression, value) elif expression.type in [UnaryOperationType.MINUS_PRE]: lvalue = TemporaryVariable() - operation = BinaryOperation(lvalue, Constant("0"), value, BinaryOperationType.SUBTRACTION) + operation = Binary(lvalue, Constant("0"), value, BinaryType.SUBTRACTION) self._result.append(operation) set_val(expression, lvalue) else: raise Exception('Unary operation to IR not supported {}'.format(expression)) - From 5c792cc954f802113ce472fcc9a9201ae909887b Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 4 Oct 2018 11:23:18 -0700 Subject: [PATCH 114/308] Factor taint code SlithIR: Fix incorrect remove of instruction --- slither/analyses/taint/calls.py | 34 +++++++++--------- slither/analyses/taint/common.py | 15 ++++++++ slither/analyses/taint/specific_variable.py | 39 +++++++++++---------- slither/analyses/taint/state_variables.py | 27 ++++++-------- slither/slithir/convert.py | 7 +++- 5 files changed, 68 insertions(+), 54 deletions(-) create mode 100644 slither/analyses/taint/common.py diff --git a/slither/analyses/taint/calls.py b/slither/analyses/taint/calls.py index b9747283a..ca2d2012b 100644 --- a/slither/analyses/taint/calls.py +++ b/slither/analyses/taint/calls.py @@ -12,32 +12,30 @@ from slither.slithir.operations import (HighLevelCall, Index, LowLevelCall, Transfer) from slither.slithir.variables import ReferenceVariable +from .common import iterate_over_irs + KEY = 'TAINT_CALL_DESTINATION' +def _transfer_func(ir, read, refs, taints): + if isinstance(ir, OperationWithLValue) and any(var_read in taints for var_read in read): + taints += [ir.lvalue] + lvalue = ir.lvalue + while isinstance(lvalue, ReferenceVariable): + taints += [refs[lvalue]] + lvalue = refs[lvalue] + if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)): + if ir.destination in taints: + ir.context[KEY] = True + + return taints + def _visit_node(node, visited, taints): if node in visited: return visited += [node] - refs = {} - for ir in node.irs: - if isinstance(ir, (Index, Member)): - refs[ir.lvalue] = ir.variable_left - - if isinstance(ir, Index): - read = [ir.variable_left] - else: - read = ir.read - if isinstance(ir, OperationWithLValue) and any(var_read in taints for var_read in read): - taints += [ir.lvalue] - lvalue = ir.lvalue - while isinstance(lvalue, ReferenceVariable): - taints += [refs[lvalue]] - lvalue = refs[lvalue] - if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)): - if ir.destination in taints: - ir.context[KEY] = True + taints = iterate_over_irs(node.irs, _transfer_func, taints) for son in node.sons: _visit_node(son, visited, taints) diff --git a/slither/analyses/taint/common.py b/slither/analyses/taint/common.py new file mode 100644 index 000000000..484f5f2d2 --- /dev/null +++ b/slither/analyses/taint/common.py @@ -0,0 +1,15 @@ +from slither.slithir.operations import (Index, Member) + +def iterate_over_irs(irs, transfer_func, taints): + refs = {} + for ir in irs: + if isinstance(ir, (Index, Member)): + refs[ir.lvalue] = ir.variable_left + + if isinstance(ir, Index): + read = [ir.variable_left] + else: + read = ir.read + taints = transfer_func(ir, read, refs, taints) + return taints + diff --git a/slither/analyses/taint/specific_variable.py b/slither/analyses/taint/specific_variable.py index bb322a992..73656d8d0 100644 --- a/slither/analyses/taint/specific_variable.py +++ b/slither/analyses/taint/specific_variable.py @@ -11,6 +11,7 @@ from slither.core.variables.variable import Variable from slither.slithir.operations import Index, Member, OperationWithLValue from slither.slithir.variables import ReferenceVariable, TemporaryVariable +from .common import iterate_over_irs def make_key(variable): if isinstance(variable, Variable): @@ -23,25 +24,9 @@ def make_key(variable): str(type(variable))) return key -def _visit_node(node, visited, key): - if node in visited: - return - - visited = visited + [node] - taints = node.function.slither.context[key] - - refs = {} - for ir in node.irs: - if not isinstance(ir, OperationWithLValue): - continue - if isinstance(ir, (Index, Member)): - refs[ir.lvalue] = ir.variable_left - - if isinstance(ir, Index): - read = [ir.variable_left] - else: - read = ir.read - if isinstance(ir, OperationWithLValue) and any(is_tainted_from_key(var_read, key) or var_read in taints for var_read in read): +def _transfer_func_with_key(ir, read, refs, taints, key): + if isinstance(ir, OperationWithLValue): + if any(is_tainted_from_key(var_read, key) or var_read in taints for var_read in read): taints += [ir.lvalue] ir.lvalue.context[key] = True lvalue = ir.lvalue @@ -49,6 +34,22 @@ def _visit_node(node, visited, key): taints += [refs[lvalue]] lvalue = refs[lvalue] lvalue.context[key] = True + return taints + +def _visit_node(node, visited, key): + if node in visited: + return + + visited = visited + [node] + taints = node.function.slither.context[key] + + # use of lambda function, as the key is required for this transfer_func + _transfer_func_ = lambda _ir, _read, _refs, _taints: _transfer_func_with_key(_ir, + _read, + _refs, + _taints, + key) + taints = iterate_over_irs(node.irs, _transfer_func_, taints) taints = [v for v in taints if not isinstance(v, (TemporaryVariable, ReferenceVariable))] diff --git a/slither/analyses/taint/state_variables.py b/slither/analyses/taint/state_variables.py index 8e955112b..f96853d7d 100644 --- a/slither/analyses/taint/state_variables.py +++ b/slither/analyses/taint/state_variables.py @@ -12,8 +12,18 @@ from slither.core.variables.state_variable import StateVariable from slither.slithir.operations import Index, Member, OperationWithLValue from slither.slithir.variables import ReferenceVariable, TemporaryVariable +from .common import iterate_over_irs KEY = 'TAINT_STATE_VARIABLES' +def _transfer_func(ir, read, refs, taints): + if isinstance(ir, OperationWithLValue) and any(var_read in taints for var_read in read): + taints += [ir.lvalue] + lvalue = ir.lvalue + while isinstance(lvalue, ReferenceVariable): + taints += [refs[lvalue]] + lvalue = refs[lvalue] + return taints + def _visit_node(node, visited): if node in visited: return @@ -21,22 +31,7 @@ def _visit_node(node, visited): visited += [node] taints = node.function.slither.context[KEY] - refs = {} - - for ir in node.irs: - if isinstance(ir, (Index, Member)): - refs[ir.lvalue] = ir.variable_left - - if isinstance(ir, Index): - read = [ir.variable_left] - else: - read = ir.read - if isinstance(ir, OperationWithLValue) and any(var_read in taints for var_read in read): - taints += [ir.lvalue] - lvalue = ir.lvalue - while isinstance(lvalue, ReferenceVariable): - taints += [refs[lvalue]] - lvalue = refs[lvalue] + taints = iterate_over_irs(node.irs, _transfer_func, taints) taints = [v for v in taints if not isinstance(v, (TemporaryVariable, ReferenceVariable))] diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index b78dcb709..ffdfeae24 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -115,7 +115,6 @@ def apply_ir_heuristics(result): result = transform_calls(result) - result = remove_unused(result) # Move the arguments operation to the call result = merge_call_parameters(result) @@ -125,6 +124,7 @@ def apply_ir_heuristics(result): result = replace_calls(result) + result = remove_unused(result) reset_variable_number(result) return result @@ -198,8 +198,13 @@ def remove_unused(result): to_keep = [] to_remove = [] + # keep variables that are read + # and reference that are written for ins in result: to_keep += [str(x) for x in ins.read] + if isinstance(ins, OperationWithLValue): + if isinstance(ins.lvalue, ReferenceVariable): + to_keep += [str(ins.lvalue)] for ins in result: if isinstance(ins, Member): From b2efb3582bcd9edab4f14e20521e280a9ae99b07 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 4 Oct 2018 11:36:56 -0700 Subject: [PATCH 115/308] Use of taint in msg.value to reduce arbitrary send FP --- slither/analyses/taint/calls.py | 6 +++--- slither/detectors/functions/arbitrary_send.py | 17 +++++++++++++++-- tests/arbitrary_send.sol | 9 +++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/slither/analyses/taint/calls.py b/slither/analyses/taint/calls.py index ca2d2012b..ac3e08260 100644 --- a/slither/analyses/taint/calls.py +++ b/slither/analyses/taint/calls.py @@ -1,9 +1,9 @@ """ - Compute taint on state call + Compute taint on call - use taint on state_variable + use taint from state_variable - an call ir with a taint set to yes means tainted destination + call from slithIR with a taint set to yes means its destination is tainted """ from slither.analyses.taint.state_variables import get_taint as get_taint_state from slither.core.declarations import SolidityVariableComposed diff --git a/slither/detectors/functions/arbitrary_send.py b/slither/detectors/functions/arbitrary_send.py index 47d960283..a922724b7 100644 --- a/slither/detectors/functions/arbitrary_send.py +++ b/slither/detectors/functions/arbitrary_send.py @@ -9,7 +9,11 @@ TODO: dont report if the value is tainted by msg.value """ -from slither.analyses.taint.calls import KEY, run_taint +from slither.analyses.taint.calls import KEY +from slither.analyses.taint.calls import run_taint as run_taint_calls +from slither.analyses.taint.specific_variable import is_tainted +from slither.analyses.taint.specific_variable import \ + run_taint as run_taint_variable from slither.core.declarations.solidity_variables import (SolidityFunction, SolidityVariableComposed) from slither.detectors.abstract_detector import (AbstractDetector, @@ -48,6 +52,8 @@ class ArbitrarySend(AbstractDetector): continue if ir.call_value == SolidityVariableComposed('msg.value'): continue + if is_tainted(ir.call_value, SolidityVariableComposed('msg.value')): + continue if KEY in ir.context: if ir.context[KEY]: @@ -73,8 +79,15 @@ class ArbitrarySend(AbstractDetector): def detect(self): """ """ - run_taint(self.slither) results = [] + + # Look if the destination of a call is tainted + run_taint_calls(self.slither) + + # Taint msg.value + taint = SolidityVariableComposed('msg.value') + run_taint_variable(self.slither, taint) + for c in self.contracts: arbitrary_send = self.detect_arbitrary_send(c) for (func, nodes) in arbitrary_send: diff --git a/tests/arbitrary_send.sol b/tests/arbitrary_send.sol index 096f6fc47..8e094af20 100644 --- a/tests/arbitrary_send.sol +++ b/tests/arbitrary_send.sol @@ -20,6 +20,8 @@ contract Test{ destination.send(this.balance); } + // these are legitimate calls + // and should not be detected function repay() payable{ msg.sender.transfer(msg.value); } @@ -28,4 +30,11 @@ contract Test{ msg.sender.send(balances[msg.sender]); } + function buy() payable{ + uint value_send = msg.value; + uint value_spent = 0 ; // simulate a buy of tokens + uint remaining = value_send - value_spent; + msg.sender.send(remaining); +} + } From 5faacb020549d8c10f9bbbbdb455830a2dd4e028 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 4 Oct 2018 11:59:36 -0700 Subject: [PATCH 116/308] Constructor declared as protected function Taint msg.sender for arbitrary send detector --- slither/analyses/taint/specific_variable.py | 2 +- slither/core/declarations/function.py | 2 ++ slither/detectors/functions/arbitrary_send.py | 6 ++++++ tests/arbitrary_send.sol | 3 ++- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/slither/analyses/taint/specific_variable.py b/slither/analyses/taint/specific_variable.py index 73656d8d0..92013aa55 100644 --- a/slither/analyses/taint/specific_variable.py +++ b/slither/analyses/taint/specific_variable.py @@ -1,7 +1,7 @@ """ Compute taint from a specific variable - Do not propagate taint on protected function + Do not propagate taint on protected function or constructor Propage to state variables Iterate until it finding a fixpoint """ diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index f8d19112b..a4734d2c1 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -576,6 +576,8 @@ class Function(ChildContract, SourceMapping): (bool) """ + if self.is_constructor: + return True conditional_vars = self.all_conditional_solidity_variables_read() args_vars = self.all_solidity_variables_used_as_args() return SolidityVariableComposed('msg.sender') in conditional_vars + args_vars diff --git a/slither/detectors/functions/arbitrary_send.py b/slither/detectors/functions/arbitrary_send.py index a922724b7..26c305b75 100644 --- a/slither/detectors/functions/arbitrary_send.py +++ b/slither/detectors/functions/arbitrary_send.py @@ -47,6 +47,8 @@ class ArbitrarySend(AbstractDetector): if isinstance(ir, Index): if ir.variable_right == SolidityVariableComposed('msg.sender'): return False + if is_tainted(ir.variable_right, SolidityVariableComposed('msg.sender')): + return False if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)): if ir.call_value is None: continue @@ -88,6 +90,10 @@ class ArbitrarySend(AbstractDetector): taint = SolidityVariableComposed('msg.value') run_taint_variable(self.slither, taint) + # Taint msg.sender + taint = SolidityVariableComposed('msg.sender') + run_taint_variable(self.slither, taint) + for c in self.contracts: arbitrary_send = self.detect_arbitrary_send(c) for (func, nodes) in arbitrary_send: diff --git a/tests/arbitrary_send.sol b/tests/arbitrary_send.sol index 8e094af20..3544a903c 100644 --- a/tests/arbitrary_send.sol +++ b/tests/arbitrary_send.sol @@ -27,7 +27,8 @@ contract Test{ } function withdraw(){ - msg.sender.send(balances[msg.sender]); + uint val = balances[msg.sender]; + msg.sender.send(val); } function buy() payable{ From 4312f213b0d2203d278243bd5c921038828458bd Mon Sep 17 00:00:00 2001 From: Josselin Date: Sat, 6 Oct 2018 17:15:06 -0700 Subject: [PATCH 117/308] SlithIR: - Add support to assign Tuple - Add printer to output slithir --- slither/__main__.py | 7 +++++- slither/printers/summary/slithir.py | 32 ++++++++++++++++++++++++ slither/slithir/convert.py | 1 - slither/slithir/operations/assignment.py | 5 ++-- 4 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 slither/printers/summary/slithir.py diff --git a/slither/__main__.py b/slither/__main__.py index ded120ea0..4fb1fa9c3 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -117,8 +117,13 @@ def main(): from slither.printers.summary.quick_summary import PrinterQuickSummary from slither.printers.inheritance.inheritance import PrinterInheritance from slither.printers.functions.authorization import PrinterWrittenVariablesAndAuthorization + from slither.printers.summary.slithir import PrinterSlithIR - printers = [PrinterSummary, PrinterQuickSummary, PrinterInheritance, PrinterWrittenVariablesAndAuthorization] + printers = [PrinterSummary, + PrinterQuickSummary, + PrinterInheritance, + PrinterWrittenVariablesAndAuthorization, + PrinterSlithIR] # Handle plugins! for entry_point in iter_entry_points(group='slither_analyzer.plugin', name=None): diff --git a/slither/printers/summary/slithir.py b/slither/printers/summary/slithir.py new file mode 100644 index 000000000..472bfbda6 --- /dev/null +++ b/slither/printers/summary/slithir.py @@ -0,0 +1,32 @@ +""" + Module printing summary of the contract +""" + +from slither.printers.abstract_printer import AbstractPrinter +from slither.utils.colors import blue, green, magenta + +class PrinterSlithIR(AbstractPrinter): + + ARGUMENT = 'slithir' + HELP = 'the slithIR' + + def output(self, _filename): + """ + _filename is not used + Args: + _filename(string) + """ + + txt = "" + for contract in self.contracts: + print('Contract {}'.format(contract.name)) + for function in contract.functions: + if function.contract == contract: + print('\tFunction {}'.format(function.full_name)) + for node in function.nodes: + if node.expression: + print('\t\tExpression: {}'.format(node.expression)) + print('\t\tIRs:') + for ir in node.irs: + print('\t\t\t{}'.format(ir)) + self.info(txt) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index ffdfeae24..02d380aca 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -354,5 +354,4 @@ def convert_expression(expression, node): assert isinstance(result[-1], (OperationWithLValue)) result.append(Return(result[-1].lvalue)) - return result diff --git a/slither/slithir/operations/assignment.py b/slither/slithir/operations/assignment.py index a2d35579d..6a4eb7d04 100644 --- a/slither/slithir/operations/assignment.py +++ b/slither/slithir/operations/assignment.py @@ -2,6 +2,7 @@ import logging from slither.slithir.operations.lvalue import OperationWithLValue from slither.core.variables.variable import Variable +from slither.slithir.variables import TupleVariable from slither.core.declarations.function import Function from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue @@ -83,7 +84,7 @@ class Assignment(OperationWithLValue): #print(type(left_variable)) assert is_valid_lvalue(left_variable) assert is_valid_rvalue(right_variable) or\ - (isinstance(right_variable, Function) and variable_type == AssignmentType.ASSIGN) + (isinstance(right_variable, (Function, TupleVariable)) and variable_type == AssignmentType.ASSIGN) super(Assignment, self).__init__() self._variables = [left_variable, right_variable] self._lvalue = left_variable @@ -98,7 +99,7 @@ class Assignment(OperationWithLValue): @property def read(self): return list(self.variables) - + @property def variable_return_type(self): return self._variable_return_type From d270e8d83532d245e45470921bccd87a5ebff2b9 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 7 Oct 2018 20:40:38 -0700 Subject: [PATCH 118/308] SlithIR: improve support of new contract/structure --- slither/slithir/convert.py | 20 ++++++------ slither/slithir/operations/new_contract.py | 32 +++++++++++++++---- .../visitors/slithir/expression_to_slithir.py | 3 +- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 02d380aca..18c5e8095 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -3,7 +3,7 @@ from slither.core.declarations import (Contract, Event, SolidityFunction, from slither.core.expressions import Identifier, Literal from slither.core.solidity_types.elementary_type import ElementaryType from slither.core.variables.variable import Variable -from slither.slithir.operations import (Call, Condition, EventCall, +from slither.slithir.operations import (Assignment, Call, Condition, EventCall, HighLevelCall, InitArray, LibraryCall, LowLevelCall, Member, NewArray, NewContract, NewElementaryType, @@ -115,13 +115,10 @@ def apply_ir_heuristics(result): result = transform_calls(result) - # Move the arguments operation to the call result = merge_call_parameters(result) - # Remove temporary result = remove_temporary(result) - result = replace_calls(result) result = remove_unused(result) @@ -169,13 +166,13 @@ def merge_call_parameters(result): assert ins.get_type() == ArgumentType.CALL call_data.append(ins.argument) - if isinstance(ins, HighLevelCall): + if isinstance(ins, (HighLevelCall, NewContract)): if ins.call_id in calls_value: ins.call_value = calls_value[ins.call_id] if ins.call_id in calls_gas: ins.call_gas = calls_gas[ins.call_id] - if isinstance(ins, Call): + if isinstance(ins, (Call, NewContract, NewStructure)): ins.arguments = call_data call_data = [] return result @@ -202,7 +199,7 @@ def remove_unused(result): # and reference that are written for ins in result: to_keep += [str(x) for x in ins.read] - if isinstance(ins, OperationWithLValue): + if isinstance(ins, Assignment): if isinstance(ins.lvalue, ReferenceVariable): to_keep += [str(ins.lvalue)] @@ -278,6 +275,7 @@ def extract_tmp_call(ins): msgcall = HighLevelCall(ins.ori.variable_left, ins.ori.variable_right, ins.nbr_arguments, ins.lvalue, ins.type_call) msgcall.call_id = ins.call_id return msgcall + if isinstance(ins.ori, TmpCall): r = extract_tmp_call(ins.ori) return r @@ -293,13 +291,17 @@ def extract_tmp_call(ins): return NewElementaryType(ins.ori.type, ins.lvalue) if isinstance(ins.ori, TmpNewContract): - return NewContract(Constant(ins.ori.contract_name), ins.lvalue) + op = NewContract(Constant(ins.ori.contract_name), ins.lvalue) + op.call_id = ins.call_id + return op if isinstance(ins.ori, TmpNewArray): return NewArray(ins.ori.depth, ins.ori.array_type, ins.lvalue) if isinstance(ins.called, Structure): - return NewStructure(ins.called, ins.lvalue) + op = NewStructure(ins.called, ins.lvalue) + op.call_id = ins.call_id + return op if isinstance(ins.called, Event): return EventCall(ins.called.name) diff --git a/slither/slithir/operations/new_contract.py b/slither/slithir/operations/new_contract.py index fb24553f2..2299351ef 100644 --- a/slither/slithir/operations/new_contract.py +++ b/slither/slithir/operations/new_contract.py @@ -1,11 +1,9 @@ -from slither.slithir.operations.call import Call -from slither.slithir.operations.lvalue import OperationWithLValue - -from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue - from slither.core.declarations.contract import Contract +from slither.slithir.operations import Call, OperationWithLValue +from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue from slither.slithir.variables.constant import Constant + class NewContract(Call, OperationWithLValue): def __init__(self, contract_name, lvalue): @@ -15,14 +13,36 @@ class NewContract(Call, OperationWithLValue): self._contract_name = contract_name # todo create analyze to add the contract instance self._lvalue = lvalue + self._callid = None # only used if gas/value != 0 + self._call_value = None + @property + def call_value(self): + return self._call_value + + @call_value.setter + def call_value(self, v): + self._call_value = v + + @property + def call_id(self): + return self._callid + + @call_id.setter + def call_id(self, c): + self._callid = c + @property def contract_name(self): return self._contract_name + @property def read(self): return list(self.arguments) def __str__(self): + value = '' + if self.call_value: + value = 'value:{}'.format(self.call_value) args = [str(a) for a in self.arguments] - return '{} = new {}({})'.format(self.lvalue, self.contract_name, ','.join(args)) + return '{} = new {}({}) {}'.format(self.lvalue, self.contract_name, ','.join(args), value) diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 8c7f715dc..88a8d713e 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -5,7 +5,8 @@ from slither.core.expressions import (AssignmentOperationType, from slither.core.solidity_types.array_type import ArrayType from slither.slithir.operations import (Assignment, Binary, BinaryType, Delete, Index, InitArray, InternalCall, Member, - TypeConversion, Unary, Unpack) + TypeConversion, Unary, Unpack, NewContract, + NewStructure, NewArray) from slither.slithir.tmp_operations.argument import Argument from slither.slithir.tmp_operations.tmp_call import TmpCall from slither.slithir.tmp_operations.tmp_new_array import TmpNewArray From 64b037ba81972250b668cf63d818b89c72e97da8 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 7 Oct 2018 21:02:42 -0700 Subject: [PATCH 119/308] Slithir: fix incorrect conversion --- slither/slithir/convert.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 18c5e8095..c12e80821 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -4,12 +4,12 @@ from slither.core.expressions import Identifier, Literal from slither.core.solidity_types.elementary_type import ElementaryType from slither.core.variables.variable import Variable from slither.slithir.operations import (Assignment, Call, Condition, EventCall, - HighLevelCall, InitArray, LibraryCall, - LowLevelCall, Member, NewArray, - NewContract, NewElementaryType, - NewStructure, OperationWithLValue, - Push, Return, Send, SolidityCall, - Transfer) + HighLevelCall, Index, InitArray, + LibraryCall, LowLevelCall, Member, + NewArray, NewContract, + NewElementaryType, NewStructure, + OperationWithLValue, Push, Return, + Send, SolidityCall, Transfer) from slither.slithir.tmp_operations.argument import Argument, ArgumentType from slither.slithir.tmp_operations.tmp_call import TmpCall from slither.slithir.tmp_operations.tmp_new_array import TmpNewArray @@ -61,14 +61,16 @@ def transform_calls(result): # Replace call to value, gas to an argument of the real call for idx in range(len(result)): ins = result[idx] - if is_value(ins): + # value can be shadowed, so we check that the prev ins + # is an Argument + if is_value(ins) and isinstance(result[idx-1], Argument): was_changed = True result[idx-1].set_type(ArgumentType.VALUE) result[idx-1].call_id = ins.ori.variable_left.name calls.append(ins.ori.variable_left) to_remove.append(ins) variable_to_replace[ins.lvalue.name] = ins.ori.variable_left - elif is_gas(ins): + elif is_gas(ins) and isinstance(result[idx-1], Argument): was_changed = True result[idx-1].set_type(ArgumentType.GAS) result[idx-1].call_id = ins.ori.variable_left.name @@ -199,7 +201,7 @@ def remove_unused(result): # and reference that are written for ins in result: to_keep += [str(x) for x in ins.read] - if isinstance(ins, Assignment): + if isinstance(ins, OperationWithLValue) and not isinstance(ins, (Index, Member)): if isinstance(ins.lvalue, ReferenceVariable): to_keep += [str(ins.lvalue)] @@ -233,8 +235,8 @@ def replace_calls(result): for idx in range(len(result)): ins = result[idx] if isinstance(ins, HighLevelCall): - if ins.function_name == 'push': - assert len(ins.arguments) == 1 + # TODO better handle collision with function named push + if ins.function_name == 'push' and len(ins.arguments) == 1: if isinstance(ins.arguments[0], list): val = TemporaryVariable() operation = InitArray(ins.arguments[0], val) From 759937cee576327f3203d77d392b2b834684229c Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 7 Oct 2018 21:23:33 -0700 Subject: [PATCH 120/308] Transfrom this.balance to SolidityFunction --- slither/core/declarations/solidity_variables.py | 6 +++++- slither/slithir/convert.py | 7 ++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py index 85eaec4ae..99e5d0df4 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -38,7 +38,11 @@ SOLIDITY_FUNCTIONS = {"gasleft()":['uint256'], "log1(bytes32,bytes32)":[], "log2(bytes32,bytes32,bytes32)":[], "log3(bytes32,bytes32,bytes32,bytes32)":[], - "blockhash(uint256)":['bytes32']} + "blockhash(uint256)":['bytes32'], + # this balance needs a special handling + # as it is first recognized as a SolidityVariableComposed + # and converted to a SolidityFunction by SlithIR + "this.balance()":['uint256']} def solidity_function_signature(name): """ diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index c12e80821..89be68d4b 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -282,9 +282,10 @@ def extract_tmp_call(ins): r = extract_tmp_call(ins.ori) return r if isinstance(ins.called, SolidityVariableComposed): - # block.blockhash is the only variable composed which is a call - assert str(ins.called) == 'block.blockhash' - ins.called = SolidityFunction('blockhash(uint256)') + if str(ins.called) == 'block.blockhash': + ins.called = SolidityFunction('blockhash(uint256)') + elif str(ins.called) == 'this.balance': + ins.called = SolidityFunction('this.balance()') if isinstance(ins.called, SolidityFunction): return SolidityCall(ins.called, ins.nbr_arguments, ins.lvalue, ins.type_call) From 25beeb584ab82df43ba36677c72ec4c12a2f1ac8 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 9 Oct 2018 11:42:29 -0700 Subject: [PATCH 121/308] WIP: new re-entrnacy heuristic, based on the IR transfer/send conversion done without checking destination type (name collision possible) --- slither/detectors/reentrancy/reentrancy.py | 130 +++++++++++++-------- slither/slithir/convert.py | 32 ++--- 2 files changed, 97 insertions(+), 65 deletions(-) diff --git a/slither/detectors/reentrancy/reentrancy.py b/slither/detectors/reentrancy/reentrancy.py index 0eacca53b..d4f71c933 100644 --- a/slither/detectors/reentrancy/reentrancy.py +++ b/slither/detectors/reentrancy/reentrancy.py @@ -11,7 +11,8 @@ from slither.core.expressions import UnaryOperation, UnaryOperationType from slither.detectors.abstract_detector import (AbstractDetector, DetectorClassification) from slither.visitors.expression.export_values import ExportValues - +from slither.slithir.operations import (HighLevelCall, LowLevelCall, + Send, Transfer) class Reentrancy(AbstractDetector): ARGUMENT = 'reentrancy' @@ -19,21 +20,37 @@ class Reentrancy(AbstractDetector): IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.MEDIUM + key = 'REENTRANCY' + @staticmethod - def _is_legit_call(call_name): + def _can_callback(node): """ - Detect if the call seems legit - Slither has no taint analysis, and do not make yet the link - to the libraries. As a result, we look for any low-level calls + Detect if the node contains a call that can + be used to re-entrance + + Consider as valid target: + - low level call + - high level call + + Do not consider Send/Transfer as there is not enough gas """ - call_str = str(call_name) - return not ('.call(' in call_str or - '.call.' in call_str or - 'delegatecall' in call_str or - 'callcode' in call_str or - '.value(' in call_str) + for ir in node.irs: + if isinstance(ir, LowLevelCall): + return True + if isinstance(ir, HighLevelCall): + return True + return False - key = 'REENTRANCY' + @staticmethod + def _can_send_eth(node): + """ + Detect if the node can send eth + """ + for ir in node.irs: + if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)): + if ir.call_value: + return True + return False def _check_on_call_returned(self, node): """ @@ -46,12 +63,8 @@ class Reentrancy(AbstractDetector): This will work only on naive implementation """ - if node.type == NodeType.IF: - external_calls = node.external_calls - if any(not self._is_legit_call(call) for call in external_calls): - return isinstance(node.expression, UnaryOperation)\ - and node.expression.type == UnaryOperationType.BANG - return False + return isinstance(node.expression, UnaryOperation)\ + and node.expression.type == UnaryOperationType.BANG def _explore(self, node, visited): """ @@ -70,30 +83,39 @@ class Reentrancy(AbstractDetector): visited = visited + [node] # First we add the external calls executed in previous nodes - node.context[self.key] = [] - - fathers_context = [] + # send_eth returns the list of calls sending value + # calls returns the list of calls that can callback + # read returns the variable read + fathers_context = {'send_eth':[], 'calls':[], 'read':[]} for father in node.fathers: if self.key in father.context: - fathers_context += father.context[self.key] + fathers_context['send_eth'] += father.context[self.key]['send_eth'] + fathers_context['calls'] += father.context[self.key]['calls'] + fathers_context['read'] += father.context[self.key]['read'] # Exclude path that dont bring further information - if node in self.visited_all_paths: - if all(f_c in self.visited_all_paths[node] for f_c in fathers_context): - return + if self.key in self.visited_all_paths: + if all(f_c['calls'] in self.visited_all_paths[node]['calls'] for f_c in fathers_context): + if all(f_c['send_eth'] in self.visited_all_paths[node]['send_eth'] for f_c in fathers_context): + if all(f_c['read'] in self.visited_all_paths[node]['read'] for f_c in fathers_context): + return else: - self.visited_all_paths[node] = [] + self.visited_all_paths[node] = {'send_eth':[], 'calls':[], 'read':[]} - self.visited_all_paths[node] = list(set(self.visited_all_paths[node] + fathers_context)) + self.visited_all_paths[node]['send_eth'] = list(set(self.visited_all_paths[node]['send_eth'] + fathers_context['send_eth'])) + self.visited_all_paths[node]['calls'] = list(set(self.visited_all_paths[node]['calls'] + fathers_context['calls'])) + self.visited_all_paths[node]['read'] = list(set(self.visited_all_paths[node]['read'] + fathers_context['read'])) node.context[self.key] = fathers_context - # Get all the new external calls - for call in node.external_calls: - if self._is_legit_call(call): - continue - node.context[self.key] += [str(call)] + contains_call = False + if self._can_callback(node): + node.context[self.key]['calls'] = list(set(node.context[self.key]['calls'] + [node])) + contains_call = True + if self._can_send_eth(node): + node.context[self.key]['send_eth'] = list(set(node.context[self.key]['send_eth'] + [node])) + # All the state variables written state_vars_written = node.state_variables_written @@ -103,22 +125,27 @@ class Reentrancy(AbstractDetector): if isinstance(internal_call, Function): state_vars_written += internal_call.all_state_variables_written() + read_then_written = [v for v in state_vars_written if v in node.context[self.key]['read']] - # If a state variables is written, and there was an external call + node.context[self.key]['read'] = list(set(node.context[self.key]['read'] + node.state_variables_read)) + # If a state variables was read and is then written, there is a dangerous call and + # ether were sent # We found a potential re-entrancy bug - if state_vars_written and node.context[self.key]: - # we save the result wth (contract, func, calls) as key + if (read_then_written and + node.context[self.key]['calls'] and + node.context[self.key]['send_eth']): # calls are ordered finding_key = (node.function.contract.name, node.function.full_name, - tuple(set(node.context[self.key]))) - finding_vars = state_vars_written + tuple(set(node.context[self.key]['calls'])), + tuple(set(node.context[self.key]['send_eth']))) + finding_vars = read_then_written if finding_key not in self.result: self.result[finding_key] = [] self.result[finding_key] = list(set(self.result[finding_key] + finding_vars)) sons = node.sons - if self._check_on_call_returned(node): + if contains_call and self._check_on_call_returned(node): sons = sons[1:] for son in sons: @@ -148,27 +175,32 @@ class Reentrancy(AbstractDetector): results = [] - for (contract, func, calls), varsWritten in self.result.items(): + for (contract, func, calls, send_eth), varsWritten in self.result.items(): varsWritten_str = list(set([str(x) for x in list(varsWritten)])) - calls = list(set([str(x) for x in list(calls)])) - info = 'Reentrancy in %s, Contract: %s, ' % (self.filename, contract) + \ - 'Func: %s, Call: %s, ' % (func, calls) + \ - 'Vars Written:%s' % (str(varsWritten_str)) + calls_str = list(set([str(x.expression) for x in list(calls)])) + send_eth_str = list(set([str(x.expression) for x in list(send_eth)])) + + if calls == send_eth: + call_info = 'Call: {},'.format(calls_str) + else: + call_info = 'Call: {}, Ether sent: {},'.format(calls_str, send_eth_str) + info = 'Reentrancy in {}, Contract: {}, '.format(self.filename, contract) + \ + 'Func: {}, '.format(func) + \ + '{}'.format(call_info) + \ + 'Vars Written: {}'.format(str(varsWritten_str)) self.log(info) source = [v.source_mapping for v in varsWritten] - # The source mapping could be kept during the analysis - # So we sould not have to re-iterate over the contracts and functions - contract_instance = self.slither.get_contract_from_name(contract) - function_instance = contract_instance.get_function_from_signature(func) - source += [function_instance.source_mapping] + source += [node.source_mapping for node in calls] + source += [node.source_mapping for node in send_eth] results.append({'vuln': 'Reentrancy', 'sourceMapping': source, 'filename': self.filename, 'contract': contract, 'function_name': func, - 'call': calls, + 'calls': calls_str, + 'send_eth': send_eth_str, 'varsWritten': varsWritten_str}) return results diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 89be68d4b..e2d3bb897 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -229,6 +229,8 @@ def replace_calls(result): return False if not isinstance(v.type, ElementaryType): return False + if isinstance(v, ReferenceVariable): + return True return v.type.type == 'address' while reset: reset = False @@ -246,22 +248,20 @@ def replace_calls(result): break else: result[idx] = Push(ins.destination, ins.arguments[0]) - if is_address(ins.destination): - if ins.function_name == 'transfer': - assert len(ins.arguments) == 1 - result[idx] = Transfer(ins.destination, ins.arguments[0]) - elif ins.function_name == 'send': - assert len(ins.arguments) == 1 - result[idx] = Send(ins.destination, ins.arguments[0], ins.lvalue) - elif ins.function_name in ['call', 'delegatecall', 'callcode']: - # TODO: handle name collision - result[idx] = LowLevelCall(ins.destination, - ins.function_name, - ins.nbr_arguments, - ins.lvalue, - ins.type_call) - result[idx].call_gas = ins.call_gas - result[idx].call_value = ins.call_value +# if is_address(ins.destination): + if ins.function_name == 'transfer' and len(ins.arguments) == 1: + result[idx] = Transfer(ins.destination, ins.arguments[0]) + elif ins.function_name == 'send' and len(ins.arguments) == 1: + result[idx] = Send(ins.destination, ins.arguments[0], ins.lvalue) + elif ins.function_name in ['call', 'delegatecall', 'callcode']: + # TODO: handle name collision + result[idx] = LowLevelCall(ins.destination, + ins.function_name, + ins.nbr_arguments, + ins.lvalue, + ins.type_call) + result[idx].call_gas = ins.call_gas + result[idx].call_value = ins.call_value # other case are library on address return result From a56854887b72d4f21d8abe2d86647a0ba059805b Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 9 Oct 2018 11:59:17 -0700 Subject: [PATCH 122/308] Add testcase --- tests/reentrancy_indirect.sol | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 tests/reentrancy_indirect.sol diff --git a/tests/reentrancy_indirect.sol b/tests/reentrancy_indirect.sol new file mode 100644 index 000000000..f70efe100 --- /dev/null +++ b/tests/reentrancy_indirect.sol @@ -0,0 +1,31 @@ +pragma solidity ^0.4.24; + +contract Token{ + function transfer(address to, uint value) returns(bool); + function transferFrom(address from, address to, uint value) returns(bool); +} + +contract Reentrancy { + + mapping(address => mapping(address => uint)) eth_deposed; + mapping(address => mapping(address => uint)) token_deposed; + + function deposit_eth(address token) payable{ + eth_deposed[token][msg.sender] += msg.value; + } + + function deposit_token(address token, uint value){ + token_deposed[token][msg.sender] += value; + require(Token(token).transferFrom(msg.sender, address(this), value)); + } + + function withdraw(address token){ + msg.sender.transfer(eth_deposed[token][msg.sender]); + require(Token(token).transfer(msg.sender, eth_deposed[token][msg.sender])); + + eth_deposed[token][msg.sender] = 0; + token_deposed[token][msg.sender] = 0; + + } + +} From 5a27736fce800070d526701b52025947e3596545 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 9 Oct 2018 12:00:56 -0700 Subject: [PATCH 123/308] Typo --- tests/reentrancy_indirect.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/reentrancy_indirect.sol b/tests/reentrancy_indirect.sol index f70efe100..73dd3db00 100644 --- a/tests/reentrancy_indirect.sol +++ b/tests/reentrancy_indirect.sol @@ -21,7 +21,7 @@ contract Reentrancy { function withdraw(address token){ msg.sender.transfer(eth_deposed[token][msg.sender]); - require(Token(token).transfer(msg.sender, eth_deposed[token][msg.sender])); + require(Token(token).transfer(msg.sender, token_deposed[token][msg.sender])); eth_deposed[token][msg.sender] = 0; token_deposed[token][msg.sender] = 0; From 31d5cf27cbc30c3b31a4b7995eda93d9538c88b1 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 10 Oct 2018 04:41:17 -0700 Subject: [PATCH 124/308] Allow index operation on msg.data --- slither/slithir/operations/index.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/slither/slithir/operations/index.py b/slither/slithir/operations/index.py index f63651fe8..5b8faeb44 100644 --- a/slither/slithir/operations/index.py +++ b/slither/slithir/operations/index.py @@ -1,5 +1,6 @@ from slither.slithir.operations.lvalue import OperationWithLValue from slither.core.variables.variable import Variable +from slither.core.declarations import SolidityVariableComposed from slither.slithir.variables.reference import ReferenceVariable from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue @@ -9,7 +10,7 @@ class Index(OperationWithLValue): def __init__(self, result, left_variable, right_variable, index_type): super(Index, self).__init__() - assert is_valid_lvalue(left_variable) + assert is_valid_lvalue(left_variable) or left_variable == SolidityVariableComposed('msg.data') assert is_valid_rvalue(right_variable) assert isinstance(result, ReferenceVariable) self._variables = [left_variable, right_variable] From d18ef50f6026c62356c5d86f561e020994764d0e Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 10 Oct 2018 17:56:54 +0100 Subject: [PATCH 125/308] Reentrancy: dont consider libraries as callback --- slither/detectors/reentrancy/reentrancy.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/slither/detectors/reentrancy/reentrancy.py b/slither/detectors/reentrancy/reentrancy.py index d4f71c933..2f8a55862 100644 --- a/slither/detectors/reentrancy/reentrancy.py +++ b/slither/detectors/reentrancy/reentrancy.py @@ -12,6 +12,7 @@ from slither.detectors.abstract_detector import (AbstractDetector, DetectorClassification) from slither.visitors.expression.export_values import ExportValues from slither.slithir.operations import (HighLevelCall, LowLevelCall, + LibraryCall, Send, Transfer) class Reentrancy(AbstractDetector): @@ -37,7 +38,7 @@ class Reentrancy(AbstractDetector): for ir in node.irs: if isinstance(ir, LowLevelCall): return True - if isinstance(ir, HighLevelCall): + if isinstance(ir, HighLevelCall) and not isinstance(ir, LibraryCall): return True return False From 53d9a9de71daa2f7c07f4a5347616fbec03e191c Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 12 Oct 2018 09:28:37 +0100 Subject: [PATCH 126/308] SlithIR: Add support for type (WIP) --- slither/core/cfg/node.py | 4 + slither/core/declarations/function.py | 12 + .../core/declarations/solidity_variables.py | 53 ++-- slither/core/solidity_types/__init__.py | 5 + .../core/solidity_types/user_defined_type.py | 11 +- slither/slithir/convert.py | 246 +++++++++++++----- slither/slithir/operations/assignment.py | 1 + slither/slithir/operations/binary.py | 24 +- slither/slithir/operations/high_level_call.py | 27 +- slither/slithir/operations/index.py | 2 +- slither/slithir/operations/init_array.py | 2 +- slither/slithir/operations/internal_call.py | 12 +- slither/slithir/operations/library_call.py | 12 +- slither/slithir/operations/low_level_call.py | 9 +- slither/slithir/operations/lvalue.py | 4 + slither/slithir/operations/member.py | 2 +- slither/slithir/operations/new_structure.py | 12 +- slither/slithir/operations/transfer.py | 4 - slither/slithir/operations/unary.py | 6 +- slither/solc_parsing/declarations/function.py | 2 + slither/solc_parsing/declarations/modifier.py | 2 - slither/solc_parsing/slitherSolc.py | 8 + .../visitors/slithir/expression_to_slithir.py | 15 +- 23 files changed, 356 insertions(+), 119 deletions(-) diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index d174a3c71..79e935e74 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -114,6 +114,10 @@ class Node(SourceMapping, ChildFunction): self._expression_vars_read = [] self._expression_calls = [] + @property + def slither(self): + return self.function.slither + @property def node_id(self): """Unique node id.""" diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index a4734d2c1..6e71ddfc7 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -52,6 +52,18 @@ class Function(ChildContract, SourceMapping): self._modifiers = [] self._payable = False + + @property + def return_type(self): + """ + Return the list of return type + If no return, return None + """ + returns = self.returns + if returns: + return [r.type for r in returns] + return None + @property def name(self): """ diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py index 99e5d0df4..1ad4a90ed 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -1,22 +1,29 @@ # https://solidity.readthedocs.io/en/v0.4.24/units-and-global-variables.html from slither.core.context.context import Context - -SOLIDITY_VARIABLES = ["block", "msg", "now", "tx", "this", "super", 'abi'] - -SOLIDITY_VARIABLES_COMPOSED = ["block.coinbase", - "block.difficulty", - "block.gaslimit", - "block.number", - "block.timestamp", - "block.blockhash", # alias for blockhash. It's a call - "msg.data", - "msg.gas", - "msg.sender", - "msg.sig", - "msg.value", - "tx.gasprice", - "tx.origin", - "this.balance"] +from slither.core.solidity_types import ElementaryType + +SOLIDITY_VARIABLES = {"now":'uint256', + "this":'address', + 'abi':'', + 'msg':'', + 'tx':'', + 'block':'', + 'super':''} + +SOLIDITY_VARIABLES_COMPOSED = {"block.coinbase":"address", + "block.difficulty":"uint256", + "block.gaslimit":"uint256", + "block.number":"uint256", + "block.timestamp":"uint256", + "block.blockhash":"uint256", # alias for blockhash. It's a call + "msg.data":"bytes", + "msg.gas":"uint256", + "msg.sender":"address", + "msg.sig":"bytes4", + "msg.value":"uint256", + "tx.gasprice":"uint256", + "tx.origin":"address", + "this.balance":"uint256"} SOLIDITY_FUNCTIONS = {"gasleft()":['uint256'], @@ -71,6 +78,10 @@ class SolidityVariable(Context): def name(self): return self._name + @property + def type(self): + return ElementaryType(SOLIDITY_VARIABLES[self.name]) + def __str__(self): return self._name @@ -91,6 +102,10 @@ class SolidityVariableComposed(SolidityVariable): def name(self): return self._name + @property + def type(self): + return SOLIDITY_VARIABLES_COMPOSED[self.name] + def __str__(self): return self._name @@ -115,6 +130,10 @@ class SolidityFunction: def full_name(self): return self.name + @property + def return_type(self): + return [ElementaryType(x) for x in SOLIDITY_FUNCTIONS[self.name]] + def __str__(self): return self._name diff --git a/slither/core/solidity_types/__init__.py b/slither/core/solidity_types/__init__.py index e69de29bb..b7ec244bf 100644 --- a/slither/core/solidity_types/__init__.py +++ b/slither/core/solidity_types/__init__.py @@ -0,0 +1,5 @@ +from .array_type import ArrayType +from .elementary_type import ElementaryType +from .function_type import FunctionType +from .mapping_type import MappingType +from .user_defined_type import UserDefinedType diff --git a/slither/core/solidity_types/user_defined_type.py b/slither/core/solidity_types/user_defined_type.py index d33fae1fe..bfabb3c85 100644 --- a/slither/core/solidity_types/user_defined_type.py +++ b/slither/core/solidity_types/user_defined_type.py @@ -1,13 +1,13 @@ from slither.core.solidity_types.type import Type -from slither.core.declarations.structure import Structure -from slither.core.declarations.enum import Enum -from slither.core.declarations.contract import Contract - class UserDefinedType(Type): def __init__(self, t): + from slither.core.declarations.structure import Structure + from slither.core.declarations.enum import Enum + from slither.core.declarations.contract import Contract + assert isinstance(t, (Contract, Enum, Structure)) super(UserDefinedType, self).__init__() self._type = t @@ -17,6 +17,9 @@ class UserDefinedType(Type): return self._type def __str__(self): + from slither.core.declarations.structure import Structure + from slither.core.declarations.enum import Enum + if isinstance(self.type, (Enum, Structure)): return str(self.type.contract)+'.'+str(self.type.name) return str(self.type.name) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 89be68d4b..c64cf805a 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -1,15 +1,20 @@ -from slither.core.declarations import (Contract, Event, SolidityFunction, - SolidityVariableComposed, Structure) +import logging + +from slither.core.declarations import (Contract, Enum, Event, SolidityFunction, + Structure, SolidityVariableComposed, Function) from slither.core.expressions import Identifier, Literal -from slither.core.solidity_types.elementary_type import ElementaryType +from slither.core.solidity_types import ElementaryType, UserDefinedType, MappingType, ArrayType from slither.core.variables.variable import Variable -from slither.slithir.operations import (Assignment, Call, Condition, EventCall, +from slither.slithir.operations import (Assignment, Binary, BinaryType, Call, + Condition, Delete, EventCall, HighLevelCall, Index, InitArray, - LibraryCall, LowLevelCall, Member, - NewArray, NewContract, - NewElementaryType, NewStructure, - OperationWithLValue, Push, Return, - Send, SolidityCall, Transfer) + InternalCall, LibraryCall, + LowLevelCall, Member, NewArray, + NewContract, NewElementaryType, + NewStructure, OperationWithLValue, + Push, Return, Send, SolidityCall, + Transfer, TypeConversion, Unary, + Unpack) from slither.slithir.tmp_operations.argument import Argument, ArgumentType from slither.slithir.tmp_operations.tmp_call import TmpCall from slither.slithir.tmp_operations.tmp_new_array import TmpNewArray @@ -21,6 +26,7 @@ from slither.slithir.variables import (Constant, ReferenceVariable, TemporaryVariable, TupleVariable) from slither.visitors.slithir.expression_to_slithir import ExpressionToSlithIR +logger = logging.getLogger('ConvertToIR') def is_value(ins): if isinstance(ins, TmpCall): @@ -36,7 +42,7 @@ def is_gas(ins): return True return False -def transform_calls(result): +def integrate_value_gas(result): was_changed = True calls = [] @@ -102,31 +108,183 @@ def transform_calls(result): calls_d[str(call)] = idx idx = idx+1 + return result + +def propage_type_and_convert_call(result, node): + calls_value = {} + calls_gas = {} + + call_data = [] + for idx in range(len(result)): ins = result[idx] + if isinstance(ins, TmpCall): - r = extract_tmp_call(ins) - if r: - result[idx] = r + new_ins = extract_tmp_call(ins) + if new_ins: + ins = new_ins + result[idx] = ins + + if isinstance(ins, Argument): + if ins.get_type() in [ArgumentType.GAS]: + assert not ins.call_id in calls_gas + calls_gas[ins.call_id] = ins.argument + elif ins.get_type() in [ArgumentType.VALUE]: + assert not ins.call_id in calls_value + calls_value[ins.call_id] = ins.argument + else: + assert ins.get_type() == ArgumentType.CALL + call_data.append(ins.argument) + + if isinstance(ins, (HighLevelCall, NewContract)): + if ins.call_id in calls_value: + ins.call_value = calls_value[ins.call_id] + if ins.call_id in calls_gas: + ins.call_gas = calls_gas[ins.call_id] + + if isinstance(ins, (Call, NewContract, NewStructure)): + ins.arguments = call_data + propagate_types(ins, node) return result -def apply_ir_heuristics(result): +def propagate_types(ir, node): + # propagate the type + if isinstance(ir, OperationWithLValue): + if not ir.lvalue.type: + if isinstance(ir, Assignment): + ir.lvalue.set_type(ir.rvalue.type) + elif isinstance(ir, Binary): + if BinaryType.return_bool(ir.type): + ir.lvalue.set_type(ElementaryType('bool')) + else: + ir.lvalue.set_type(ir.left_variable.type) + elif isinstance(ir, Delete): + # nothing to propagate + pass + elif isinstance(ir, HighLevelCall): + t = ir.destination.type + # can be None due to temporary operation + if t: + if isinstance(t, UserDefinedType): + # UserdefinedType + t = t.type + if isinstance(t, Contract): + sig = '{}({})'.format(ir.function_name, + ','.join([str(x.type) for x in ir.arguments])) + contract = node.slither.get_contract_from_name(t.name) + func = contract.get_function_from_signature(sig) + if not func: + func = t.get_state_variable_from_name(ir.function_name) + else: + return_type = func.return_type + if not func and ir.function_name in ['call', 'delegatecall','codecall']: + return + if not func: + logger.error('Function not found {}'.format(sig)) + ir.function = func + if isinstance(func, Function): + t = func.return_type + else: + # otherwise its a variable (getter) + t = func.type + if t: + ir.lvalue.set_type(t) + else: + ir.lvalue = None + if isinstance(t, ElementaryType): + print(t.name) + # TODO here handle library call + # we can probably directly remove the ins, as alow level + # or a lib + if t.name == 'address': + ir.lvalue.set_type(ElementaryType('bool')) + elif isinstance(ir, Index): + if isinstance(ir.variable_left.type, MappingType): + ir.lvalue.set_type(ir.variable_left.type.type_to) + else: + assert isinstance(ir.variable_left.type, ArrayType) + ir.lvalue.set_type(ir.variable_left.type.type) + + elif isinstance(ir, InitArray): + length = len(ir.init_values) + t = ir.init_values[0].type + ir.lvalue.set_type(ArrayType(t, length)) + elif isinstance(ir, InternalCall): + return_type = ir.function.return_type + if return_type: + ir.lvalue.set_type(return_type) + else: + ir.lvalue = None + elif isinstance(ir, LowLevelCall): + # Call are not yet converted + # This should not happen + assert False + elif isinstance(ir, Member): + t = ir.variable_left.type + # can be None due to temporary operation + if t: + if isinstance(t, UserDefinedType): + # UserdefinedType + t = t.type + if isinstance(t, Enum): + elems = t.values + for elem in elems: + if elem == ir.variable_right: + ir.lvalue.set_type(elems[elem].type) + elif isinstance(t, Structure): + elems = t.elems + for elem in elems: + if elem == ir.variable_right: + ir.lvalue.set_type(elems[elem].type) + else: + assert isinstance(t, Contract) + elif isinstance(ir, NewArray): + ir.lvalue.set_type(ir.array_type) + elif isinstance(ir, NewContract): + contract = node.slither.get_contract_from_name(ir.contract_name) + ir.lvalue.set_type(UserDefinedType(contract)) + elif isinstance(ir, NewElementaryType): + ir.lvalue.set_type(ir.type) + elif isinstance(ir, NewStructure): + ir.lvalue.set_type(UserDefinedType(ir.structure)) + elif isinstance(ir, Push): + # No change required + pass + elif isinstance(ir, Send): + ir.lvalue.set_type(ElementaryType('bool')) + elif isinstance(ir, SolidityCall): + ir.lvalue.set_type(ir.function.return_type) + elif isinstance(ir, TypeConversion): + ir.lvalue.set_type(ir.type) + elif isinstance(ir, Unary): + ir.lvalue.set_type(ir.rvalue.type) + elif isinstance(ir, Unpack): + types = ir.tuple.type.type + idx = ir.index + t = types[idx] + ir.lvalue.set_type(t) + elif isinstance(ir, (Argument, TmpCall, TmpNewArray, TmpNewContract, TmpNewStructure, TmpNewElementaryType)): + # temporary operation; they will be removed + pass + else: + logger.error('Not handling {} during type propgation'.format(type(ir))) + exit(0) + +def apply_ir_heuristics(irs, node): """ Apply a set of heuristic to improve slithIR """ - result = transform_calls(result) + irs = integrate_value_gas(irs) - # Move the arguments operation to the call - result = merge_call_parameters(result) - # Remove temporary - result = remove_temporary(result) - result = replace_calls(result) + irs = propage_type_and_convert_call(irs, node) + irs = remove_temporary(irs) + irs = replace_calls(irs) + irs = remove_unused(irs) - result = remove_unused(result) - reset_variable_number(result) + reset_variable_number(irs) - return result + return irs def reset_variable_number(result): """ @@ -149,35 +307,6 @@ def reset_variable_number(result): tuple_variables[idx].index = idx -def merge_call_parameters(result): - - calls_value = {} - calls_gas = {} - - call_data = [] - - for ins in result: - if isinstance(ins, Argument): - if ins.get_type() in [ArgumentType.GAS]: - assert not ins.call_id in calls_gas - calls_gas[ins.call_id] = ins.argument - elif ins.get_type() in [ArgumentType.VALUE]: - assert not ins.call_id in calls_value - calls_value[ins.call_id] = ins.argument - else: - assert ins.get_type() == ArgumentType.CALL - call_data.append(ins.argument) - - if isinstance(ins, (HighLevelCall, NewContract)): - if ins.call_id in calls_value: - ins.call_value = calls_value[ins.call_id] - if ins.call_id in calls_gas: - ins.call_gas = calls_gas[ins.call_id] - - if isinstance(ins, (Call, NewContract, NewStructure)): - ins.arguments = call_data - call_data = [] - return result def remove_temporary(result): result = [ins for ins in result if not isinstance(ins, (Argument, @@ -262,6 +391,7 @@ def replace_calls(result): ins.type_call) result[idx].call_gas = ins.call_gas result[idx].call_value = ins.call_value + result[idx].arguments = ins.arguments # other case are library on address return result @@ -269,11 +399,11 @@ def replace_calls(result): def extract_tmp_call(ins): assert isinstance(ins, TmpCall) if isinstance(ins.ori, Member): - if isinstance(ins.ori.variable_left, Contract): - libcall = LibraryCall(ins.ori.variable_left, ins.ori.variable_right, ins.nbr_arguments, ins.lvalue, ins.type_call) - libcall.call_id = ins.call_id - return libcall - else: +# if isinstance(ins.ori.variable_left, Contract): +# libcall = LibraryCall(ins.ori.variable_left, ins.ori.variable_right, ins.nbr_arguments, ins.lvalue, ins.type_call) +# libcall.call_id = ins.call_id +# return libcall +# else: msgcall = HighLevelCall(ins.ori.variable_left, ins.ori.variable_right, ins.nbr_arguments, ins.lvalue, ins.type_call) msgcall.call_id = ins.call_id return msgcall @@ -347,7 +477,7 @@ def convert_expression(expression, node): visitor = ExpressionToSlithIR(expression) result = visitor.result() - result = apply_ir_heuristics(result) + result = apply_ir_heuristics(result, node) result = convert_libs(result, node.function.contract) diff --git a/slither/slithir/operations/assignment.py b/slither/slithir/operations/assignment.py index 6a4eb7d04..19ba53480 100644 --- a/slither/slithir/operations/assignment.py +++ b/slither/slithir/operations/assignment.py @@ -91,6 +91,7 @@ class Assignment(OperationWithLValue): self._rvalue = right_variable self._type = variable_type self._variable_return_type = variable_return_type + #left_variable.set_type(right_variable.type) @property def variables(self): diff --git a/slither/slithir/operations/binary.py b/slither/slithir/operations/binary.py index 0da8ad60e..8abe46351 100644 --- a/slither/slithir/operations/binary.py +++ b/slither/slithir/operations/binary.py @@ -26,6 +26,16 @@ class BinaryType(object): ANDAND = 17 # && OROR = 18 # || + @staticmethod + def return_bool(operation_type): + return operation_type in [BinaryType.OROR, + BinaryType.ANDAND, + BinaryType.LESS, + BinaryType.GREATER, + BinaryType.LESS_EQUAL, + BinaryType.GREATER_EQUAL, + BinaryType.EQUAL, + BinaryType.NOT_EQUAL] @staticmethod def get_type(operation_type): @@ -124,6 +134,10 @@ class Binary(OperationWithLValue): self._variables = [left_variable, right_variable] self._type = operation_type self._lvalue = result + if BinaryType.return_bool(operation_type): + result.set_type('bool') + else: + result.set_type(left_variable.type) @property def read(self): @@ -141,9 +155,17 @@ class Binary(OperationWithLValue): def variable_right(self): return self._variables[1] + @property + def type(self): + return self._type + @property def type_str(self): return BinaryType.str(self._type) def __str__(self): - return str(self.lvalue)+ ' = ' + str(self.variable_left) + ' ' + self.type_str + ' ' + str(self.variable_right) + return '{}({}) = {} {} {}'.format(str(self.lvalue), + str(self.lvalue.type), + self.variable_left, + self.type_str, + self.variable_right) diff --git a/slither/slithir/operations/high_level_call.py b/slither/slithir/operations/high_level_call.py index a77bb785b..a37e532eb 100644 --- a/slither/slithir/operations/high_level_call.py +++ b/slither/slithir/operations/high_level_call.py @@ -22,6 +22,7 @@ class HighLevelCall(Call, OperationWithLValue): self._type_call = type_call self._lvalue = result self._callid = None # only used if gas/value != 0 + self._function_instance = None self._call_value = None self._call_gas = None @@ -67,6 +68,14 @@ class HighLevelCall(Call, OperationWithLValue): def function_name(self): return self._function_name + @property + def function(self): + return self._function_instance + + @function.setter + def function(self, function): + self._function_instance = function + @property def nbr_arguments(self): return self._nbr_arguments @@ -85,9 +94,17 @@ class HighLevelCall(Call, OperationWithLValue): arguments = [] if self.arguments: arguments = self.arguments - return str(self.lvalue) +' = HIGH_LEVEL_CALL dest:{} function:{} arguments:{} {} {}'.format(self.destination, self.function_name, [str(x) for x in arguments], value, gas) -# if self.call_id: -# call_id = '(id ({}))'.format(self.call_id) -# return str(self.lvalue) +' = EXTERNALCALL dest:{} function:{} (#arg {}) {}'.format(self.destination, self.function_name, self.nbr_arguments) - + txt = '{}HIGH_LEVEL_CALL, dest:{}({}), function:{}, arguments:{} {} {}' + if not self.lvalue: + lvalue = '' + else: + print(self.lvalue) + lvalue = '{}({}) = '.format(self.lvalue, ','.join(str(x) for x in self.lvalue.type)) + return txt.format(lvalue, + self.destination, + self.destination.type, + self.function_name, + [str(x) for x in arguments], + value, + gas) diff --git a/slither/slithir/operations/index.py b/slither/slithir/operations/index.py index 5b8faeb44..ac3140f46 100644 --- a/slither/slithir/operations/index.py +++ b/slither/slithir/operations/index.py @@ -34,4 +34,4 @@ class Index(OperationWithLValue): return self._variables[1] def __str__(self): - return "{} -> {}[{}]".format(self.lvalue, self.variable_left, self.variable_right) + return "{}({}) -> {}[{}]".format(self.lvalue, self.lvalue.type, self.variable_left, self.variable_right) diff --git a/slither/slithir/operations/init_array.py b/slither/slithir/operations/init_array.py index 8df153dd5..3530e73e0 100644 --- a/slither/slithir/operations/init_array.py +++ b/slither/slithir/operations/init_array.py @@ -19,4 +19,4 @@ class InitArray(OperationWithLValue): return list(self._init_values) def __str__(self): - return "{} = {}".format(self.lvalue, [str(x) for x in self.init_values]) + return "{}({}) = {}".format(self.lvalue, self.lvalue.type, [str(x) for x in self.init_values]) diff --git a/slither/slithir/operations/internal_call.py b/slither/slithir/operations/internal_call.py index 5cf50903c..4a7edb8e3 100644 --- a/slither/slithir/operations/internal_call.py +++ b/slither/slithir/operations/internal_call.py @@ -32,7 +32,13 @@ class InternalCall(Call, OperationWithLValue): def __str__(self): args = [str(a) for a in self.arguments] - return str(self.lvalue) +' = INTERNAL_CALL {}.{} ({}) '.format(self.function.contract.name, self.function.full_name, ','.join(args)) - # return str(self.lvalue) +' = INTERNALCALL {} (arg {})'.format(self.function, - # self.nbr_arguments) + if not self.lvalue: + lvalue = '' + else: + lvalue = '{}({}) = '.format(self.lvalue, ','.join(str(x) for x in self.lvalue.type)) + txt = '{}INTERNAL_CALL, {}.{}({})' + return txt.format(lvalue, + self.function.contract.name, + self.function.full_name, + ','.join(args)) diff --git a/slither/slithir/operations/library_call.py b/slither/slithir/operations/library_call.py index 7bf0988f0..c1b3821eb 100644 --- a/slither/slithir/operations/library_call.py +++ b/slither/slithir/operations/library_call.py @@ -16,9 +16,13 @@ class LibraryCall(HighLevelCall): arguments = [] if self.arguments: arguments = self.arguments - return str(self.lvalue) +' = LIBRARY_CALL dest:{} function:{} arguments:{} {}'.format(self.destination, self.function_name, [str(x) for x in arguments], gas) -# if self.call_id: -# call_id = '(id ({}))'.format(self.call_id) -# return str(self.lvalue) +' = EXTERNALCALL dest:{} function:{} (#arg {}) {}'.format(self.destination, self.function_name, self.nbr_arguments) + txt = '{}({}) = LIBRARY_CALL, dest:{}, function:{}, arguments:{} {}' + return txt.format(self.lvalue, + self.lvalue.type, + self.destination, + self.function_name, + [str(x) for x in arguments], + gas) + diff --git a/slither/slithir/operations/low_level_call.py b/slither/slithir/operations/low_level_call.py index f883db279..4197e969a 100644 --- a/slither/slithir/operations/low_level_call.py +++ b/slither/slithir/operations/low_level_call.py @@ -78,5 +78,12 @@ class LowLevelCall(Call, OperationWithLValue): arguments = [] if self.arguments: arguments = self.arguments - return str(self.lvalue) +' = LOW_LEVEL_CALL dest:{} function:{} arguments:{} {} {}'.format(self.destination, self.function_name, [str(x) for x in arguments], value, gas) + txt = '{}({}) = LOW_LEVEL_CALL, dest:{}, function:{}, arguments:{} {} {}' + return txt.format(self.lvalue, + self.lvalue.type, + self.destination, + self.function_name, + [str(x) for x in arguments], + value, + gas) diff --git a/slither/slithir/operations/lvalue.py b/slither/slithir/operations/lvalue.py index ccea68249..da15e14fe 100644 --- a/slither/slithir/operations/lvalue.py +++ b/slither/slithir/operations/lvalue.py @@ -13,3 +13,7 @@ class OperationWithLValue(Operation): @property def lvalue(self): return self._lvalue + + @lvalue.setter + def lvalue(self, lvalue): + self._lvalue = lvalue diff --git a/slither/slithir/operations/member.py b/slither/slithir/operations/member.py index 909be69ad..c3d157df1 100644 --- a/slither/slithir/operations/member.py +++ b/slither/slithir/operations/member.py @@ -33,5 +33,5 @@ class Member(OperationWithLValue): return self._variable_right def __str__(self): - return str(self.lvalue) + ' -> ' + str(self.variable_left) + '.' + str(self.variable_right) + return '{}({}) -> {}.{}'.format(self.lvalue, self.lvalue.type, self.variable_left, self.variable_right) diff --git a/slither/slithir/operations/new_structure.py b/slither/slithir/operations/new_structure.py index cfb93e531..6c41aaaac 100644 --- a/slither/slithir/operations/new_structure.py +++ b/slither/slithir/operations/new_structure.py @@ -7,11 +7,11 @@ from slither.core.declarations.structure import Structure class NewStructure(Call, OperationWithLValue): - def __init__(self, structure_name, lvalue): + def __init__(self, structure, lvalue): super(NewStructure, self).__init__() - assert isinstance(structure_name, Structure) + assert isinstance(structure, Structure) assert is_valid_lvalue(lvalue) - self._structure_name = structure_name + self._structure = structure # todo create analyze to add the contract instance self._lvalue = lvalue @@ -19,9 +19,13 @@ class NewStructure(Call, OperationWithLValue): def read(self): return list(self.arguments) + @property + def structure(self): + return self._structure + @property def structure_name(self): - return self._structure_name + return self.structure.name def __str__(self): args = [str(a) for a in self.arguments] diff --git a/slither/slithir/operations/transfer.py b/slither/slithir/operations/transfer.py index 48187d210..8dda83d59 100644 --- a/slither/slithir/operations/transfer.py +++ b/slither/slithir/operations/transfer.py @@ -1,11 +1,7 @@ from slither.slithir.operations.call import Call -from slither.slithir.operations.lvalue import OperationWithLValue from slither.core.variables.variable import Variable from slither.core.declarations.solidity_variables import SolidityVariable -from slither.slithir.utils.utils import is_valid_lvalue -from slither.slithir.variables.constant import Constant - class Transfer(Call): def __init__(self, destination, value): diff --git a/slither/slithir/operations/unary.py b/slither/slithir/operations/unary.py index edcb1c92d..44bcdf0bc 100644 --- a/slither/slithir/operations/unary.py +++ b/slither/slithir/operations/unary.py @@ -42,10 +42,10 @@ class Unary(OperationWithLValue): @property def read(self): - return [self.variable] + return [self._variable] @property - def variable(self): + def rvalue(self): return self._variable @property @@ -53,4 +53,4 @@ class Unary(OperationWithLValue): return UnaryType.str(self._type) def __str__(self): - return "{} = {} {} ".format(self.lvalue, self.type_str, self.variable) + return "{} = {} {} ".format(self.lvalue, self.type_str, self.rvalue) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 869da06d2..995cfbd0e 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -631,6 +631,8 @@ class FunctionSolc(Function): self._analyze_read_write() self._analyze_calls() + + def convert_expression_to_slithir(self): for node in self.nodes: node.slithir_generation() diff --git a/slither/solc_parsing/declarations/modifier.py b/slither/solc_parsing/declarations/modifier.py index 4797a6b74..cd7e7d756 100644 --- a/slither/solc_parsing/declarations/modifier.py +++ b/slither/solc_parsing/declarations/modifier.py @@ -50,8 +50,6 @@ class ModifierSolc(Modifier, FunctionSolc): self._analyze_read_write() self._analyze_calls() - for node in self.nodes: - node.slithir_generation() def _parse_statement(self, statement, node): name = statement['name'] diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index 265162664..7f3157534 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -118,6 +118,8 @@ class SlitherSolc(Slither): self._analyze_third_part(contracts_to_be_analyzed, libraries) self._analyzed = True + + self._convert_to_slithir() # TODO refactor the following functions, and use a lambda function @@ -235,3 +237,9 @@ class SlitherSolc(Slither): contract.analyze_content_functions() contract.set_is_analyzed(True) + + def _convert_to_slithir(self): + for contract in self.contracts: + for func in contract.functions + contract.modifiers: + if func.contract == contract: + func.convert_expression_to_slithir() diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 88a8d713e..d030f2fb1 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -93,7 +93,7 @@ class ExpressionToSlithIR(ExpressionVisitor): if expression.type_call.startswith('tuple(') and expression.type_call != 'tuple()': val = TupleVariable() else: - val = TemporaryVariable() + val = TemporaryVariable() internal_call = InternalCall(called, len(args), val, expression.type_call) self._result.append(internal_call) set_val(expression, val) @@ -104,16 +104,11 @@ class ExpressionToSlithIR(ExpressionVisitor): if expression.type_call.startswith('tuple(') and expression.type_call != 'tuple()': val = TupleVariable() else: - val = TemporaryVariable() + val = TemporaryVariable() - if isinstance(called, Structure) and False: - operation = TmpNewStructure(called, val) -# self._result.append(message_call) -# set_val(expression, val) - else: - message_call = TmpCall(called, len(args), val, expression.type_call) - self._result.append(message_call) - set_val(expression, val) + message_call = TmpCall(called, len(args), val, expression.type_call) + self._result.append(message_call) + set_val(expression, val) def _post_conditional_expression(self, expression): raise Exception('Ternary operator are not convertible to SlithIR {}'.format(expression)) From 10b3b4037d48c902770bdf0c93a1f45dd84af708 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 12 Oct 2018 11:51:48 +0100 Subject: [PATCH 127/308] SlithIR: improve support for type --- .../core/declarations/solidity_variables.py | 2 +- slither/slithir/convert.py | 401 ++++++++++++------ slither/slithir/operations/binary.py | 2 +- slither/slithir/operations/high_level_call.py | 5 +- slither/slithir/operations/internal_call.py | 4 +- slither/slithir/operations/library_call.py | 11 +- .../slithir/operations/return_operation.py | 5 +- slither/solc_parsing/slitherSolc.py | 1 - 8 files changed, 303 insertions(+), 128 deletions(-) diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py index 1ad4a90ed..96e5dc2e9 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -104,7 +104,7 @@ class SolidityVariableComposed(SolidityVariable): @property def type(self): - return SOLIDITY_VARIABLES_COMPOSED[self.name] + return ElementaryType(SOLIDITY_VARIABLES_COMPOSED[self.name]) def __str__(self): return self._name diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index c64cf805a..a5eb2d468 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -116,7 +116,9 @@ def propage_type_and_convert_call(result, node): call_data = [] - for idx in range(len(result)): + idx = 0 + # use of while len() as result can be modified during the iteration + while idx < len(result): ins = result[idx] if isinstance(ins, TmpCall): @@ -144,11 +146,165 @@ def propage_type_and_convert_call(result, node): if isinstance(ins, (Call, NewContract, NewStructure)): ins.arguments = call_data - propagate_types(ins, node) + call_data = [] + + if is_temporary(ins): + del result[idx] + continue + + new_ins = propagate_types(ins, node) + if new_ins: + if isinstance(new_ins, (list,)): + assert len(new_ins) == 2 + result.insert(idx, new_ins[0]) + result.insert(idx+1, new_ins[1]) + idx = idx + 1 + else: + result[idx] = new_ins + idx = idx +1 return result +def convert_to_low_level(ir): + """ + Convert to a transfer/send/or low level call + The funciton assume to receive a correct IR + The checks must be done by the caller + """ + if ir.function_name == 'transfer': + assert len(ir.arguments) == 1 + ir = Transfer(ir.destination, ir.arguments[0]) + return ir + elif ir.function_name == 'send': + assert len(ir.arguments) == 1 + ir = Send(ir.destination, ir.arguments[0], ir.lvalue) + ir.lvalue.set_type(ElementaryType('bool')) + return ir + elif ir.function_name in ['call', 'delegatecall', 'callcode']: + new_ir = LowLevelCall(ir.destination, + ir.function_name, + ir.nbr_arguments, + ir.lvalue, + ir.type_call) + new_ir.call_gas = ir.call_gas + new_ir.call_value = ir.call_value + new_ir.arguments = ir.arguments + new_ir.lvalue.set_type(ElementaryType('bool')) + return new_ir + logger.error('Incorrect conversion to low level {}'.format(ir)) + exit(0) + +def convert_to_push(ir): + """ + Convert a call to a PUSH operaiton + + The funciton assume to receive a correct IR + The checks must be done by the caller + + May necessitate to create an intermediate operation (InitArray) + As a result, the function return may return a list + """ + if isinstance(ir.arguments[0], list): + ret = [] + + val = TemporaryVariable() + operation = InitArray(ir.arguments[0], val) + ret.append(operation) + + ir = Push(ir.destination, val) + + length = len(operation.init_values) + t = operation.init_values[0].type + ir.lvalue.set_type(ArrayType(t, length)) + + ret.insert(ir) + return ret + + ir = Push(ir.destination, ir.arguments[0]) + return ir + +def convert_to_library(ir, node, using_for): + contract = node.function.contract + t = ir.destination.type + for destination in using_for[t]: + lib_contract = contract.slither.get_contract_from_name(str(destination)) + if destination: + lib_call = LibraryCall(lib_contract, + ir.function_name, + ir.nbr_arguments, + ir.lvalue, + ir.type_call) + lib_call.call_gas = ir.call_gas + lib_call.arguments = [ir.destination] + ir.arguments + prev = ir + ir = lib_call + sig = '{}({})'.format(ir.function_name, + ','.join([str(x.type) for x in ir.arguments])) + func = lib_contract.get_function_from_signature(sig) + if not func: + func = lib_contract.get_state_variable_from_name(ir.function_name) + assert func + ir.function = func + if isinstance(func, Function): + t = func.return_type + # if its not a tuple, return a singleton + if len(t) == 1: + t = t[0] + else: + # otherwise its a variable (getter) + t = func.type + if t: + ir.lvalue.set_type(t) + else: + ir.lvalue = None + return ir + logger.error('Library not found {}'.format(ir)) + exit(0) + +def get_type(t): + """ + Convert a type to a str + If the instance is a Contract, return 'address' instead + """ + if isinstance(t, UserDefinedType): + if isinstance(t.type, Contract): + return 'address' + return str(t) + +def convert_type_of_high_level_call(ir, contract): + sig = '{}({})'.format(ir.function_name, + ','.join([get_type(x.type) for x in ir.arguments])) + func = contract.get_function_from_signature(sig) + if not func: + func = contract.get_state_variable_from_name(ir.function_name) + else: + return_type = func.return_type + # if its not a tuple; return a singleton + if return_type and len(return_type) == 1: + return_type = return_type[0] + if not func and ir.function_name in ['call', + 'delegatecall', + 'codecall', + 'transfer', + 'send']: + return convert_to_low_level(ir) + if not func: + logger.error('Function not found {}'.format(sig)) + ir.function = func + if isinstance(func, Function): + t = return_type + else: + # otherwise its a variable (getter) + t = func.type + if t: + ir.lvalue.set_type(t) + else: + ir.lvalue = None + + return None + def propagate_types(ir, node): # propagate the type + using_for = node.function.contract.using_for if isinstance(ir, OperationWithLValue): if not ir.lvalue.type: if isinstance(ir, Assignment): @@ -157,52 +313,47 @@ def propagate_types(ir, node): if BinaryType.return_bool(ir.type): ir.lvalue.set_type(ElementaryType('bool')) else: - ir.lvalue.set_type(ir.left_variable.type) + ir.lvalue.set_type(ir.variable_left.type) elif isinstance(ir, Delete): # nothing to propagate pass elif isinstance(ir, HighLevelCall): t = ir.destination.type - # can be None due to temporary operation - if t: - if isinstance(t, UserDefinedType): - # UserdefinedType - t = t.type - if isinstance(t, Contract): - sig = '{}({})'.format(ir.function_name, - ','.join([str(x.type) for x in ir.arguments])) - contract = node.slither.get_contract_from_name(t.name) - func = contract.get_function_from_signature(sig) - if not func: - func = t.get_state_variable_from_name(ir.function_name) - else: - return_type = func.return_type - if not func and ir.function_name in ['call', 'delegatecall','codecall']: - return - if not func: - logger.error('Function not found {}'.format(sig)) - ir.function = func - if isinstance(func, Function): - t = func.return_type - else: - # otherwise its a variable (getter) - t = func.type - if t: - ir.lvalue.set_type(t) - else: - ir.lvalue = None - if isinstance(t, ElementaryType): - print(t.name) - # TODO here handle library call - # we can probably directly remove the ins, as alow level - # or a lib - if t.name == 'address': - ir.lvalue.set_type(ElementaryType('bool')) + + # Temporary operaiton (they are removed later) + if t is None: + return + # convert library + if t in using_for: + return convert_to_library(ir, node, using_for) + + if isinstance(t, UserDefinedType): + # UserdefinedType + t = t.type + if isinstance(t, Contract): + contract = node.slither.get_contract_from_name(t.name) + return convert_type_of_high_level_call(ir, contract) + else: + return None + + # Convert HighLevelCall to LowLevelCall + if isinstance(t, ElementaryType) and t.name == 'address': + if ir.destination.name == 'this': + return convert_type_of_high_level_call(ir, node.function.contract) + else: + return convert_to_low_level(ir) + + # Convert push operations + # May need to insert a new operation + # Which leads to return a list of operation + if isinstance(t, ArrayType): + if ir.function_name == 'push' and len(ir.arguments) == 1: + return convert_to_push(ir) + elif isinstance(ir, Index): if isinstance(ir.variable_left.type, MappingType): ir.lvalue.set_type(ir.variable_left.type.type_to) - else: - assert isinstance(ir.variable_left.type, ArrayType) + elif isinstance(ir.variable_left.type, ArrayType): ir.lvalue.set_type(ir.variable_left.type.type) elif isinstance(ir, InitArray): @@ -210,9 +361,13 @@ def propagate_types(ir, node): t = ir.init_values[0].type ir.lvalue.set_type(ArrayType(t, length)) elif isinstance(ir, InternalCall): + # if its not a tuple, return a singleton return_type = ir.function.return_type if return_type: - ir.lvalue.set_type(return_type) + if len(return_type) == 1: + ir.lvalue.set_type(return_type[0]) + else: + ir.lvalue.set_type(return_type) else: ir.lvalue = None elif isinstance(ir, LowLevelCall): @@ -253,7 +408,11 @@ def propagate_types(ir, node): elif isinstance(ir, Send): ir.lvalue.set_type(ElementaryType('bool')) elif isinstance(ir, SolidityCall): - ir.lvalue.set_type(ir.function.return_type) + return_type = ir.function.return_type + if len(return_type) == 1: + ir.lvalue.set_type(return_type[0]) + else: + ir.lvalue.set_type(return_type) elif isinstance(ir, TypeConversion): ir.lvalue.set_type(ir.type) elif isinstance(ir, Unary): @@ -262,7 +421,7 @@ def propagate_types(ir, node): types = ir.tuple.type.type idx = ir.index t = types[idx] - ir.lvalue.set_type(t) + ir.lvalue.set_type(t) elif isinstance(ir, (Argument, TmpCall, TmpNewArray, TmpNewContract, TmpNewStructure, TmpNewElementaryType)): # temporary operation; they will be removed pass @@ -278,8 +437,8 @@ def apply_ir_heuristics(irs, node): irs = integrate_value_gas(irs) irs = propage_type_and_convert_call(irs, node) - irs = remove_temporary(irs) - irs = replace_calls(irs) +# irs = remove_temporary(irs) +# irs = replace_calls(irs) irs = remove_unused(irs) reset_variable_number(irs) @@ -306,6 +465,12 @@ def reset_variable_number(result): for idx in range(len(tuple_variables)): tuple_variables[idx].index = idx +def is_temporary(ins): + return isinstance(ins, (Argument, + TmpNewElementaryType, + TmpNewContract, + TmpNewArray, + TmpNewStructure)) def remove_temporary(result): @@ -344,56 +509,56 @@ def remove_unused(result): return result -def replace_calls(result): - ''' - replace call to push to a Push Operation - Replace to call 'call' 'delegatecall', 'callcode' to an LowLevelCall - ''' - reset = True - def is_address(v): - if v in [SolidityVariableComposed('msg.sender'), - SolidityVariableComposed('tx.origin')]: - return True - if not isinstance(v, Variable): - return False - if not isinstance(v.type, ElementaryType): - return False - return v.type.type == 'address' - while reset: - reset = False - for idx in range(len(result)): - ins = result[idx] - if isinstance(ins, HighLevelCall): - # TODO better handle collision with function named push - if ins.function_name == 'push' and len(ins.arguments) == 1: - if isinstance(ins.arguments[0], list): - val = TemporaryVariable() - operation = InitArray(ins.arguments[0], val) - result.insert(idx, operation) - result[idx+1] = Push(ins.destination, val) - reset = True - break - else: - result[idx] = Push(ins.destination, ins.arguments[0]) - if is_address(ins.destination): - if ins.function_name == 'transfer': - assert len(ins.arguments) == 1 - result[idx] = Transfer(ins.destination, ins.arguments[0]) - elif ins.function_name == 'send': - assert len(ins.arguments) == 1 - result[idx] = Send(ins.destination, ins.arguments[0], ins.lvalue) - elif ins.function_name in ['call', 'delegatecall', 'callcode']: - # TODO: handle name collision - result[idx] = LowLevelCall(ins.destination, - ins.function_name, - ins.nbr_arguments, - ins.lvalue, - ins.type_call) - result[idx].call_gas = ins.call_gas - result[idx].call_value = ins.call_value - result[idx].arguments = ins.arguments - # other case are library on address - return result +#def replace_calls(result): +# ''' +# replace call to push to a Push Operation +# Replace to call 'call' 'delegatecall', 'callcode' to an LowLevelCall +# ''' +# reset = True +# def is_address(v): +# if v in [SolidityVariableComposed('msg.sender'), +# SolidityVariableComposed('tx.origin')]: +# return True +# if not isinstance(v, Variable): +# return False +# if not isinstance(v.type, ElementaryType): +# return False +# return v.type.type == 'address' +# while reset: +# reset = False +# for idx in range(len(result)): +# ins = result[idx] +# if isinstance(ins, HighLevelCall): +# # TODO better handle collision with function named push +# if ins.function_name == 'push' and len(ins.arguments) == 1: +# if isinstance(ins.arguments[0], list): +# val = TemporaryVariable() +# operation = InitArray(ins.arguments[0], val) +# result.insert(idx, operation) +# result[idx+1] = Push(ins.destination, val) +# reset = True +# break +# else: +# result[idx] = Push(ins.destination, ins.arguments[0]) +# if is_address(ins.destination): +# if ins.function_name == 'transfer': +# assert len(ins.arguments) == 1 +# result[idx] = Transfer(ins.destination, ins.arguments[0]) +# elif ins.function_name == 'send': +# assert len(ins.arguments) == 1 +# result[idx] = Send(ins.destination, ins.arguments[0], ins.lvalue) +# elif ins.function_name in ['call', 'delegatecall', 'callcode']: +# # TODO: handle name collision +# result[idx] = LowLevelCall(ins.destination, +# ins.function_name, +# ins.nbr_arguments, +# ins.lvalue, +# ins.type_call) +# result[idx].call_gas = ins.call_gas +# result[idx].call_value = ins.call_value +# result[idx].arguments = ins.arguments +# # other case are library on address +# return result def extract_tmp_call(ins): @@ -441,28 +606,28 @@ def extract_tmp_call(ins): raise Exception('Not extracted {} {}'.format(type(ins.called), ins)) -def convert_libs(result, contract): - using_for = contract.using_for - for idx in range(len(result)): - ir = result[idx] - if isinstance(ir, HighLevelCall) and isinstance(ir.destination, Variable): - if ir.destination.type in using_for: - for destination in using_for[ir.destination.type]: - # destination is a UserDefinedType - destination = contract.slither.get_contract_from_name(str(destination)) - if destination: - lib_call = LibraryCall(destination, - ir.function_name, - ir.nbr_arguments, - ir.lvalue, - ir.type_call) - lib_call.call_gas = ir.call_gas - lib_call.arguments = [ir.destination] + ir.arguments - result[idx] = lib_call - break - assert destination - - return result +#def convert_libs(result, contract): +# using_for = contract.using_for +# for idx in range(len(result)): +# ir = result[idx] +# if isinstance(ir, HighLevelCall) and isinstance(ir.destination, Variable): +# if ir.destination.type in using_for: +# for destination in using_for[ir.destination.type]: +# # destination is a UserDefinedType +# destination = contract.slither.get_contract_from_name(str(destination)) +# if destination: +# lib_call = LibraryCall(destination, +# ir.function_name, +# ir.nbr_arguments, +# ir.lvalue, +# ir.type_call) +# lib_call.call_gas = ir.call_gas +# lib_call.arguments = [ir.destination] + ir.arguments +# result[idx] = lib_call +# break +# assert destination +# +# return result def convert_expression(expression, node): # handle standlone expression @@ -479,7 +644,7 @@ def convert_expression(expression, node): result = apply_ir_heuristics(result, node) - result = convert_libs(result, node.function.contract) +# result = convert_libs(result, node.function.contract) if result: if node.type in [NodeType.IF, NodeType.IFLOOP]: diff --git a/slither/slithir/operations/binary.py b/slither/slithir/operations/binary.py index 8abe46351..5a770f417 100644 --- a/slither/slithir/operations/binary.py +++ b/slither/slithir/operations/binary.py @@ -165,7 +165,7 @@ class Binary(OperationWithLValue): def __str__(self): return '{}({}) = {} {} {}'.format(str(self.lvalue), - str(self.lvalue.type), + self.lvalue.type, self.variable_left, self.type_str, self.variable_right) diff --git a/slither/slithir/operations/high_level_call.py b/slither/slithir/operations/high_level_call.py index a37e532eb..ad4cc5956 100644 --- a/slither/slithir/operations/high_level_call.py +++ b/slither/slithir/operations/high_level_call.py @@ -98,9 +98,10 @@ class HighLevelCall(Call, OperationWithLValue): txt = '{}HIGH_LEVEL_CALL, dest:{}({}), function:{}, arguments:{} {} {}' if not self.lvalue: lvalue = '' - else: - print(self.lvalue) + elif isinstance(self.lvalue.type, (list,)): lvalue = '{}({}) = '.format(self.lvalue, ','.join(str(x) for x in self.lvalue.type)) + else: + lvalue = '{}({}) = '.format(self.lvalue, self.lvalue.type) return txt.format(lvalue, self.destination, self.destination.type, diff --git a/slither/slithir/operations/internal_call.py b/slither/slithir/operations/internal_call.py index 4a7edb8e3..e8bbb3db7 100644 --- a/slither/slithir/operations/internal_call.py +++ b/slither/slithir/operations/internal_call.py @@ -34,8 +34,10 @@ class InternalCall(Call, OperationWithLValue): args = [str(a) for a in self.arguments] if not self.lvalue: lvalue = '' - else: + elif isinstance(self.lvalue.type, (list,)): lvalue = '{}({}) = '.format(self.lvalue, ','.join(str(x) for x in self.lvalue.type)) + else: + lvalue = '{}({}) = '.format(self.lvalue, self.lvalue.type) txt = '{}INTERNAL_CALL, {}.{}({})' return txt.format(lvalue, self.function.contract.name, diff --git a/slither/slithir/operations/library_call.py b/slither/slithir/operations/library_call.py index c1b3821eb..76abfbe8a 100644 --- a/slither/slithir/operations/library_call.py +++ b/slither/slithir/operations/library_call.py @@ -16,9 +16,14 @@ class LibraryCall(HighLevelCall): arguments = [] if self.arguments: arguments = self.arguments - txt = '{}({}) = LIBRARY_CALL, dest:{}, function:{}, arguments:{} {}' - return txt.format(self.lvalue, - self.lvalue.type, + if not self.lvalue: + lvalue = '' + elif isinstance(self.lvalue.type, (list,)): + lvalue = '{}({}) = '.format(self.lvalue, ','.join(str(x) for x in self.lvalue.type)) + else: + lvalue = '{}({}) = '.format(self.lvalue, self.lvalue.type) + txt = '{}LIBRARY_CALL, dest:{}, function:{}, arguments:{} {}' + return txt.format(lvalue, self.destination, self.function_name, [str(x) for x in arguments], diff --git a/slither/slithir/operations/return_operation.py b/slither/slithir/operations/return_operation.py index 5489087ea..225e40823 100644 --- a/slither/slithir/operations/return_operation.py +++ b/slither/slithir/operations/return_operation.py @@ -8,7 +8,10 @@ class Return(Operation): Only present as last operation in RETURN node """ def __init__(self, value): - assert is_valid_rvalue(value) or isinstance(value, TupleVariable) + # Note: Can return None + # ex: return call() + # where call() dont return + assert is_valid_rvalue(value) or isinstance(value, TupleVariable) or value == None super(Return, self).__init__() self._value = value diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index 7f3157534..d0a69cc13 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -17,7 +17,6 @@ class SlitherSolc(Slither): self._contractsNotParsed = [] self._contracts_by_id = {} self._analyzed = False - print(filename) def _parse_contracts_from_json(self, json_data): first = json_data.find('{') From fd7ba8167ffa243d2528d3eb56efeaec45835390 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 12 Oct 2018 15:31:56 +0100 Subject: [PATCH 128/308] SlithIR: fix bugs --- slither/analyses/taint/specific_variable.py | 2 +- .../core/declarations/solidity_variables.py | 12 +- slither/slithir/convert.py | 142 ++++++++++++------ 3 files changed, 105 insertions(+), 51 deletions(-) diff --git a/slither/analyses/taint/specific_variable.py b/slither/analyses/taint/specific_variable.py index 92013aa55..5e6c110d8 100644 --- a/slither/analyses/taint/specific_variable.py +++ b/slither/analyses/taint/specific_variable.py @@ -25,7 +25,7 @@ def make_key(variable): return key def _transfer_func_with_key(ir, read, refs, taints, key): - if isinstance(ir, OperationWithLValue): + if isinstance(ir, OperationWithLValue) and ir.lvalue: if any(is_tainted_from_key(var_read, key) or var_read in taints for var_read in read): taints += [ir.lvalue] ir.lvalue.context[key] = True diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py index 96e5dc2e9..777b5ce83 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -4,7 +4,7 @@ from slither.core.solidity_types import ElementaryType SOLIDITY_VARIABLES = {"now":'uint256', "this":'address', - 'abi':'', + 'abi':'address', # to simplify the conversion, assume that abi return an address 'msg':'', 'tx':'', 'block':'', @@ -46,10 +46,14 @@ SOLIDITY_FUNCTIONS = {"gasleft()":['uint256'], "log2(bytes32,bytes32,bytes32)":[], "log3(bytes32,bytes32,bytes32,bytes32)":[], "blockhash(uint256)":['bytes32'], - # this balance needs a special handling - # as it is first recognized as a SolidityVariableComposed + # the following need a special handling + # as they are recognized as a SolidityVariableComposed # and converted to a SolidityFunction by SlithIR - "this.balance()":['uint256']} + "this.balance()":['uint256'], + "abi.encode()":['bytes'], + "abi.encodePacked()":['bytes'], + "abi.encodeWithSelector()":["bytes"], + "abi.encodeWithSignature()":["bytes"]} def solidity_function_signature(name): """ diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index b2a0a00aa..2f05e88ab 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -1,7 +1,7 @@ import logging from slither.core.declarations import (Contract, Enum, Event, SolidityFunction, - Structure, SolidityVariableComposed, Function) + Structure, SolidityVariableComposed, Function, SolidityVariable) from slither.core.expressions import Identifier, Literal from slither.core.solidity_types import ElementaryType, UserDefinedType, MappingType, ArrayType from slither.core.variables.variable import Variable @@ -169,6 +169,8 @@ def convert_to_low_level(ir): Convert to a transfer/send/or low level call The funciton assume to receive a correct IR The checks must be done by the caller + + Additionally convert abi... to solidityfunction """ if ir.function_name == 'transfer': assert len(ir.arguments) == 1 @@ -179,6 +181,16 @@ def convert_to_low_level(ir): ir = Send(ir.destination, ir.arguments[0], ir.lvalue) ir.lvalue.set_type(ElementaryType('bool')) return ir + elif ir.destination.name == 'abi' and ir.function_name in ['encode', + 'encodePacked', + 'encodeWithSelector', + 'encodeWithSignature']: + + call = SolidityFunction('abi.{}()'.format(ir.function_name)) + new_ir = SolidityCall(call, ir.nbr_arguments, ir.lvalue, ir.type_call) + new_ir.arguments = ir.arguments + new_ir.lvalue.set_type(call.return_type) + return new_ir elif ir.function_name in ['call', 'delegatecall', 'callcode']: new_ir = LowLevelCall(ir.destination, ir.function_name, @@ -222,9 +234,7 @@ def convert_to_push(ir): ir = Push(ir.destination, ir.arguments[0]) return ir -def convert_to_library(ir, node, using_for): - contract = node.function.contract - t = ir.destination.type +def look_for_library(contract, ir, node, using_for, t): for destination in using_for[t]: lib_contract = contract.slither.get_contract_from_name(str(destination)) if destination: @@ -235,28 +245,24 @@ def convert_to_library(ir, node, using_for): ir.type_call) lib_call.call_gas = ir.call_gas lib_call.arguments = [ir.destination] + ir.arguments - prev = ir - ir = lib_call - sig = '{}({})'.format(ir.function_name, - ','.join([str(x.type) for x in ir.arguments])) - func = lib_contract.get_function_from_signature(sig) - if not func: - func = lib_contract.get_state_variable_from_name(ir.function_name) - assert func - ir.function = func - if isinstance(func, Function): - t = func.return_type - # if its not a tuple, return a singleton - if len(t) == 1: - t = t[0] - else: - # otherwise its a variable (getter) - t = func.type - if t: - ir.lvalue.set_type(t) - else: - ir.lvalue = None - return ir + new_ir = convert_type_library_call(lib_call, lib_contract) + if new_ir: + return new_ir + return None + +def convert_to_library(ir, node, using_for): + contract = node.function.contract + t = ir.destination.type + + new_ir = look_for_library(contract, ir, node, using_for, t) + if new_ir: + return new_ir + + if '*' in using_for: + new_ir = look_for_library(contract, ir, node, using_for, '*') + if new_ir: + return new_ir + logger.error('Library not found {}'.format(ir)) exit(0) @@ -270,33 +276,64 @@ def get_type(t): return 'address' return str(t) +def convert_type_library_call(ir, lib_contract): + sig = '{}({})'.format(ir.function_name, + ','.join([get_type(x.type) for x in ir.arguments])) + func = lib_contract.get_function_from_signature(sig) + if not func: + func = lib_contract.get_state_variable_from_name(ir.function_name) + # In case of multiple binding to the same type + if not func: + return None + ir.function = func + if isinstance(func, Function): + t = func.return_type + # if its not a tuple, return a singleton + if t and len(t) == 1: + t = t[0] + else: + # otherwise its a variable (getter) + t = func.type + if t: + ir.lvalue.set_type(t) + else: + ir.lvalue = None + return ir + def convert_type_of_high_level_call(ir, contract): sig = '{}({})'.format(ir.function_name, ','.join([get_type(x.type) for x in ir.arguments])) func = contract.get_function_from_signature(sig) if not func: func = contract.get_state_variable_from_name(ir.function_name) - else: - return_type = func.return_type - # if its not a tuple; return a singleton - if return_type and len(return_type) == 1: - return_type = return_type[0] if not func and ir.function_name in ['call', 'delegatecall', 'codecall', 'transfer', 'send']: return convert_to_low_level(ir) + if not func: + # specific lookup when the compiler does implicit conversion + # for example + # myFunc(uint) + # can be called with an uint8 + for function in contract.functions: + if function.name == ir.function_name and len(function.parameters) == len(ir.arguments): + func = function + break if not func: logger.error('Function not found {}'.format(sig)) ir.function = func if isinstance(func, Function): - t = return_type + return_type = func.return_type + # if its not a tuple; return a singleton + if return_type and len(return_type) == 1: + return_type = return_type[0] else: # otherwise its a variable (getter) - t = func.type - if t: - ir.lvalue.set_type(t) + return_type = func.type + if return_type: + ir.lvalue.set_type(return_type) else: ir.lvalue = None @@ -317,6 +354,8 @@ def propagate_types(ir, node): elif isinstance(ir, Delete): # nothing to propagate pass + elif isinstance(ir, LibraryCall): + return convert_type_library_call(ir, ir.destination) elif isinstance(ir, HighLevelCall): t = ir.destination.type @@ -375,24 +414,31 @@ def propagate_types(ir, node): # This should not happen assert False elif isinstance(ir, Member): - t = ir.variable_left.type + left = ir.variable_left + if isinstance(left, (Variable, SolidityVariable)): + t = ir.variable_left.type + elif isinstance(left, (Contract, Enum, Structure)): + t = UserDefinedType(left) # can be None due to temporary operation if t: if isinstance(t, UserDefinedType): # UserdefinedType - t = t.type - if isinstance(t, Enum): - elems = t.values - for elem in elems: - if elem == ir.variable_right: - ir.lvalue.set_type(elems[elem].type) - elif isinstance(t, Structure): - elems = t.elems + type_t = t.type + if isinstance(type_t, Enum): + ir.lvalue.set_type(t) +# elems = t.values +# print(elems) +# for elem in elems: +# print(elem) +# if elem == ir.variable_right: +# ir.lvalue.set_type(elems[elem].type) + elif isinstance(type_t, Structure): + elems = type_t.elems for elem in elems: if elem == ir.variable_right: ir.lvalue.set_type(elems[elem].type) else: - assert isinstance(t, Contract) + assert isinstance(type_t, Contract) elif isinstance(ir, NewArray): ir.lvalue.set_type(ir.array_type) elif isinstance(ir, NewContract): @@ -513,6 +559,10 @@ def remove_unused(result): def extract_tmp_call(ins): assert isinstance(ins, TmpCall) if isinstance(ins.ori, Member): + if isinstance(ins.ori.variable_left, Contract): + libcall = LibraryCall(ins.ori.variable_left, ins.ori.variable_right, ins.nbr_arguments, ins.lvalue, ins.type_call) + libcall.call_id = ins.call_id + return libcall msgcall = HighLevelCall(ins.ori.variable_left, ins.ori.variable_right, ins.nbr_arguments, ins.lvalue, ins.type_call) msgcall.call_id = ins.call_id return msgcall @@ -524,7 +574,7 @@ def extract_tmp_call(ins): if str(ins.called) == 'block.blockhash': ins.called = SolidityFunction('blockhash(uint256)') elif str(ins.called) == 'this.balance': - ins.called = SolidityFunction('this.balance()') + return SolidityCall(SolidityFunction('this.balance()'), ins.nbr_arguments, ins.lvalue, ins.type_call) if isinstance(ins.called, SolidityFunction): return SolidityCall(ins.called, ins.nbr_arguments, ins.lvalue, ins.type_call) From 96587d729e8ac663b7accfca87f17de208db7b78 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 15 Oct 2018 10:57:13 +0100 Subject: [PATCH 129/308] Use SolidityVariable in LowLevelCall (allow this) --- slither/slithir/operations/low_level_call.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slither/slithir/operations/low_level_call.py b/slither/slithir/operations/low_level_call.py index 4197e969a..e7de73593 100644 --- a/slither/slithir/operations/low_level_call.py +++ b/slither/slithir/operations/low_level_call.py @@ -1,7 +1,7 @@ from slither.slithir.operations.call import Call from slither.slithir.operations.lvalue import OperationWithLValue from slither.core.variables.variable import Variable -from slither.core.declarations.solidity_variables import SolidityVariableComposed +from slither.core.declarations.solidity_variables import SolidityVariable from slither.slithir.variables.constant import Constant @@ -11,7 +11,7 @@ class LowLevelCall(Call, OperationWithLValue): """ def __init__(self, destination, function_name, nbr_arguments, result, type_call): - assert isinstance(destination, (Variable, SolidityVariableComposed)) + assert isinstance(destination, (Variable, SolidityVariable)) assert isinstance(function_name, Constant) super(LowLevelCall, self).__init__() self._destination = destination From bd5fd2f9f46f8e56b0678933a6aef19f6ae7ad08 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 15 Oct 2018 11:03:40 +0100 Subject: [PATCH 130/308] Invert order for low level lookup --- slither/slithir/convert.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 2f05e88ab..43c76dfb1 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -306,12 +306,6 @@ def convert_type_of_high_level_call(ir, contract): func = contract.get_function_from_signature(sig) if not func: func = contract.get_state_variable_from_name(ir.function_name) - if not func and ir.function_name in ['call', - 'delegatecall', - 'codecall', - 'transfer', - 'send']: - return convert_to_low_level(ir) if not func: # specific lookup when the compiler does implicit conversion # for example @@ -321,6 +315,13 @@ def convert_type_of_high_level_call(ir, contract): if function.name == ir.function_name and len(function.parameters) == len(ir.arguments): func = function break + # lowlelvel lookup needs to be done at last step + if not func and ir.function_name in ['call', + 'delegatecall', + 'codecall', + 'transfer', + 'send']: + return convert_to_low_level(ir) if not func: logger.error('Function not found {}'.format(sig)) ir.function = func From 58121ce24d3f5f5934acb8fc28d40861463ea6a1 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 15 Oct 2018 11:17:42 +0100 Subject: [PATCH 131/308] Fix incorrect fixpoint computation in reentrancy detector --- slither/detectors/reentrancy/reentrancy.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/slither/detectors/reentrancy/reentrancy.py b/slither/detectors/reentrancy/reentrancy.py index 2f8a55862..e7c6b6e97 100644 --- a/slither/detectors/reentrancy/reentrancy.py +++ b/slither/detectors/reentrancy/reentrancy.py @@ -96,10 +96,10 @@ class Reentrancy(AbstractDetector): fathers_context['read'] += father.context[self.key]['read'] # Exclude path that dont bring further information - if self.key in self.visited_all_paths: - if all(f_c['calls'] in self.visited_all_paths[node]['calls'] for f_c in fathers_context): - if all(f_c['send_eth'] in self.visited_all_paths[node]['send_eth'] for f_c in fathers_context): - if all(f_c['read'] in self.visited_all_paths[node]['read'] for f_c in fathers_context): + if node in self.visited_all_paths: + if all(call in self.visited_all_paths[node]['calls'] for call in fathers_context['calls']): + if all(send in self.visited_all_paths[node]['send_eth'] for send in fathers_context['send_eth']): + if all(read in self.visited_all_paths[node]['read'] for read in fathers_context['read']): return else: self.visited_all_paths[node] = {'send_eth':[], 'calls':[], 'read':[]} From 8c34abe1c32f8b8b03dc5f2e86c192367245c17e Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 15 Oct 2018 13:05:24 +0100 Subject: [PATCH 132/308] Add constructors to contract.all_functions_called --- slither/core/declarations/contract.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index e7b80817c..59bef5a93 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -95,6 +95,10 @@ class Contract(ChildSlither, SourceMapping): def modifiers_as_dict(self): return self._modifiers + @property + def constructor(self): + return next((func for func in self.functions if func.is_constructor), None) + @property def functions(self): ''' @@ -116,7 +120,13 @@ class Contract(ChildSlither, SourceMapping): ''' all_calls = (f.all_internal_calls() for f in self.functions) all_calls = [item for sublist in all_calls for item in sublist] + self.functions - all_calls = set(all_calls) + all_calls = list(set(all_calls)) + + all_constructors = [c.constructor for c in self.inheritance] + all_constructors = list(set([c for c in all_constructors if c])) + + all_calls = set(all_calls+all_constructors) + return [c for c in all_calls if isinstance(c, Function)] def functions_as_dict(self): From 5745be7d209ba69195dae247d9c8bf431091d939 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 15 Oct 2018 13:08:10 +0100 Subject: [PATCH 133/308] Fix bug when convertir push([1,2]) --- slither/slithir/convert.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 43c76dfb1..ab03ee1f5 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -224,11 +224,11 @@ def convert_to_push(ir): ir = Push(ir.destination, val) - length = len(operation.init_values) + length = Literal(len(operation.init_values)) t = operation.init_values[0].type ir.lvalue.set_type(ArrayType(t, length)) - ret.insert(ir) + ret.append(ir) return ret ir = Push(ir.destination, ir.arguments[0]) From 6ee8c9b2dd62ec9539379365285dcebc41b8756a Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 15 Oct 2018 13:49:16 +0100 Subject: [PATCH 134/308] Fix incorrect conversion of init_array with n dimension --- slither/slithir/operations/init_array.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/slither/slithir/operations/init_array.py b/slither/slithir/operations/init_array.py index 3530e73e0..bbd939c65 100644 --- a/slither/slithir/operations/init_array.py +++ b/slither/slithir/operations/init_array.py @@ -6,7 +6,18 @@ from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue class InitArray(OperationWithLValue): def __init__(self, init_values, lvalue): - assert all(is_valid_rvalue(v) for v in init_values) + # init_values can be an array of n dimension + # reduce was removed in py3 + def reduce(xs): + result = True + for i in xs: + result = result and i + return result + def check(elem): + if isinstance(elem, (list,)): + return reduce(elem) + return is_valid_rvalue(elem) + assert check(init_values) self._init_values = init_values self._lvalue = lvalue @@ -19,4 +30,10 @@ class InitArray(OperationWithLValue): return list(self._init_values) def __str__(self): - return "{}({}) = {}".format(self.lvalue, self.lvalue.type, [str(x) for x in self.init_values]) + + def convert(elem): + if isinstance(elem, (list,)): + return str([convert(x) for x in elem]) + return str(elem) + init_values = convert(self.init_values) + return "{}({}) = {}".format(self.lvalue, self.lvalue.type, init_values) From a06ef44038e430f3b8b1383481ff241bd03e68a2 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 15 Oct 2018 14:10:04 +0100 Subject: [PATCH 135/308] SlithIr: fix incorrect lib lookup. Re-order type lookup (loop first for contract) --- slither/slithir/convert.py | 2 +- .../solc_parsing/solidity_types/type_parsing.py | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index ab03ee1f5..e83a3ccd6 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -237,7 +237,7 @@ def convert_to_push(ir): def look_for_library(contract, ir, node, using_for, t): for destination in using_for[t]: lib_contract = contract.slither.get_contract_from_name(str(destination)) - if destination: + if lib_contract: lib_call = LibraryCall(lib_contract, ir.function_name, ir.nbr_arguments, diff --git a/slither/solc_parsing/solidity_types/type_parsing.py b/slither/solc_parsing/solidity_types/type_parsing.py index 2bcb27380..e2ccfa6ce 100644 --- a/slither/solc_parsing/solidity_types/type_parsing.py +++ b/slither/solc_parsing/solidity_types/type_parsing.py @@ -35,8 +35,16 @@ def _find_from_type_name(name, contract, contracts, structures, enums): return ArrayType(ElementaryType(name_elementary), Literal(depth)) else: return ElementaryType(name_elementary) + # We first look for contract + # To avoid collision + # Ex: a structure with the name of a contract + name_contract = name + if name_contract.startswith('contract '): + name_contract = name_contract[len('contract '):] + var_type = next((c for c in contracts if c.name == name_contract), None) - var_type = next((st for st in structures if st.name == name), None) + if not var_type: + var_type = next((st for st in structures if st.name == name), None) if not var_type: var_type = next((e for e in enums if e.name == name), None) if not var_type: @@ -69,11 +77,7 @@ def _find_from_type_name(name, contract, contracts, structures, enums): var_type = next((st for st in all_structures if st.contract.name+"."+st.name == name_struct), None) if var_type: return ArrayType(UserDefinedType(var_type), Literal(depth)) - if not var_type: - name_contract = name - if name_contract.startswith('contract '): - name_contract = name_contract[len('contract '):] - var_type = next((c for c in contracts if c.name == name_contract), None) + if not var_type: var_type = next((f for f in contract.functions if f.name == name), None) if not var_type: From 0d4e993155b67817f8344bf855b01f7e333f0534 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 15 Oct 2018 17:45:54 +0100 Subject: [PATCH 136/308] Improve fix point computation for specific taint --- slither/analyses/taint/specific_variable.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/slither/analyses/taint/specific_variable.py b/slither/analyses/taint/specific_variable.py index 5e6c110d8..304b89b4d 100644 --- a/slither/analyses/taint/specific_variable.py +++ b/slither/analyses/taint/specific_variable.py @@ -43,6 +43,15 @@ def _visit_node(node, visited, key): visited = visited + [node] taints = node.function.slither.context[key] + # taints only increase + # if we already see this node with the last taint set + # we dont need to explore itÅ“ + if node in node.slither.context['visited_all_paths']: + if node.slither.context['visited_all_paths'][node] == taints: + return + + node.slither.context['visited_all_paths'][node] = taints + # use of lambda function, as the key is required for this transfer_func _transfer_func_ = lambda _ir, _read, _refs, _taints: _transfer_func_with_key(_ir, _read, @@ -62,6 +71,13 @@ def run_taint(slither, taint): key = make_key(taint) + # if a node was already visited by another path + # we will only explore it if the traversal brings + # new variables written + # This speedup the exploration through a light fixpoint + # Its particular useful on 'complex' functions with several loops and conditions + slither.context['visited_all_paths'] = {} + prev_taints = [] slither.context[key] = [taint] # Loop until reaching a fixpoint From 050d5932328a39faec1c8868c112ff4e32afed4d Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 16 Oct 2018 12:26:20 +0100 Subject: [PATCH 137/308] SlihIR: Add InternalDynamicCall operator + fix minor bugs --- slither/core/solidity_types/function_type.py | 4 ++ slither/slithir/convert.py | 49 +++++++++++++------ slither/slithir/operations/__init__.py | 1 + .../operations/internal_dynamic_call.py | 44 +++++++++++++++++ 4 files changed, 83 insertions(+), 15 deletions(-) create mode 100644 slither/slithir/operations/internal_dynamic_call.py diff --git a/slither/core/solidity_types/function_type.py b/slither/core/solidity_types/function_type.py index ce853c065..3afb18de4 100644 --- a/slither/core/solidity_types/function_type.py +++ b/slither/core/solidity_types/function_type.py @@ -19,6 +19,10 @@ class FunctionType(Type): def return_values(self): return self._return_values + @property + def return_type(self): + return [x.type for x in self.return_values] + def __str__(self): # Use x.type # x.name may be empty diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index e83a3ccd6..1b381457c 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -3,12 +3,12 @@ import logging from slither.core.declarations import (Contract, Enum, Event, SolidityFunction, Structure, SolidityVariableComposed, Function, SolidityVariable) from slither.core.expressions import Identifier, Literal -from slither.core.solidity_types import ElementaryType, UserDefinedType, MappingType, ArrayType +from slither.core.solidity_types import ElementaryType, UserDefinedType, MappingType, ArrayType, FunctionType from slither.core.variables.variable import Variable from slither.slithir.operations import (Assignment, Binary, BinaryType, Call, Condition, Delete, EventCall, HighLevelCall, Index, InitArray, - InternalCall, LibraryCall, + InternalCall, InternalDynamicCall, LibraryCall, LowLevelCall, Member, NewArray, NewContract, NewElementaryType, NewStructure, OperationWithLValue, @@ -57,7 +57,7 @@ def integrate_value_gas(result): if isinstance(i, OperationWithLValue): assigments[i.lvalue.name] = i if isinstance(i, TmpCall): - if isinstance(i.called, Variable): + if isinstance(i.called, Variable) and i.called.name in assigments: ins_ori = assigments[i.called.name] i.set_ori(ins_ori) @@ -276,9 +276,21 @@ def get_type(t): return 'address' return str(t) +def get_sig(ir): + sig = '{}({})' + name = ir.function_name + + args = [] + for arg in ir.arguments: + if isinstance(arg, (list,)): + type_arg = '{}[{}]'.format(get_type(arg[0].type), len(arg)) + else: + type_arg = get_type(arg.type) + args.append(type_arg) + return sig.format(name, ','.join(args)) + def convert_type_library_call(ir, lib_contract): - sig = '{}({})'.format(ir.function_name, - ','.join([get_type(x.type) for x in ir.arguments])) + sig = get_sig(ir) func = lib_contract.get_function_from_signature(sig) if not func: func = lib_contract.get_state_variable_from_name(ir.function_name) @@ -301,8 +313,7 @@ def convert_type_library_call(ir, lib_contract): return ir def convert_type_of_high_level_call(ir, contract): - sig = '{}({})'.format(ir.function_name, - ','.join([get_type(x.type) for x in ir.arguments])) + sig = get_sig(ir) func = contract.get_function_from_signature(sig) if not func: func = contract.get_state_variable_from_name(ir.function_name) @@ -410,6 +421,16 @@ def propagate_types(ir, node): ir.lvalue.set_type(return_type) else: ir.lvalue = None + elif isinstance(ir, InternalDynamicCall): + # if its not a tuple, return a singleton + return_type = ir.function_type.return_type + if return_type: + if len(return_type) == 1: + ir.lvalue.set_type(return_type[0]) + else: + ir.lvalue.set_type(return_type) + else: + ir.lvalue = None elif isinstance(ir, LowLevelCall): # Call are not yet converted # This should not happen @@ -427,12 +448,6 @@ def propagate_types(ir, node): type_t = t.type if isinstance(type_t, Enum): ir.lvalue.set_type(t) -# elems = t.values -# print(elems) -# for elem in elems: -# print(elem) -# if elem == ir.variable_right: -# ir.lvalue.set_type(elems[elem].type) elif isinstance(type_t, Structure): elems = type_t.elems for elem in elems: @@ -599,6 +614,9 @@ def extract_tmp_call(ins): if isinstance(ins.called, Event): return EventCall(ins.called.name) + if isinstance(ins.called, Variable) and isinstance(ins.called.type, FunctionType): + return InternalDynamicCall(ins.lvalue, ins.called, ins.called.type) + raise Exception('Not extracted {} {}'.format(type(ins.called), ins)) def convert_expression(expression, node): @@ -621,7 +639,8 @@ def convert_expression(expression, node): assert isinstance(result[-1], (OperationWithLValue)) result.append(Condition(result[-1].lvalue)) elif node.type == NodeType.RETURN: - assert isinstance(result[-1], (OperationWithLValue)) - result.append(Return(result[-1].lvalue)) + # May return None + if isinstance(result[-1], (OperationWithLValue)): + result.append(Return(result[-1].lvalue)) return result diff --git a/slither/slithir/operations/__init__.py b/slither/slithir/operations/__init__.py index e4f4a459b..b97636d1d 100644 --- a/slither/slithir/operations/__init__.py +++ b/slither/slithir/operations/__init__.py @@ -8,6 +8,7 @@ from .high_level_call import HighLevelCall from .index import Index from .init_array import InitArray from .internal_call import InternalCall +from .internal_dynamic_call import InternalDynamicCall from .library_call import LibraryCall from .low_level_call import LowLevelCall from .lvalue import OperationWithLValue diff --git a/slither/slithir/operations/internal_dynamic_call.py b/slither/slithir/operations/internal_dynamic_call.py new file mode 100644 index 000000000..bed892156 --- /dev/null +++ b/slither/slithir/operations/internal_dynamic_call.py @@ -0,0 +1,44 @@ +from slither.core.declarations.function import Function +from slither.core.solidity_types import FunctionType +from slither.core.variables.variable import Variable +from slither.slithir.operations.call import Call +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.slithir.utils.utils import is_valid_lvalue + + +class InternalDynamicCall(Call, OperationWithLValue): + + def __init__(self, lvalue, function, function_type): + assert isinstance(function_type, FunctionType) + assert isinstance(function, Variable) + assert is_valid_lvalue(lvalue) + super(InternalDynamicCall, self).__init__() + self._function = function + self._function_type = function_type + self._lvalue = lvalue + + @property + def read(self): + return list(self.arguments) + + @property + def function(self): + return self._function + + @property + def function_type(self): + return self._function_type + + def __str__(self): + args = [str(a) for a in self.arguments] + if not self.lvalue: + lvalue = '' + elif isinstance(self.lvalue.type, (list,)): + lvalue = '{}({}) = '.format(self.lvalue, ','.join(str(x) for x in self.lvalue.type)) + else: + lvalue = '{}({}) = '.format(self.lvalue, self.lvalue.type) + txt = '{}INTERNAL_DYNAMIC_CALL, {}({})' + return txt.format(lvalue, + self.function.name, + ','.join(args)) + From 029e8531169d6071d9ef26b32b48ac7da97375f5 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 17 Oct 2018 10:51:53 +0100 Subject: [PATCH 138/308] slithir: Improve library lookup --- slither/slithir/convert.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 1b381457c..9e0fd21c6 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -203,7 +203,7 @@ def convert_to_low_level(ir): new_ir.lvalue.set_type(ElementaryType('bool')) return new_ir logger.error('Incorrect conversion to low level {}'.format(ir)) - exit(0) + exit(-1) def convert_to_push(ir): """ @@ -263,8 +263,7 @@ def convert_to_library(ir, node, using_for): if new_ir: return new_ir - logger.error('Library not found {}'.format(ir)) - exit(0) + return None def get_type(t): """ @@ -295,6 +294,15 @@ def convert_type_library_call(ir, lib_contract): if not func: func = lib_contract.get_state_variable_from_name(ir.function_name) # In case of multiple binding to the same type + if not func: + # specific lookup when the compiler does implicit conversion + # for example + # myFunc(uint) + # can be called with an uint8 + for function in lib_contract.functions: + if function.name == ir.function_name and len(function.parameters) == len(ir.arguments): + func = function + break if not func: return None ir.function = func @@ -371,28 +379,28 @@ def propagate_types(ir, node): elif isinstance(ir, HighLevelCall): t = ir.destination.type - # Temporary operaiton (they are removed later) + # Temporary operation (they are removed later) if t is None: return + # convert library if t in using_for: - return convert_to_library(ir, node, using_for) + new_ir = convert_to_library(ir, node, using_for) + if new_ir: + return new_ir if isinstance(t, UserDefinedType): # UserdefinedType - t = t.type - if isinstance(t, Contract): - contract = node.slither.get_contract_from_name(t.name) + t_type = t.type + if isinstance(t_type, Contract): + contract = node.slither.get_contract_from_name(t_type.name) return convert_type_of_high_level_call(ir, contract) - else: - return None # Convert HighLevelCall to LowLevelCall if isinstance(t, ElementaryType) and t.name == 'address': if ir.destination.name == 'this': return convert_type_of_high_level_call(ir, node.function.contract) - else: - return convert_to_low_level(ir) + return convert_to_low_level(ir) # Convert push operations # May need to insert a new operation @@ -489,7 +497,7 @@ def propagate_types(ir, node): pass else: logger.error('Not handling {} during type propgation'.format(type(ir))) - exit(0) + exit(-1) def apply_ir_heuristics(irs, node): """ From 105b594e47988a6bf5ecf426f6854337f3a5e63f Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 17 Oct 2018 10:56:04 +0100 Subject: [PATCH 139/308] Add is_constant to Variable --- slither/core/variables/variable.py | 5 +++++ slither/solc_parsing/variables/variable_declaration.py | 3 +++ 2 files changed, 8 insertions(+) diff --git a/slither/core/variables/variable.py b/slither/core/variables/variable.py index b80e56453..64373b33a 100644 --- a/slither/core/variables/variable.py +++ b/slither/core/variables/variable.py @@ -19,6 +19,7 @@ class Variable(SourceMapping): self._type = None self._initialized = None self._visibility = None + self._is_constant = False @property def expression(self): @@ -60,6 +61,10 @@ class Variable(SourceMapping): def type(self): return self._type + @property + def is_constant(self): + return self._is_constant + @property def visibility(self): ''' diff --git a/slither/solc_parsing/variables/variable_declaration.py b/slither/solc_parsing/variables/variable_declaration.py index 3e9601a6a..37bd899be 100644 --- a/slither/solc_parsing/variables/variable_declaration.py +++ b/slither/solc_parsing/variables/variable_declaration.py @@ -80,6 +80,9 @@ class VariableDeclarationSolc(Variable): self._initial_expression = None self._type = None + if 'constant' in attributes: + self._is_constant = attributes['constant'] + self._analyze_variable_attributes(attributes) if not var['children']: From c021ec92dd1c1c321ac019cf91bfd5b5961fe5ac Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 17 Oct 2018 13:38:32 +0100 Subject: [PATCH 140/308] If the length of an ArrayType is constant, use its value --- slither/core/solidity_types/array_type.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/slither/core/solidity_types/array_type.py b/slither/core/solidity_types/array_type.py index e6ce14abc..49cb825f1 100644 --- a/slither/core/solidity_types/array_type.py +++ b/slither/core/solidity_types/array_type.py @@ -1,3 +1,4 @@ +from slither.core.variables.variable import Variable from slither.core.solidity_types.type import Type from slither.core.expressions.expression import Expression @@ -21,6 +22,8 @@ class ArrayType(Type): def __str__(self): if self._length: + if isinstance(self._length.value, Variable) and self._length.value.is_constant: + return str(self._type)+'[{}]'.format(str(self._length.value.expression)) return str(self._type)+'[{}]'.format(str(self._length)) return str(self._type)+'[]' From 9ea0194c438d046224a53ade1d8710c0bd060f61 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 18 Oct 2018 09:24:35 +0100 Subject: [PATCH 141/308] SlitIR: reduce Assignment operator to pure assignment, convert others to Binary() --- slither/slithir/operations/__init__.py | 2 +- slither/slithir/operations/assignment.py | 84 +------------------ slither/slithir/operations/binary.py | 2 +- .../visitors/slithir/expression_to_slithir.py | 42 ++++++++-- 4 files changed, 41 insertions(+), 89 deletions(-) diff --git a/slither/slithir/operations/__init__.py b/slither/slithir/operations/__init__.py index b97636d1d..13200f273 100644 --- a/slither/slithir/operations/__init__.py +++ b/slither/slithir/operations/__init__.py @@ -1,4 +1,4 @@ -from .assignment import Assignment, AssignmentType +from .assignment import Assignment from .binary import Binary, BinaryType from .call import Call from .condition import Condition diff --git a/slither/slithir/operations/assignment.py b/slither/slithir/operations/assignment.py index 19ba53480..ad4b9d7fe 100644 --- a/slither/slithir/operations/assignment.py +++ b/slither/slithir/operations/assignment.py @@ -8,90 +8,16 @@ from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue logger = logging.getLogger("AssignmentOperationIR") -class AssignmentType(object): - ASSIGN = 0 # = - ASSIGN_OR = 1 # |= - ASSIGN_CARET = 2 # ^= - ASSIGN_AND = 3 # &= - ASSIGN_LEFT_SHIFT = 4 # <<= - ASSIGN_RIGHT_SHIFT = 5 # >>= - ASSIGN_ADDITION = 6 # += - ASSIGN_SUBTRACTION = 7 # -= - ASSIGN_MULTIPLICATION = 8 # *= - ASSIGN_DIVISION = 9 # /= - ASSIGN_MODULO = 10 # %= - - @staticmethod - def get_type(operation_type): - if operation_type == '=': - return AssignmentType.ASSIGN - if operation_type == '|=': - return AssignmentType.ASSIGN_OR - if operation_type == '^=': - return AssignmentType.ASSIGN_CARET - if operation_type == '&=': - return AssignmentType.ASSIGN_AND - if operation_type == '<<=': - return AssignmentType.ASSIGN_LEFT_SHIFT - if operation_type == '>>=': - return AssignmentType.ASSIGN_RIGHT_SHIFT - if operation_type == '+=': - return AssignmentType.ASSIGN_ADDITION - if operation_type == '-=': - return AssignmentType.ASSIGN_SUBTRACTION - if operation_type == '*=': - return AssignmentType.ASSIGN_MULTIPLICATION - if operation_type == '/=': - return AssignmentType.ASSIGN_DIVISION - if operation_type == '%=': - return AssignmentType.ASSIGN_MODULO - - logger.error('get_type: Unknown operation type {})'.format(operation_type)) - exit(-1) - - @staticmethod - def str(operation_type): - if operation_type == AssignmentType.ASSIGN: - return '=' - if operation_type == AssignmentType.ASSIGN_OR: - return '|=' - if operation_type == AssignmentType.ASSIGN_CARET: - return '^=' - if operation_type == AssignmentType.ASSIGN_AND: - return '&=' - if operation_type == AssignmentType.ASSIGN_LEFT_SHIFT: - return '<<=' - if operation_type == AssignmentType.ASSIGN_RIGHT_SHIFT: - return '>>=' - if operation_type == AssignmentType.ASSIGN_ADDITION: - return '+=' - if operation_type == AssignmentType.ASSIGN_SUBTRACTION: - return '-=' - if operation_type == AssignmentType.ASSIGN_MULTIPLICATION: - return '*=' - if operation_type == AssignmentType.ASSIGN_DIVISION: - return '/=' - if operation_type == AssignmentType.ASSIGN_MODULO: - return '%=' - - logger.error('str: Unknown operation type {})'.format(operation_type)) - exit(-1) - class Assignment(OperationWithLValue): - def __init__(self, left_variable, right_variable, variable_type, variable_return_type): - #print(type(right_variable)) - #print(type(left_variable)) + def __init__(self, left_variable, right_variable, variable_return_type): assert is_valid_lvalue(left_variable) - assert is_valid_rvalue(right_variable) or\ - (isinstance(right_variable, (Function, TupleVariable)) and variable_type == AssignmentType.ASSIGN) + assert is_valid_rvalue(right_variable) or isinstance(right_variable, (Function, TupleVariable)) super(Assignment, self).__init__() self._variables = [left_variable, right_variable] self._lvalue = left_variable self._rvalue = right_variable - self._type = variable_type self._variable_return_type = variable_return_type - #left_variable.set_type(right_variable.type) @property def variables(self): @@ -109,9 +35,5 @@ class Assignment(OperationWithLValue): def rvalue(self): return self._rvalue - @property - def type_str(self): - return AssignmentType.str(self._type) - def __str__(self): - return '{} {} {}'.format(self.lvalue, self.type_str, self.rvalue) + return '{} := {}'.format(self.lvalue, self.rvalue) diff --git a/slither/slithir/operations/binary.py b/slither/slithir/operations/binary.py index 5a770f417..1485f01b0 100644 --- a/slither/slithir/operations/binary.py +++ b/slither/slithir/operations/binary.py @@ -13,7 +13,7 @@ class BinaryType(object): ADDITION = 4 # + SUBTRACTION = 5 # - LEFT_SHIFT = 6 # << - RIGHT_SHIT = 7 # >>> + RIGHT_SHIT = 7 # >> AND = 8 # & CARET = 9 # ^ OR = 10 # | diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index d030f2fb1..3dfe07ce9 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -1,3 +1,4 @@ +import logging from slither.core.declarations import Function, Structure from slither.core.expressions import (AssignmentOperationType, @@ -5,8 +6,8 @@ from slither.core.expressions import (AssignmentOperationType, from slither.core.solidity_types.array_type import ArrayType from slither.slithir.operations import (Assignment, Binary, BinaryType, Delete, Index, InitArray, InternalCall, Member, - TypeConversion, Unary, Unpack, NewContract, - NewStructure, NewArray) + NewArray, NewContract, NewStructure, + TypeConversion, Unary, Unpack) from slither.slithir.tmp_operations.argument import Argument from slither.slithir.tmp_operations.tmp_call import TmpCall from slither.slithir.tmp_operations.tmp_new_array import TmpNewArray @@ -18,6 +19,8 @@ from slither.slithir.variables import (Constant, ReferenceVariable, TemporaryVariable, TupleVariable) from slither.visitors.expression.expression import ExpressionVisitor +logger = logging.getLogger("VISTIOR:ExpressionToSlithIR") + key = 'expressionToSlithIR' def get(expression): @@ -29,6 +32,33 @@ def get(expression): def set_val(expression, val): expression.context[key] = val +def convert_assignment(left, right, t, return_type): + if t == AssignmentOperationType.ASSIGN: + return Assignment(left, right, return_type) + elif t == AssignmentOperationType.ASSIGN_OR: + return Binary(left, left, right, BinaryType.OR) + elif t == AssignmentOperationType.ASSIGN_CARET: + return Binary(left, left, right, BinaryType.CARET) + elif t == AssignmentOperationType.ASSIGN_AND: + return Binary(left, left, right, BinaryType.AND) + elif t == AssignmentOperationType.ASSIGN_LEFT_SHIFT: + return Binary(left, left, right, BinaryType.LEFT_SHIFT) + elif t == AssignmentOperationType.ASSIGN_RIGHT_SHIFT: + return Binary(left, left, right, BinaryType.RIGHT_SHIT) + elif t == AssignmentOperationType.ASSIGN_ADDITION: + return Binary(left, left, right, BinaryType.ADDITION) + elif t == AssignmentOperationType.ASSIGN_SUBTRACTION: + return Binary(left, left, right, BinaryType.SUBTRACTION) + elif t == AssignmentOperationType.ASSIGN_MULTIPLICATION: + return Binary(left, left, right, BinaryType.MULTIPLICATION) + elif t == AssignmentOperationType.ASSIGN_DIVISION: + return Binary(left, left, right, BinaryType.DIVISION) + elif t == AssignmentOperationType.ASSIGN_MODULO: + return Binary(left, left, right, BinaryType.MODULO) + + logger.error('Missing type during assignment conversion') + exit(-1) + class ExpressionToSlithIR(ExpressionVisitor): def __init__(self, expression): @@ -47,7 +77,7 @@ class ExpressionToSlithIR(ExpressionVisitor): assert len(left) == len(right) for idx in range(len(left)): if not left[idx] is None: - operation = Assignment(left[idx], right[idx], expression.type, expression.expression_return_type) + operation = convert_assignment(left[idx], right[idx], expression.type, expression.expression_return_type) self._result.append(operation) set_val(expression, None) else: @@ -65,7 +95,7 @@ class ExpressionToSlithIR(ExpressionVisitor): self._result.append(operation) set_val(expression, left) else: - operation = Assignment(left, right, expression.type, expression.expression_return_type) + operation = convert_assignment(left, right, expression.type, expression.expression_return_type) self._result.append(operation) # Return left to handle # a = b = 1; @@ -192,14 +222,14 @@ class ExpressionToSlithIR(ExpressionVisitor): set_val(expression, value) elif expression.type in [UnaryOperationType.PLUSPLUS_POST]: lvalue = TemporaryVariable() - operation = Assignment(lvalue, value, AssignmentOperationType.ASSIGN, value.type) + operation = Assignment(lvalue, value, value.type) self._result.append(operation) operation = Binary(value, value, Constant("1"), BinaryType.ADDITION) self._result.append(operation) set_val(expression, lvalue) elif expression.type in [UnaryOperationType.MINUSMINUS_POST]: lvalue = TemporaryVariable() - operation = Assignment(lvalue, value, AssignmentOperationType.ASSIGN, value.type) + operation = Assignment(lvalue, value, value.type) self._result.append(operation) operation = Binary(value, value, Constant("1"), BinaryType.SUBTRACTION) self._result.append(operation) From cb56e2808532874f196103fcf2a895ed0986c447 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 18 Oct 2018 11:14:33 +0100 Subject: [PATCH 142/308] Update README --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d2ded9a68..3e4c5c9c8 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Slither is a Solidity static analysis framework written in Python 3. It runs a s * Detector API to write custom analyses in Python * Ability to analyze contracts written with Solidity > 0.4 -Support for advanced value- and taint-tracking is [coming soon](https://github.com/trailofbits/slither/issues/6)! +Slither possesses it's own intermediate representation, called [SlithIR](https://github.com/trailofbits/slither/wiki/SlithIR). ## Usage @@ -83,7 +83,7 @@ $ python setup.py install ## Getting Help -Feel free to stop by our [Slack channel](https://empirehacking.slack.com/messages/C7KKY517H/) for help using or extending Slither. +Feel free to stop by our [Slack channel](https://empireslacking.herokuapp.com) (#ethereum) for help using or extending Slither. * The [Printer documentation](https://github.com/trailofbits/slither/wiki/Printer-documentation) describes the information Slither is capable of visualizing for each contract. @@ -91,6 +91,8 @@ Feel free to stop by our [Slack channel](https://empirehacking.slack.com/message * The [API documentation](https://github.com/trailofbits/slither/wiki/API-examples) describes the methods and objects available for custom analyses. +* The [SlithIR documentation](https://github.com/trailofbits/slither/wiki/SlithIR) describes the SlithIR intermediate representation. + ## License Slither is licensed and distributed under the AGPLv3 license. [Contact us](mailto:opensource@trailofbits.com) if you're looking for an exception to the terms. From 1c5f48affe8d6967358c123e6753d84cc69209a4 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 18 Oct 2018 11:30:37 +0100 Subject: [PATCH 143/308] Add slithir printer testcase --- examples/printers/slihtir.sol | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 examples/printers/slihtir.sol diff --git a/examples/printers/slihtir.sol b/examples/printers/slihtir.sol new file mode 100644 index 000000000..4f898d817 --- /dev/null +++ b/examples/printers/slihtir.sol @@ -0,0 +1,27 @@ +pragma solidity ^0.4.24; + +library UnsafeMath{ + + function add(uint a, uint b) public pure returns(uint){ + return a + b; + } + + function min(uint a, uint b) public pure returns(uint){ + return a - b; + } +} + +contract MyContract{ + using UnsafeMath for uint; + + mapping(address => uint) balances; + + function transfer(address to, uint val) public{ + + balances[msg.sender] = balances[msg.sender].min(val); + balances[to] = balances[to].add(val); + + } + + +} From 0352b0652ade524857d5ae923c35409cf1727663 Mon Sep 17 00:00:00 2001 From: akhavr Date: Thu, 18 Oct 2018 14:02:42 +0300 Subject: [PATCH 144/308] Fix typo in the installation instruction --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e4c5c9c8..27f845e3c 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ or --> ```bash -$ git clone https://github.com/trailofbits/slither.git & cd slither +$ git clone https://github.com/trailofbits/slither.git && cd slither $ python setup.py install ``` From b069d9340ef10ae5529b0b31312b5eb0869300cb Mon Sep 17 00:00:00 2001 From: Cryptomental Date: Thu, 18 Oct 2018 12:21:59 +0200 Subject: [PATCH 145/308] detectors: Add assembly detector. Detect functions using inline assembly. Refs: https://github.com/trailofbits/slither/issues/26 --- scripts/travis_test.sh | 9 ++++ slither/__main__.py | 4 +- slither/detectors/statements/assembly.py | 59 ++++++++++++++++++++++++ tests/inline_assembly_contract.sol | 22 +++++++++ tests/inline_assembly_library.sol | 49 ++++++++++++++++++++ 5 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 slither/detectors/statements/assembly.py create mode 100644 tests/inline_assembly_contract.sol create mode 100644 tests/inline_assembly_library.sol diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 45c005d6f..d8e49f558 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -53,6 +53,15 @@ if [ $? -ne 2 ]; then exit 1 fi +slither tests/inline_assembly_contract.sol --disable-solc-warnings +if [ $? -ne 2 ]; then + exit 1 +fi + +slither tests/inline_assembly_library.sol --disable-solc-warnings +if [ $? -ne 3 ]; then + exit 1 +fi ### Test scripts diff --git a/slither/__main__.py b/slither/__main__.py index 4fb1fa9c3..4fb2b387f 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -100,6 +100,7 @@ def main(): from slither.detectors.variables.uninitialized_storage_variables import UninitializedStorageVars from slither.detectors.variables.unused_state_variables import UnusedStateVars from slither.detectors.statements.tx_origin import TxOrigin + from slither.detectors.statements.assembly import Assembly detectors = [Backdoor, UninitializedStateVarsDetection, @@ -111,7 +112,8 @@ def main(): ArbitrarySend, Suicidal, UnusedStateVars, - TxOrigin] + TxOrigin, + Assembly] from slither.printers.summary.summary import PrinterSummary from slither.printers.summary.quick_summary import PrinterQuickSummary diff --git a/slither/detectors/statements/assembly.py b/slither/detectors/statements/assembly.py new file mode 100644 index 000000000..8d47a074a --- /dev/null +++ b/slither/detectors/statements/assembly.py @@ -0,0 +1,59 @@ +""" +Module detecting usage of inline assembly +""" + +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.core.cfg.node import NodeType + + +class Assembly(AbstractDetector): + """ + Detect usage of inline assembly + """ + + ARGUMENT = 'assembly' + HELP = 'assembly usage' + IMPACT = DetectorClassification.INFORMATIONAL + CONFIDENCE = DetectorClassification.HIGH + + @staticmethod + def _contains_inline_assembly_use(node): + """ + Check if the node contains ASSEMBLY type + Returns: + (bool) + """ + return node.type == NodeType.ASSEMBLY + + def detect_assembly(self, contract): + ret = [] + for f in contract.functions: + nodes = f.nodes + assembly_nodes = [n for n in nodes if + self._contains_inline_assembly_use(n)] + if assembly_nodes: + ret.append((f, assembly_nodes)) + return ret + + def detect(self): + """ Detect the functions that use inline assembly + """ + results = [] + for c in self.contracts: + values = self.detect_assembly(c) + for func, nodes in values: + func_name = func.name + info = "Assembly in %s, Contract: %s, Function: %s" % (self.filename, + c.name, + func_name) + self.log(info) + + sourceMapping = [n.source_mapping for n in nodes] + + results.append({'vuln': 'Assembly', + 'sourceMapping': sourceMapping, + 'filename': self.filename, + 'contract': c.name, + 'function_name': func_name}) + + return results diff --git a/tests/inline_assembly_contract.sol b/tests/inline_assembly_contract.sol new file mode 100644 index 000000000..fd5aa7942 --- /dev/null +++ b/tests/inline_assembly_contract.sol @@ -0,0 +1,22 @@ +pragma solidity ^0.4.0; + +// taken from https://solidity.readthedocs.io/en/v0.4.25/assembly.html + +library GetCode { + function at(address _addr) public view returns (bytes o_code) { + assembly { + // retrieve the size of the code, this needs assembly + let size := extcodesize(_addr) + // allocate output byte array - this could also be done without assembly + // by using o_code = new bytes(size) + o_code := mload(0x40) + // new "memory end" including padding + mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) + // store length in memory + mstore(o_code, size) + // actually retrieve the code, this needs assembly + extcodecopy(_addr, add(o_code, 0x20), 0, size) + } + } +} + diff --git a/tests/inline_assembly_library.sol b/tests/inline_assembly_library.sol new file mode 100644 index 000000000..ce7aef7a5 --- /dev/null +++ b/tests/inline_assembly_library.sol @@ -0,0 +1,49 @@ +pragma solidity ^0.4.16; + +// taken from https://solidity.readthedocs.io/en/v0.4.25/assembly.html + +library VectorSum { + // This function is less efficient because the optimizer currently fails to + // remove the bounds checks in array access. + function sumSolidity(uint[] _data) public view returns (uint o_sum) { + for (uint i = 0; i < _data.length; ++i) + o_sum += _data[i]; + } + + // We know that we only access the array in bounds, so we can avoid the check. + // 0x20 needs to be added to an array because the first slot contains the + // array length. + function sumAsm(uint[] _data) public view returns (uint o_sum) { + for (uint i = 0; i < _data.length; ++i) { + assembly { + o_sum := add(o_sum, mload(add(add(_data, 0x20), mul(i, 0x20)))) + } + } + } + + // Same as above, but accomplish the entire code within inline assembly. + function sumPureAsm(uint[] _data) public view returns (uint o_sum) { + assembly { + // Load the length (first 32 bytes) + let len := mload(_data) + + // Skip over the length field. + // + // Keep temporary variable so it can be incremented in place. + // + // NOTE: incrementing _data would result in an unusable + // _data variable after this assembly block + let data := add(_data, 0x20) + + // Iterate until the bound is not met. + for + { let end := add(data, len) } + lt(data, end) + { data := add(data, 0x20) } + { + o_sum := add(o_sum, mload(data)) + } + } + } +} + From 495de912178c96ea24f4c06e36ccc4f86935804a Mon Sep 17 00:00:00 2001 From: redshark1802 Date: Thu, 18 Oct 2018 14:42:29 +0200 Subject: [PATCH 146/308] add Naming Convention Detector --- scripts/travis_test.sh | 10 +- slither/__main__.py | 4 +- .../detectors/naming_convention/__init__.py | 0 .../naming_convention/naming_convention.py | 154 ++++++++++++++++++ tests/naming_convention.sol | 60 +++++++ 5 files changed, 224 insertions(+), 4 deletions(-) create mode 100644 slither/detectors/naming_convention/__init__.py create mode 100644 slither/detectors/naming_convention/naming_convention.py create mode 100644 tests/naming_convention.sol diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 45c005d6f..20f8ab75f 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -9,7 +9,7 @@ fi # contains also the test for the suicidal detector slither tests/backdoor.sol --disable-solc-warnings -if [ $? -ne 2 ]; then +if [ $? -ne 3 ]; then exit 1 fi @@ -24,7 +24,7 @@ if [ $? -ne 1 ]; then fi slither tests/reentrancy.sol --disable-solc-warnings -if [ $? -ne 1 ]; then +if [ $? -ne 4 ]; then exit 1 fi @@ -53,6 +53,10 @@ if [ $? -ne 2 ]; then exit 1 fi +slither tests/naming_convention.sol --disable-solc-warnings +if [ $? -ne 10 ]; then + exit 1 +fi ### Test scripts @@ -71,4 +75,4 @@ python examples/scripts/variable_in_condition.py examples/scripts/variable_in_co if [ $? -ne 0 ]; then exit 1 fi -exit 0 +exit 0 \ No newline at end of file diff --git a/slither/__main__.py b/slither/__main__.py index 4fb1fa9c3..35b472b4c 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -100,6 +100,7 @@ def main(): from slither.detectors.variables.uninitialized_storage_variables import UninitializedStorageVars from slither.detectors.variables.unused_state_variables import UnusedStateVars from slither.detectors.statements.tx_origin import TxOrigin + from slither.detectors.naming_convention.naming_convention import NamingConvention detectors = [Backdoor, UninitializedStateVarsDetection, @@ -111,7 +112,8 @@ def main(): ArbitrarySend, Suicidal, UnusedStateVars, - TxOrigin] + TxOrigin, + NamingConvention] from slither.printers.summary.summary import PrinterSummary from slither.printers.summary.quick_summary import PrinterQuickSummary diff --git a/slither/detectors/naming_convention/__init__.py b/slither/detectors/naming_convention/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py new file mode 100644 index 000000000..d29265e88 --- /dev/null +++ b/slither/detectors/naming_convention/naming_convention.py @@ -0,0 +1,154 @@ +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +import re + + +class NamingConvention(AbstractDetector): + """ + Check if naming conventions are followed + https://solidity.readthedocs.io/en/v0.4.25/style-guide.html?highlight=naming_convention%20convention#naming_convention-conventions + """ + + ARGUMENT = 'naming-convention' + HELP = 'naming convention violations' + IMPACT = DetectorClassification.INFORMATIONAL + CONFIDENCE = DetectorClassification.INFORMATIONAL + + @staticmethod + def is_cap_words(name): + return re.search('^[A-Z]([A-Za-z0-9]+)?_?$', name) is not None + + @staticmethod + def is_mixed_case(name): + return re.search('^[a-z]([A-Za-z0-9]+)?_?$', name) is not None + + @staticmethod + def is_upper_case_with_underscores(name): + return re.search('^[A-Z0-9_]+_?$', name) is not None + + @staticmethod + def should_avoid_name(name): + return re.search('^[lOI]$', name) is not None + + def detect(self): + + results = [] + for contract in self.contracts: + + if self.is_cap_words(contract.name) is False: + info = " Contract '{}' is not in CapWords".format(contract.name) + self.log(info) + + results.append({'vuln': 'NamingConvention', + 'filename': self.filename, + 'contract': contract.name, + 'sourceMapping': contract.source_mapping}) + + for struct in contract.structures: + + if self.is_cap_words(struct.name) is False: + info = " Struct '{}' is not in CapWords, Contract: '{}' ".format(struct.name, contract.name) + self.log(info) + + results.append({'vuln': 'NamingConvention', + 'filename': self.filename, + 'contract': contract.name, + 'struct': struct.name, + 'sourceMapping': struct.source_mapping}) + + for event in contract.events: + + if self.is_cap_words(event.name) is False: + info = " Event '{}' is not in CapWords, Contract: '{}' ".format(event.name, contract.name) + self.log(info) + + results.append({'vuln': 'NamingConvention', + 'filename': self.filename, + 'contract': contract.name, + 'event': event.name, + 'sourceMapping': event.source_mapping}) + + for func in contract.functions: + + if self.is_mixed_case(func.name) is False: + info = " Function '{}' is not in mixedCase, Contract: '{}' ".format(func.name, contract.name) + self.log(info) + + results.append({'vuln': 'NamingConvention', + 'filename': self.filename, + 'contract': contract.name, + 'function': func.name, + 'sourceMapping': func.source_mapping}) + + for argument in func.parameters: + + if self.is_mixed_case(argument.name) is False: + info = " Parameter '{}' is not in mixedCase, Contract: '{}', Function: '{}'' " \ + .format(argument.name, argument.name, contract.name) + self.log(info) + + results.append({'vuln': 'NamingConvention', + 'filename': self.filename, + 'contract': contract.name, + 'function': func.name, + 'argument': argument.name, + 'sourceMapping': argument.source_mapping}) + + for var in contract.state_variables: + + if self.should_avoid_name(var.name): + if self.is_upper_case_with_underscores(var.name) is False: + info = " Variable '{}' l, O, I should not be used, Contract: '{}' " \ + .format(var.name, contract.name) + self.log(info) + + results.append({'vuln': 'NamingConvention', + 'filename': self.filename, + 'contract': contract.name, + 'constant': var.name, + 'sourceMapping': var.source_mapping}) + + if var.is_constant is True: + if self.is_upper_case_with_underscores(var.name) is False: + info = " Constant '{}' is not in UPPER_CASE_WITH_UNDERSCORES, Contract: '{}' " \ + .format(var.name, contract.name) + self.log(info) + + results.append({'vuln': 'NamingConvention', + 'filename': self.filename, + 'contract': contract.name, + 'constant': var.name, + 'sourceMapping': var.source_mapping}) + else: + if self.is_mixed_case(var.name) is False: + info = " Variable '{}' is not in mixedCase, Contract: '{}' ".format(var.name, contract.name) + self.log(info) + + results.append({'vuln': 'NamingConvention', + 'filename': self.filename, + 'contract': contract.name, + 'variable': var.name, + 'sourceMapping': var.source_mapping}) + + for enum in contract.enums: + if self.is_cap_words(enum.name) is False: + info = " Enum '{}' is not in CapWords, Contract: '{}' ".format(enum.name, contract.name) + self.log(info) + + results.append({'vuln': 'NamingConvention', + 'filename': self.filename, + 'contract': contract.name, + 'enum': enum.name, + 'sourceMapping': enum.source_mapping}) + + for modifier in contract.modifiers: + if self.is_mixed_case(modifier.name) is False: + info = " Modifier '{}' is not in mixedCase, Contract: '{}' ".format(modifier.name, contract.name) + self.log(info) + + results.append({'vuln': 'NamingConvention', + 'filename': self.filename, + 'contract': contract.name, + 'modifier': modifier.name, + 'sourceMapping': modifier.source_mapping}) + + return results diff --git a/tests/naming_convention.sol b/tests/naming_convention.sol new file mode 100644 index 000000000..769739052 --- /dev/null +++ b/tests/naming_convention.sol @@ -0,0 +1,60 @@ +pragma solidity ^0.4.24; + +contract naming { + + enum Numbers {ONE, TWO} + enum numbers {ONE, TWO} + + uint constant MY_CONSTANT = 1; + uint constant MY_other_CONSTANT = 2; + + uint Var_One = 1; + uint varTwo = 2; + + struct test { + + } + + struct Test { + + } + + event Event_(uint); + event event_(uint); + + function getOne() constant returns (uint) + { + return 1; + } + + function GetOne() constant returns (uint) + { + return 1; + } + + function setInt(uint number1, uint Number2) + { + + } + + + modifier CantDo() { + _; + } + + modifier canDo() { + _; + } +} + +contract Test { + +} + +contract T { + uint k = 1; + + uint constant M = 1; + + uint l = 1; +} From c3c8e74ee90dfc60b860cc68647bb81fe50c7588 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 18 Oct 2018 14:17:57 +0100 Subject: [PATCH 147/308] Rename printers --- slither/__main__.py | 8 ++++---- .../printers/summary/{quick_summary.py => contract.py} | 6 +++--- slither/printers/summary/{summary.py => function.py} | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) rename slither/printers/summary/{quick_summary.py => contract.py} (88%) rename slither/printers/summary/{summary.py => function.py} (95%) diff --git a/slither/__main__.py b/slither/__main__.py index 4fb1fa9c3..7735b4423 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -113,14 +113,14 @@ def main(): UnusedStateVars, TxOrigin] - from slither.printers.summary.summary import PrinterSummary - from slither.printers.summary.quick_summary import PrinterQuickSummary + from slither.printers.summary.function import FunctionSummary + from slither.printers.summary.contract import ContractSummary from slither.printers.inheritance.inheritance import PrinterInheritance from slither.printers.functions.authorization import PrinterWrittenVariablesAndAuthorization from slither.printers.summary.slithir import PrinterSlithIR - printers = [PrinterSummary, - PrinterQuickSummary, + printers = [FunctionSummary, + ContractSummary, PrinterInheritance, PrinterWrittenVariablesAndAuthorization, PrinterSlithIR] diff --git a/slither/printers/summary/quick_summary.py b/slither/printers/summary/contract.py similarity index 88% rename from slither/printers/summary/quick_summary.py rename to slither/printers/summary/contract.py index f51ea87a4..bbcf1e9b3 100644 --- a/slither/printers/summary/quick_summary.py +++ b/slither/printers/summary/contract.py @@ -5,10 +5,10 @@ from slither.printers.abstract_printer import AbstractPrinter from slither.utils.colors import blue, green, magenta -class PrinterQuickSummary(AbstractPrinter): +class ContractSummary(AbstractPrinter): - ARGUMENT = 'quick-summary' - HELP = 'a quick summary of the contract' + ARGUMENT = 'contract-summary' + HELP = 'a summary of the contract' def output(self, _filename): """ diff --git a/slither/printers/summary/summary.py b/slither/printers/summary/function.py similarity index 95% rename from slither/printers/summary/summary.py rename to slither/printers/summary/function.py index d34d3f152..1fca36b68 100644 --- a/slither/printers/summary/summary.py +++ b/slither/printers/summary/function.py @@ -5,10 +5,10 @@ from prettytable import PrettyTable from slither.printers.abstract_printer import AbstractPrinter -class PrinterSummary(AbstractPrinter): +class FunctionSummary(AbstractPrinter): - ARGUMENT = 'summary' - HELP = 'the summary of the contract' + ARGUMENT = 'function-summary' + HELP = 'the summary of the functions' @staticmethod def _convert(l): From 6e3b7c2db53766addde113e53cb5c2aabb9e6359 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 18 Oct 2018 14:21:14 +0100 Subject: [PATCH 148/308] Update README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e4c5c9c8..e4934b32b 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,9 @@ Check | Purpose | Impact | Confidence `--detect-suicidal`| Detect suicidal functions | High | High `--detect-uninitialized-state`| Detect uninitialized state variables | High | High `--detect-uninitialized-storage`| Detect uninitialized storage variables | High | High -`--detect-locked-ether`| Detect contracts with payable functions that do not send ether | Medium | High +`--detect-locked-ether`| Detect contracts with a payable function that do not send ether | Medium | High `--detect-tx-origin`| Detect dangerous usage of `tx.origin` | Medium | Medium +`--detect-assembly`| Detect assembly usage | Informational | High `--detect-pragma`| Detect if different pragma directives are used | Informational | High `--detect-solc-version`| Detect if an old version of Solidity used (<0.4.23) | Informational | High `--detect-unused-state`| Detect unused state variables | Informational | High From 1d6cebd9919b87431779fa6379c607be9cef7b90 Mon Sep 17 00:00:00 2001 From: Cryptomental Date: Thu, 18 Oct 2018 12:21:59 +0200 Subject: [PATCH 149/308] detectors: Add assembly detector. Detect functions using inline assembly. Refs: https://github.com/trailofbits/slither/issues/26 --- scripts/travis_test.sh | 9 ++++ slither/__main__.py | 4 +- slither/detectors/statements/assembly.py | 59 ++++++++++++++++++++++++ tests/inline_assembly_contract.sol | 22 +++++++++ tests/inline_assembly_library.sol | 49 ++++++++++++++++++++ 5 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 slither/detectors/statements/assembly.py create mode 100644 tests/inline_assembly_contract.sol create mode 100644 tests/inline_assembly_library.sol diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 45c005d6f..d8e49f558 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -53,6 +53,15 @@ if [ $? -ne 2 ]; then exit 1 fi +slither tests/inline_assembly_contract.sol --disable-solc-warnings +if [ $? -ne 2 ]; then + exit 1 +fi + +slither tests/inline_assembly_library.sol --disable-solc-warnings +if [ $? -ne 3 ]; then + exit 1 +fi ### Test scripts diff --git a/slither/__main__.py b/slither/__main__.py index 4fb1fa9c3..4fb2b387f 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -100,6 +100,7 @@ def main(): from slither.detectors.variables.uninitialized_storage_variables import UninitializedStorageVars from slither.detectors.variables.unused_state_variables import UnusedStateVars from slither.detectors.statements.tx_origin import TxOrigin + from slither.detectors.statements.assembly import Assembly detectors = [Backdoor, UninitializedStateVarsDetection, @@ -111,7 +112,8 @@ def main(): ArbitrarySend, Suicidal, UnusedStateVars, - TxOrigin] + TxOrigin, + Assembly] from slither.printers.summary.summary import PrinterSummary from slither.printers.summary.quick_summary import PrinterQuickSummary diff --git a/slither/detectors/statements/assembly.py b/slither/detectors/statements/assembly.py new file mode 100644 index 000000000..8d47a074a --- /dev/null +++ b/slither/detectors/statements/assembly.py @@ -0,0 +1,59 @@ +""" +Module detecting usage of inline assembly +""" + +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.core.cfg.node import NodeType + + +class Assembly(AbstractDetector): + """ + Detect usage of inline assembly + """ + + ARGUMENT = 'assembly' + HELP = 'assembly usage' + IMPACT = DetectorClassification.INFORMATIONAL + CONFIDENCE = DetectorClassification.HIGH + + @staticmethod + def _contains_inline_assembly_use(node): + """ + Check if the node contains ASSEMBLY type + Returns: + (bool) + """ + return node.type == NodeType.ASSEMBLY + + def detect_assembly(self, contract): + ret = [] + for f in contract.functions: + nodes = f.nodes + assembly_nodes = [n for n in nodes if + self._contains_inline_assembly_use(n)] + if assembly_nodes: + ret.append((f, assembly_nodes)) + return ret + + def detect(self): + """ Detect the functions that use inline assembly + """ + results = [] + for c in self.contracts: + values = self.detect_assembly(c) + for func, nodes in values: + func_name = func.name + info = "Assembly in %s, Contract: %s, Function: %s" % (self.filename, + c.name, + func_name) + self.log(info) + + sourceMapping = [n.source_mapping for n in nodes] + + results.append({'vuln': 'Assembly', + 'sourceMapping': sourceMapping, + 'filename': self.filename, + 'contract': c.name, + 'function_name': func_name}) + + return results diff --git a/tests/inline_assembly_contract.sol b/tests/inline_assembly_contract.sol new file mode 100644 index 000000000..fd5aa7942 --- /dev/null +++ b/tests/inline_assembly_contract.sol @@ -0,0 +1,22 @@ +pragma solidity ^0.4.0; + +// taken from https://solidity.readthedocs.io/en/v0.4.25/assembly.html + +library GetCode { + function at(address _addr) public view returns (bytes o_code) { + assembly { + // retrieve the size of the code, this needs assembly + let size := extcodesize(_addr) + // allocate output byte array - this could also be done without assembly + // by using o_code = new bytes(size) + o_code := mload(0x40) + // new "memory end" including padding + mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) + // store length in memory + mstore(o_code, size) + // actually retrieve the code, this needs assembly + extcodecopy(_addr, add(o_code, 0x20), 0, size) + } + } +} + diff --git a/tests/inline_assembly_library.sol b/tests/inline_assembly_library.sol new file mode 100644 index 000000000..ce7aef7a5 --- /dev/null +++ b/tests/inline_assembly_library.sol @@ -0,0 +1,49 @@ +pragma solidity ^0.4.16; + +// taken from https://solidity.readthedocs.io/en/v0.4.25/assembly.html + +library VectorSum { + // This function is less efficient because the optimizer currently fails to + // remove the bounds checks in array access. + function sumSolidity(uint[] _data) public view returns (uint o_sum) { + for (uint i = 0; i < _data.length; ++i) + o_sum += _data[i]; + } + + // We know that we only access the array in bounds, so we can avoid the check. + // 0x20 needs to be added to an array because the first slot contains the + // array length. + function sumAsm(uint[] _data) public view returns (uint o_sum) { + for (uint i = 0; i < _data.length; ++i) { + assembly { + o_sum := add(o_sum, mload(add(add(_data, 0x20), mul(i, 0x20)))) + } + } + } + + // Same as above, but accomplish the entire code within inline assembly. + function sumPureAsm(uint[] _data) public view returns (uint o_sum) { + assembly { + // Load the length (first 32 bytes) + let len := mload(_data) + + // Skip over the length field. + // + // Keep temporary variable so it can be incremented in place. + // + // NOTE: incrementing _data would result in an unusable + // _data variable after this assembly block + let data := add(_data, 0x20) + + // Iterate until the bound is not met. + for + { let end := add(data, len) } + lt(data, end) + { data := add(data, 0x20) } + { + o_sum := add(o_sum, mload(data)) + } + } + } +} + From 1987fe0e8b5e3d4889261be5abd444883c387041 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 18 Oct 2018 14:21:14 +0100 Subject: [PATCH 150/308] Update README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 27f845e3c..4cea1c4f9 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,9 @@ Check | Purpose | Impact | Confidence `--detect-suicidal`| Detect suicidal functions | High | High `--detect-uninitialized-state`| Detect uninitialized state variables | High | High `--detect-uninitialized-storage`| Detect uninitialized storage variables | High | High -`--detect-locked-ether`| Detect contracts with payable functions that do not send ether | Medium | High +`--detect-locked-ether`| Detect contracts with a payable function that do not send ether | Medium | High `--detect-tx-origin`| Detect dangerous usage of `tx.origin` | Medium | Medium +`--detect-assembly`| Detect assembly usage | Informational | High `--detect-pragma`| Detect if different pragma directives are used | Informational | High `--detect-solc-version`| Detect if an old version of Solidity used (<0.4.23) | Informational | High `--detect-unused-state`| Detect unused state variables | Informational | High From a0cb5ecf83177f825d0496207871b864efb49ed0 Mon Sep 17 00:00:00 2001 From: redshark1802 Date: Thu, 18 Oct 2018 16:27:06 +0200 Subject: [PATCH 151/308] fix test --- README.md | 1 + scripts/travis_test.sh | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4cea1c4f9..d7dfc1843 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ Check | Purpose | Impact | Confidence `--detect-pragma`| Detect if different pragma directives are used | Informational | High `--detect-solc-version`| Detect if an old version of Solidity used (<0.4.23) | Informational | High `--detect-unused-state`| Detect unused state variables | Informational | High +`--detect-naming-convention`| Detect Naming Convention violations | Informational | Informational [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 2f4ceca69..ec045f6f0 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -55,12 +55,12 @@ fi slither tests/inline_assembly_contract.sol --disable-solc-warnings -if [ $? -ne 2 ]; then +if [ $? -ne 3 ]; then exit 1 fi slither tests/inline_assembly_library.sol --disable-solc-warnings -if [ $? -ne 3 ]; then +if [ $? -ne 6 ]; then exit 1 fi From 156ba6a9bb58510853e03869fe4c186855803144 Mon Sep 17 00:00:00 2001 From: Cryptomental Date: Thu, 18 Oct 2018 16:17:20 +0200 Subject: [PATCH 152/308] detectors: Add low level calls detector. Refs: https://github.com/trailofbits/slither/issues/27 --- README.md | 1 + scripts/travis_test.sh | 6 +- slither/__main__.py | 4 +- slither/detectors/operations/__init__.py | 0 .../detectors/operations/low_level_calls.py | 59 +++++++++++++++++++ tests/low_level_calls.sol | 17 ++++++ 6 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 slither/detectors/operations/__init__.py create mode 100644 slither/detectors/operations/low_level_calls.py create mode 100644 tests/low_level_calls.sol diff --git a/README.md b/README.md index 3e4c5c9c8..0c3ea0704 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ Check | Purpose | Impact | Confidence `--detect-pragma`| Detect if different pragma directives are used | Informational | High `--detect-solc-version`| Detect if an old version of Solidity used (<0.4.23) | Informational | High `--detect-unused-state`| Detect unused state variables | Informational | High +`--detect-low-level-calls`| Detect low level calls | Informational | High [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index d8e49f558..d2137a8a8 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -24,7 +24,7 @@ if [ $? -ne 1 ]; then fi slither tests/reentrancy.sol --disable-solc-warnings -if [ $? -ne 1 ]; then +if [ $? -ne 4 ]; then exit 1 fi @@ -63,6 +63,10 @@ if [ $? -ne 3 ]; then exit 1 fi +slither tests/low_level_calls.sol --disable-solc-warnings --detect-low-level-calls +if [ $? -ne 1 ]; then + exit 1 +fi ### Test scripts diff --git a/slither/__main__.py b/slither/__main__.py index 4fb2b387f..e3d056130 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -101,6 +101,7 @@ def main(): from slither.detectors.variables.unused_state_variables import UnusedStateVars from slither.detectors.statements.tx_origin import TxOrigin from slither.detectors.statements.assembly import Assembly + from slither.detectors.operations.low_level_calls import LowLevelCalls detectors = [Backdoor, UninitializedStateVarsDetection, @@ -113,7 +114,8 @@ def main(): Suicidal, UnusedStateVars, TxOrigin, - Assembly] + Assembly, + LowLevelCalls] from slither.printers.summary.summary import PrinterSummary from slither.printers.summary.quick_summary import PrinterQuickSummary diff --git a/slither/detectors/operations/__init__.py b/slither/detectors/operations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/detectors/operations/low_level_calls.py b/slither/detectors/operations/low_level_calls.py new file mode 100644 index 000000000..1f06cb51c --- /dev/null +++ b/slither/detectors/operations/low_level_calls.py @@ -0,0 +1,59 @@ +""" +Module detecting usage of low level calls +""" + +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.slithir.operations import LowLevelCall + + +class LowLevelCalls(AbstractDetector): + """ + Detect usage of low level calls + """ + + ARGUMENT = 'low-level-calls' + HELP = 'detects low level calls' + IMPACT = DetectorClassification.INFORMATIONAL + CONFIDENCE = DetectorClassification.HIGH + + @staticmethod + def _contains_low_level_calls(node): + """ + Check if the node contains Low Level Calls + Returns: + (bool) + """ + return any(isinstance(ir, LowLevelCall) for ir in node.irs) + + def detect_low_level_calls(self, contract): + ret = [] + for f in contract.functions: + nodes = f.nodes + assembly_nodes = [n for n in nodes if + self._contains_low_level_calls(n)] + if assembly_nodes: + ret.append((f, assembly_nodes)) + return ret + + def detect(self): + """ Detect the functions that use low level calls + """ + results = [] + for c in self.contracts: + values = self.detect_low_level_calls(c) + for func, nodes in values: + func_name = func.name + info = "Low level call in %s, Contract: %s, Function: %s" % (self.filename, + c.name, + func_name) + self.log(info) + + sourceMapping = [n.source_mapping for n in nodes] + + results.append({'vuln': 'Low level call', + 'sourceMapping': sourceMapping, + 'filename': self.filename, + 'contract': c.name, + 'function_name': func_name}) + + return results diff --git a/tests/low_level_calls.sol b/tests/low_level_calls.sol new file mode 100644 index 000000000..c5f1e2e46 --- /dev/null +++ b/tests/low_level_calls.sol @@ -0,0 +1,17 @@ +pragma solidity ^0.4.24; + + +contract Sender { + function send(address _receiver) payable { + _receiver.call.value(msg.value).gas(7777)(); + } +} + + +contract Receiver { + uint public balance = 0; + + function () payable { + balance += msg.value; + } +} \ No newline at end of file From 114b4c9e1c54cc5740d7bd1b7f8d4bb210a8fd2c Mon Sep 17 00:00:00 2001 From: redshark1802 Date: Thu, 18 Oct 2018 20:46:14 +0200 Subject: [PATCH 153/308] set confidence to high --- slither/detectors/naming_convention/naming_convention.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index d29265e88..788ab976a 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -11,7 +11,7 @@ class NamingConvention(AbstractDetector): ARGUMENT = 'naming-convention' HELP = 'naming convention violations' IMPACT = DetectorClassification.INFORMATIONAL - CONFIDENCE = DetectorClassification.INFORMATIONAL + CONFIDENCE = DetectorClassification.HIGH @staticmethod def is_cap_words(name): From 433ca4fcc9dd6f80170070fd93dc2d1aa543edaa Mon Sep 17 00:00:00 2001 From: Cryptomental Date: Thu, 18 Oct 2018 18:53:25 +0200 Subject: [PATCH 154/308] detectors: Add state variables that could be const detector. Refs: https://github.com/trailofbits/slither/issues/25 --- README.md | 1 + scripts/travis_test.sh | 10 ++- slither/__main__.py | 4 +- .../possible_const_state_variables.py | 71 +++++++++++++++++++ tests/const_state_variables.sol | 37 ++++++++++ 5 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 slither/detectors/variables/possible_const_state_variables.py create mode 100644 tests/const_state_variables.sol diff --git a/README.md b/README.md index 3e4c5c9c8..ea35d26f0 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ Check | Purpose | Impact | Confidence `--detect-pragma`| Detect if different pragma directives are used | Informational | High `--detect-solc-version`| Detect if an old version of Solidity used (<0.4.23) | Informational | High `--detect-unused-state`| Detect unused state variables | Informational | High +`--detect-const-candidates-state`| Detect state variables that could be declared constant | Informational | High [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index d8e49f558..ab5169eac 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -3,7 +3,7 @@ ### Test Detectors slither tests/uninitialized.sol --disable-solc-warnings -if [ $? -ne 1 ]; then +if [ $? -ne 2 ]; then exit 1 fi @@ -39,12 +39,12 @@ if [ $? -ne 2 ]; then fi slither tests/unused_state.sol -if [ $? -ne 1 ]; then +if [ $? -ne 3 ]; then exit 1 fi slither tests/locked_ether.sol -if [ $? -ne 1 ]; then +if [ $? -ne 3 ]; then exit 1 fi @@ -63,6 +63,10 @@ if [ $? -ne 3 ]; then exit 1 fi +slither tests/const_state_variables.sol --detect-const-candidates-state +if [ $? -ne 2 ]; then + exit 1 +fi ### Test scripts diff --git a/slither/__main__.py b/slither/__main__.py index 4fb2b387f..531e374d8 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -99,6 +99,7 @@ def main(): from slither.detectors.reentrancy.reentrancy import Reentrancy from slither.detectors.variables.uninitialized_storage_variables import UninitializedStorageVars from slither.detectors.variables.unused_state_variables import UnusedStateVars + from slither.detectors.variables.possible_const_state_variables import ConstCandidateStateVars from slither.detectors.statements.tx_origin import TxOrigin from slither.detectors.statements.assembly import Assembly @@ -113,7 +114,8 @@ def main(): Suicidal, UnusedStateVars, TxOrigin, - Assembly] + Assembly, + ConstCandidateStateVars] from slither.printers.summary.summary import PrinterSummary from slither.printers.summary.quick_summary import PrinterQuickSummary diff --git a/slither/detectors/variables/possible_const_state_variables.py b/slither/detectors/variables/possible_const_state_variables.py new file mode 100644 index 000000000..9f6bc0f03 --- /dev/null +++ b/slither/detectors/variables/possible_const_state_variables.py @@ -0,0 +1,71 @@ +""" +Module detecting state variables that could be declared as constant +""" + +from slither.core.solidity_types.elementary_type import ElementaryType +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.slithir.operations import OperationWithLValue + + +class ConstCandidateStateVars(AbstractDetector): + """ + State variables that could be declared as constant detector. + Not all types for constants are implemented in Solidity as of 0.4.25. + The only supported types are value types and strings (ElementaryType). + Reference: https://solidity.readthedocs.io/en/latest/contracts.html#constant-state-variables + """ + + ARGUMENT = 'const-candidates-state' + HELP = 'detect state variables that could be const' + IMPACT = DetectorClassification.INFORMATIONAL + CONFIDENCE = DetectorClassification.HIGH + + @staticmethod + def lvalues_of_operations_with_lvalue(contract): + ret = [] + for f in contract.functions: + if f.name == 'fallback': # Skip fallback function to avoid false positives + continue + for n in f.nodes: + for ir in n.irs: + if isinstance(ir, OperationWithLValue): + ret.append(ir.lvalue) + return ret + + @staticmethod + def non_const_state_variables(contract): + return [variable for variable in contract.state_variables if not variable.is_constant] + + def detect_const_candidates(self, contract): + const_candidates = [] + non_const_state_vars = self.non_const_state_variables(contract) + lvalues_of_operations = self.lvalues_of_operations_with_lvalue(contract) + for non_const in non_const_state_vars: + if non_const not in lvalues_of_operations \ + and non_const not in const_candidates \ + and isinstance(non_const.type, ElementaryType): + const_candidates.append(non_const) + + return const_candidates + + def detect(self): + """ Detect state variables that could be const + """ + results = [] + for c in self.contracts: + const_candidates = self.detect_const_candidates(c) + if const_candidates: + variable_names = [v.name for v in const_candidates] + info = "State variables that could be const in %s, Contract: %s, Vars %s" % (self.filename, + c.name, + str(variable_names)) + self.log(info) + + sourceMapping = [v.source_mapping for v in const_candidates] + + results.append({'vuln': 'ConstStateVariableCandidates', + 'sourceMapping': sourceMapping, + 'filename': self.filename, + 'contract': c.name, + 'unusedVars': variable_names}) + return results diff --git a/tests/const_state_variables.sol b/tests/const_state_variables.sol new file mode 100644 index 000000000..f518ceb84 --- /dev/null +++ b/tests/const_state_variables.sol @@ -0,0 +1,37 @@ +pragma solidity ^0.4.24; + + +contract A { + + address constant public MY_ADDRESS = 0xE0f5206BBD039e7b0592d8918820024e2a7437b9; + address public myFriendsAddress = 0xc0ffee254729296a45a3885639AC7E10F9d54979; + + uint public used; + uint public test = 5; + + uint constant X = 32**22 + 8; + string constant TEXT1 = "abc"; + string text2 = "xyz"; + + function setUsed() public { + if (msg.sender == MY_ADDRESS) { + used = test; + } + } +} + + +contract B is A { + + address public mySistersAddress = 0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E; + + function () public { + used = 0; + } + + function setUsed(uint a) public { + if (msg.sender == MY_ADDRESS) { + used = a; + } + } +} From 41eeb700651ee6995ffe98f3a73668f2d47d0dbd Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Thu, 18 Oct 2018 17:39:42 -0400 Subject: [PATCH 155/308] nit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d7dfc1843..10de04f8d 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Check | Purpose | Impact | Confidence `--detect-pragma`| Detect if different pragma directives are used | Informational | High `--detect-solc-version`| Detect if an old version of Solidity used (<0.4.23) | Informational | High `--detect-unused-state`| Detect unused state variables | Informational | High -`--detect-naming-convention`| Detect Naming Convention violations | Informational | Informational +`--detect-naming-convention`| Detect conformance to Solidity naming conventions | Informational | Informational [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. From c31763ad4f369cbfc4fb451113cd7acd724293f1 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Thu, 18 Oct 2018 22:38:02 -0400 Subject: [PATCH 156/308] Update README.md --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4cea1c4f9..d4aef5c20 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,12 @@ Slither is a Solidity static analysis framework written in Python 3. It runs a s ## Features * Detects vulnerable Solidity code with low false positives - * Detection of most major smart contract vulnerabilities - * Detection of poor coding practices * Identifies where the error condition occurs in the source code * Easy integration into continuous integration pipelines -* Four built-in 'printers' quickly report crucial contract information +* Built-in 'printers' quickly report crucial contract information * Detector API to write custom analyses in Python * Ability to analyze contracts written with Solidity > 0.4 - -Slither possesses it's own intermediate representation, called [SlithIR](https://github.com/trailofbits/slither/wiki/SlithIR). +* Intermediate representation ([SlithIR](https://github.com/trailofbits/slither/wiki/SlithIR)) enables simple, high-precision analyses ## Usage From bd1a056dd954ca8222abcc294f6e1397f4b35558 Mon Sep 17 00:00:00 2001 From: Praveen Gupta Date: Fri, 19 Oct 2018 12:04:54 +0530 Subject: [PATCH 157/308] Adds printer for inheritance and updates inheritance-graph printer --- README.md | 3 +- slither/printers/inheritance/inheritance.py | 136 ++++-------------- .../printers/inheritance/inheritance_graph.py | 122 ++++++++++++++++ 3 files changed, 153 insertions(+), 108 deletions(-) create mode 100644 slither/printers/inheritance/inheritance_graph.py diff --git a/README.md b/README.md index 27f845e3c..efaeba13a 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,8 @@ If Slither is run on a directory, it will run on every `.sol` file of the direct * `--printer-summary`: Print a summary of the contracts * `--printer-quick-summary`: Print a quick summary of the contracts -* `--printer-inheritance`: Print the inheritance graph +* `--printer-inheritance`: Print the inheritance relations +* `--printer-inheritance-graph`: Print the inheritance graph in a file * `--printer-vars-and-auth`: Print the variables written and the check on `msg.sender` of each function ## Checks available diff --git a/slither/printers/inheritance/inheritance.py b/slither/printers/inheritance/inheritance.py index 27f55f21c..4ce8f94bc 100644 --- a/slither/printers/inheritance/inheritance.py +++ b/slither/printers/inheritance/inheritance.py @@ -1,123 +1,45 @@ """ - Module printing the inheritance graph + Module printing the inheritance relation - The inheritance graph shows the relation between the contracts - and their functions/modifiers/public variables. - The output is a dot file named filename.dot + The inheritance shows the relation between the contracts """ -from slither.core.declarations.contract import Contract -from slither.detectors.shadowing.shadowing_functions import ShadowingFunctionsDetection from slither.printers.abstract_printer import AbstractPrinter +from slither.utils.colors import blue, green, magenta class PrinterInheritance(AbstractPrinter): ARGUMENT = 'inheritance' - HELP = 'the inheritance graph' + HELP = 'the inheritance relation between contracts' - def __init__(self, slither, logger): - super(PrinterInheritance, self).__init__(slither, logger) - - inheritance = [x.inheritance for x in slither.contracts] - self.inheritance = set([item for sublist in inheritance for item in sublist]) - - shadow = ShadowingFunctionsDetection(slither, None) - ret = shadow.detect() - functions_shadowed = {} - for s in ret: - if s['contractShadower'] not in functions_shadowed: - functions_shadowed[s['contractShadower']] = [] - functions_shadowed[s['contractShadower']] += s['funcs'] - self.functions_shadowed = functions_shadowed - - def _get_pattern_func(self, func, contract): - # Html pattern, each line is a row in a table - func_name = func.full_name - pattern = ' %s' - pattern_shadow = ' %s' - if contract.name in self.functions_shadowed: - if func_name in self.functions_shadowed[contract.name]: - return pattern_shadow % func_name - return pattern % func_name - - def _get_pattern_var(self, var, contract): - # Html pattern, each line is a row in a table - var_name = var.name - pattern = ' %s' - pattern_contract = ' %s (%s)' - # pattern_arrow = ' %s' - if isinstance(var.type, Contract): - return pattern_contract % (var_name, str(var.type)) - # return pattern_arrow%(self._get_port_id(var, contract), var_name) - return pattern % var_name - - def _get_port_id(self, var, contract): - return "%s%s" % (var.name, contract.name) - - def _summary(self, contract): - """ - Build summary using HTML - """ - ret = '' - # Add arrows - for i in contract.inheritance: - ret += '%s -> %s;\n' % (contract.name, i) - - # Functions - visibilities = ['public', 'external'] - public_functions = [self._get_pattern_func(f, contract) for f in contract.functions if - not f.is_constructor and f.contract == contract and f.visibility in visibilities] - public_functions = ''.join(public_functions) - private_functions = [self._get_pattern_func(f, contract) for f in contract.functions if - not f.is_constructor and f.contract == contract and f.visibility not in visibilities] - private_functions = ''.join(private_functions) - # Modifiers - modifiers = [self._get_pattern_func(m, contract) for m in contract.modifiers if m.contract == contract] - modifiers = ''.join(modifiers) - # Public variables - public_variables = [self._get_pattern_var(v, contract) for v in contract.variables if - v.visibility in visibilities] - public_variables = ''.join(public_variables) - - private_variables = [self._get_pattern_var(v, contract) for v in contract.variables if - not v.visibility in visibilities] - private_variables = ''.join(private_variables) - - # Build the node label - ret += '%s[shape="box"' % contract.name - ret += 'label=< ' - ret += '' % contract.name - if public_functions: - ret += '' - ret += '%s' % public_functions - if private_functions: - ret += '' - ret += '%s' % private_functions - if modifiers: - ret += '' - ret += '%s' % modifiers - if public_variables: - ret += '' - ret += '%s' % public_variables - if private_variables: - ret += '' - ret += '%s' % private_variables - ret += '
%s
Public Functions:
Private Functions:
Modifiers:
Public Variables:
Private Variables:
>];\n' - - return ret + def _get_child_contracts(self, base): + for child in self.contracts: + if base in child.inheritance: + yield child def output(self, filename): """ - Output the graph in filename + Output the inheritance relation + + _filename is not used Args: - filename(string) + _filename(string) """ - if not filename.endswith('.dot'): - filename += ".dot" - info = 'Inheritance Graph: ' + filename + info = 'Inheritance\n' + info += blue('Child_Contract -> ') + green('Base_Contracts') + for contract in self.contracts: + info += blue(f'\n+ {contract.name} -> ') + if contract.inheritance: + info += green(", ".join(map(str, contract.inheritance))) + else: + info += magenta("Root_Contract") + + info += green('\n\nBase_Contract -> ') + blue('Child_Contracts') + for contract in self.contracts: + info += green(f'\n+ {contract.name} -> ') + children = list(self._get_child_contracts(contract)) + if children: + info += blue(", ".join(map(str, children))) + else: + info += magenta("Leaf_Contract") self.info(info) - with open(filename, 'w') as f: - f.write('digraph{\n') - for c in self.contracts: - f.write(self._summary(c)) - f.write('}') diff --git a/slither/printers/inheritance/inheritance_graph.py b/slither/printers/inheritance/inheritance_graph.py new file mode 100644 index 000000000..680bb8ed8 --- /dev/null +++ b/slither/printers/inheritance/inheritance_graph.py @@ -0,0 +1,122 @@ +""" + Module printing the inheritance graph + + The inheritance graph shows the relation between the contracts + and their functions/modifiers/public variables. + The output is a dot file named filename.dot +""" + +from slither.core.declarations.contract import Contract +from slither.detectors.shadowing.shadowing_functions import ShadowingFunctionsDetection +from slither.printers.abstract_printer import AbstractPrinter + +class PrinterInheritanceGraph(AbstractPrinter): + ARGUMENT = 'inheritance-graph' + HELP = 'the inheritance graph' + + def __init__(self, slither, logger): + super(PrinterInheritanceGraph, self).__init__(slither, logger) + + inheritance = [x.inheritance for x in slither.contracts] + self.inheritance = set([item for sublist in inheritance for item in sublist]) + + shadow = ShadowingFunctionsDetection(slither, None) + ret = shadow.detect() + functions_shadowed = {} + for s in ret: + if s['contractShadower'] not in functions_shadowed: + functions_shadowed[s['contractShadower']] = [] + functions_shadowed[s['contractShadower']] += s['funcs'] + self.functions_shadowed = functions_shadowed + + def _get_pattern_func(self, func, contract): + # Html pattern, each line is a row in a table + func_name = func.full_name + pattern = ' %s' + pattern_shadow = ' %s' + if contract.name in self.functions_shadowed: + if func_name in self.functions_shadowed[contract.name]: + return pattern_shadow % func_name + return pattern % func_name + + def _get_pattern_var(self, var, contract): + # Html pattern, each line is a row in a table + var_name = var.name + pattern = ' %s' + pattern_contract = ' %s (%s)' + # pattern_arrow = ' %s' + if isinstance(var.type, Contract): + return pattern_contract % (var_name, str(var.type)) + # return pattern_arrow%(self._get_port_id(var, contract), var_name) + return pattern % var_name + + def _get_port_id(self, var, contract): + return "%s%s" % (var.name, contract.name) + + def _summary(self, contract): + """ + Build summary using HTML + """ + ret = '' + # Add arrows + for i in contract.inheritance: + ret += '%s -> %s;\n' % (contract.name, i) + + # Functions + visibilities = ['public', 'external'] + public_functions = [self._get_pattern_func(f, contract) for f in contract.functions if + not f.is_constructor and f.contract == contract and f.visibility in visibilities] + public_functions = ''.join(public_functions) + private_functions = [self._get_pattern_func(f, contract) for f in contract.functions if + not f.is_constructor and f.contract == contract and f.visibility not in visibilities] + private_functions = ''.join(private_functions) + # Modifiers + modifiers = [self._get_pattern_func(m, contract) for m in contract.modifiers if m.contract == contract] + modifiers = ''.join(modifiers) + # Public variables + public_variables = [self._get_pattern_var(v, contract) for v in contract.variables if + v.visibility in visibilities] + public_variables = ''.join(public_variables) + + private_variables = [self._get_pattern_var(v, contract) for v in contract.variables if + not v.visibility in visibilities] + private_variables = ''.join(private_variables) + + # Build the node label + ret += '%s[shape="box"' % contract.name + ret += 'label=< ' + ret += '' % contract.name + if public_functions: + ret += '' + ret += '%s' % public_functions + if private_functions: + ret += '' + ret += '%s' % private_functions + if modifiers: + ret += '' + ret += '%s' % modifiers + if public_variables: + ret += '' + ret += '%s' % public_variables + if private_variables: + ret += '' + ret += '%s' % private_variables + ret += '
%s
Public Functions:
Private Functions:
Modifiers:
Public Variables:
Private Variables:
>];\n' + + return ret + + def output(self, filename): + """ + Output the graph in filename + Args: + filename(string) + """ + if not filename.endswith('.dot'): + filename += ".dot" + info = 'Inheritance Graph: ' + filename + self.info(info) + with open(filename, 'w') as f: + f.write('digraph{\n') + for c in self.contracts: + f.write(self._summary(c)) + f.write('}') From 04a824752fef791bf61b7d31c07fd9540ea4de80 Mon Sep 17 00:00:00 2001 From: Praveen Gupta Date: Fri, 19 Oct 2018 12:36:50 +0530 Subject: [PATCH 158/308] Adds PrinterInheritanceGraph in main.py --- slither/__main__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/slither/__main__.py b/slither/__main__.py index 4fb1fa9c3..244b774f2 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -116,12 +116,14 @@ def main(): from slither.printers.summary.summary import PrinterSummary from slither.printers.summary.quick_summary import PrinterQuickSummary from slither.printers.inheritance.inheritance import PrinterInheritance + from slither.printers.inheritance.inheritance_graph import PrinterInheritanceGraph from slither.printers.functions.authorization import PrinterWrittenVariablesAndAuthorization from slither.printers.summary.slithir import PrinterSlithIR printers = [PrinterSummary, PrinterQuickSummary, PrinterInheritance, + PrinterInheritanceGraph, PrinterWrittenVariablesAndAuthorization, PrinterSlithIR] From 98be00c0a4ebd9d8aa0d1645f1e47c42d1578d42 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 19 Oct 2018 08:11:15 +0100 Subject: [PATCH 159/308] Initial support of compact AST (WIP) --- slither/solc_parsing/declarations/contract.py | 63 +++--- slither/solc_parsing/declarations/function.py | 181 ++++++++++++------ .../expressions/expression_parsing.py | 119 ++++++++---- slither/solc_parsing/slitherSolc.py | 47 +++-- .../variables/variable_declaration.py | 30 ++- 5 files changed, 296 insertions(+), 144 deletions(-) diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index a59385f45..bb3e913cb 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -37,7 +37,11 @@ class ContractSolc04(Contract): self._is_analyzed = False # Export info - self._name = self._data['attributes']['name'] + if self.is_compact_ast: + self._name = self._data[self.get_key()] + else: + self._name = self._data['attributes'][self.get_key()] + self._id = self._data['id'] self._inheritance = [] @@ -48,11 +52,25 @@ class ContractSolc04(Contract): def is_analyzed(self): return self._is_analyzed + def get_key(self): + return self.slither.get_key() + + def get_children(self): + return self.slither.get_children() + + @property + def is_compact_ast(self): + return self.slither.is_compact_ast + def set_is_analyzed(self, is_analyzed): self._is_analyzed = is_analyzed def _parse_contract_info(self): - attributes = self._data['attributes'] + if self.is_compact_ast: + attributes = self._data + else: + attributes = self._data['attributes'] + self.isInterface = False if 'contractKind' in attributes: if attributes['contractKind'] == 'interface': @@ -62,29 +80,29 @@ class ContractSolc04(Contract): self.fullyImplemented = attributes['fullyImplemented'] def _parse_contract_items(self): - if not 'children' in self._data: # empty contract + if not self.get_children() in self._data: # empty contract return - for item in self._data['children']: - if item['name'] == 'FunctionDefinition': + for item in self._data[self.get_children()]: + if item[self.get_key()] == 'FunctionDefinition': self._functionsNotParsed.append(item) - elif item['name'] == 'EventDefinition': + elif item[self.get_key()] == 'EventDefinition': self._eventsNotParsed.append(item) - elif item['name'] == 'InheritanceSpecifier': + elif item[self.get_key()] == 'InheritanceSpecifier': # we dont need to parse it as it is redundant # with self.linearizedBaseContracts continue - elif item['name'] == 'VariableDeclaration': + elif item[self.get_key()] == 'VariableDeclaration': self._variablesNotParsed.append(item) - elif item['name'] == 'EnumDefinition': + elif item[self.get_key()] == 'EnumDefinition': self._enumsNotParsed.append(item) - elif item['name'] == 'ModifierDefinition': + elif item[self.get_key()] == 'ModifierDefinition': self._modifiersNotParsed.append(item) - elif item['name'] == 'StructDefinition': + elif item[self.get_key()] == 'StructDefinition': self._structuresNotParsed.append(item) - elif item['name'] == 'UsingForDirective': + elif item[self.get_key()] == 'UsingForDirective': self._usingForNotParsed.append(item) else: - logger.error('Unknown contract item: '+item['name']) + logger.error('Unknown contract item: '+item[self.get_key()]) exit(-1) return @@ -93,7 +111,7 @@ class ContractSolc04(Contract): self._using_for.update(father.using_for) for using_for in self._usingForNotParsed: - children = using_for['children'] + children = using_for[self.get_children()] assert children and len(children) <= 2 if len(children) == 2: new = parse_type(children[0], self) @@ -119,15 +137,15 @@ class ContractSolc04(Contract): def _analyze_enum(self, enum): # Enum can be parsed in one pass - name = enum['attributes']['name'] + name = enum['attributes'][self.get_key()] if 'canonicalName' in enum['attributes']: canonicalName = enum['attributes']['canonicalName'] else: canonicalName = self.name + '.' + name values = [] - for child in enum['children']: - assert child['name'] == 'EnumValue' - values.append(child['attributes']['name']) + for child in enum[self.get_children()]: + assert child[self.get_key()] == 'EnumValue' + values.append(child['attributes'][self.get_key()]) new_enum = Enum(name, canonicalName, values) new_enum.set_contract(self) @@ -135,14 +153,14 @@ class ContractSolc04(Contract): self._enums[canonicalName] = new_enum def _parse_struct(self, struct): - name = struct['attributes']['name'] + name = struct['attributes'][self.get_key()] if 'canonicalName' in struct['attributes']: canonicalName = struct['attributes']['canonicalName'] else: canonicalName = self.name + '.' + name - if 'children' in struct: - children = struct['children'] + if self.get_children() in struct: + children = struct[self.get_children()] else: children = [] # empty struct st = StructureSolc(name, canonicalName, children) @@ -209,8 +227,7 @@ class ContractSolc04(Contract): return def _parse_function(self, function): - func = FunctionSolc(function) - func.set_contract(self) + func = FunctionSolc(function, self) func.set_offset(function['src'], self.slither) self._functions_no_params.append(func) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 995cfbd0e..c29c34c67 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -28,16 +28,35 @@ class FunctionSolc(Function): """ # elems = [(type, name)] - def __init__(self, function): + def __init__(self, function, contract): super(FunctionSolc, self).__init__() - self._name = function['attributes']['name'] + self._contract = contract + if self.is_compact_ast: + self._name = function[self.get_key()] + else: + self._name = function['attributes'][self.get_key()] self._functionNotParsed = function self._params_was_analyzed = False self._content_was_analyzed = False self._counter_nodes = 0 + def get_key(self): + return self.slither.get_key() + + def get_children(self, key): + if self.is_compact_ast: + return key + return 'children' + + @property + def is_compact_ast(self): + return self.slither.is_compact_ast + def _analyze_attributes(self): - attributes = self._functionNotParsed['attributes'] + if self.is_compact_ast: + attributes = self._functionNotParsed + else: + attributes = self._functionNotParsed['attributes'] if 'payable' in attributes: self._payable = attributes['payable'] @@ -80,7 +99,7 @@ class FunctionSolc(Function): def _parse_if(self, ifStatement, node): # IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )? - children = ifStatement['children'] + children = ifStatement[self.get_children()] condition_node = self._new_node(NodeType.IF) #condition = parse_expression(children[0], self) condition = children[0] @@ -107,7 +126,7 @@ class FunctionSolc(Function): def _parse_while(self, whileStatement, node): # WhileStatement = 'while' '(' Expression ')' Statement - children = whileStatement['children'] + children = whileStatement[self.get_children()] node_startWhile = self._new_node(NodeType.STARTLOOP) @@ -152,11 +171,11 @@ class FunctionSolc(Function): node_startLoop = self._new_node(NodeType.STARTLOOP) node_endLoop = self._new_node(NodeType.ENDLOOP) - children = statement['children'] + children = statement[self.get_children()] if hasInitExession: if len(children) >= 2: - if children[0]['name'] in ['VariableDefinitionStatement', + if children[0][self.get_key()] in ['VariableDefinitionStatement', 'VariableDeclarationStatement', 'ExpressionStatement']: node_initExpression = self._parse_statement(children[0], node) @@ -175,7 +194,7 @@ class FunctionSolc(Function): candidate = children[1] else: candidate = children[0] - if candidate['name'] not in ['VariableDefinitionStatement', + if candidate[self.get_key()] not in ['VariableDefinitionStatement', 'VariableDeclarationStatement', 'ExpressionStatement']: node_condition = self._new_node(NodeType.IFLOOP) @@ -194,7 +213,7 @@ class FunctionSolc(Function): node_LoopExpression = node_statement if hasLoopExpression: if len(children) > 2: - if children[-2]['name'] == 'ExpressionStatement': + if children[-2][self.get_key()] == 'ExpressionStatement': node_LoopExpression = self._parse_statement(children[-2], node_statement) link_nodes(node_LoopExpression, node_startLoop) @@ -202,7 +221,7 @@ class FunctionSolc(Function): return node_endLoop def _parse_dowhile(self, doWhilestatement, node): - children = doWhilestatement['children'] + children = doWhilestatement[self.get_children()] node_startDoWhile = self._new_node(NodeType.STARTLOOP) @@ -223,13 +242,13 @@ class FunctionSolc(Function): return node_endDoWhile def _parse_variable_definition(self, statement, node): - #assert len(statement['children']) == 1 + #assert len(statement[self.get_children()]) == 1 # if there is, parse default value #assert not 'attributes' in statement try: local_var = LocalVariableSolc(statement) - #local_var = LocalVariableSolc(statement['children'][0], statement['children'][1::]) + #local_var = LocalVariableSolc(statement[self.get_children()][0], statement[self.get_children()][1::]) local_var.set_function(self) local_var.set_offset(statement['src'], self.contract.slither) @@ -243,9 +262,9 @@ class FunctionSolc(Function): except MultipleVariablesDeclaration: # Custom handling of var (a,b) = .. style declaration count = 0 - children = statement['children'] + children = statement[self.get_children()] child = children[0] - while child['name'] == 'VariableDeclaration': + while child[self.get_key()] == 'VariableDeclaration': count = count +1 child = children[count] @@ -256,16 +275,16 @@ class FunctionSolc(Function): variables_declaration = children[0:count] i = 0 new_node = node - if tuple_vars['name'] == 'TupleExpression': - assert len(tuple_vars['children']) == count + if tuple_vars[self.get_key()] == 'TupleExpression': + assert len(tuple_vars[self.get_children()]) == count for variable in variables_declaration: - init = tuple_vars['children'][i] + init = tuple_vars[self.get_children()][i] src = variable['src'] i= i+1 # Create a fake statement to be consistent - new_statement = {'name':'VariableDefinitionStatement', + new_statement = {self.get_key():'VariableDefinitionStatement', 'src': src, - 'children':[variable, init]} + self.get_children():[variable, init]} new_node = self._parse_variable_definition(new_statement, new_node) else: @@ -273,15 +292,15 @@ class FunctionSolc(Function): # var (a, b) = f() # we can split in multiple declarations, without init # Then we craft one expression that does not assignment - assert tuple_vars['name'] in ['FunctionCall', 'Conditional'] + assert tuple_vars[self.get_key()] in ['FunctionCall', 'Conditional'] variables = [] for variable in variables_declaration: src = variable['src'] i= i+1 # Create a fake statement to be consistent - new_statement = {'name':'VariableDefinitionStatement', + new_statement = {self.get_key():'VariableDefinitionStatement', 'src': src, - 'children':[variable]} + self.get_children():[variable]} variables.append(variable) new_node = self._parse_variable_definition_init_tuple(new_statement, i, new_node) @@ -289,23 +308,23 @@ class FunctionSolc(Function): # craft of the expression doing the assignement for v in variables: identifier = { - 'name' : 'Identifier', + self.get_key() : 'Identifier', 'src': v['src'], 'attributes': { - 'value': v['attributes']['name'], + 'value': v['attributes'][self.get_key()], 'type': v['attributes']['type']} } var_identifiers.append(identifier) expression = { - 'name' : 'Assignment', + self.get_key() : 'Assignment', 'src':statement['src'], 'attributes': {'operator': '=', 'type':'tuple()'}, - 'children': - [{'name': 'TupleExpression', + self.get_children(): + [{self.get_key(): 'TupleExpression', 'src': statement['src'], - 'children': var_identifiers}, + self.get_children(): var_identifiers}, tuple_vars]} node = new_node new_node = self._new_node(NodeType.EXPRESSION) @@ -317,7 +336,7 @@ class FunctionSolc(Function): def _parse_variable_definition_init_tuple(self, statement, index, node): local_var = LocalVariableInitFromTupleSolc(statement, index) - #local_var = LocalVariableSolc(statement['children'][0], statement['children'][1::]) + #local_var = LocalVariableSolc(statement[self.get_children()][0], statement[self.get_children()][1::]) local_var.set_function(self) local_var.set_offset(statement['src'], self.contract.slither) @@ -341,7 +360,7 @@ class FunctionSolc(Function): # Throw | EmitStatement | SimpleStatement ) ';' # SimpleStatement = VariableDefinition | ExpressionStatement - name = statement['name'] + name = statement[self.get_key()] # SimpleStatement = VariableDefinition | ExpressionStatement if name == 'IfStatement': node = self._parse_if(statement, node) @@ -370,9 +389,9 @@ class FunctionSolc(Function): elif name == 'Return': return_node = self._new_node(NodeType.RETURN) link_nodes(node, return_node) - if 'children' in statement and statement['children']: - assert len(statement['children']) == 1 - expression = statement['children'][0] + if self.get_children() in statement and statement[self.get_children()]: + assert len(statement[self.get_children()]) == 1 + expression = statement[self.get_children()][0] return_node.add_unparsed_expression(expression) node = return_node elif name == 'Throw': @@ -380,8 +399,8 @@ class FunctionSolc(Function): link_nodes(node, throw_node) node = throw_node elif name == 'EmitStatement': - #expression = parse_expression(statement['children'][0], self) - expression = statement['children'][0] + #expression = parse_expression(statement[self.get_children()][0], self) + expression = statement[self.get_children()][0] new_node = self._new_node(NodeType.EXPRESSION) new_node.add_unparsed_expression(expression) link_nodes(node, new_node) @@ -389,10 +408,13 @@ class FunctionSolc(Function): elif name in ['VariableDefinitionStatement', 'VariableDeclarationStatement']: node = self._parse_variable_definition(statement, node) elif name == 'ExpressionStatement': - assert len(statement['children']) == 1 - assert not 'attributes' in statement - #expression = parse_expression(statement['children'][0], self) - expression = statement['children'][0] + #assert len(statement[self.get_children('expression')]) == 1 + #assert not 'attributes' in statement + #expression = parse_expression(statement[self.get_children()][0], self) + if self.is_compact_ast: + expression = statement[self.get_children('expression')] + else: + expression = statement[self.get_children('expression')][0] new_node = self._new_node(NodeType.EXPRESSION) new_node.add_unparsed_expression(expression) link_nodes(node, new_node) @@ -408,20 +430,30 @@ class FunctionSolc(Function): Return: Node ''' - assert block['name'] == 'Block' + assert block[self.get_key()] == 'Block' - for child in block['children']: - node = self._parse_statement(child, node) + if self.is_compact_ast: + statements = block['statements'] + else: + statements = block[self.get_children()] + + for statement in statements: + node = self._parse_statement(statement, node) return node def _parse_cfg(self, cfg): - assert cfg['name'] == 'Block' + assert cfg[self.get_key()] == 'Block' node = self._new_node(NodeType.ENTRYPOINT) self._entry_point = node - if not cfg['children']: + if self.is_compact_ast: + statements = cfg['statements'] + else: + statements = cfg[self.get_children()] + + if not statements: self._is_empty = True else: self._is_empty = False @@ -526,10 +558,15 @@ class FunctionSolc(Function): self._nodes = [n for n in self.nodes if not n in to_remove] # def _parse_params(self, params): + assert params[self.get_key()] == 'ParameterList' - assert params['name'] == 'ParameterList' - for param in params['children']: - assert param['name'] == 'VariableDeclaration' + if self.is_compact_ast: + params = params['parameters'] + else: + params = params[self.get_children()] + + for param in params: + assert param[self.get_key()] == 'VariableDeclaration' local_var = LocalVariableSolc(param) @@ -546,9 +583,15 @@ class FunctionSolc(Function): def _parse_returns(self, returns): - assert returns['name'] == 'ParameterList' - for ret in returns['children']: - assert ret['name'] == 'VariableDeclaration' + assert returns[self.get_key()] == 'ParameterList' + + if self.is_compact_ast: + returns = returns['parameters'] + else: + returns = returns[self.get_children()] + + for ret in returns: + assert ret[self.get_key()] == 'VariableDeclaration' local_var = LocalVariableSolc(ret) @@ -579,10 +622,13 @@ class FunctionSolc(Function): self._analyze_attributes() - children = self._functionNotParsed['children'] - - params = children[0] - returns = children[1] + if self.is_compact_ast: + params = self._functionNotParsed['parameters'] + returns = self._functionNotParsed['returnParameters'] + else: + children = self._functionNotParsed[self.get_children('children')] + params = children[0] + returns = children[1] if params: self._parse_params(params) @@ -595,17 +641,28 @@ class FunctionSolc(Function): self._content_was_analyzed = True - children = self._functionNotParsed['children'] - self._is_implemented = False - for child in children[2:]: - if child['name'] == 'Block': + if self.is_compact_ast: + body = self._functionNotParsed['body'] + + if body[self.get_key()] == 'Block': self._is_implemented = True - self._parse_cfg(child) - continue + self._parse_cfg(body) + + for modifier in self._functionNotParsed['modifiers']: + self._parse_modifier(modifier) + + else: + children = self._functionNotParsed[self.get_children('children')] + self._is_implemented = False + for child in children[2:]: + if child[self.get_key()] == 'Block': + self._is_implemented = True + self._parse_cfg(child) + continue - assert child['name'] == 'ModifierInvocation' + assert child[self.get_key()] == 'ModifierInvocation' - self._parse_modifier(child) + self._parse_modifier(child) for local_vars in self.variables: local_vars.analyze(self) diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index a2206f128..10f9ea75c 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -109,16 +109,29 @@ def find_variable(var_name, caller_context): def parse_call(expression, caller_context): - attributes = expression['attributes'] - type_conversion = attributes['type_conversion'] + if caller_context.is_compact_ast: + attributes = expression + type_conversion = expression['kind'] == 'typeConversion' + type_return = attributes['typeDescriptions']['typeString'] + + else: + attributes = expression['attributes'] + type_conversion = attributes['type_conversion'] + type_return = attributes['type'] - children = expression['children'] if type_conversion: - assert len(children) == 2 + type_call = parse_type(UnknownType(type_return), caller_context) + - type_call = parse_type(UnknownType(attributes['type']), caller_context) - type_info = children[0] + if caller_context.is_compact_ast: + type_info = expression['expression'] + expression_to_parse = expression['value'] + else: + children = expression['children'] + assert len(children) == 2 + type_info = children[0] + expression_to_parse = children[1] assert type_info['name'] in ['ElementaryTypenameExpression', 'ElementaryTypeNameExpression', 'Identifier', @@ -126,19 +139,21 @@ def parse_call(expression, caller_context): 'IndexAccess', 'MemberAccess'] - expression = parse_expression(children[1], caller_context) + expression = parse_expression(expression_to_parse, caller_context) t = TypeConversion(expression, type_call) return t - assert children - - type_call = attributes['type'] - called = parse_expression(children[0], caller_context) - arguments = [parse_expression(a, caller_context) for a in children[1::]] + if caller_context.is_compact_ast: + called = parse_expression(expression['expression'], caller_context) + arguments = [parse_expression(a, caller_context) for a in expression['arguments']] + else: + children = expression['children'] + called = parse_expression(children[0], caller_context) + arguments = [parse_expression(a, caller_context) for a in children[1::]] if isinstance(called, SuperCallExpression): - return SuperCallExpression(called, arguments, type_call) - return CallExpression(called, arguments, type_call) + return SuperCallExpression(called, arguments, type_return) + return CallExpression(called, arguments, type_return) def parse_super_name(expression): assert expression['name'] == 'MemberAccess' @@ -219,15 +234,20 @@ def parse_expression(expression, caller_context): # | PrimaryExpression # The AST naming does not follow the spec - name = expression['name'] + name = expression[caller_context.get_key()] + + is_compact_ast = caller_context.is_compact_ast if name == 'UnaryOperation': attributes = expression['attributes'] assert 'prefix' in attributes operation_type = UnaryOperationType.get_type(attributes['operator'], attributes['prefix']) - assert len(expression['children']) == 1 - expression = parse_expression(expression['children'][0], caller_context) + if is_compact_ast: + expression = parse_expression(expression['subExpression'], caller_context) + else: + assert len(expression['children']) == 1 + expression = parse_expression(expression['children'][0], caller_context) unary_op = UnaryOperation(expression, operation_type) return unary_op @@ -235,9 +255,13 @@ def parse_expression(expression, caller_context): attributes = expression['attributes'] operation_type = BinaryOperationType.get_type(attributes['operator']) - assert len(expression['children']) == 2 - left_expression = parse_expression(expression['children'][0], caller_context) - right_expression = parse_expression(expression['children'][1], caller_context) + if is_compact_ast: + left_expression = parse_expression(expression['leftExpression'], caller_context) + right_expression = parse_expression(expression['rightExpression'], caller_context) + else: + assert len(expression['children']) == 2 + left_expression = parse_expression(expression['children'][0], caller_context) + right_expression = parse_expression(expression['children'][1], caller_context) binary_op = BinaryOperation(left_expression, right_expression, operation_type) return binary_op @@ -256,12 +280,15 @@ def parse_expression(expression, caller_context): Note: this is only possible with Solidity >= 0.4.12 """ - if 'children' not in expression : - attributes = expression['attributes'] - components = attributes['components'] - expressions = [parse_expression(c, caller_context) if c else None for c in components] + if caller_context.is_compact_ast: + expressions = [parse_expression(e, caller_context) for e in expression['components']] else: - expressions = [parse_expression(e, caller_context) for e in expression['children']] + if 'children' not in expression : + attributes = expression['attributes'] + components = attributes['components'] + expressions = [parse_expression(c, caller_context) if c else None for c in components] + else: + expressions = [parse_expression(e, caller_context) for e in expression['children']] # Add none for empty tuple items if "attributes" in expression: if "type" in expression['attributes']: @@ -272,6 +299,7 @@ def parse_expression(expression, caller_context): for idx in range(len(elems)): if elems[idx] == '': expressions.insert(idx, None) + print(expressions) t = TupleExpression(expressions) return t @@ -311,15 +339,23 @@ def parse_expression(expression, caller_context): elif name == 'Identifier': assert 'children' not in expression - value = expression['attributes']['value'] - if 'type' in expression['attributes']: - t = expression['attributes']['type'] - if t: - found = re.findall('[struct|enum|function|modifier] \(([\[\] ()a-zA-Z0-9\.,_]*)\)', t) - assert len(found) <= 1 - if found: - value = value+'('+found[0]+')' - value = filter_name(value) + + t = None + + if caller_context.is_compact_ast: + value = expression['name'] + t = expression['typeDescriptions']['typeString'] + else: + value = expression['attributes']['value'] + if 'type' in expression['attributes']: + t = expression['attributes']['type'] + + if t: + found = re.findall('[struct|enum|function|modifier] \(([\[\] ()a-zA-Z0-9\.,_]*)\)', t) + assert len(found) <= 1 + if found: + value = value+'('+found[0]+')' + value = filter_name(value) var = find_variable(value, caller_context) @@ -336,11 +372,16 @@ def parse_expression(expression, caller_context): return index elif name == 'MemberAccess': - member_name = expression['attributes']['member_name'] - member_type = expression['attributes']['type'] - children = expression['children'] - assert len(children) == 1 - member_expression = parse_expression(children[0], caller_context) + if caller_context.is_compact_ast: + member_name = expression['memberName'] + member_type = expression['typeDescriptions']['typeString'] + member_expression = parse_expression(expression['expression'], caller_context) + else: + member_name = expression['attributes']['member_name'] + member_type = expression['attributes']['type'] + children = expression['children'] + assert len(children) == 1 + member_expression = parse_expression(children[0], caller_context) if str(member_expression) == 'super': super_name = parse_super_name(expression) if isinstance(caller_context, Contract): diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index d0a69cc13..1f6f0cd71 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -18,6 +18,22 @@ class SlitherSolc(Slither): self._contracts_by_id = {} self._analyzed = False + self._is_compact_ast = False + + def get_key(self): + if self._is_compact_ast: + return 'nodeType' + return 'name' + + def get_children(self): + if self._is_compact_ast: + return 'nodes' + return 'children' + + @property + def is_compact_ast(self): + return self._is_compact_ast + def _parse_contracts_from_json(self, json_data): first = json_data.find('{') if first != -1: @@ -27,33 +43,40 @@ class SlitherSolc(Slither): data_loaded = json.loads(json_data) - if data_loaded['name'] == 'root': + + if 'nodeType' in data_loaded: + self._is_compact_ast = True + + if data_loaded[self.get_key()] == 'root': self._solc_version = '0.3' logger.error('solc <0.4 is not supported') return - elif data_loaded['name'] == 'SourceUnit': + elif data_loaded[self.get_key()] == 'SourceUnit': self._solc_version = '0.4' self._parse_source_unit(data_loaded, filename) else: logger.error('solc version is not supported') return - for contract_data in data_loaded['children']: + for contract_data in data_loaded[self.get_children()]: # if self.solc_version == '0.3': - # assert contract_data['name'] == 'Contract' + # assert contract_data[self.get_key()] == 'Contract' # contract = ContractSolc03(self, contract_data) if self.solc_version == '0.4': - assert contract_data['name'] in ['ContractDefinition', 'PragmaDirective', 'ImportDirective'] - if contract_data['name'] == 'ContractDefinition': + assert contract_data[self.get_key()] in ['ContractDefinition', 'PragmaDirective', 'ImportDirective'] + if contract_data[self.get_key()] == 'ContractDefinition': contract = ContractSolc04(self, contract_data) if 'src' in contract_data: contract.set_offset(contract_data['src'], self) self._contractsNotParsed.append(contract) - elif contract_data['name'] == 'PragmaDirective': - pragma = Pragma(contract_data['attributes']["literals"]) + elif contract_data[self.get_key()] == 'PragmaDirective': + if self._is_compact_ast: + pragma = Pragma(contract_data['literals']) + else: + pragma = Pragma(contract_data['attributes']["literals"]) pragma.set_offset(contract_data['src'], self) self._pragma_directives.append(pragma) - elif contract_data['name'] == 'ImportDirective': + elif contract_data[self.get_key()] == 'ImportDirective': import_directive = Import(contract_data['attributes']["absolutePath"]) import_directive.set_offset(contract_data['src'], self) self._import_directives.append(import_directive) @@ -62,7 +85,7 @@ class SlitherSolc(Slither): return False def _parse_source_unit(self, data, filename): - if data['name'] != 'SourceUnit': + if data[self.get_key()] != 'SourceUnit': return -1 # handle solc prior 0.3.6 # match any char for filename @@ -117,7 +140,7 @@ class SlitherSolc(Slither): self._analyze_third_part(contracts_to_be_analyzed, libraries) self._analyzed = True - + self._convert_to_slithir() # TODO refactor the following functions, and use a lambda function @@ -236,7 +259,7 @@ class SlitherSolc(Slither): contract.analyze_content_functions() contract.set_is_analyzed(True) - + def _convert_to_slithir(self): for contract in self.contracts: for func in contract.functions + contract.modifiers: diff --git a/slither/solc_parsing/variables/variable_declaration.py b/slither/solc_parsing/variables/variable_declaration.py index cf1cc362d..706e80faf 100644 --- a/slither/solc_parsing/variables/variable_declaration.py +++ b/slither/solc_parsing/variables/variable_declaration.py @@ -33,7 +33,17 @@ class VariableDeclarationSolc(Variable): self._elem_to_parse = None self._initializedNotParsed = None - if var['name'] in ['VariableDeclarationStatement', 'VariableDefinitionStatement']: + self._is_compact_ast = False + + if 'nodeType' in var: + self._is_compact_ast = True + + if self._is_compact_ast: + nodeType = var['nodeType'] + else: + nodeType = var['name'] + + if nodeType in ['VariableDeclarationStatement', 'VariableDefinitionStatement']: if len(var['children']) == 2: init = var['children'][1] elif len(var['children']) == 1: @@ -45,10 +55,10 @@ class VariableDeclarationSolc(Variable): exit(-1) declaration = var['children'][0] self._init_from_declaration(declaration, init) - elif var['name'] == 'VariableDeclaration': + elif nodeType == 'VariableDeclaration': self._init_from_declaration(var, None) else: - logger.error('Incorrect variable declaration type {}'.format(var['name'])) + logger.error('Incorrect variable declaration type {}'.format(nodeType)) exit(-1) @property @@ -66,13 +76,17 @@ class VariableDeclarationSolc(Variable): self._visibility = 'internal' def _init_from_declaration(self, var, init): - assert len(var['children']) <= 2 - assert var['name'] == 'VariableDeclaration' + if self._is_compact_ast: + attributes = var + self._typeName = attributes['typeDescriptions']['typeString'] + else: + assert len(var['children']) <= 2 + assert var['name'] == 'VariableDeclaration' - attributes = var['attributes'] - self._name = attributes['name'] + attributes = var['attributes'] + self._typeName = attributes['type'] - self._typeName = attributes['type'] + self._name = attributes['name'] self._arrayDepth = 0 self._isMapping = False self._mappingFrom = None From 64eb44c74e60163e1b5ed952399770c3e7ba2e51 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Fri, 19 Oct 2018 08:29:06 +0100 Subject: [PATCH 160/308] Update naming_convention.py --- slither/detectors/naming_convention/naming_convention.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index 788ab976a..488fbaf01 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -9,7 +9,7 @@ class NamingConvention(AbstractDetector): """ ARGUMENT = 'naming-convention' - HELP = 'naming convention violations' + HELP = 'conformance to Solidity naming conventions' IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH From f8c1ce729ea645b5297d2c33e141f2715d613d68 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Fri, 19 Oct 2018 08:29:31 +0100 Subject: [PATCH 161/308] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 10de04f8d..e3e6dd52a 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Check | Purpose | Impact | Confidence `--detect-pragma`| Detect if different pragma directives are used | Informational | High `--detect-solc-version`| Detect if an old version of Solidity used (<0.4.23) | Informational | High `--detect-unused-state`| Detect unused state variables | Informational | High -`--detect-naming-convention`| Detect conformance to Solidity naming conventions | Informational | Informational +`--detect-naming-convention`| Detect conformance to Solidity naming conventions | Informational | High [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. From b9c5df3409b8519cab778e1ddfb122b7adc3e6cd Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 19 Oct 2018 08:51:08 +0100 Subject: [PATCH 162/308] Update travis script to use one detector per testcase (close #37) --- scripts/travis_test.sh | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index ec045f6f0..b6074e96b 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -2,69 +2,69 @@ ### Test Detectors -slither tests/uninitialized.sol --disable-solc-warnings +slither tests/uninitialized.sol --disable-solc-warnings --detect-uninitialized-state if [ $? -ne 1 ]; then exit 1 fi # contains also the test for the suicidal detector -slither tests/backdoor.sol --disable-solc-warnings -if [ $? -ne 3 ]; then +slither tests/backdoor.sol --disable-solc-warnings --detect-backdoor +if [ $? -ne 1 ]; then exit 1 fi -slither tests/pragma.0.4.24.sol --disable-solc-warnings +slither tests/pragma.0.4.24.sol --disable-solc-warnings --detect-pragma if [ $? -ne 1 ]; then exit 1 fi -slither tests/old_solc.sol.json --solc-ast +slither tests/old_solc.sol.json --solc-ast --detect-solc-version if [ $? -ne 1 ]; then exit 1 fi -slither tests/reentrancy.sol --disable-solc-warnings -if [ $? -ne 4 ]; then +slither tests/reentrancy.sol --disable-solc-warnings --detect-reentrancy +if [ $? -ne 1 ]; then exit 1 fi -slither tests/uninitialized_storage_pointer.sol --disable-solc-warnings +slither tests/uninitialized_storage_pointer.sol --disable-solc-warnings --detect-uninitialized-storage if [ $? -ne 1 ]; then exit 1 fi -slither tests/tx_origin.sol --disable-solc-warnings +slither tests/tx_origin.sol --disable-solc-warnings --detect-tx-origin if [ $? -ne 2 ]; then exit 1 fi -slither tests/unused_state.sol +slither tests/unused_state.sol --detect-unused-state if [ $? -ne 1 ]; then exit 1 fi -slither tests/locked_ether.sol +slither tests/locked_ether.sol --detect-locked-ether if [ $? -ne 1 ]; then exit 1 fi -slither tests/arbitrary_send.sol --disable-solc-warnings +slither tests/arbitrary_send.sol --disable-solc-warnings --detect-arbitrary-send if [ $? -ne 2 ]; then exit 1 fi -slither tests/inline_assembly_contract.sol --disable-solc-warnings -if [ $? -ne 3 ]; then +slither tests/inline_assembly_contract.sol --disable-solc-warnings --detect-assembly +if [ $? -ne 1 ]; then exit 1 fi -slither tests/inline_assembly_library.sol --disable-solc-warnings -if [ $? -ne 6 ]; then +slither tests/inline_assembly_library.sol --disable-solc-warnings --detect-assembly +if [ $? -ne 2 ]; then exit 1 fi -slither tests/naming_convention.sol --disable-solc-warnings +slither tests/naming_convention.sol --disable-solc-warnings --detect-naming-convention if [ $? -ne 10 ]; then exit 1 fi @@ -86,4 +86,4 @@ python examples/scripts/variable_in_condition.py examples/scripts/variable_in_co if [ $? -ne 0 ]; then exit 1 fi -exit 0 \ No newline at end of file +exit 0 From c3878006abe108783e8c41068fcf1859bb59ad50 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Fri, 19 Oct 2018 08:56:28 +0100 Subject: [PATCH 163/308] Update low_level_calls.py --- slither/detectors/operations/low_level_calls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/detectors/operations/low_level_calls.py b/slither/detectors/operations/low_level_calls.py index 1f06cb51c..f97beeb32 100644 --- a/slither/detectors/operations/low_level_calls.py +++ b/slither/detectors/operations/low_level_calls.py @@ -12,7 +12,7 @@ class LowLevelCalls(AbstractDetector): """ ARGUMENT = 'low-level-calls' - HELP = 'detects low level calls' + HELP = 'low level calls' IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH From 91cd139202e92a85a6736622e0ff055b77186c50 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 19 Oct 2018 09:00:55 +0100 Subject: [PATCH 164/308] Update README Add a space by default at the beginning of detector.logger output --- README.md | 4 ++-- slither/detectors/abstract_detector.py | 1 + .../naming_convention/naming_convention.py | 20 +++++++++---------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 2af28f13a..84b23bb58 100644 --- a/README.md +++ b/README.md @@ -55,11 +55,11 @@ Check | Purpose | Impact | Confidence `--detect-locked-ether`| Detect contracts with a payable function that do not send ether | Medium | High `--detect-tx-origin`| Detect dangerous usage of `tx.origin` | Medium | Medium `--detect-assembly`| Detect assembly usage | Informational | High +`--detect-low-level-calls`| Detect low level calls | Informational | High +`--detect-naming-convention`| Detect conformance to Solidity naming conventions | Informational | High `--detect-pragma`| Detect if different pragma directives are used | Informational | High `--detect-solc-version`| Detect if an old version of Solidity used (<0.4.23) | Informational | High `--detect-unused-state`| Detect unused state variables | Informational | High -`--detect-low-level-calls`| Detect low level calls | Informational | High -`--detect-naming-convention`| Detect conformance to Solidity naming conventions | Informational | High [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. diff --git a/slither/detectors/abstract_detector.py b/slither/detectors/abstract_detector.py index 1eab711d1..2575bc652 100644 --- a/slither/detectors/abstract_detector.py +++ b/slither/detectors/abstract_detector.py @@ -64,6 +64,7 @@ class AbstractDetector(metaclass=abc.ABCMeta): def log(self, info): if self.logger: + info = " "+info self.logger.info(self.color(info)) @abc.abstractmethod diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index 488fbaf01..91acac4d2 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -35,7 +35,7 @@ class NamingConvention(AbstractDetector): for contract in self.contracts: if self.is_cap_words(contract.name) is False: - info = " Contract '{}' is not in CapWords".format(contract.name) + info = "Contract '{}' is not in CapWords".format(contract.name) self.log(info) results.append({'vuln': 'NamingConvention', @@ -46,7 +46,7 @@ class NamingConvention(AbstractDetector): for struct in contract.structures: if self.is_cap_words(struct.name) is False: - info = " Struct '{}' is not in CapWords, Contract: '{}' ".format(struct.name, contract.name) + info = "Struct '{}' is not in CapWords, Contract: '{}' ".format(struct.name, contract.name) self.log(info) results.append({'vuln': 'NamingConvention', @@ -58,7 +58,7 @@ class NamingConvention(AbstractDetector): for event in contract.events: if self.is_cap_words(event.name) is False: - info = " Event '{}' is not in CapWords, Contract: '{}' ".format(event.name, contract.name) + info = "Event '{}' is not in CapWords, Contract: '{}' ".format(event.name, contract.name) self.log(info) results.append({'vuln': 'NamingConvention', @@ -70,7 +70,7 @@ class NamingConvention(AbstractDetector): for func in contract.functions: if self.is_mixed_case(func.name) is False: - info = " Function '{}' is not in mixedCase, Contract: '{}' ".format(func.name, contract.name) + info = "Function '{}' is not in mixedCase, Contract: '{}' ".format(func.name, contract.name) self.log(info) results.append({'vuln': 'NamingConvention', @@ -82,7 +82,7 @@ class NamingConvention(AbstractDetector): for argument in func.parameters: if self.is_mixed_case(argument.name) is False: - info = " Parameter '{}' is not in mixedCase, Contract: '{}', Function: '{}'' " \ + info = "Parameter '{}' is not in mixedCase, Contract: '{}', Function: '{}'' " \ .format(argument.name, argument.name, contract.name) self.log(info) @@ -97,7 +97,7 @@ class NamingConvention(AbstractDetector): if self.should_avoid_name(var.name): if self.is_upper_case_with_underscores(var.name) is False: - info = " Variable '{}' l, O, I should not be used, Contract: '{}' " \ + info = "Variable '{}' l, O, I should not be used, Contract: '{}' " \ .format(var.name, contract.name) self.log(info) @@ -109,7 +109,7 @@ class NamingConvention(AbstractDetector): if var.is_constant is True: if self.is_upper_case_with_underscores(var.name) is False: - info = " Constant '{}' is not in UPPER_CASE_WITH_UNDERSCORES, Contract: '{}' " \ + info = "Constant '{}' is not in UPPER_CASE_WITH_UNDERSCORES, Contract: '{}' " \ .format(var.name, contract.name) self.log(info) @@ -120,7 +120,7 @@ class NamingConvention(AbstractDetector): 'sourceMapping': var.source_mapping}) else: if self.is_mixed_case(var.name) is False: - info = " Variable '{}' is not in mixedCase, Contract: '{}' ".format(var.name, contract.name) + info = "Variable '{}' is not in mixedCase, Contract: '{}' ".format(var.name, contract.name) self.log(info) results.append({'vuln': 'NamingConvention', @@ -131,7 +131,7 @@ class NamingConvention(AbstractDetector): for enum in contract.enums: if self.is_cap_words(enum.name) is False: - info = " Enum '{}' is not in CapWords, Contract: '{}' ".format(enum.name, contract.name) + info = "Enum '{}' is not in CapWords, Contract: '{}' ".format(enum.name, contract.name) self.log(info) results.append({'vuln': 'NamingConvention', @@ -142,7 +142,7 @@ class NamingConvention(AbstractDetector): for modifier in contract.modifiers: if self.is_mixed_case(modifier.name) is False: - info = " Modifier '{}' is not in mixedCase, Contract: '{}' ".format(modifier.name, contract.name) + info = "Modifier '{}' is not in mixedCase, Contract: '{}' ".format(modifier.name, contract.name) self.log(info) results.append({'vuln': 'NamingConvention', From 4548a91323b9e42f46be77ee0797a39ba773330f Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 19 Oct 2018 09:46:57 +0100 Subject: [PATCH 165/308] SlithIR: improve the assignment of tmp variable names --- slither/slithir/convert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 9e0fd21c6..da72b560e 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -522,7 +522,7 @@ def reset_variable_number(result): variables = [] for ins in result: variables += ins.read - if isinstance(ins, OperationWithLValue): + if isinstance(ins, OperationWithLValue) and not ins.lvalue in variables: variables += [ins.lvalue] tmp_variables = [v for v in variables if isinstance(v, TemporaryVariable)] From f31bdbcdf05fb620360aa19ee851b29973179c13 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 19 Oct 2018 10:04:03 +0100 Subject: [PATCH 166/308] Add script example to print slithIR operations --- examples/scripts/slithIR.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 examples/scripts/slithIR.py diff --git a/examples/scripts/slithIR.py b/examples/scripts/slithIR.py new file mode 100644 index 000000000..f676e6cd5 --- /dev/null +++ b/examples/scripts/slithIR.py @@ -0,0 +1,32 @@ +import sys +from slither import Slither + +if len(sys.argv) != 2: + print('python.py slithIR.py contract.sol') + exit(-1) + +# Init slither +slither = Slither(sys.argv[1]) + +# Iterate over all the contracts +for contract in slither.contracts: + + # Iterate over all the functions + for function in contract.functions: + + # Dont explore inherited functions + if function.contract == contract: + + print('Function: {}'.format(function.name)) + + # Iterate over the nodes of the function + for node in function.nodes: + + # Print the Solidity expression of the nodes + # And the SlithIR operations + if node.expression: + + print('\tSolidity expression: {}'.format(node.expression)) + print('\tSlithIR:') + for ir in node.irs: + print('\t\t\t{}'.format(ir)) From 9c294c7c885ef6144d8193f390ec2a869f683832 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 19 Oct 2018 10:12:44 +0100 Subject: [PATCH 167/308] Remove results information if printers are used --- slither/__main__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/slither/__main__.py b/slither/__main__.py index 7e5e0164f..d51b1b391 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -203,7 +203,11 @@ def main_impl(all_detector_classes, all_printer_classes): if args.json: output_json(results, args.json) - logger.info('%s analyzed (%d contracts), %d result(s) found', filename, number_contracts, len(results)) + # Dont print the number of result for printers + if printer_classes: + logger.info('%s analyzed (%d contracts)', filename, number_contracts) + else: + logger.info('%s analyzed (%d contracts), %d result(s) found', filename, number_contracts, len(results)) exit(results) except Exception: From 15845cb5e30a5c4440c914b46398ae76c3afcf20 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 19 Oct 2018 13:58:02 +0100 Subject: [PATCH 168/308] Add slither-analyzer to pypi (close #41) Version 0.1.0 --- README.md | 6 ++---- setup.py | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 84b23bb58..0f87dc524 100644 --- a/README.md +++ b/README.md @@ -66,15 +66,13 @@ Check | Purpose | Impact | Confidence ## How to install Slither requires Python 3.6+ and [solc](https://github.com/ethereum/solidity/), the Solidity compiler. - +### Using Git ```bash $ git clone https://github.com/trailofbits/slither.git && cd slither diff --git a/setup.py b/setup.py index 9b2010206..8de27eb00 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( description='Slither is a Solidity static analysis framework written in Python 3.', url='https://github.com/trailofbits/slither', author='Trail of Bits', - version='0.1', + version='0.1.0', packages=find_packages(), python_requires='>=3.6', install_requires=['prettytable>=0.7.2'], From 14ed734d82bb36cbbf2065b73de7b4862431fbe8 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Fri, 19 Oct 2018 14:04:18 +0100 Subject: [PATCH 169/308] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f87dc524..983780e20 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Slither is a Solidity static analysis framework written in Python 3. It runs a s * Easy integration into continuous integration pipelines * Built-in 'printers' quickly report crucial contract information * Detector API to write custom analyses in Python -* Ability to analyze contracts written with Solidity > 0.4 +* Ability to analyze contracts written with Solidity >= 0.4 * Intermediate representation ([SlithIR](https://github.com/trailofbits/slither/wiki/SlithIR)) enables simple, high-precision analyses ## Usage From 9db29140316be2c978b7758dd896a327ae4b23f6 Mon Sep 17 00:00:00 2001 From: Cryptomental Date: Fri, 19 Oct 2018 13:09:34 +0200 Subject: [PATCH 170/308] possible_const_state_variables.py: Improve detector with more strict rules. Improve the detector as advised during pull request review: * contracts: iterate over contracts_derived * functions: iterate over contract.all_functions_called + contract.modifiers * check if ir.lvalue is StateVariable * check the initial value with slither.core.expressions.literal.Literal Refs: https://github.com/trailofbits/slither/issues/25 --- .../possible_const_state_variables.py | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/slither/detectors/variables/possible_const_state_variables.py b/slither/detectors/variables/possible_const_state_variables.py index 9f6bc0f03..920a42586 100644 --- a/slither/detectors/variables/possible_const_state_variables.py +++ b/slither/detectors/variables/possible_const_state_variables.py @@ -2,9 +2,12 @@ Module detecting state variables that could be declared as constant """ +from collections import defaultdict from slither.core.solidity_types.elementary_type import ElementaryType from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations import OperationWithLValue +from slither.core.variables.state_variable import StateVariable +from slither.core.expressions.literal import Literal class ConstCandidateStateVars(AbstractDetector): @@ -23,18 +26,17 @@ class ConstCandidateStateVars(AbstractDetector): @staticmethod def lvalues_of_operations_with_lvalue(contract): ret = [] - for f in contract.functions: - if f.name == 'fallback': # Skip fallback function to avoid false positives - continue + for f in contract.all_functions_called + contract.modifiers: for n in f.nodes: for ir in n.irs: - if isinstance(ir, OperationWithLValue): + if isinstance(ir, OperationWithLValue) and isinstance(ir.lvalue, StateVariable): ret.append(ir.lvalue) return ret @staticmethod def non_const_state_variables(contract): - return [variable for variable in contract.state_variables if not variable.is_constant] + return [variable for variable in contract.state_variables + if not variable.is_constant and type(variable.expression) == Literal] def detect_const_candidates(self, contract): const_candidates = [] @@ -52,20 +54,26 @@ class ConstCandidateStateVars(AbstractDetector): """ Detect state variables that could be const """ results = [] - for c in self.contracts: + for c in self.slither.contracts_derived: const_candidates = self.detect_const_candidates(c) if const_candidates: - variable_names = [v.name for v in const_candidates] - info = "State variables that could be const in %s, Contract: %s, Vars %s" % (self.filename, - c.name, - str(variable_names)) - self.log(info) + variables_by_contract = defaultdict(list) - sourceMapping = [v.source_mapping for v in const_candidates] + for state_var in const_candidates: + variables_by_contract[state_var.contract.name].append(state_var) - results.append({'vuln': 'ConstStateVariableCandidates', - 'sourceMapping': sourceMapping, - 'filename': self.filename, - 'contract': c.name, - 'unusedVars': variable_names}) + for contract, variables in variables_by_contract.items(): + variable_names = [v.name for v in variables] + info = "State variables that could be const in %s, Contract: %s, Vars %s" % (self.filename, + contract, + str(variable_names)) + self.log(info) + + sourceMapping = [v.source_mapping for v in const_candidates] + + results.append({'vuln': 'ConstStateVariableCandidates', + 'sourceMapping': sourceMapping, + 'filename': self.filename, + 'contract': c.name, + 'unusedVars': variable_names}) return results From 93c6151ce68e348c049d40b9a2d3ee13bc726f26 Mon Sep 17 00:00:00 2001 From: Praveen Gupta Date: Fri, 19 Oct 2018 21:11:12 +0530 Subject: [PATCH 171/308] Formatting fixes for inheritance printer --- slither/printers/inheritance/inheritance.py | 27 +++++++++++---------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/slither/printers/inheritance/inheritance.py b/slither/printers/inheritance/inheritance.py index 4ce8f94bc..3d8c0ad63 100644 --- a/slither/printers/inheritance/inheritance.py +++ b/slither/printers/inheritance/inheritance.py @@ -5,7 +5,7 @@ """ from slither.printers.abstract_printer import AbstractPrinter -from slither.utils.colors import blue, green, magenta +from slither.utils.colors import blue, green class PrinterInheritance(AbstractPrinter): @@ -13,6 +13,7 @@ class PrinterInheritance(AbstractPrinter): HELP = 'the inheritance relation between contracts' def _get_child_contracts(self, base): + # Generate function to get all child contracts of a base contract for child in self.contracts: if base in child.inheritance: yield child @@ -26,20 +27,20 @@ class PrinterInheritance(AbstractPrinter): _filename(string) """ info = 'Inheritance\n' + + if not self.contracts: + return + info += blue('Child_Contract -> ') + green('Base_Contracts') - for contract in self.contracts: - info += blue(f'\n+ {contract.name} -> ') - if contract.inheritance: - info += green(", ".join(map(str, contract.inheritance))) - else: - info += magenta("Root_Contract") + for child in self.contracts: + info += blue(f'\n+ {child.name}') + if child.inheritance: + info += ' -> ' + green(", ".join(map(str, child.inheritance))) info += green('\n\nBase_Contract -> ') + blue('Child_Contracts') - for contract in self.contracts: - info += green(f'\n+ {contract.name} -> ') - children = list(self._get_child_contracts(contract)) + for base in self.contracts: + info += green(f'\n+ {base.name}') + children = list(self._get_child_contracts(base)) if children: - info += blue(", ".join(map(str, children))) - else: - info += magenta("Leaf_Contract") + info += ' -> ' + blue(", ".join(map(str, children))) self.info(info) From cbb64a719a4889f0e0a8a485e019c380da4605fe Mon Sep 17 00:00:00 2001 From: Praveen Gupta Date: Fri, 19 Oct 2018 21:11:52 +0530 Subject: [PATCH 172/308] Adds example contracts for inheritance printer --- examples/printers/inheritances.sol | 23 +++++++----------- examples/printers/inheritances_graph.sol | 21 ++++++++++++++++ ...ces.sol.dot => inheritances_graph.sol.dot} | 0 ...ces.sol.png => inheritances_graph.sol.png} | Bin 4 files changed, 30 insertions(+), 14 deletions(-) create mode 100644 examples/printers/inheritances_graph.sol rename examples/printers/{inheritances.sol.dot => inheritances_graph.sol.dot} (100%) rename examples/printers/{inheritances.sol.png => inheritances_graph.sol.png} (100%) diff --git a/examples/printers/inheritances.sol b/examples/printers/inheritances.sol index a8b383c76..adda74ef7 100644 --- a/examples/printers/inheritances.sol +++ b/examples/printers/inheritances.sol @@ -1,21 +1,16 @@ -contract Contract1{ +pragma solidity ^0.4.24; - uint myvar; - - function myfunc() public{} +contract BaseContract1{ } -contract Contract2{ - - uint public myvar2; - - function myfunc2() public{} - - function privatefunc() private{} +contract BaseContract2{ } -contract Contract3 is Contract1, Contract2{ - - function myfunc() public{} // override myfunc +contract ChildContract1 is BaseContract1{ +} +contract ChildContract2 is BaseContract1, BaseContract2{ } + +contract GrandchildContract1 is ChildContract1{ +} \ No newline at end of file diff --git a/examples/printers/inheritances_graph.sol b/examples/printers/inheritances_graph.sol new file mode 100644 index 000000000..a8b383c76 --- /dev/null +++ b/examples/printers/inheritances_graph.sol @@ -0,0 +1,21 @@ +contract Contract1{ + + uint myvar; + + function myfunc() public{} +} + +contract Contract2{ + + uint public myvar2; + + function myfunc2() public{} + + function privatefunc() private{} +} + +contract Contract3 is Contract1, Contract2{ + + function myfunc() public{} // override myfunc + +} diff --git a/examples/printers/inheritances.sol.dot b/examples/printers/inheritances_graph.sol.dot similarity index 100% rename from examples/printers/inheritances.sol.dot rename to examples/printers/inheritances_graph.sol.dot diff --git a/examples/printers/inheritances.sol.png b/examples/printers/inheritances_graph.sol.png similarity index 100% rename from examples/printers/inheritances.sol.png rename to examples/printers/inheritances_graph.sol.png From ef389c9916b1fccb1b53e77c75c7e26864f293bc Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Fri, 19 Oct 2018 12:45:29 -0400 Subject: [PATCH 173/308] Update README.md --- README.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 983780e20..023efbe69 100644 --- a/README.md +++ b/README.md @@ -43,23 +43,23 @@ If Slither is run on a directory, it will run on every `.sol` file of the direct ## Checks available -By default, all the checks are run. - -Check | Purpose | Impact | Confidence ---- | --- | --- | --- -`--detect-arbitrary-send`| Detect functions sending ethers to an arbitrary destination | High | Medium -`--detect-reentrancy`| Detect reentrancy vulnerabilities | High | Medium -`--detect-suicidal`| Detect suicidal functions | High | High -`--detect-uninitialized-state`| Detect uninitialized state variables | High | High -`--detect-uninitialized-storage`| Detect uninitialized storage variables | High | High -`--detect-locked-ether`| Detect contracts with a payable function that do not send ether | Medium | High -`--detect-tx-origin`| Detect dangerous usage of `tx.origin` | Medium | Medium -`--detect-assembly`| Detect assembly usage | Informational | High -`--detect-low-level-calls`| Detect low level calls | Informational | High -`--detect-naming-convention`| Detect conformance to Solidity naming conventions | Informational | High -`--detect-pragma`| Detect if different pragma directives are used | Informational | High -`--detect-solc-version`| Detect if an old version of Solidity used (<0.4.23) | Informational | High -`--detect-unused-state`| Detect unused state variables | Informational | High +By default, all the checks are run. Use --detect-_name-of-check_ to run one check at a time. + +Num | Check | Purpose | Impact | Confidence +--- | --- | --- | --- | --- +1 | `suicidal`| Detect suicidal functions | High | High +2 | `uninitialized-state`| Detect uninitialized state variables | High | High +3 | `uninitialized-storage`| Detect uninitialized storage variables | High | High +4 | `arbitrary-send`| Detect functions sending ethers to an arbitrary destination | High | Medium +5 | `reentrancy`| Detect reentrancy vulnerabilities | High | Medium +6 | `locked-ether`| Detect contracts with a payable function that do not send ether | Medium | High +7 | `tx-origin`| Detect dangerous usage of `tx.origin` | Medium | Medium +8 | `assembly`| Detect assembly usage | Informational | High +9 | `low-level-calls`| Detect low level calls | Informational | High +10 | `naming-convention`| Detect conformance to Solidity naming conventions | Informational | High +11 | `pragma`| Detect if different pragma directives are used | Informational | High +12 | `solc-version`| Detect if an old version of Solidity used (<0.4.23) | Informational | High +13 | `unused-state`| Detect unused state variables | Informational | High [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. From 0b9e71f3f2ec7fd8f906268bf4f34283da676235 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Fri, 19 Oct 2018 12:55:02 -0400 Subject: [PATCH 174/308] Update README.md --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 023efbe69..bbe891a0d 100644 --- a/README.md +++ b/README.md @@ -45,21 +45,21 @@ If Slither is run on a directory, it will run on every `.sol` file of the direct By default, all the checks are run. Use --detect-_name-of-check_ to run one check at a time. -Num | Check | Purpose | Impact | Confidence +Num | Check | What it Detects | Impact | Confidence --- | --- | --- | --- | --- -1 | `suicidal`| Detect suicidal functions | High | High -2 | `uninitialized-state`| Detect uninitialized state variables | High | High -3 | `uninitialized-storage`| Detect uninitialized storage variables | High | High -4 | `arbitrary-send`| Detect functions sending ethers to an arbitrary destination | High | Medium -5 | `reentrancy`| Detect reentrancy vulnerabilities | High | Medium -6 | `locked-ether`| Detect contracts with a payable function that do not send ether | Medium | High -7 | `tx-origin`| Detect dangerous usage of `tx.origin` | Medium | Medium -8 | `assembly`| Detect assembly usage | Informational | High -9 | `low-level-calls`| Detect low level calls | Informational | High -10 | `naming-convention`| Detect conformance to Solidity naming conventions | Informational | High -11 | `pragma`| Detect if different pragma directives are used | Informational | High -12 | `solc-version`| Detect if an old version of Solidity used (<0.4.23) | Informational | High -13 | `unused-state`| Detect unused state variables | Informational | High +1 | `suicidal`| Suicidal functions | High | High +2 | `uninitialized-state`| Uninitialized state variables | High | High +3 | `uninitialized-storage`| Uninitialized storage variables | High | High +4 | `arbitrary-send`| Functions that send ether to an arbitrary destination | High | Medium +5 | `reentrancy`| Reentrancy vulnerabilities | High | Medium +6 | `locked-ether`| Payable functions that do not send ether | Medium | High +7 | `tx-origin`| Dangerous usage of `tx.origin` | Medium | Medium +8 | `assembly`| Assembly usage | Informational | High +9 | `low-level-calls`| Low level calls | Informational | High +10 | `naming-convention`| Conformance to Solidity naming conventions | Informational | High +11 | `pragma`| If different pragma directives are used | Informational | High +12 | `solc-version`| If an old version of Solidity used (<0.4.23) | Informational | High +13 | `unused-state`| Unused state variables | Informational | High [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. From 0197093a4c082dd7323543e51fbe2136968e07fd Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 21 Oct 2018 19:17:43 +0100 Subject: [PATCH 175/308] Add exceptions to the naming convention rules --- .../detectors/naming_convention/naming_convention.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index 91acac4d2..74bf8acb6 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -6,6 +6,10 @@ class NamingConvention(AbstractDetector): """ Check if naming conventions are followed https://solidity.readthedocs.io/en/v0.4.25/style-guide.html?highlight=naming_convention%20convention#naming_convention-conventions + + Exceptions: + - Allow constant variables name/symbol/decimals to be lowercase (ERC20) + - Allow '_' at the beggining of the mixed_case match, to represent private variable and unused parameters """ ARGUMENT = 'naming-convention' @@ -19,7 +23,9 @@ class NamingConvention(AbstractDetector): @staticmethod def is_mixed_case(name): - return re.search('^[a-z]([A-Za-z0-9]+)?_?$', name) is not None + # Allow _ at the beginning to represent private variable + # or unused parameters + return re.search('^[a-z_]([A-Za-z0-9]+)?_?$', name) is not None @staticmethod def is_upper_case_with_underscores(name): @@ -108,6 +114,10 @@ class NamingConvention(AbstractDetector): 'sourceMapping': var.source_mapping}) if var.is_constant is True: + # For ERC20 compatibility + if var.name in ['symbol', 'name', 'decimals']: + continue + if self.is_upper_case_with_underscores(var.name) is False: info = "Constant '{}' is not in UPPER_CASE_WITH_UNDERSCORES, Contract: '{}' " \ .format(var.name, contract.name) From 0cfcdc7ccfbe4a5c7b1f7f624423fda9c531514e Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Sun, 21 Oct 2018 15:53:03 -0400 Subject: [PATCH 176/308] add const candidates to readme --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 366f3097e..05876e54b 100644 --- a/README.md +++ b/README.md @@ -56,11 +56,12 @@ Num | Check | What it Detects | Impact | Confidence 6 | `locked-ether`| Payable functions that do not send ether | Medium | High 7 | `tx-origin`| Dangerous usage of `tx.origin` | Medium | Medium 8 | `assembly`| Assembly usage | Informational | High -9 | `low-level-calls`| Low level calls | Informational | High -10 | `naming-convention`| Conformance to Solidity naming conventions | Informational | High -11 | `pragma`| If different pragma directives are used | Informational | High -12 | `solc-version`| If an old version of Solidity used (<0.4.23) | Informational | High -13 | `unused-state`| Unused state variables | Informational | High +9 | `const-candidates-state`| State variables that could be declared constant | Informational | High +10 | `low-level-calls`| Low level calls | Informational | High +11 | `naming-convention`| Conformance to Solidity naming conventions | Informational | High +12 | `pragma`| If different pragma directives are used | Informational | High +13 | `solc-version`| If an old version of Solidity used (<0.4.23) | Informational | High +14 | `unused-state`| Unused state variables | Informational | High [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. From 836f3bab337d12e6e7a92294af92a2a90e398d5c Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 21 Oct 2018 21:23:54 +0100 Subject: [PATCH 177/308] Update detectors helper to be consistent with README.md Update locked-ether description in README Update README generation in slither.py --- README.md | 28 +++++++++---------- slither/__main__.py | 17 ++++++----- .../detectors/attributes/constant_pragma.py | 2 +- slither/detectors/attributes/locked_ether.py | 2 +- slither/detectors/attributes/old_solc.py | 2 +- slither/detectors/examples/backdoor.py | 2 +- slither/detectors/functions/arbitrary_send.py | 2 +- slither/detectors/functions/suicidal.py | 2 +- .../naming_convention/naming_convention.py | 2 +- .../detectors/operations/low_level_calls.py | 2 +- slither/detectors/reentrancy/reentrancy.py | 2 +- slither/detectors/statements/assembly.py | 2 +- slither/detectors/statements/tx_origin.py | 2 +- .../possible_const_state_variables.py | 2 +- .../uninitialized_state_variables.py | 2 +- .../uninitialized_storage_variables.py | 2 +- .../variables/unused_state_variables.py | 2 +- 17 files changed, 39 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 05876e54b..71ce1d8fd 100644 --- a/README.md +++ b/README.md @@ -48,20 +48,20 @@ By default, all the checks are run. Use --detect-_name-of-check_ to run one chec Num | Check | What it Detects | Impact | Confidence --- | --- | --- | --- | --- -1 | `suicidal`| Suicidal functions | High | High -2 | `uninitialized-state`| Uninitialized state variables | High | High -3 | `uninitialized-storage`| Uninitialized storage variables | High | High -4 | `arbitrary-send`| Functions that send ether to an arbitrary destination | High | Medium -5 | `reentrancy`| Reentrancy vulnerabilities | High | Medium -6 | `locked-ether`| Payable functions that do not send ether | Medium | High -7 | `tx-origin`| Dangerous usage of `tx.origin` | Medium | Medium -8 | `assembly`| Assembly usage | Informational | High -9 | `const-candidates-state`| State variables that could be declared constant | Informational | High -10 | `low-level-calls`| Low level calls | Informational | High -11 | `naming-convention`| Conformance to Solidity naming conventions | Informational | High -12 | `pragma`| If different pragma directives are used | Informational | High -13 | `solc-version`| If an old version of Solidity used (<0.4.23) | Informational | High -14 | `unused-state`| Unused state variables | Informational | High +1 | `suicidal` | Suicidal functions | High | High +2 | `uninitialized-state` | Uninitialized state variables | High | High +3 | `uninitialized-storage` | Uninitialized storage variables | High | High +4 | `arbitrary-send` | Functions that send ether to an arbitrary destination | High | Medium +5 | `reentrancy` | Reentrancy vulnerabilities | High | Medium +6 | `locked-ether` | Contracts that lock ether | Medium | High +7 | `tx-origin` | Dangerous usage of `tx.origin` | Medium | Medium +8 | `assembly` | Assembly usage | Informational | High +9 | `const-candidates-state` | State variables that could be declared constant | Informational | High +10 | `low-level-calls` | Low level calls | Informational | High +11 | `naming-convention` | Conformance to Solidity naming conventions | Informational | High +12 | `pragma` | If different pragma directives are used | Informational | High +13 | `solc-version` | If an old version of Solidity used (<0.4.23) | Informational | High +14 | `unused-state` | Unused state variables | Informational | High [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. diff --git a/slither/__main__.py b/slither/__main__.py index dda49ad9f..4b03483a1 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -34,13 +34,16 @@ def output_to_markdown(detector_classes): confidence = classification_txt[detector.CONFIDENCE] detectors_list.append((argument, help_info, impact, confidence)) - # Sort by impact and name - detectors_list = sorted(detectors_list, key=lambda element: (element[2], element[0])) + # Sort by impact, confidence, and name + detectors_list = sorted(detectors_list, key=lambda element: (element[2], element[3], element[0])) + idx = 1 for (argument, help_info, impact, confidence) in detectors_list: - print('`--detect-{}`| Detect {} | {} | {}'.format(argument, - help_info, - classification_txt[impact], - confidence)) + print('{} | `{}` | {} | {} | {}'.format(idx, + argument, + help_info, + classification_txt[impact], + confidence)) + idx = idx +1 def process(filename, args, detector_classes, printer_classes): """ @@ -275,7 +278,7 @@ def parse_args(detector_classes, printer_classes): for detector_cls in detector_classes: detector_arg = '--detect-{}'.format(detector_cls.ARGUMENT) - detector_help = 'Detection of {}'.format(detector_cls.HELP) + detector_help = '{}'.format(detector_cls.HELP) parser.add_argument(detector_arg, help=detector_help, action="append_const", diff --git a/slither/detectors/attributes/constant_pragma.py b/slither/detectors/attributes/constant_pragma.py index 51b6a2b9d..7a1e272c0 100644 --- a/slither/detectors/attributes/constant_pragma.py +++ b/slither/detectors/attributes/constant_pragma.py @@ -11,7 +11,7 @@ class ConstantPragma(AbstractDetector): """ ARGUMENT = 'pragma' - HELP = 'if different pragma directives are used' + HELP = 'If different pragma directives are used' IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH diff --git a/slither/detectors/attributes/locked_ether.py b/slither/detectors/attributes/locked_ether.py index eb84a402b..682224f02 100644 --- a/slither/detectors/attributes/locked_ether.py +++ b/slither/detectors/attributes/locked_ether.py @@ -13,7 +13,7 @@ class LockedEther(AbstractDetector): """ ARGUMENT = 'locked-ether' - HELP = "contracts with a payable function that do not send ether" + HELP = "Contracts that lock ether" IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.HIGH diff --git a/slither/detectors/attributes/old_solc.py b/slither/detectors/attributes/old_solc.py index 13f5f8393..f322deb1c 100644 --- a/slither/detectors/attributes/old_solc.py +++ b/slither/detectors/attributes/old_solc.py @@ -12,7 +12,7 @@ class OldSolc(AbstractDetector): """ ARGUMENT = 'solc-version' - HELP = 'if an old version of Solidity used (<0.4.23)' + HELP = 'If an old version of Solidity used (<0.4.23)' IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH diff --git a/slither/detectors/examples/backdoor.py b/slither/detectors/examples/backdoor.py index e3bf25cca..9e29724f9 100644 --- a/slither/detectors/examples/backdoor.py +++ b/slither/detectors/examples/backdoor.py @@ -7,7 +7,7 @@ class Backdoor(AbstractDetector): """ ARGUMENT = 'backdoor' # slither will launch the detector with slither.py --mydetector - HELP = 'function named backdoor (detector example)' + HELP = 'Function named backdoor (detector example)' IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.HIGH diff --git a/slither/detectors/functions/arbitrary_send.py b/slither/detectors/functions/arbitrary_send.py index 26c305b75..ae115ede3 100644 --- a/slither/detectors/functions/arbitrary_send.py +++ b/slither/detectors/functions/arbitrary_send.py @@ -27,7 +27,7 @@ class ArbitrarySend(AbstractDetector): """ ARGUMENT = 'arbitrary-send' - HELP = 'functions sending ethers to an arbitrary destination' + HELP = 'Functions that send ether to an arbitrary destination' IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.MEDIUM diff --git a/slither/detectors/functions/suicidal.py b/slither/detectors/functions/suicidal.py index 6652cde9d..41b6f9bc5 100644 --- a/slither/detectors/functions/suicidal.py +++ b/slither/detectors/functions/suicidal.py @@ -12,7 +12,7 @@ class Suicidal(AbstractDetector): """ ARGUMENT = 'suicidal' - HELP = 'suicidal functions' + HELP = 'Suicidal functions' IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.HIGH diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index 91acac4d2..ba4d01c0a 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -9,7 +9,7 @@ class NamingConvention(AbstractDetector): """ ARGUMENT = 'naming-convention' - HELP = 'conformance to Solidity naming conventions' + HELP = 'Conformance to Solidity naming conventions' IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH diff --git a/slither/detectors/operations/low_level_calls.py b/slither/detectors/operations/low_level_calls.py index f97beeb32..ec0f546a7 100644 --- a/slither/detectors/operations/low_level_calls.py +++ b/slither/detectors/operations/low_level_calls.py @@ -12,7 +12,7 @@ class LowLevelCalls(AbstractDetector): """ ARGUMENT = 'low-level-calls' - HELP = 'low level calls' + HELP = 'Low level calls' IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH diff --git a/slither/detectors/reentrancy/reentrancy.py b/slither/detectors/reentrancy/reentrancy.py index e7c6b6e97..e5c9966a7 100644 --- a/slither/detectors/reentrancy/reentrancy.py +++ b/slither/detectors/reentrancy/reentrancy.py @@ -17,7 +17,7 @@ from slither.slithir.operations import (HighLevelCall, LowLevelCall, class Reentrancy(AbstractDetector): ARGUMENT = 'reentrancy' - HELP = 'reentrancy vulnerabilities' + HELP = 'Reentrancy vulnerabilities' IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.MEDIUM diff --git a/slither/detectors/statements/assembly.py b/slither/detectors/statements/assembly.py index 8d47a074a..93e9ea598 100644 --- a/slither/detectors/statements/assembly.py +++ b/slither/detectors/statements/assembly.py @@ -12,7 +12,7 @@ class Assembly(AbstractDetector): """ ARGUMENT = 'assembly' - HELP = 'assembly usage' + HELP = 'Assembly usage' IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH diff --git a/slither/detectors/statements/tx_origin.py b/slither/detectors/statements/tx_origin.py index 9b02f008d..f5c474f8f 100644 --- a/slither/detectors/statements/tx_origin.py +++ b/slither/detectors/statements/tx_origin.py @@ -10,7 +10,7 @@ class TxOrigin(AbstractDetector): """ ARGUMENT = 'tx-origin' - HELP = 'dangerous usage of `tx.origin`' + HELP = 'Dangerous usage of `tx.origin`' IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.MEDIUM diff --git a/slither/detectors/variables/possible_const_state_variables.py b/slither/detectors/variables/possible_const_state_variables.py index 920a42586..53f4ca697 100644 --- a/slither/detectors/variables/possible_const_state_variables.py +++ b/slither/detectors/variables/possible_const_state_variables.py @@ -19,7 +19,7 @@ class ConstCandidateStateVars(AbstractDetector): """ ARGUMENT = 'const-candidates-state' - HELP = 'detect state variables that could be const' + HELP = 'State variables that could be declared constant' IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index 47691ba43..717425e26 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -20,7 +20,7 @@ class UninitializedStateVarsDetection(AbstractDetector): """ ARGUMENT = 'uninitialized-state' - HELP = 'uninitialized state variables' + HELP = 'Uninitialized state variables' IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.HIGH diff --git a/slither/detectors/variables/uninitialized_storage_variables.py b/slither/detectors/variables/uninitialized_storage_variables.py index 70ffdc625..37201b33c 100644 --- a/slither/detectors/variables/uninitialized_storage_variables.py +++ b/slither/detectors/variables/uninitialized_storage_variables.py @@ -15,7 +15,7 @@ class UninitializedStorageVars(AbstractDetector): """ ARGUMENT = 'uninitialized-storage' - HELP = 'uninitialized storage variables' + HELP = 'Uninitialized storage variables' IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.HIGH diff --git a/slither/detectors/variables/unused_state_variables.py b/slither/detectors/variables/unused_state_variables.py index b23ac8e57..5be36163e 100644 --- a/slither/detectors/variables/unused_state_variables.py +++ b/slither/detectors/variables/unused_state_variables.py @@ -10,7 +10,7 @@ class UnusedStateVars(AbstractDetector): """ ARGUMENT = 'unused-state' - HELP = 'unused state variables' + HELP = 'Unused state variables' IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH From 2ebbe025a8fa33c74e43063d82e0ae31299ea54c Mon Sep 17 00:00:00 2001 From: redshark1802 Date: Sat, 20 Oct 2018 11:59:16 +0200 Subject: [PATCH 178/308] add support for supplying a glob in addition to file and folder --- README.md | 2 +- slither/__main__.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 366f3097e..c9c198a55 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Slither is a Solidity static analysis framework written in Python 3. It runs a s ## Usage ``` -$ slither tests/uninitialized.sol +$ slither tests/uninitialized.sol # argument can be file, folder or glob, be sure to quote the argument when using a glob [..] INFO:Detectors:Uninitialized state variables in tests/uninitialized.sol, Contract: Uninitialized, Vars: destination, Used in ['transfer'] [..] diff --git a/slither/__main__.py b/slither/__main__.py index 8a3eecac4..1c0bb591c 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -184,12 +184,16 @@ def main_impl(all_detector_classes, all_printer_classes): try: filename = args.filename + globbed_filenames = glob.glob(filename, recursive=True) + if os.path.isfile(filename): (results, number_contracts) = process(filename, args, detector_classes, printer_classes) - elif os.path.isdir(filename): + elif os.path.isdir(filename) or len(globbed_filenames) > 0: extension = "*.sol" if not args.solc_ast else "*.json" filenames = glob.glob(os.path.join(filename, extension)) + if len(filenames) == 0: + filenames = globbed_filenames number_contracts = 0 results = [] for filename in filenames: From 83c1335ec747c099ded8556ee5ce500ead3cf9b5 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 19 Oct 2018 17:56:08 +0100 Subject: [PATCH 179/308] Add support to compact AST (WIP) Switch to compact AST by default (testing purpose, it breaks compatibility with old solc versions) --- .../naming_convention/naming_convention.py | 13 +- slither/slither.py | 2 +- slither/solc_parsing/declarations/contract.py | 79 ++-- slither/solc_parsing/declarations/event.py | 28 +- slither/solc_parsing/declarations/function.py | 379 ++++++++++++------ slither/solc_parsing/declarations/modifier.py | 33 +- .../expressions/expression_parsing.py | 178 +++++--- slither/solc_parsing/slitherSolc.py | 5 +- .../solidity_types/type_parsing.py | 55 ++- .../variables/variable_declaration.py | 84 ++-- 10 files changed, 577 insertions(+), 279 deletions(-) diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index 91acac4d2..5a7b9977a 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -81,7 +81,11 @@ class NamingConvention(AbstractDetector): for argument in func.parameters: - if self.is_mixed_case(argument.name) is False: + argument_name = argument.name + if argument_name and argument_name.startswith('_'): + argument_name = argument_name[1::] + + if self.is_mixed_case(argument_name) is False: info = "Parameter '{}' is not in mixedCase, Contract: '{}', Function: '{}'' " \ .format(argument.name, argument.name, contract.name) self.log(info) @@ -119,7 +123,12 @@ class NamingConvention(AbstractDetector): 'constant': var.name, 'sourceMapping': var.source_mapping}) else: - if self.is_mixed_case(var.name) is False: + + var_name = var.name + if var_name and var_name.startswith('_'): + var_name = var_name[1::] + + if self.is_mixed_case(var_name) is False: info = "Variable '{}' is not in mixedCase, Contract: '{}' ".format(var.name, contract.name) self.log(info) diff --git a/slither/slither.py b/slither/slither.py index 30276b93d..2b277b378 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -93,7 +93,7 @@ class Slither(SlitherSolc): logger.info('Empty AST file: %s', filename) sys.exit(-1) else: - cmd = [solc, filename, '--ast-json'] + cmd = [solc, filename, '--ast-compact-json'] if solc_arguments: # To parse, we first split the string on each '--' solc_args = solc_arguments.split('--') diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index bb3e913cb..426d19b5d 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -38,7 +38,7 @@ class ContractSolc04(Contract): # Export info if self.is_compact_ast: - self._name = self._data[self.get_key()] + self._name = self._data['name'] else: self._name = self._data['attributes'][self.get_key()] @@ -55,8 +55,10 @@ class ContractSolc04(Contract): def get_key(self): return self.slither.get_key() - def get_children(self): - return self.slither.get_children() + def get_children(self, key='nodes'): + if self.is_compact_ast: + return key + return 'children' @property def is_compact_ast(self): @@ -110,18 +112,27 @@ class ContractSolc04(Contract): for father in self.inheritance: self._using_for.update(father.using_for) - for using_for in self._usingForNotParsed: - children = using_for[self.get_children()] - assert children and len(children) <= 2 - if len(children) == 2: - new = parse_type(children[0], self) - old = parse_type(children[1], self) - else: - new = parse_type(children[0], self) - old = '*' - if not old in self._using_for: - self.using_for[old] = [] - self._using_for[old].append(new) + if self.is_compact_ast: + for using_for in self._usingForNotParsed: + lib_name = parse_type(using_for['libraryName'], self) + if 'typeName' in using_for: + type_name = parse_type(using_for['typeName'], self) + else: + type_name = '*' + self._using_for[type_name] = lib_name + else: + for using_for in self._usingForNotParsed: + children = using_for[self.get_children()] + assert children and len(children) <= 2 + if len(children) == 2: + new = parse_type(children[0], self) + old = parse_type(children[1], self) + else: + new = parse_type(children[0], self) + old = '*' + if not old in self._using_for: + self.using_for[old] = [] + self._using_for[old].append(new) self._usingForNotParsed = [] def analyze_enums(self): @@ -137,15 +148,22 @@ class ContractSolc04(Contract): def _analyze_enum(self, enum): # Enum can be parsed in one pass - name = enum['attributes'][self.get_key()] - if 'canonicalName' in enum['attributes']: - canonicalName = enum['attributes']['canonicalName'] + if self.is_compact_ast: + name = enum['name'] + canonicalName = enum['canonicalName'] else: - canonicalName = self.name + '.' + name + name = enum['attributes'][self.get_key()] + if 'canonicalName' in enum['attributes']: + canonicalName = enum['attributes']['canonicalName'] + else: + canonicalName = self.name + '.' + name values = [] - for child in enum[self.get_children()]: + for child in enum[self.get_children('members')]: assert child[self.get_key()] == 'EnumValue' - values.append(child['attributes'][self.get_key()]) + if self.is_compact_ast: + values.append(child['name']) + else: + values.append(child['attributes'][self.get_key()]) new_enum = Enum(name, canonicalName, values) new_enum.set_contract(self) @@ -153,14 +171,19 @@ class ContractSolc04(Contract): self._enums[canonicalName] = new_enum def _parse_struct(self, struct): - name = struct['attributes'][self.get_key()] - if 'canonicalName' in struct['attributes']: - canonicalName = struct['attributes']['canonicalName'] + if self.is_compact_ast: + name = struct['name'] + attributes = struct + else: + name = struct['attributes'][self.get_key()] + attributes = struct['attributes'] + if 'canonicalName' in attributes: + canonicalName = attributes['canonicalName'] else: canonicalName = self.name + '.' + name - if self.get_children() in struct: - children = struct[self.get_children()] + if self.get_children('members') in struct: + children = struct[self.get_children('members')] else: children = [] # empty struct st = StructureSolc(name, canonicalName, children) @@ -189,7 +212,7 @@ class ContractSolc04(Contract): self._events.update(father.events_as_dict()) for event_to_parse in self._eventsNotParsed: - event = EventSolc(event_to_parse) + event = EventSolc(event_to_parse, self) event.analyze(self) self._events[event.full_name] = event @@ -213,7 +236,7 @@ class ContractSolc04(Contract): def _parse_modifier(self, modifier): - modif = ModifierSolc(modifier) + modif = ModifierSolc(modifier, self) modif.set_contract(self) modif.set_offset(modifier['src'], self.slither) self._modifiers_no_params.append(modif) diff --git a/slither/solc_parsing/declarations/event.py b/slither/solc_parsing/declarations/event.py index 777a114ad..d37a478f2 100644 --- a/slither/solc_parsing/declarations/event.py +++ b/slither/solc_parsing/declarations/event.py @@ -9,17 +9,29 @@ class EventSolc(Event): Event class """ - def __init__(self, event): + def __init__(self, event, contract): super(EventSolc, self).__init__() - self._name = event['attributes']['name'] - self._elems = [] + self._contract = contract - elems = event['children'][0] - assert elems['name'] == 'ParameterList' - if 'children' in elems: - self._elemsNotParsed = elems['children'] + self._elems = [] + if self.is_compact_ast: + self._name = event['name'] + elems = event['parameters'] + assert elems['nodeType'] == 'ParameterList' + self._elemsNotParsed = elems['parameters'] else: - self._elemsNotParsed = [] + self._name = event['attributes']['name'] + elems = event['children'][0] + + assert elems['name'] == 'ParameterList' + if 'children' in elems: + self._elemsNotParsed = elems['children'] + else: + self._elemsNotParsed = [] + + @property + def is_compact_ast(self): + return self.contract.is_compact_ast def analyze(self, contract): for elem_to_parse in self._elemsNotParsed: diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index c29c34c67..f19115d8a 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -32,7 +32,7 @@ class FunctionSolc(Function): super(FunctionSolc, self).__init__() self._contract = contract if self.is_compact_ast: - self._name = function[self.get_key()] + self._name = function['name'] else: self._name = function['attributes'][self.get_key()] self._functionNotParsed = function @@ -98,44 +98,80 @@ class FunctionSolc(Function): def _parse_if(self, ifStatement, node): # IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )? + falseStatement = None - children = ifStatement[self.get_children()] - condition_node = self._new_node(NodeType.IF) - #condition = parse_expression(children[0], self) - condition = children[0] - condition_node.add_unparsed_expression(condition) - - link_nodes(node, condition_node) - - trueStatement = self._parse_statement(children[1], condition_node) + if self.is_compact_ast: + condition = ifStatement['condition'] + # Note: check if the expression could be directly + # parsed here + condition_node = self._new_node(NodeType.IF) + condition_node.add_unparsed_expression(condition) + link_nodes(node, condition_node) + trueStatement = self._parse_statement(ifStatement['trueBody'], condition_node) + if ifStatement['falseBody']: + falseStatement = self._parse_statement(ifStatement['falseBody'], condition_node) + else: + children = ifStatement[self.get_children('children')] + condition = children[0] + # Note: check if the expression could be directly + # parsed here + condition_node = self._new_node(NodeType.IF) + condition_node.add_unparsed_expression(condition) + link_nodes(node, condition_node) + trueStatement = self._parse_statement(children[1], condition_node) + if len(children) == 3: + falseStatement = self._parse_statement(children[2], condition_node) - endIf_node = self._new_node(NodeType.ENDIF) link_nodes(trueStatement, endIf_node) - if len(children) == 3: - falseStatement = self._parse_statement(children[2], condition_node) - + if falseStatement: link_nodes(falseStatement, endIf_node) - else: link_nodes(condition_node, endIf_node) - return endIf_node +# def _parse_if(self, ifStatement, node): +# # IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )? +# +# children = ifStatement[self.get_children('children')] +# condition_node = self._new_node(NodeType.IF) +# #condition = parse_expression(children[0], self) +# condition = children[0] +# condition_node.add_unparsed_expression(condition) +# +# link_nodes(node, condition_node) +# +# trueStatement = self._parse_statement(children[1], condition_node) +# +# +# endIf_node = self._new_node(NodeType.ENDIF) +# link_nodes(trueStatement, endIf_node) +# +# if len(children) == 3: +# falseStatement = self._parse_statement(children[2], condition_node) +# +# link_nodes(falseStatement, endIf_node) +# +# else: +# link_nodes(condition_node, endIf_node) +# +# return endIf_node + def _parse_while(self, whileStatement, node): # WhileStatement = 'while' '(' Expression ')' Statement - children = whileStatement[self.get_children()] - node_startWhile = self._new_node(NodeType.STARTLOOP) - node_condition = self._new_node(NodeType.IFLOOP) - #expression = parse_expression(children[0], self) - expression = children[0] - node_condition.add_unparsed_expression(expression) - statement = self._parse_statement(children[1], node_condition) + if self.is_compact_ast: + node_condition.add_unparsed_expression(whileStatement['condition']) + statement = self._parse_statement(whileStatement['body'], node_condition) + else: + children = whileStatement[self.get_children('children')] + expression = children[0] + node_condition.add_unparsed_expression(expression) + statement = self._parse_statement(children[1], node_condition) node_endWhile = self._new_node(NodeType.ENDLOOP) @@ -146,9 +182,49 @@ class FunctionSolc(Function): return node_endWhile + def _parse_for_compact_ast(self, statement, node): + body = statement['body'] + init_expression = statement['initializationExpression'] + condition = statement['condition'] + loop_expression = statement['loopExpression'] + + node_startLoop = self._new_node(NodeType.STARTLOOP) + node_endLoop = self._new_node(NodeType.ENDLOOP) + + if init_expression: + node_init_expression = self._parse_statement(init_expression, node) + link_nodes(node_init_expression, node_startLoop) + else: + link_nodes(node, node_startLoop) + + if condition: + node_condition = self._new_node(NodeType.IFLOOP) + node_condition.add_unparsed_expression(condition) + link_nodes(node_startLoop, node_condition) + link_nodes(node_condition, node_endLoop) + else: + node_condition = node_startLoop + + node_body = self._parse_statement(body, node_condition) + + if loop_expression: + node_LoopExpression = self._parse_statement(loop_expression, node_body) + link_nodes(node_LoopExpression, node_startLoop) + else: + link_nodes(node_body, node_startLoop) + + return node_endLoop + + def _parse_for(self, statement, node): # ForStatement = 'for' '(' (SimpleStatement)? ';' (Expression)? ';' (ExpressionStatement)? ')' Statement + # the handling of loop in the legacy ast is too complex + # to integrate the comapct ast + # its cleaner to do it separately + if self.is_compact_ast: + return self._parse_for_compact_ast(statement, node) + hasInitExession = True hasCondition = True hasLoopExpression = True @@ -171,7 +247,7 @@ class FunctionSolc(Function): node_startLoop = self._new_node(NodeType.STARTLOOP) node_endLoop = self._new_node(NodeType.ENDLOOP) - children = statement[self.get_children()] + children = statement[self.get_children('children')] if hasInitExession: if len(children) >= 2: @@ -221,34 +297,32 @@ class FunctionSolc(Function): return node_endLoop def _parse_dowhile(self, doWhilestatement, node): - children = doWhilestatement[self.get_children()] node_startDoWhile = self._new_node(NodeType.STARTLOOP) - - # same order in the AST as while node_condition = self._new_node(NodeType.IFLOOP) - #expression = parse_expression(children[0], self) - expression = children[0] - node_condition.add_unparsed_expression(expression) - statement = self._parse_statement(children[1], node_condition) + if self.is_compact_ast: + node_condition.add_unparsed_expression(doWhilestatement['condition']) + statement = self._parse_statement(doWhilestatement['body'], node_condition) + else: + children = doWhilestatement[self.get_children('children')] + # same order in the AST as while + expression = children[0] + node_condition.add_unparsed_expression(expression) + statement = self._parse_statement(children[1], node_condition) + node_endDoWhile = self._new_node(NodeType.ENDLOOP) link_nodes(node, node_startDoWhile) - link_nodes(node_startDoWhile, node_condition) + link_nodes(node_startDoWhile, statement) link_nodes(statement, node_condition) link_nodes(node_condition, node_endDoWhile) return node_endDoWhile def _parse_variable_definition(self, statement, node): - #assert len(statement[self.get_children()]) == 1 - # if there is, parse default value - #assert not 'attributes' in statement - try: local_var = LocalVariableSolc(statement) - #local_var = LocalVariableSolc(statement[self.get_children()][0], statement[self.get_children()][1::]) local_var.set_function(self) local_var.set_offset(statement['src'], self.contract.slither) @@ -261,82 +335,150 @@ class FunctionSolc(Function): return new_node except MultipleVariablesDeclaration: # Custom handling of var (a,b) = .. style declaration - count = 0 - children = statement[self.get_children()] - child = children[0] - while child[self.get_key()] == 'VariableDeclaration': - count = count +1 - child = children[count] - - assert len(children) == (count + 1) - tuple_vars = children[count] - - - variables_declaration = children[0:count] - i = 0 - new_node = node - if tuple_vars[self.get_key()] == 'TupleExpression': - assert len(tuple_vars[self.get_children()]) == count - for variable in variables_declaration: - init = tuple_vars[self.get_children()][i] - src = variable['src'] - i= i+1 - # Create a fake statement to be consistent - new_statement = {self.get_key():'VariableDefinitionStatement', - 'src': src, - self.get_children():[variable, init]} - - new_node = self._parse_variable_definition(new_statement, new_node) + if self.is_compact_ast: + variables = statement['declarations'] + count = len(variables) + + if statement['initialValue']['nodeType'] == 'TupleExpression': + inits = statement['initialValue']['components'] + i = 0 + new_node = node + for variable in variables: + init = inits[i] + src = variable['src'] + i = i+1 + + new_statement = {'nodeType':'VariableDefinitionStatement', + 'src': src, + 'declarations':[variable], + 'initialValue':init} + new_node = self._parse_variable_definition(new_statement, new_node) + + else: + # If we have + # var (a, b) = f() + # we can split in multiple declarations, without init + # Then we craft one expression that does the assignment + variables = [] + i = 0 + for variable in statement['declarations']: + src = variable['src'] + i = i+1 + # Create a fake statement to be consistent + new_statement = {'nodeType':'VariableDefinitionStatement', + 'src': src, + 'declarations':[variable]} + variables.append(variable) + + new_node = self._parse_variable_definition_init_tuple(new_statement, i, new_node) + + var_identifiers = [] + # craft of the expression doing the assignement + for v in variables: + identifier = { + 'nodeType':'Identifier', + 'src': v['src'], + 'name': v['name'], + 'typeDescriptions': { + 'typeString':v['typeDescriptions']['typeString'] + } + } + var_identifiers.append(identifier) + + tuple_expression = {'nodeType':'TuleExpression', + 'src': statement['src'], + 'components':var_identifiers} + + expression = { + 'nodeType' : 'Assignment', + 'src':statement['src'], + 'operator': '=', + 'type':'tuple()', + 'leftHandSide': tuple_expression, + 'rightHandSide': statement['initialValue']} + node = new_node + new_node = self._new_node(NodeType.EXPRESSION) + new_node.add_unparsed_expression(expression) + link_nodes(node, new_node) + + else: - # If we have - # var (a, b) = f() - # we can split in multiple declarations, without init - # Then we craft one expression that does not assignment - assert tuple_vars[self.get_key()] in ['FunctionCall', 'Conditional'] - variables = [] - for variable in variables_declaration: - src = variable['src'] - i= i+1 - # Create a fake statement to be consistent - new_statement = {self.get_key():'VariableDefinitionStatement', - 'src': src, - self.get_children():[variable]} - variables.append(variable) - - new_node = self._parse_variable_definition_init_tuple(new_statement, i, new_node) - var_identifiers = [] - # craft of the expression doing the assignement - for v in variables: - identifier = { - self.get_key() : 'Identifier', - 'src': v['src'], - 'attributes': { - 'value': v['attributes'][self.get_key()], - 'type': v['attributes']['type']} - } - var_identifiers.append(identifier) - - expression = { - self.get_key() : 'Assignment', - 'src':statement['src'], - 'attributes': {'operator': '=', - 'type':'tuple()'}, - self.get_children(): - [{self.get_key(): 'TupleExpression', - 'src': statement['src'], - self.get_children(): var_identifiers}, - tuple_vars]} - node = new_node - new_node = self._new_node(NodeType.EXPRESSION) - new_node.add_unparsed_expression(expression) - link_nodes(node, new_node) + count = 0 + children = statement[self.get_children('children')] + child = children[0] + while child[self.get_key()] == 'VariableDeclaration': + count = count +1 + child = children[count] + + assert len(children) == (count + 1) + tuple_vars = children[count] + + + variables_declaration = children[0:count] + i = 0 + new_node = node + if tuple_vars[self.get_key()] == 'TupleExpression': + assert len(tuple_vars[self.get_children('children')]) == count + for variable in variables_declaration: + init = tuple_vars[self.get_children('children')][i] + src = variable['src'] + i = i+1 + # Create a fake statement to be consistent + new_statement = {self.get_key():'VariableDefinitionStatement', + 'src': src, + self.get_children('children'):[variable, init]} + + new_node = self._parse_variable_definition(new_statement, new_node) + else: + # If we have + # var (a, b) = f() + # we can split in multiple declarations, without init + # Then we craft one expression that does the assignment + assert tuple_vars[self.get_key()] in ['FunctionCall', 'Conditional'] + variables = [] + for variable in variables_declaration: + src = variable['src'] + i = i+1 + # Create a fake statement to be consistent + new_statement = {self.get_key():'VariableDefinitionStatement', + 'src': src, + self.get_children('children'):[variable]} + variables.append(variable) + + new_node = self._parse_variable_definition_init_tuple(new_statement, i, new_node) + var_identifiers = [] + # craft of the expression doing the assignement + for v in variables: + identifier = { + self.get_key() : 'Identifier', + 'src': v['src'], + 'attributes': { + 'value': v['attributes'][self.get_key()], + 'type': v['attributes']['type']} + } + var_identifiers.append(identifier) + + expression = { + self.get_key() : 'Assignment', + 'src':statement['src'], + 'attributes': {'operator': '=', + 'type':'tuple()'}, + self.get_children('children'): + [{self.get_key(): 'TupleExpression', + 'src': statement['src'], + self.get_children('children'): var_identifiers}, + tuple_vars]} + node = new_node + new_node = self._new_node(NodeType.EXPRESSION) + new_node.add_unparsed_expression(expression) + link_nodes(node, new_node) return new_node def _parse_variable_definition_init_tuple(self, statement, index, node): local_var = LocalVariableInitFromTupleSolc(statement, index) - #local_var = LocalVariableSolc(statement[self.get_children()][0], statement[self.get_children()][1::]) + #local_var = LocalVariableSolc(statement[self.get_children('children')][0], statement[self.get_children('children')][1::]) local_var.set_function(self) local_var.set_offset(statement['src'], self.contract.slither) @@ -389,9 +531,9 @@ class FunctionSolc(Function): elif name == 'Return': return_node = self._new_node(NodeType.RETURN) link_nodes(node, return_node) - if self.get_children() in statement and statement[self.get_children()]: - assert len(statement[self.get_children()]) == 1 - expression = statement[self.get_children()][0] + if self.get_children('children') in statement and statement[self.get_children('children')]: + assert len(statement[self.get_children('children')]) == 1 + expression = statement[self.get_children('children')][0] return_node.add_unparsed_expression(expression) node = return_node elif name == 'Throw': @@ -399,8 +541,11 @@ class FunctionSolc(Function): link_nodes(node, throw_node) node = throw_node elif name == 'EmitStatement': - #expression = parse_expression(statement[self.get_children()][0], self) - expression = statement[self.get_children()][0] + #expression = parse_expression(statement[self.get_children('children')][0], self) + if self.is_compact_ast: + expression = statement['eventCall'] + else: + expression = statement[self.get_children('children')][0] new_node = self._new_node(NodeType.EXPRESSION) new_node.add_unparsed_expression(expression) link_nodes(node, new_node) @@ -410,7 +555,7 @@ class FunctionSolc(Function): elif name == 'ExpressionStatement': #assert len(statement[self.get_children('expression')]) == 1 #assert not 'attributes' in statement - #expression = parse_expression(statement[self.get_children()][0], self) + #expression = parse_expression(statement[self.get_children('children')][0], self) if self.is_compact_ast: expression = statement[self.get_children('expression')] else: @@ -435,7 +580,7 @@ class FunctionSolc(Function): if self.is_compact_ast: statements = block['statements'] else: - statements = block[self.get_children()] + statements = block[self.get_children('children')] for statement in statements: node = self._parse_statement(statement, node) @@ -451,7 +596,7 @@ class FunctionSolc(Function): if self.is_compact_ast: statements = cfg['statements'] else: - statements = cfg[self.get_children()] + statements = cfg[self.get_children('children')] if not statements: self._is_empty = True @@ -563,7 +708,7 @@ class FunctionSolc(Function): if self.is_compact_ast: params = params['parameters'] else: - params = params[self.get_children()] + params = params[self.get_children('children')] for param in params: assert param[self.get_key()] == 'VariableDeclaration' @@ -588,7 +733,7 @@ class FunctionSolc(Function): if self.is_compact_ast: returns = returns['parameters'] else: - returns = returns[self.get_children()] + returns = returns[self.get_children('children')] for ret in returns: assert ret[self.get_key()] == 'VariableDeclaration' @@ -644,7 +789,7 @@ class FunctionSolc(Function): if self.is_compact_ast: body = self._functionNotParsed['body'] - if body[self.get_key()] == 'Block': + if body and body[self.get_key()] == 'Block': self._is_implemented = True self._parse_cfg(body) diff --git a/slither/solc_parsing/declarations/modifier.py b/slither/solc_parsing/declarations/modifier.py index cd7e7d756..527c439e8 100644 --- a/slither/solc_parsing/declarations/modifier.py +++ b/slither/solc_parsing/declarations/modifier.py @@ -19,9 +19,11 @@ class ModifierSolc(Modifier, FunctionSolc): self._analyze_attributes() - children = self._functionNotParsed['children'] - - params = children[0] + if self.is_compact_ast: + params = self._functionNotParsed['parameters'] + else: + children = self._functionNotParsed['children'] + params = children[0] if params: self._parse_params(params) @@ -32,15 +34,24 @@ class ModifierSolc(Modifier, FunctionSolc): self._content_was_analyzed = True - children = self._functionNotParsed['children'] - self._isImplemented = False - if len(children) > 1: - assert len(children) == 2 - block = children[1] - assert block['name'] == 'Block' + if self.is_compact_ast: + body = self._functionNotParsed['body'] + + if body and body[self.get_key()] == 'Block': + self._is_implemented = True + self._parse_cfg(body) + + else: + children = self._functionNotParsed['children'] + self._isImplemented = False - self._parse_cfg(block) + if len(children) > 1: + assert len(children) == 2 + block = children[1] + assert block['name'] == 'Block' + self._isImplemented = True + self._parse_cfg(block) for local_vars in self.variables: local_vars.analyze(self) @@ -52,7 +63,7 @@ class ModifierSolc(Modifier, FunctionSolc): self._analyze_calls() def _parse_statement(self, statement, node): - name = statement['name'] + name = statement[self.get_key()] if name == 'PlaceholderStatement': placeholder_node = self._new_node(NodeType.PLACEHOLDER) link_nodes(node, placeholder_node) diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index 10f9ea75c..a491d72ca 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -126,13 +126,14 @@ def parse_call(expression, caller_context): if caller_context.is_compact_ast: type_info = expression['expression'] - expression_to_parse = expression['value'] + assert len(expression['arguments']) == 1 + expression_to_parse = expression['arguments'][0] else: children = expression['children'] assert len(children) == 2 type_info = children[0] expression_to_parse = children[1] - assert type_info['name'] in ['ElementaryTypenameExpression', + assert type_info['name'] in ['ElementaryTypenameExpression', 'ElementaryTypeNameExpression', 'Identifier', 'TupleExpression', @@ -155,12 +156,18 @@ def parse_call(expression, caller_context): return SuperCallExpression(called, arguments, type_return) return CallExpression(called, arguments, type_return) -def parse_super_name(expression): - assert expression['name'] == 'MemberAccess' - attributes = expression['attributes'] - base_name = attributes['member_name'] +def parse_super_name(expression, is_compact_ast): + if is_compact_ast: + assert expression['nodeType'] == 'MemberAccess' + attributes = expression + base_name = expression['memberName'] + arguments = expression['typeDescriptions']['typeString'] + else: + assert expression['name'] == 'MemberAccess' + attributes = expression['attributes'] + base_name = attributes['member_name'] + arguments = attributes['type'] - arguments = attributes['type'] assert arguments.startswith('function ') # remove function (...() arguments = arguments[len('function '):] @@ -239,7 +246,10 @@ def parse_expression(expression, caller_context): is_compact_ast = caller_context.is_compact_ast if name == 'UnaryOperation': - attributes = expression['attributes'] + if is_compact_ast: + attributes = expression + else: + attributes = expression['attributes'] assert 'prefix' in attributes operation_type = UnaryOperationType.get_type(attributes['operator'], attributes['prefix']) @@ -252,7 +262,10 @@ def parse_expression(expression, caller_context): return unary_op elif name == 'BinaryOperation': - attributes = expression['attributes'] + if is_compact_ast: + attributes = expression + else: + attributes = expression['attributes'] operation_type = BinaryOperationType.get_type(attributes['operator']) if is_compact_ast: @@ -280,7 +293,7 @@ def parse_expression(expression, caller_context): Note: this is only possible with Solidity >= 0.4.12 """ - if caller_context.is_compact_ast: + if is_compact_ast: expressions = [parse_expression(e, caller_context) for e in expression['components']] else: if 'children' not in expression : @@ -299,41 +312,56 @@ def parse_expression(expression, caller_context): for idx in range(len(elems)): if elems[idx] == '': expressions.insert(idx, None) - print(expressions) t = TupleExpression(expressions) return t elif name == 'Conditional': - children = expression['children'] - assert len(children) == 3 - if_expression = parse_expression(children[0], caller_context) - then_expression = parse_expression(children[1], caller_context) - else_expression = parse_expression(children[2], caller_context) + if is_compact_ast: + if_expression = parse_expression(expression['condition'], caller_context) + then_expression = parse_expression(expression['trueExpression'], caller_context) + else_expression = parse_expression(expression['falseExpression'], caller_context) + else: + children = expression['children'] + assert len(children) == 3 + if_expression = parse_expression(children[0], caller_context) + then_expression = parse_expression(children[1], caller_context) + else_expression = parse_expression(children[2], caller_context) conditional = ConditionalExpression(if_expression, then_expression, else_expression) - #print(conditional) return conditional elif name == 'Assignment': - attributes = expression['attributes'] - children = expression['children'] - assert len(expression['children']) == 2 + if is_compact_ast: + left_expression = parse_expression(expression['leftHandSide'], caller_context) + right_expression = parse_expression(expression['rightHandSide'], caller_context) - left_expression = parse_expression(children[0], caller_context) - right_expression = parse_expression(children[1], caller_context) - operation_type = AssignmentOperationType.get_type(attributes['operator']) - operation_return_type = attributes['type'] + operation_type = AssignmentOperationType.get_type(expression['operator']) + + operation_return_type = expression['typeDescriptions']['typeString'] + else: + attributes = expression['attributes'] + children = expression['children'] + assert len(expression['children']) == 2 + left_expression = parse_expression(children[0], caller_context) + right_expression = parse_expression(children[1], caller_context) + + operation_type = AssignmentOperationType.get_type(attributes['operator']) + operation_return_type = attributes['type'] assignement = AssignmentOperation(left_expression, right_expression, operation_type, operation_return_type) return assignement elif name == 'Literal': assert 'children' not in expression - value = expression['attributes']['value'] - if value is None: - # for literal declared as hex - # see https://solidity.readthedocs.io/en/v0.4.25/types.html?highlight=hex#hexadecimal-literals - assert 'hexvalue' in expression['attributes'] - value = '0x'+expression['attributes']['hexvalue'] + + if is_compact_ast: + value = expression['value'] + else: + value = expression['attributes']['value'] + if value is None: + # for literal declared as hex + # see https://solidity.readthedocs.io/en/v0.4.25/types.html?highlight=hex#hexadecimal-literals + assert 'hexvalue' in expression['attributes'] + value = '0x'+expression['attributes']['hexvalue'] literal = Literal(value) return literal @@ -363,11 +391,18 @@ def parse_expression(expression, caller_context): return identifier elif name == 'IndexAccess': - index_type = expression['attributes']['type'] - children = expression['children'] - assert len(children) == 2 - left_expression = parse_expression(children[0], caller_context) - right_expression = parse_expression(children[1], caller_context) + if is_compact_ast: + index_type = expression['typeDescriptions']['typeString'] + left = expression['baseExpression'] + right = expression['indexExpression'] + else: + index_type = expression['attributes']['type'] + children = expression['children'] + assert len(children) == 2 + left = children[0] + right = children[1] + left_expression = parse_expression(left, caller_context) + right_expression = parse_expression(right, caller_context) index = IndexAccess(left_expression, right_expression, index_type) return index @@ -383,7 +418,7 @@ def parse_expression(expression, caller_context): assert len(children) == 1 member_expression = parse_expression(children[0], caller_context) if str(member_expression) == 'super': - super_name = parse_super_name(expression) + super_name = parse_super_name(expression, is_compact_ast) if isinstance(caller_context, Contract): inheritance = caller_context.inheritance else: @@ -407,8 +442,11 @@ def parse_expression(expression, caller_context): elif name == 'ElementaryTypeNameExpression': # nop exression # uint; - assert 'children' not in expression - value = expression['attributes']['value'] + if is_compact_ast: + value = expression['typeName'] + else: + assert 'children' not in expression + value = expression['attributes']['value'] t = parse_type(UnknownType(value), caller_context) return ElementaryTypeNameExpression(t) @@ -416,48 +454,66 @@ def parse_expression(expression, caller_context): # NewExpression is not a root expression, it's always the child of another expression elif name == 'NewExpression': - new_type = expression['attributes']['type'] - children = expression['children'] - assert len(children) == 1 - #new_expression = parse_expression(children[0]) - - child = children[0] + if is_compact_ast: + type_name = expression['typeName'] + else: + children = expression['children'] + assert len(children) == 1 + type_name = children[0] - if child['name'] == 'ArrayTypeName': + if type_name[caller_context.get_key()] == 'ArrayTypeName': depth = 0 - while child['name'] == 'ArrayTypeName': + while type_name[caller_context.get_key()] == 'ArrayTypeName': # Note: dont conserve the size of the array if provided - #assert len(child['children']) == 1 - child = child['children'][0] + # We compute it directly + if is_compact_ast: + type_name = type_name['baseType'] + else: + type_name = type_name['children'][0] depth += 1 - - if child['name'] == 'ElementaryTypeName': - array_type = ElementaryType(child['attributes']['name']) - elif child['name'] == 'UserDefinedTypeName': - array_type = parse_type(UnknownType(child['attributes']['name']), caller_context) + if type_name[caller_context.get_key()] == 'ElementaryTypeName': + if is_compact_ast: + array_type = ElementaryType(type_name['name']) + else: + array_type = ElementaryType(type_name['attributes']['name']) + elif type_name[caller_context.get_key()] == 'UserDefinedTypeName': + if is_compact_ast: + array_type = parse_type(UnknownType(type_name['name']), caller_context) + else: + array_type = parse_type(UnknownType(type_name['attributes']['name']), caller_context) else: - logger.error('Incorrect type array {}'.format(child)) + logger.error('Incorrect type array {}'.format(type_name)) exit(-1) array = NewArray(depth, array_type) return array - if child['name'] == 'ElementaryTypeName': - elem_type = ElementaryType(child['attributes']['name']) + if type_name[caller_context.get_key()] == 'ElementaryTypeName': + if is_compact_ast: + elem_type = ElementaryType(type_name['name']) + else: + elem_type = ElementaryType(type_name['attributes']['name']) new_elem = NewElementaryType(elem_type) return new_elem - assert child['name'] == 'UserDefinedTypeName' + assert type_name[caller_context.get_key()] == 'UserDefinedTypeName' - contract_name = child['attributes']['name'] + if is_compact_ast: + contract_name = type_name['name'] + else: + contract_name = type_name['attributes']['name'] new = NewContract(contract_name) return new elif name == 'ModifierInvocation': - children = expression['children'] - called = parse_expression(children[0], caller_context) - arguments = [parse_expression(a, caller_context) for a in children[1::]] + if is_compact_ast: + called = parse_expression(expression['modifierName'], caller_context) + arguments = [parse_expression(a, caller_context) for a in expression['arguments']] + else: + children = expression['children'] + called = parse_expression(children[0], caller_context) + arguments = [parse_expression(a, caller_context) for a in children[1::]] call = CallExpression(called, arguments, 'Modifier') return call diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index 1f6f0cd71..eeaba630d 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -77,7 +77,10 @@ class SlitherSolc(Slither): pragma.set_offset(contract_data['src'], self) self._pragma_directives.append(pragma) elif contract_data[self.get_key()] == 'ImportDirective': - import_directive = Import(contract_data['attributes']["absolutePath"]) + if self.is_compact_ast: + import_directive = Import(contract_data["absolutePath"]) + else: + import_directive = Import(contract_data['attributes']["absolutePath"]) import_directive.set_offset(contract_data['src'], self) self._import_directives.append(import_directive) diff --git a/slither/solc_parsing/solidity_types/type_parsing.py b/slither/solc_parsing/solidity_types/type_parsing.py index e2ccfa6ce..2df12be75 100644 --- a/slither/solc_parsing/solidity_types/type_parsing.py +++ b/slither/solc_parsing/solidity_types/type_parsing.py @@ -41,6 +41,8 @@ def _find_from_type_name(name, contract, contracts, structures, enums): name_contract = name if name_contract.startswith('contract '): name_contract = name_contract[len('contract '):] + if name_contract.startswith('library '): + name_contract = name_contract[len('library '):] var_type = next((c for c in contracts if c.name == name_contract), None) if not var_type: @@ -131,6 +133,14 @@ def parse_type(t, caller_context): logger.error('Incorrect caller context') exit(-1) + + is_compact_ast = caller_context.is_compact_ast + + if is_compact_ast: + key = 'nodeType' + else: + key = 'name' + structures = contract.structures enums = contract.enums contracts = contract.slither.contracts @@ -138,38 +148,51 @@ def parse_type(t, caller_context): if isinstance(t, UnknownType): return _find_from_type_name(t.name, contract, contracts, structures, enums) - elif t['name'] == 'ElementaryTypeName': - return ElementaryType(t['attributes']['name']) + elif t[key] == 'ElementaryTypeName': + if is_compact_ast: + return ElementaryType(t['typeDescriptions']['typeString']) + return ElementaryType(t['attributes'][key]) - elif t['name'] == 'UserDefinedTypeName': - return _find_from_type_name(t['attributes']['name'], contract, contracts, structures, enums) + elif t[key] == 'UserDefinedTypeName': + if is_compact_ast: + return _find_from_type_name(t['typeDescriptions']['typeString'], contract, contracts, structures, enums) + return _find_from_type_name(t['attributes'][key], contract, contracts, structures, enums) - elif t['name'] == 'ArrayTypeName': + elif t[key] == 'ArrayTypeName': length = None - if len(t['children']) == 2: - length = parse_expression(t['children'][1], caller_context) + if is_compact_ast: + if t['length']: + length = parse_expression(t['length'], caller_context) + array_type = parse_type(t['baseType'], contract) else: - assert len(t['children']) == 1 - array_type = parse_type(t['children'][0], contract) + if len(t['children']) == 2: + length = parse_expression(t['children'][1], caller_context) + else: + assert len(t['children']) == 1 + array_type = parse_type(t['children'][0], contract) return ArrayType(array_type, length) - elif t['name'] == 'Mapping': + elif t[key] == 'Mapping': - assert len(t['children']) == 2 + if is_compact_ast: + mappingFrom = parse_type(t['keyType'], contract) + mappingTo = parse_type(t['valueType'], contract) + else: + assert len(t['children']) == 2 - mappingFrom = parse_type(t['children'][0], contract) - mappingTo = parse_type(t['children'][1], contract) + mappingFrom = parse_type(t['children'][0], contract) + mappingTo = parse_type(t['children'][1], contract) return MappingType(mappingFrom, mappingTo) - elif t['name'] == 'FunctionTypeName': + elif t[key] == 'FunctionTypeName': assert len(t['children']) == 2 params = t['children'][0] return_values = t['children'][1] - assert params['name'] == 'ParameterList' - assert return_values['name'] == 'ParameterList' + assert params[key] == 'ParameterList' + assert return_values[key] == 'ParameterList' params_vars = [] return_values_vars = [] diff --git a/slither/solc_parsing/variables/variable_declaration.py b/slither/solc_parsing/variables/variable_declaration.py index 706e80faf..1631622d3 100644 --- a/slither/solc_parsing/variables/variable_declaration.py +++ b/slither/solc_parsing/variables/variable_declaration.py @@ -37,29 +37,37 @@ class VariableDeclarationSolc(Variable): if 'nodeType' in var: self._is_compact_ast = True - - if self._is_compact_ast: nodeType = var['nodeType'] + if nodeType in ['VariableDeclarationStatement', 'VariableDefinitionStatement']: + if len(var['declarations'])>1: + raise MultipleVariablesDeclaration + self._init_from_declaration(var['declarations'][0], var['initialValue']) + elif nodeType == 'VariableDeclaration': + self._init_from_declaration(var, None) + else: + logger.error('Incorrect variable declaration type {}'.format(nodeType)) + exit(-1) + else: nodeType = var['name'] - if nodeType in ['VariableDeclarationStatement', 'VariableDefinitionStatement']: - if len(var['children']) == 2: - init = var['children'][1] - elif len(var['children']) == 1: - init = None - elif len(var['children']) > 2: - raise MultipleVariablesDeclaration + if nodeType in ['VariableDeclarationStatement', 'VariableDefinitionStatement']: + if len(var['children']) == 2: + init = var['children'][1] + elif len(var['children']) == 1: + init = None + elif len(var['children']) > 2: + raise MultipleVariablesDeclaration + else: + logger.error('Variable declaration without children?'+var) + exit(-1) + declaration = var['children'][0] + self._init_from_declaration(declaration, init) + elif nodeType == 'VariableDeclaration': + self._init_from_declaration(var, None) else: - logger.error('Variable declaration without children?'+var) + logger.error('Incorrect variable declaration type {}'.format(nodeType)) exit(-1) - declaration = var['children'][0] - self._init_from_declaration(declaration, init) - elif nodeType == 'VariableDeclaration': - self._init_from_declaration(var, None) - else: - logger.error('Incorrect variable declaration type {}'.format(nodeType)) - exit(-1) @property def initialized(self): @@ -99,27 +107,35 @@ class VariableDeclarationSolc(Variable): self._analyze_variable_attributes(attributes) - if not var['children']: - # It happens on variable declared inside loop declaration - try: - self._type = ElementaryType(self._typeName) - self._elem_to_parse = None - except NonElementaryType: - self._elem_to_parse = UnknownType(self._typeName) + if self._is_compact_ast: + self._elem_to_parse = var['typeName'] else: - self._elem_to_parse = var['children'][0] + if not var['children']: + # It happens on variable declared inside loop declaration + try: + self._type = ElementaryType(self._typeName) + self._elem_to_parse = None + except NonElementaryType: + self._elem_to_parse = UnknownType(self._typeName) + else: + self._elem_to_parse = var['children'][0] - if init: # there are two way to init a var local in the AST - assert len(var['children']) <= 1 - self._initialized = True + if self._is_compact_ast: self._initializedNotParsed = init - elif len(var['children']) in [0, 1]: - self._initialized = False - self._initializedNotParsed = [] + if init: + self._initialized = True else: - assert len(var['children']) == 2 - self._initialized = True - self._initializedNotParsed = var['children'][1] + if init: # there are two way to init a var local in the AST + assert len(var['children']) <= 1 + self._initialized = True + self._initializedNotParsed = init + elif len(var['children']) in [0, 1]: + self._initialized = False + self._initializedNotParsed = [] + else: + assert len(var['children']) == 2 + self._initialized = True + self._initializedNotParsed = var['children'][1] def analyze(self, caller_context): # Can be re-analyzed due to inheritance From 7bd4a7a9e87ac53d41d31b6ba51ae658c292cd98 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 22 Oct 2018 15:24:06 +0100 Subject: [PATCH 180/308] Compact AST: Fix missing var init --- slither/solc_parsing/variables/variable_declaration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/solc_parsing/variables/variable_declaration.py b/slither/solc_parsing/variables/variable_declaration.py index 1631622d3..e9af3c070 100644 --- a/slither/solc_parsing/variables/variable_declaration.py +++ b/slither/solc_parsing/variables/variable_declaration.py @@ -43,7 +43,7 @@ class VariableDeclarationSolc(Variable): raise MultipleVariablesDeclaration self._init_from_declaration(var['declarations'][0], var['initialValue']) elif nodeType == 'VariableDeclaration': - self._init_from_declaration(var, None) + self._init_from_declaration(var, var['value']) else: logger.error('Incorrect variable declaration type {}'.format(nodeType)) exit(-1) From 3d6d15138bee927d79f5c00c8eef5e4a9336a03e Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 22 Oct 2018 16:59:19 +0100 Subject: [PATCH 181/308] Json parsing split to faciliate truffle integration --- slither/solc_parsing/declarations/contract.py | 1 + slither/solc_parsing/slitherSolc.py | 112 ++++++++++-------- 2 files changed, 62 insertions(+), 51 deletions(-) diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index 426d19b5d..17803a27e 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -43,6 +43,7 @@ class ContractSolc04(Contract): self._name = self._data['attributes'][self.get_key()] self._id = self._data['id'] + self._inheritance = [] self._parse_contract_info() diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index eeaba630d..f172ab6b2 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -35,58 +35,65 @@ class SlitherSolc(Slither): return self._is_compact_ast def _parse_contracts_from_json(self, json_data): - first = json_data.find('{') - if first != -1: - last = json_data.rfind('}') + 1 - filename = json_data[0:first] - json_data = json_data[first:last] - + try: data_loaded = json.loads(json_data) + self._parse_contracts_from_loaded_json(data_loaded['ast'], data_loaded['sourcePath']) + return True + except ValueError: + + first = json_data.find('{') + if first != -1: + last = json_data.rfind('}') + 1 + filename = json_data[0:first] + json_data = json_data[first:last] + + data_loaded = json.loads(json_data) + self._parse_contracts_from_loaded_json(data_loaded, filename) + return True + return False + + def _parse_contracts_from_loaded_json(self, data_loaded, filename): + if 'nodeType' in data_loaded: + self._is_compact_ast = True + + if data_loaded[self.get_key()] == 'root': + self._solc_version = '0.3' + logger.error('solc <0.4 is not supported') + return + elif data_loaded[self.get_key()] == 'SourceUnit': + self._solc_version = '0.4' + self._parse_source_unit(data_loaded, filename) + else: + logger.error('solc version is not supported') + return + + for contract_data in data_loaded[self.get_children()]: + # if self.solc_version == '0.3': + # assert contract_data[self.get_key()] == 'Contract' + # contract = ContractSolc03(self, contract_data) + if self.solc_version == '0.4': + assert contract_data[self.get_key()] in ['ContractDefinition', 'PragmaDirective', 'ImportDirective'] + if contract_data[self.get_key()] == 'ContractDefinition': + contract = ContractSolc04(self, contract_data) + if 'src' in contract_data: + contract.set_offset(contract_data['src'], self) + self._contractsNotParsed.append(contract) + elif contract_data[self.get_key()] == 'PragmaDirective': + if self._is_compact_ast: + pragma = Pragma(contract_data['literals']) + else: + pragma = Pragma(contract_data['attributes']["literals"]) + pragma.set_offset(contract_data['src'], self) + self._pragma_directives.append(pragma) + elif contract_data[self.get_key()] == 'ImportDirective': + if self.is_compact_ast: + import_directive = Import(contract_data["absolutePath"]) + else: + import_directive = Import(contract_data['attributes']["absolutePath"]) + import_directive.set_offset(contract_data['src'], self) + self._import_directives.append(import_directive) - if 'nodeType' in data_loaded: - self._is_compact_ast = True - - if data_loaded[self.get_key()] == 'root': - self._solc_version = '0.3' - logger.error('solc <0.4 is not supported') - return - elif data_loaded[self.get_key()] == 'SourceUnit': - self._solc_version = '0.4' - self._parse_source_unit(data_loaded, filename) - else: - logger.error('solc version is not supported') - return - - for contract_data in data_loaded[self.get_children()]: - # if self.solc_version == '0.3': - # assert contract_data[self.get_key()] == 'Contract' - # contract = ContractSolc03(self, contract_data) - if self.solc_version == '0.4': - assert contract_data[self.get_key()] in ['ContractDefinition', 'PragmaDirective', 'ImportDirective'] - if contract_data[self.get_key()] == 'ContractDefinition': - contract = ContractSolc04(self, contract_data) - if 'src' in contract_data: - contract.set_offset(contract_data['src'], self) - self._contractsNotParsed.append(contract) - elif contract_data[self.get_key()] == 'PragmaDirective': - if self._is_compact_ast: - pragma = Pragma(contract_data['literals']) - else: - pragma = Pragma(contract_data['attributes']["literals"]) - pragma.set_offset(contract_data['src'], self) - self._pragma_directives.append(pragma) - elif contract_data[self.get_key()] == 'ImportDirective': - if self.is_compact_ast: - import_directive = Import(contract_data["absolutePath"]) - else: - import_directive = Import(contract_data['attributes']["absolutePath"]) - import_directive.set_offset(contract_data['src'], self) - self._import_directives.append(import_directive) - - return True - return False - def _parse_source_unit(self, data, filename): if data[self.get_key()] != 'SourceUnit': return -1 # handle solc prior 0.3.6 @@ -94,8 +101,11 @@ class SlitherSolc(Slither): # match any char for filename # filename can contain space, /, -, .. name = re.findall('=* (.+) =*', filename) - assert len(name) == 1 - name = name[0] + if name: + assert len(name) == 1 + name = name[0] + else: + name =filename sourceUnit = -1 # handle old solc, or error if 'src' in data: From 36d5fff6b22203d2ebee2256e6c0c1c29698fbcf Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 22 Oct 2018 17:23:34 +0100 Subject: [PATCH 182/308] Undo modif on naming_convention.py --- .../naming_convention/naming_convention.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index 18798af15..ba4d01c0a 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -81,11 +81,7 @@ class NamingConvention(AbstractDetector): for argument in func.parameters: - argument_name = argument.name - if argument_name and argument_name.startswith('_'): - argument_name = argument_name[1::] - - if self.is_mixed_case(argument_name) is False: + if self.is_mixed_case(argument.name) is False: info = "Parameter '{}' is not in mixedCase, Contract: '{}', Function: '{}'' " \ .format(argument.name, argument.name, contract.name) self.log(info) @@ -123,12 +119,7 @@ class NamingConvention(AbstractDetector): 'constant': var.name, 'sourceMapping': var.source_mapping}) else: - - var_name = var.name - if var_name and var_name.startswith('_'): - var_name = var_name[1::] - - if self.is_mixed_case(var_name) is False: + if self.is_mixed_case(var.name) is False: info = "Variable '{}' is not in mixedCase, Contract: '{}' ".format(var.name, contract.name) self.log(info) From 0c7a4542ca5a08104d4ba528cf007583876cf0a6 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 22 Oct 2018 18:23:46 +0100 Subject: [PATCH 183/308] Add support for dynamic function as state variable Add support for array/mapping of dynamic function Allow PUSH operator (slithIR) to push Function --- .../operations/internal_dynamic_call.py | 2 +- slither/slithir/operations/push.py | 3 +- .../expressions/expression_parsing.py | 28 +++++++++++++++---- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/slither/slithir/operations/internal_dynamic_call.py b/slither/slithir/operations/internal_dynamic_call.py index bed892156..7be424b04 100644 --- a/slither/slithir/operations/internal_dynamic_call.py +++ b/slither/slithir/operations/internal_dynamic_call.py @@ -37,7 +37,7 @@ class InternalDynamicCall(Call, OperationWithLValue): lvalue = '{}({}) = '.format(self.lvalue, ','.join(str(x) for x in self.lvalue.type)) else: lvalue = '{}({}) = '.format(self.lvalue, self.lvalue.type) - txt = '{}INTERNAL_DYNAMIC_CALL, {}({})' + txt = '{}INTERNAL_DYNAMIC_CALL {}({})' return txt.format(lvalue, self.function.name, ','.join(args)) diff --git a/slither/slithir/operations/push.py b/slither/slithir/operations/push.py index cde9bbf2c..77a97e65e 100644 --- a/slither/slithir/operations/push.py +++ b/slither/slithir/operations/push.py @@ -1,12 +1,13 @@ import logging from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.declarations import Function from slither.core.variables.variable import Variable from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue class Push(OperationWithLValue): def __init__(self, array, value): - assert is_valid_rvalue(value) + assert is_valid_rvalue(value) or isinstance(value, Function) assert is_valid_lvalue(array) self._value = value self._lvalue = array diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index a2206f128..b39e7b67e 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -26,14 +26,27 @@ from slither.core.declarations.function import Function from slither.core.declarations.solidity_variables import SOLIDITY_VARIABLES, SOLIDITY_FUNCTIONS, SOLIDITY_VARIABLES_COMPOSED from slither.core.declarations.solidity_variables import SolidityVariable, SolidityFunction, SolidityVariableComposed, solidity_function_signature -from slither.core.solidity_types.elementary_type import ElementaryType -from slither.core.solidity_types.function_type import FunctionType +from slither.core.solidity_types import ElementaryType, ArrayType, MappingType, FunctionType logger = logging.getLogger("ExpressionParsing") class VariableNotFound(Exception): pass +def get_pointer_name(variable): + curr_type = variable.type + while(isinstance(curr_type, (ArrayType, MappingType))): + if isinstance(curr_type, ArrayType): + curr_type = curr_type.type + else: + assert isinstance(curr_type, MappingType) + curr_type = curr_type.type_to + + if isinstance(curr_type, (FunctionType)): + return variable.name + curr_type.parameters_signature + return None + + def find_variable(var_name, caller_context): if isinstance(caller_context, Contract): @@ -55,15 +68,20 @@ def find_variable(var_name, caller_context): # function test(function(uint) internal returns(bool) t) interna{ # Will have a local variable t which will match the signature # t(uint256) - func_variables_ptr = {f.name + f.type.parameters_signature : f for f in function.variables - if isinstance(f.type, FunctionType)} - if var_name in func_variables_ptr: + func_variables_ptr = {get_pointer_name(f) : f for f in function.variables} + if var_name and var_name in func_variables_ptr: return func_variables_ptr[var_name] contract_variables = contract.variables_as_dict() if var_name in contract_variables: return contract_variables[var_name] + # A state variable can be a pointer + conc_variables_ptr = {get_pointer_name(f) : f for f in contract.variables} + if var_name and var_name in conc_variables_ptr: + return conc_variables_ptr[var_name] + + functions = contract.functions_as_dict() if var_name in functions: return functions[var_name] From 87acb738412d1462ec7192b5fb33f6ffc4e84cd0 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 22 Oct 2018 19:01:55 +0100 Subject: [PATCH 184/308] SlithIR: add points_to property to ReferenceVariable --- slither/slithir/convert.py | 11 +++++++++++ slither/slithir/variables/reference.py | 24 +++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index da72b560e..03834b84c 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -511,10 +511,21 @@ def apply_ir_heuristics(irs, node): # irs = replace_calls(irs) irs = remove_unused(irs) + find_references_origin(irs) + reset_variable_number(irs) return irs +def find_references_origin(irs): + """ + Make lvalue of each Index, Member operation + points to the left variable + """ + for ir in irs: + if isinstance(ir, (Index, Member)): + ir.lvalue.points_to = ir.variable_left + def reset_variable_number(result): """ Reset the number associated to slithIR variables diff --git a/slither/slithir/variables/reference.py b/slither/slithir/variables/reference.py index 18f33733e..bd93f9f1f 100644 --- a/slither/slithir/variables/reference.py +++ b/slither/slithir/variables/reference.py @@ -1,6 +1,8 @@ -from slither.core.variables.variable import Variable from slither.core.children.child_node import ChildNode +from slither.core.declarations import Contract, Enum, SolidityVariableComposed +from slither.core.variables.variable import Variable + class ReferenceVariable(ChildNode, Variable): @@ -10,6 +12,7 @@ class ReferenceVariable(ChildNode, Variable): super(ReferenceVariable, self).__init__() self._index = ReferenceVariable.COUNTER ReferenceVariable.COUNTER += 1 + self._points_to = None @property def index(self): @@ -19,6 +22,25 @@ class ReferenceVariable(ChildNode, Variable): def index(self, idx): self._index = idx + @property + def points_to(self): + """ + Return the variable pointer by the reference + It is the left member of a Index or Member operator + """ + return self._points_to + + @points_to.setter + def points_to(self, points_to): + # Can only be a rvalue of + # Member or Index operator + from slither.slithir.utils.utils import is_valid_lvalue + assert is_valid_lvalue(points_to) \ + or points_to == SolidityVariableComposed('msg.data') \ + or isinstance(points_to, (Contract, Enum)) + + self._points_to = points_to + @property def name(self): return 'REF_{}'.format(self.index) From 550d14a7a1a4c79fe842035163ad3b8a4cc3ff75 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 22 Oct 2018 21:50:05 +0100 Subject: [PATCH 185/308] SlithIR: Use SSA for slithIr variables (TemporaryVariable/ReferenceVariable/TupleVariable) --- slither/slithir/convert.py | 22 +------------------ slither/solc_parsing/declarations/function.py | 2 ++ 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 03834b84c..990ea756f 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -513,7 +513,7 @@ def apply_ir_heuristics(irs, node): find_references_origin(irs) - reset_variable_number(irs) + #reset_variable_number(irs) return irs @@ -526,26 +526,6 @@ def find_references_origin(irs): if isinstance(ir, (Index, Member)): ir.lvalue.points_to = ir.variable_left -def reset_variable_number(result): - """ - Reset the number associated to slithIR variables - """ - variables = [] - for ins in result: - variables += ins.read - if isinstance(ins, OperationWithLValue) and not ins.lvalue in variables: - variables += [ins.lvalue] - - tmp_variables = [v for v in variables if isinstance(v, TemporaryVariable)] - for idx in range(len(tmp_variables)): - tmp_variables[idx].index = idx - ref_variables = [v for v in variables if isinstance(v, ReferenceVariable)] - for idx in range(len(ref_variables)): - ref_variables[idx].index = idx - tuple_variables = [v for v in variables if isinstance(v, TupleVariable)] - for idx in range(len(tuple_variables)): - tuple_variables[idx].index = idx - def is_temporary(ins): return isinstance(ins, (Argument, TmpNewElementaryType, diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 995cfbd0e..407acdd29 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -19,6 +19,7 @@ from slither.visitors.expression.has_conditional import HasConditional from slither.utils.expression_manipulations import SplitTernaryExpression +from slither.slithir.utils.variable_number import transform_slithir_vars_to_ssa logger = logging.getLogger("FunctionSolc") @@ -635,6 +636,7 @@ class FunctionSolc(Function): def convert_expression_to_slithir(self): for node in self.nodes: node.slithir_generation() + transform_slithir_vars_to_ssa(self) def split_ternary_node(self, node, condition, true_expr, false_expr): From 204b5798aeb3946e3519313114524c82c2317b52 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 23 Oct 2018 10:28:20 +0100 Subject: [PATCH 186/308] Parsing: Add .contract to Event Naming_convention detector: remove dupplicated results --- .../naming_convention/naming_convention.py | 14 ++++++++++++++ slither/solc_parsing/declarations/contract.py | 1 + 2 files changed, 15 insertions(+) diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index ba4d01c0a..c6cd4a6d7 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -44,6 +44,8 @@ class NamingConvention(AbstractDetector): 'sourceMapping': contract.source_mapping}) for struct in contract.structures: + if struct.contract != contract: + continue if self.is_cap_words(struct.name) is False: info = "Struct '{}' is not in CapWords, Contract: '{}' ".format(struct.name, contract.name) @@ -56,6 +58,8 @@ class NamingConvention(AbstractDetector): 'sourceMapping': struct.source_mapping}) for event in contract.events: + if event.contract != contract: + continue if self.is_cap_words(event.name) is False: info = "Event '{}' is not in CapWords, Contract: '{}' ".format(event.name, contract.name) @@ -68,6 +72,8 @@ class NamingConvention(AbstractDetector): 'sourceMapping': event.source_mapping}) for func in contract.functions: + if func.contract != contract: + continue if self.is_mixed_case(func.name) is False: info = "Function '{}' is not in mixedCase, Contract: '{}' ".format(func.name, contract.name) @@ -94,6 +100,8 @@ class NamingConvention(AbstractDetector): 'sourceMapping': argument.source_mapping}) for var in contract.state_variables: + if var.contract != contract: + continue if self.should_avoid_name(var.name): if self.is_upper_case_with_underscores(var.name) is False: @@ -130,6 +138,9 @@ class NamingConvention(AbstractDetector): 'sourceMapping': var.source_mapping}) for enum in contract.enums: + if enum.contract != contract: + continue + if self.is_cap_words(enum.name) is False: info = "Enum '{}' is not in CapWords, Contract: '{}' ".format(enum.name, contract.name) self.log(info) @@ -141,6 +152,9 @@ class NamingConvention(AbstractDetector): 'sourceMapping': enum.source_mapping}) for modifier in contract.modifiers: + if modifier.contract != contract: + continue + if self.is_mixed_case(modifier.name) is False: info = "Modifier '{}' is not in mixedCase, Contract: '{}' ".format(modifier.name, contract.name) self.log(info) diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index a59385f45..1bfb8d4b0 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -173,6 +173,7 @@ class ContractSolc04(Contract): for event_to_parse in self._eventsNotParsed: event = EventSolc(event_to_parse) event.analyze(self) + event.set_contract(self) self._events[event.full_name] = event self._eventsNotParsed = None From e4b010af8afebfad8a40dfe6cf1f5bd31ae6b587 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 23 Oct 2018 10:53:12 +0100 Subject: [PATCH 187/308] SlithIR: - Add .used attributes to all operators, it returns all the variables used - Use ABC for Operation --- slither/slithir/operations/high_level_call.py | 4 ++- .../operations/internal_dynamic_call.py | 2 +- slither/slithir/operations/low_level_call.py | 4 ++- slither/slithir/operations/lvalue.py | 4 +++ slither/slithir/operations/operation.py | 26 ++++++++++++++++--- slither/slithir/operations/send.py | 2 +- slither/slithir/operations/transfer.py | 2 +- 7 files changed, 36 insertions(+), 8 deletions(-) diff --git a/slither/slithir/operations/high_level_call.py b/slither/slithir/operations/high_level_call.py index ad4cc5956..1681bb263 100644 --- a/slither/slithir/operations/high_level_call.py +++ b/slither/slithir/operations/high_level_call.py @@ -58,7 +58,9 @@ class HighLevelCall(Call, OperationWithLValue): @property def read(self): - return [self.destination] + all_read = [self.destination, self.call_gas, self.call_value] + self.arguments + # remove None + return [x for x in all_read if x] @property def destination(self): diff --git a/slither/slithir/operations/internal_dynamic_call.py b/slither/slithir/operations/internal_dynamic_call.py index 7be424b04..e39fc9d65 100644 --- a/slither/slithir/operations/internal_dynamic_call.py +++ b/slither/slithir/operations/internal_dynamic_call.py @@ -19,7 +19,7 @@ class InternalDynamicCall(Call, OperationWithLValue): @property def read(self): - return list(self.arguments) + return list(self.arguments) + [self.function] @property def function(self): diff --git a/slither/slithir/operations/low_level_call.py b/slither/slithir/operations/low_level_call.py index e7de73593..4f6bf6f34 100644 --- a/slither/slithir/operations/low_level_call.py +++ b/slither/slithir/operations/low_level_call.py @@ -50,7 +50,9 @@ class LowLevelCall(Call, OperationWithLValue): @property def read(self): - return [self.destination] + all_read = [self.destination, self.call_gas, self.call_value] + self.arguments + # remove None + return [x for x in all_read if x] @property def destination(self): diff --git a/slither/slithir/operations/lvalue.py b/slither/slithir/operations/lvalue.py index da15e14fe..ce7976d34 100644 --- a/slither/slithir/operations/lvalue.py +++ b/slither/slithir/operations/lvalue.py @@ -14,6 +14,10 @@ class OperationWithLValue(Operation): def lvalue(self): return self._lvalue + @property + def used(self): + return self.read + [self.lvalue] + @lvalue.setter def lvalue(self, lvalue): self._lvalue = lvalue diff --git a/slither/slithir/operations/operation.py b/slither/slithir/operations/operation.py index 5a88b85b7..c4c338c25 100644 --- a/slither/slithir/operations/operation.py +++ b/slither/slithir/operations/operation.py @@ -1,9 +1,29 @@ +import abc from slither.core.context.context import Context -class Operation(Context): + +class AbstractOperation(abc.ABC): @property + @abc.abstractmethod def read(self): """ - Must be ovveriden + Return the list of variables READ + """ + pass + + @property + @abc.abstractmethod + def used(self): + """ + Return the list of variables used + """ + pass + +class Operation(Context, AbstractOperation): + + @property + def used(self): + """ + By default used is all the variables read """ - raise Exception('Not overrided {}'.format(type(self))) + return self.read diff --git a/slither/slithir/operations/send.py b/slither/slithir/operations/send.py index 9a521e5e3..201de989a 100644 --- a/slither/slithir/operations/send.py +++ b/slither/slithir/operations/send.py @@ -23,7 +23,7 @@ class Send(Call, OperationWithLValue): @property def read(self): - return [self.destination] + return [self.destination, self.call_value] @property def destination(self): diff --git a/slither/slithir/operations/transfer.py b/slither/slithir/operations/transfer.py index 8dda83d59..b334d02ce 100644 --- a/slither/slithir/operations/transfer.py +++ b/slither/slithir/operations/transfer.py @@ -18,7 +18,7 @@ class Transfer(Call): @property def read(self): - return [self.destination] + return [self.destination, self.call_value] @property def destination(self): From 3ed88b6c20eb6d456b6ff9a30ef67026817b001f Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 23 Oct 2018 11:05:21 +0100 Subject: [PATCH 188/308] Add missing file --- slither/slithir/utils/variable_number.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 slither/slithir/utils/variable_number.py diff --git a/slither/slithir/utils/variable_number.py b/slither/slithir/utils/variable_number.py new file mode 100644 index 000000000..d1ebc5b65 --- /dev/null +++ b/slither/slithir/utils/variable_number.py @@ -0,0 +1,23 @@ +from slither.slithir.variables import (Constant, ReferenceVariable, + TemporaryVariable, TupleVariable) +from slither.slithir.operations import OperationWithLValue + +def transform_slithir_vars_to_ssa(function): + """ + Transform slithIR vars to SSA + """ + variables = [] + for node in function.nodes: + for ir in node.irs: + if isinstance(ir, OperationWithLValue) and not ir.lvalue in variables: + variables += [ir.lvalue] + + tmp_variables = [v for v in variables if isinstance(v, TemporaryVariable)] + for idx in range(len(tmp_variables)): + tmp_variables[idx].index = idx + ref_variables = [v for v in variables if isinstance(v, ReferenceVariable)] + for idx in range(len(ref_variables)): + ref_variables[idx].index = idx + tuple_variables = [v for v in variables if isinstance(v, TupleVariable)] + for idx in range(len(tuple_variables)): + tuple_variables[idx].index = idx From c307d1b0d00159f83a1670ed903d0631a6506b3b Mon Sep 17 00:00:00 2001 From: Cryptomental Date: Sat, 20 Oct 2018 12:13:53 +0200 Subject: [PATCH 189/308] detectors: Rewrite unitialized state variables with IR. Refs: https://github.com/trailofbits/slither/issues/30 --- scripts/travis_test.sh | 2 +- .../uninitialized_state_variables.py | 67 +++++++++++-------- tests/uninitialized.sol | 46 +++++++++++++ 3 files changed, 86 insertions(+), 29 deletions(-) diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 0e4e4030a..754661234 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -4,7 +4,7 @@ slither tests/uninitialized.sol --disable-solc-warnings --detect-uninitialized-state -if [ $? -ne 1 ]; then +if [ $? -ne 3 ]; then exit 1 fi diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index 717425e26..e24fdf298 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -2,16 +2,20 @@ Module detecting state uninitialized variables Recursively check the called functions - The heuristic chekcs that: - - state variables are read or called - - the variables does not call push (avoid too many FP) + The heuristic checks: + - state variables including mappings/refs + - LibraryCalls, InternalCalls, InternalDynamicCalls with storage variables Only analyze "leaf" contracts (contracts that are not inherited by another contract) """ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.core.variables.state_variable import StateVariable +from slither.slithir.variables import ReferenceVariable +from slither.slithir.operations.assignment import Assignment -from slither.visitors.expression.find_push import FindPush +from slither.slithir.operations import (OperationWithLValue, Index, Member, + InternalCall, InternalDynamicCall, LibraryCall) class UninitializedStateVarsDetection(AbstractDetector): @@ -24,29 +28,36 @@ class UninitializedStateVarsDetection(AbstractDetector): IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.HIGH - def detect_uninitialized(self, contract): - # get all the state variables read by all functions - var_read = [f.state_variables_read for f in contract.all_functions_called + contract.modifiers] - # flat list - var_read = [item for sublist in var_read for item in sublist] - # remove state variable that are initiliazed at contract construction - var_read = [v for v in var_read if v.uninitialized] - - # get all the state variables written by the functions - var_written = [f.state_variables_written for f in contract.all_functions_called + contract.modifiers] - # flat list - var_written = [item for sublist in var_written for item in sublist] - - all_push = [f.apply_visitor(FindPush) for f in contract.functions] - # flat list - all_push = [item for sublist in all_push for item in sublist] + @staticmethod + def written_variables(contract): + ret = [] + for f in contract.all_functions_called + contract.modifiers: + for n in f.nodes: + for ir in n.irs: + if isinstance(ir, (Index, Member)): + continue # Don't consider Member and Index operations -> ReferenceVariable + elif isinstance(ir, OperationWithLValue) and isinstance(ir.lvalue, StateVariable): + ret.append(ir.lvalue) + elif isinstance(ir, Assignment) and isinstance(ir.lvalue, ReferenceVariable): + dest = ir.lvalue + while isinstance(dest, ReferenceVariable): + dest = dest.points_to + ret.append(dest) + elif isinstance(ir, LibraryCall) \ + or isinstance(ir, InternalCall) \ + or isinstance(ir, InternalDynamicCall): + for v in ir.arguments: + ret.append(v) + for param in f.parameters: + if param.location == 'storage': + ret.append(param) + + return ret - uninitialized_vars = list(set([v for v in var_read if \ - v not in var_written and \ - v not in all_push and \ - v.type not in contract.using_for])) # Note: does not handle using X for * - - return [(v, contract.get_functions_reading_from_variable(v)) for v in uninitialized_vars] + def detect_uninitialized(self, contract): + written_variables = self.written_variables(contract) + return [(variable, contract.get_functions_reading_from_variable(variable)) + for variable in contract.state_variables if variable not in written_variables] def detect(self): """ Detect uninitialized state variables @@ -61,8 +72,8 @@ class UninitializedStateVarsDetection(AbstractDetector): for variable, functions in ret: info = "Uninitialized state variable in %s, " % self.filename + \ "Contract: %s, Variable: %s, Used in %s" % (c.name, - str(variable), - [str(f) for f in functions]) + str(variable), + [str(f) for f in functions]) self.log(info) source = [variable.source_mapping] diff --git a/tests/uninitialized.sol b/tests/uninitialized.sol index ff3f1addc..d31eb10bf 100644 --- a/tests/uninitialized.sol +++ b/tests/uninitialized.sol @@ -9,3 +9,49 @@ contract Uninitialized{ } } + + +contract Test { + mapping (address => uint) balances; + mapping (address => uint) balancesInitialized; + + + function init() { + balancesInitialized[msg.sender] = 0; + } + + function use() { + // random operation to use the mapping + require(balances[msg.sender] == balancesInitialized[msg.sender]); + } +} + +library Lib{ + + struct MyStruct{ + uint val; + } + + function set(MyStruct storage st){ + st.val = 4; + } + +} + + +contract Test2 { + using Lib for Lib.MyStruct; + + Lib.MyStruct st; + Lib.MyStruct stInitiliazed; + + function init(){ + stInitiliazed.set(); + } + + function use(){ + // random operation to use the structure + require(st.val == stInitiliazed.val); + } + +} \ No newline at end of file From 5b877ddceb5dba6a99a7d4649bc3a031d8dffa24 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 23 Oct 2018 17:55:35 +0100 Subject: [PATCH 190/308] Allow reference to other SolidityVariableComposed --- slither/slithir/variables/reference.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/slithir/variables/reference.py b/slither/slithir/variables/reference.py index bd93f9f1f..e99311886 100644 --- a/slither/slithir/variables/reference.py +++ b/slither/slithir/variables/reference.py @@ -36,7 +36,7 @@ class ReferenceVariable(ChildNode, Variable): # Member or Index operator from slither.slithir.utils.utils import is_valid_lvalue assert is_valid_lvalue(points_to) \ - or points_to == SolidityVariableComposed('msg.data') \ + or isinstance(points_to, SolidityVariableComposed) \ or isinstance(points_to, (Contract, Enum)) self._points_to = points_to From e33da127d03ff488c5694611ecf7be4a206b8129 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 23 Oct 2018 20:28:11 +0100 Subject: [PATCH 191/308] Compact AST: Various bugfixes --- slither/slithir/operations/unpack.py | 5 +++- slither/slithir/variables/reference.py | 2 +- slither/solc_parsing/declarations/contract.py | 6 +++-- slither/solc_parsing/declarations/function.py | 26 ++++++++++++------- .../expressions/expression_parsing.py | 13 +++++++--- .../solidity_types/type_parsing.py | 18 ++++++++----- .../variables/variable_declaration.py | 10 +++++-- 7 files changed, 54 insertions(+), 26 deletions(-) diff --git a/slither/slithir/operations/unpack.py b/slither/slithir/operations/unpack.py index ec9c86385..5841aec59 100644 --- a/slither/slithir/operations/unpack.py +++ b/slither/slithir/operations/unpack.py @@ -28,4 +28,7 @@ class Unpack(OperationWithLValue): return self._idx def __str__(self): - return "{} = UNPACK {} index: {} ".format(self.lvalue, self.tuple, self.index) + return "{}({})= UNPACK {} index: {} ".format(self.lvalue, + self.lvalue.type, + self.tuple, + self.index) diff --git a/slither/slithir/variables/reference.py b/slither/slithir/variables/reference.py index bd93f9f1f..e99311886 100644 --- a/slither/slithir/variables/reference.py +++ b/slither/slithir/variables/reference.py @@ -36,7 +36,7 @@ class ReferenceVariable(ChildNode, Variable): # Member or Index operator from slither.slithir.utils.utils import is_valid_lvalue assert is_valid_lvalue(points_to) \ - or points_to == SolidityVariableComposed('msg.data') \ + or isinstance(points_to, SolidityVariableComposed) \ or isinstance(points_to, (Contract, Enum)) self._points_to = points_to diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index 116a7ea88..314f6573e 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -116,11 +116,13 @@ class ContractSolc04(Contract): if self.is_compact_ast: for using_for in self._usingForNotParsed: lib_name = parse_type(using_for['libraryName'], self) - if 'typeName' in using_for: + if 'typeName' in using_for and using_for['typeName']: type_name = parse_type(using_for['typeName'], self) else: type_name = '*' - self._using_for[type_name] = lib_name + if not type_name in self._using_for: + self.using_for[type_name] = [] + self._using_for[type_name].append(lib_name) else: for using_for in self._usingForNotParsed: children = using_for[self.get_children()] diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 5d9e5ea7e..cf3fed8f0 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -362,16 +362,20 @@ class FunctionSolc(Function): # Then we craft one expression that does the assignment variables = [] i = 0 + new_node = node for variable in statement['declarations']: - src = variable['src'] i = i+1 - # Create a fake statement to be consistent - new_statement = {'nodeType':'VariableDefinitionStatement', - 'src': src, - 'declarations':[variable]} - variables.append(variable) - - new_node = self._parse_variable_definition_init_tuple(new_statement, i, new_node) + if variable: + src = variable['src'] + # Create a fake statement to be consistent + new_statement = {'nodeType':'VariableDefinitionStatement', + 'src': src, + 'declarations':[variable]} + variables.append(variable) + + new_node = self._parse_variable_definition_init_tuple(new_statement, + i, + new_node) var_identifiers = [] # craft of the expression doing the assignement @@ -386,7 +390,7 @@ class FunctionSolc(Function): } var_identifiers.append(identifier) - tuple_expression = {'nodeType':'TuleExpression', + tuple_expression = {'nodeType':'TupleExpression', 'src': statement['src'], 'components':var_identifiers} @@ -396,7 +400,9 @@ class FunctionSolc(Function): 'operator': '=', 'type':'tuple()', 'leftHandSide': tuple_expression, - 'rightHandSide': statement['initialValue']} + 'rightHandSide': statement['initialValue'], + 'typeDescriptions': {'typeString':'tuple()'} + } node = new_node new_node = self._new_node(NodeType.EXPRESSION) new_node.add_unparsed_expression(expression) diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index 51916a12f..688482177 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -164,7 +164,9 @@ def parse_call(expression, caller_context): if caller_context.is_compact_ast: called = parse_expression(expression['expression'], caller_context) - arguments = [parse_expression(a, caller_context) for a in expression['arguments']] + arguments = [] + if expression['arguments']: + arguments = [parse_expression(a, caller_context) for a in expression['arguments']] else: children = expression['children'] called = parse_expression(children[0], caller_context) @@ -260,7 +262,6 @@ def parse_expression(expression, caller_context): # The AST naming does not follow the spec name = expression[caller_context.get_key()] - is_compact_ast = caller_context.is_compact_ast if name == 'UnaryOperation': @@ -312,7 +313,7 @@ def parse_expression(expression, caller_context): Note: this is only possible with Solidity >= 0.4.12 """ if is_compact_ast: - expressions = [parse_expression(e, caller_context) for e in expression['components']] + expressions = [parse_expression(e, caller_context) if e else None for e in expression['components']] else: if 'children' not in expression : attributes = expression['attributes'] @@ -373,6 +374,8 @@ def parse_expression(expression, caller_context): if is_compact_ast: value = expression['value'] + if not value: + value = '0x'+expression['hexValue'] else: value = expression['attributes']['value'] if value is None: @@ -527,7 +530,9 @@ def parse_expression(expression, caller_context): if is_compact_ast: called = parse_expression(expression['modifierName'], caller_context) - arguments = [parse_expression(a, caller_context) for a in expression['arguments']] + arguments = [] + if expression['arguments']: + arguments = [parse_expression(a, caller_context) for a in expression['arguments']] else: children = expression['children'] called = parse_expression(children[0], caller_context) diff --git a/slither/solc_parsing/solidity_types/type_parsing.py b/slither/solc_parsing/solidity_types/type_parsing.py index 2df12be75..76e6d3110 100644 --- a/slither/solc_parsing/solidity_types/type_parsing.py +++ b/slither/solc_parsing/solidity_types/type_parsing.py @@ -150,7 +150,7 @@ def parse_type(t, caller_context): elif t[key] == 'ElementaryTypeName': if is_compact_ast: - return ElementaryType(t['typeDescriptions']['typeString']) + return ElementaryType(t['name']) return ElementaryType(t['attributes'][key]) elif t[key] == 'UserDefinedTypeName': @@ -186,22 +186,28 @@ def parse_type(t, caller_context): return MappingType(mappingFrom, mappingTo) elif t[key] == 'FunctionTypeName': - assert len(t['children']) == 2 - params = t['children'][0] - return_values = t['children'][1] + if is_compact_ast: + params = t['parameterTypes'] + return_values = t['returnParameterTypes'] + index = 'parameters' + else: + assert len(t['children']) == 2 + params = t['children'][0] + return_values = t['children'][1] + index = 'children' assert params[key] == 'ParameterList' assert return_values[key] == 'ParameterList' params_vars = [] return_values_vars = [] - for p in params['children']: + for p in params[index]: var = FunctionTypeVariableSolc(p) var.set_offset(p['src'], caller_context.slither) var.analyze(caller_context) params_vars.append(var) - for p in return_values['children']: + for p in return_values[index]: var = FunctionTypeVariableSolc(p) var.set_offset(p['src'], caller_context.slither) diff --git a/slither/solc_parsing/variables/variable_declaration.py b/slither/solc_parsing/variables/variable_declaration.py index e9af3c070..17b1e9c37 100644 --- a/slither/solc_parsing/variables/variable_declaration.py +++ b/slither/solc_parsing/variables/variable_declaration.py @@ -41,7 +41,10 @@ class VariableDeclarationSolc(Variable): if nodeType in ['VariableDeclarationStatement', 'VariableDefinitionStatement']: if len(var['declarations'])>1: raise MultipleVariablesDeclaration - self._init_from_declaration(var['declarations'][0], var['initialValue']) + init = None + if 'initialValue' in var: + init = var['initialValue'] + self._init_from_declaration(var['declarations'][0], init) elif nodeType == 'VariableDeclaration': self._init_from_declaration(var, var['value']) else: @@ -108,7 +111,10 @@ class VariableDeclarationSolc(Variable): self._analyze_variable_attributes(attributes) if self._is_compact_ast: - self._elem_to_parse = var['typeName'] + if var['typeName']: + self._elem_to_parse = var['typeName'] + else: + self._elem_to_parse = UnknownType(var['typeDescriptions']['typeString']) else: if not var['children']: # It happens on variable declared inside loop declaration From 69020a2dfa78300076dc016d319957a44e6c3a62 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 23 Oct 2018 21:01:00 +0100 Subject: [PATCH 192/308] Add initial Truffle support --- slither/__main__.py | 30 ++++++++++++++++++++++++++++++ slither/slither.py | 16 +++++++++++----- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/slither/__main__.py b/slither/__main__.py index 9c59f0205..aab50cd14 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -15,6 +15,7 @@ from slither.detectors.abstract_detector import (AbstractDetector, classification_txt) from slither.printers.abstract_printer import AbstractPrinter from slither.slither import Slither +from slither.utils.colors import red logging.basicConfig() logger = logging.getLogger("Slither") @@ -54,6 +55,9 @@ def process(filename, args, detector_classes, printer_classes): """ slither = Slither(filename, args.solc, args.disable_solc_warnings, args.solc_args) + return _process(slither, detector_classes, printer_classes) + +def _process(slither, detector_classes, printer_classes): for detector_cls in detector_classes: slither.register_detector(detector_cls) @@ -76,6 +80,28 @@ def process(filename, args, detector_classes, printer_classes): return results, analyzed_contracts_count +def process_truffle(dirname, args, detector_classes, printer_classes): + if not os.path.isdir(os.path.join(dirname, 'build'))\ + or not os.path.isdir(os.path.join(dirname, 'build', 'contracts')): + logger.info(red('No truffle build directory found, did you run `truffle compile`?')) + return (0,0) + + filenames = glob.glob(os.path.join(dirname,'build','contracts', '*.json')) + + all_contracts = [] + + for filename in filenames: + with open(filename) as f: + contract_loaded = json.load(f) + all_contracts += contract_loaded['ast']['nodes'] + + contract = { + "nodeType": "SourceUnit", + "nodes" : all_contracts} + + slither = Slither(contract, args.solc, args.disable_solc_warnings, args.solc_args) + return _process(slither, detector_classes, printer_classes) + def output_json(results, filename): with open(filename, 'w') as f: @@ -194,6 +220,9 @@ def main_impl(all_detector_classes, all_printer_classes): if os.path.isfile(filename): (results, number_contracts) = process(filename, args, detector_classes, printer_classes) + elif os.path.isfile(os.path.join(filename, 'truffle.js')): + (results, number_contracts) = process_truffle(filename, args, detector_classes, printer_classes) + elif os.path.isdir(filename) or len(globbed_filenames) > 0: extension = "*.sol" if not args.solc_ast else "*.json" filenames = glob.glob(os.path.join(filename, extension)) @@ -209,6 +238,7 @@ def main_impl(all_detector_classes, all_printer_classes): # output_json(results, args.json) # exit(results) + else: raise Exception("Unrecognised file/dir path: '#{filename}'".format(filename=filename)) diff --git a/slither/slither.py b/slither/slither.py index 2b277b378..cb443bc86 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -17,16 +17,22 @@ logger_printer = logging.getLogger("Printers") class Slither(SlitherSolc): - def __init__(self, filename, solc='solc', disable_solc_warnings=False, solc_arguments=''): + def __init__(self, contract, solc='solc', disable_solc_warnings=False, solc_arguments=''): self._detectors = [] self._printers = [] - stdout = self._run_solc(filename, solc, disable_solc_warnings, solc_arguments) - super(Slither, self).__init__(filename) + # json text provided + if isinstance(contract, dict): + super(Slither, self).__init__('') + self._parse_contracts_from_loaded_json(contract, '') + # .json or .sol provided + else: + contracts_json = self._run_solc(contract, solc, disable_solc_warnings, solc_arguments) + super(Slither, self).__init__(contract) - for d in stdout: - self._parse_contracts_from_json(d) + for c in contracts_json: + self._parse_contracts_from_json(c) self._analyze_contracts() From c6b90f262a8146025c20ef8cf56b4731cd397bac Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 24 Oct 2018 10:38:32 +0100 Subject: [PATCH 193/308] SlithIR: dont remove last IR for optimization, as it may be used by RETURN statement --- slither/slithir/convert.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 990ea756f..3fca07f06 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -544,8 +544,14 @@ def remove_temporary(result): return result def remove_unused(result): - removed = True + + if not result: + return result + + # dont remove the last elem, as it may be used by RETURN + last_elem = result[-1] + while removed: removed = False @@ -562,7 +568,7 @@ def remove_unused(result): for ins in result: if isinstance(ins, Member): - if not ins.lvalue.name in to_keep: + if not ins.lvalue.name in to_keep and ins != last_elem: to_remove.append(ins) removed = True From 5c1e405969c62791b5fd956ac24c6e625965085b Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 24 Oct 2018 15:26:16 +0100 Subject: [PATCH 194/308] API changes: - use slihtIR for node.state_vars read/write and calls - remove external_calls of node/function, split into node.low_level_calls/high_level_calls --- slither/core/cfg/node.py | 102 ++++++++++++++++-- slither/core/declarations/function.py | 71 +++++++++--- slither/slithir/convert.py | 10 +- slither/solc_parsing/cfg/node.py | 26 ++--- slither/solc_parsing/declarations/function.py | 4 +- 5 files changed, 173 insertions(+), 40 deletions(-) diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index 79e935e74..9626a4a87 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -5,6 +5,7 @@ import logging from slither.core.source_mapping.source_mapping import SourceMapping from slither.core.variables.variable import Variable +from slither.core.variables.state_variable import StateVariable from slither.visitors.expression.expression_printer import ExpressionPrinter from slither.visitors.expression.read_var import ReadVar @@ -12,10 +13,17 @@ from slither.visitors.expression.write_var import WriteVar from slither.core.children.child_function import ChildFunction -from slither.core.declarations.solidity_variables import SolidityFunction +from slither.core.declarations.solidity_variables import SolidityVariable, SolidityFunction from slither.slithir.convert import convert_expression +from slither.slithir.operations import OperationWithLValue, Index, Member, LowLevelCall, SolidityCall, HighLevelCall, InternalCall, LibraryCall + + +from slither.slithir.variables import Constant, ReferenceVariable, TemporaryVariable, TupleVariable + +from slither.core.declarations import Contract + logger = logging.getLogger("Node") class NodeType: @@ -103,7 +111,10 @@ class Node(SourceMapping, ChildFunction): self._vars_written = [] self._vars_read = [] self._internal_calls = [] - self._external_calls = [] + self._solidity_calls = [] + self._high_level_calls = [] + self._low_level_calls = [] + self._external_calls_as_expressions = [] self._irs = [] self._state_vars_written = [] @@ -176,16 +187,40 @@ class Node(SourceMapping, ChildFunction): @property def internal_calls(self): """ - list(Function or SolidityFunction): List of function calls (that does not create a transaction) + list(Function or SolidityFunction): List of internal/soldiity function calls """ return list(self._internal_calls) @property - def external_calls(self): + def solidity_calls(self): + """ + list(SolidityFunction): List of Soldity calls + """ + return list(self._internal_calls) + + @property + def high_level_calls(self): + """ + list((Contract, Function)): List of high level calls (external calls). Include library calls + """ + return list(self._high_level_calls) + + @property + def low_level_calls(self): + """ + list((Variable|SolidityVariable, str)): List of low_level call + A low level call is defined by + - the variable called + - the name of the function (call/delegatecall/codecall) + """ + return list(self._low_level_calls) + + @property + def external_calls_as_expressions(self): """ list(CallExpression): List of message calls (that creates a transaction) """ - return self._external_calls + return self._external_calls_as_expressions @property def calls_as_expression(self): @@ -226,10 +261,7 @@ class Node(SourceMapping, ChildFunction): Returns: bool: True if the node has a require or assert call """ - return self.internal_calls and\ - any(isinstance(c, SolidityFunction) and\ - (c.name in ['require(bool)', 'require(bool,string)', 'assert(bool)'])\ - for c in self.internal_calls) + return any(c.name in ['require(bool)', 'require(bool,string)', 'assert(bool)'] for c in self.internal_calls) def contains_if(self): """ @@ -328,3 +360,55 @@ class Node(SourceMapping, ChildFunction): if self.expression: expression = self.expression self._irs = convert_expression(expression, self) + + self._find_read_write_call() + + def _find_read_write_call(self): + + def is_slithir_var(var): + return isinstance(var, (Constant, ReferenceVariable, TemporaryVariable, TupleVariable)) + + for ir in self.irs: + self._vars_read += [v for v in ir.read if not is_slithir_var(v)] + if isinstance(ir, OperationWithLValue): + if isinstance(ir, (Index, Member)): + continue # Don't consider Member and Index operations -> ReferenceVariable + var = ir.lvalue + # If its a reference, we loop until finding the origin + if isinstance(var, (ReferenceVariable)): + while isinstance(var, ReferenceVariable): + var = var.points_to + # Only store non-slithIR variables + if not is_slithir_var(var): + self._vars_written.append(var) + + if isinstance(ir, InternalCall): + self._internal_calls.append(ir.function) + if isinstance(ir, SolidityCall): + # TODO: consider removing dependancy of solidity_call to internal_call + self._solidity_calls.append(ir.function) + self._internal_calls.append(ir.function) + if isinstance(ir, LowLevelCall): + assert isinstance(ir.destination, (Variable, SolidityVariable)) + self._low_level_calls.append((ir.destination, ir.function_name.value)) + elif isinstance(ir, (HighLevelCall)) and not isinstance(ir, LibraryCall): + if isinstance(ir.destination.type, Contract): + self._high_level_calls.append((ir.destination.type, ir.function)) + else: + self._high_level_calls.append((ir.destination.type.type, ir.function)) + elif isinstance(ir, LibraryCall): + assert isinstance(ir.destination, Contract) + self._high_level_calls.append((ir.destination, ir.function)) + + self._vars_read = list(set(self._vars_read)) + self._state_vars_read = [v for v in self._vars_read if isinstance(v, StateVariable)] + self._solidity_vars_read = [v for v in self._vars_read if isinstance(v, SolidityVariable)] + self._vars_written = list(set(self._vars_written)) + self._state_vars_written = [v for v in self._vars_written if isinstance(v, StateVariable)] + self._internal_calls = list(set(self._internal_calls)) + self._solidity_calls = list(set(self._solidity_calls)) + self._high_level_calls = list(set(self._high_level_calls)) + self._low_level_calls = list(set(self._low_level_calls)) + + + diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 6e71ddfc7..c3551cd19 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -44,7 +44,10 @@ class Function(ChildContract, SourceMapping): self._solidity_vars_read = [] self._state_vars_written = [] self._internal_calls = [] - self._external_calls = [] + self._solidity_calls = [] + self._low_level_calls = [] + self._high_level_calls = [] + self._external_calls_as_expressions = [] self._expression_vars_read = [] self._expression_vars_written = [] self._expression_calls = [] @@ -239,14 +242,39 @@ class Function(ChildContract, SourceMapping): return list(self._internal_calls) @property - def external_calls(self): + def solidity_calls(self): + """ + list(SolidityFunction): List of Soldity calls + """ + return list(self._internal_calls) + + @property + def high_level_calls(self): + """ + list((Contract, Function)): List of high level calls (external calls). Include library calls + """ + return list(self._high_level_calls) + + @property + def low_level_calls(self): + """ + list((Variable|SolidityVariable, str)): List of low_level call + A low level call is defined by + - the variable called + - the name of the function (call/delegatecall/codecall) + """ + return list(self._low_level_calls) + + + @property + def external_calls_as_expressions(self): """ list(ExpressionCall): List of message calls (that creates a transaction) """ - return list(self._external_calls) + return list(self._external_calls_as_expressions) @property - def calls_as_expression(self): + def calls_as_expressions(self): return self._expression_calls @property @@ -353,6 +381,7 @@ class Function(ChildContract, SourceMapping): calls = [x for x in calls if x] calls = [item for sublist in calls for item in sublist] # Remove dupplicate if they share the same string representation + # TODO: check if groupby is still necessary here calls = [next(obj) for i, obj in\ groupby(sorted(calls, key=lambda x: str(x)), lambda x: str(x))] self._expression_calls = calls @@ -364,12 +393,30 @@ class Function(ChildContract, SourceMapping): groupby(sorted(internal_calls, key=lambda x: str(x)), lambda x: str(x))] self._internal_calls = internal_calls - external_calls = [x.external_calls for x in self.nodes] - external_calls = [x for x in external_calls if x] - external_calls = [item for sublist in external_calls for item in sublist] - external_calls = [next(obj) for i, obj in - groupby(sorted(external_calls, key=lambda x: str(x)), lambda x: str(x))] - self._external_calls = external_calls + self._solidity_calls = [c for c in internal_calls if isinstance(c, SolidityFunction)] + + low_level_calls = [x.low_level_calls for x in self.nodes] + low_level_calls = [x for x in low_level_calls if x] + low_level_calls = [item for sublist in low_level_calls for item in sublist] + low_level_calls = [next(obj) for i, obj in + groupby(sorted(low_level_calls, key=lambda x: str(x)), lambda x: str(x))] + + self._low_level_calls = low_level_calls + + high_level_calls = [x.high_level_calls for x in self.nodes] + high_level_calls = [x for x in high_level_calls if x] + high_level_calls = [item for sublist in high_level_calls for item in sublist] + high_level_calls = [next(obj) for i, obj in + groupby(sorted(high_level_calls, key=lambda x: str(x)), lambda x: str(x))] + + self._high_level_calls = high_level_calls + + external_calls_as_expressions = [x.external_calls_as_expressions for x in self.nodes] + external_calls_as_expressions = [x for x in external_calls_as_expressions if x] + external_calls_as_expressions = [item for sublist in external_calls_as_expressions for item in sublist] + external_calls_as_expressions = [next(obj) for i, obj in + groupby(sorted(external_calls_as_expressions, key=lambda x: str(x)), lambda x: str(x))] + self._external_calls_as_expressions = external_calls_as_expressions def _explore_functions(self, f_new_values): @@ -567,14 +614,14 @@ class Function(ChildContract, SourceMapping): Return the function summary Returns: (str, str, list(str), list(str), listr(str), list(str), list(str); - name, visibility, modifiers, vars read, vars written, internal_calls, external_calls + name, visibility, modifiers, vars read, vars written, internal_calls, external_calls_as_expressions """ return (self.name, self.visibility, [str(x) for x in self.modifiers], [str(x) for x in self.state_variables_read + self.solidity_variables_read], [str(x) for x in self.state_variables_written], [str(x) for x in self.internal_calls], - [str(x) for x in self.external_calls]) + [str(x) for x in self.external_calls_as_expressions]) def is_protected(self): """ diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 990ea756f..3fca07f06 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -544,8 +544,14 @@ def remove_temporary(result): return result def remove_unused(result): - removed = True + + if not result: + return result + + # dont remove the last elem, as it may be used by RETURN + last_elem = result[-1] + while removed: removed = False @@ -562,7 +568,7 @@ def remove_unused(result): for ins in result: if isinstance(ins, Member): - if not ins.lvalue.name in to_keep: + if not ins.lvalue.name in to_keep and ins != last_elem: to_remove.append(ins) removed = True diff --git a/slither/solc_parsing/cfg/node.py b/slither/solc_parsing/cfg/node.py index f72e7aaa7..56e73e32c 100644 --- a/slither/solc_parsing/cfg/node.py +++ b/slither/solc_parsing/cfg/node.py @@ -45,25 +45,21 @@ class NodeSolc(Node): expression = self.expression pp = ReadVar(expression) self._expression_vars_read = pp.result() - vars_read = [ExportValues(v).result() for v in self._expression_vars_read] - self._vars_read = [item for sublist in vars_read for item in sublist] - self._state_vars_read = [x for x in self.variables_read if\ - isinstance(x, (StateVariable))] - self._solidity_vars_read = [x for x in self.variables_read if\ - isinstance(x, (SolidityVariable))] + +# self._vars_read = [item for sublist in vars_read for item in sublist] +# self._state_vars_read = [x for x in self.variables_read if\ +# isinstance(x, (StateVariable))] +# self._solidity_vars_read = [x for x in self.variables_read if\ +# isinstance(x, (SolidityVariable))] pp = WriteVar(expression) self._expression_vars_written = pp.result() - vars_written = [ExportValues(v).result() for v in self._expression_vars_written] - self._vars_written = [item for sublist in vars_written for item in sublist] - self._state_vars_written = [x for x in self.variables_written if\ - isinstance(x, StateVariable)] + +# self._vars_written = [item for sublist in vars_written for item in sublist] +# self._state_vars_written = [x for x in self.variables_written if\ +# isinstance(x, StateVariable)] pp = FindCalls(expression) self._expression_calls = pp.result() - calls = [ExportValues(c).result() for c in self.calls_as_expression] - calls = [item for sublist in calls for item in sublist] - self._internal_calls = [c for c in calls if isinstance(c, (Function, SolidityFunction))] - - self._external_calls = [c for c in self.calls_as_expression if not isinstance(c.called, Identifier)] + self._external_calls_as_expressions = [c for c in self.calls_as_expression if not isinstance(c.called, Identifier)] diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 407acdd29..021444360 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -630,13 +630,13 @@ class FunctionSolc(Function): break self._remove_alone_endif() - self._analyze_read_write() - self._analyze_calls() def convert_expression_to_slithir(self): for node in self.nodes: node.slithir_generation() transform_slithir_vars_to_ssa(self) + self._analyze_read_write() + self._analyze_calls() def split_ternary_node(self, node, condition, true_expr, false_expr): From edf4f37cf88de3d0a5ffe6fb5476ba2196f99270 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 24 Oct 2018 16:02:01 +0100 Subject: [PATCH 195/308] Improve high_level_calls documentation --- slither/core/cfg/node.py | 5 ++++- slither/core/declarations/function.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index 9626a4a87..03aec416f 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -201,7 +201,10 @@ class Node(SourceMapping, ChildFunction): @property def high_level_calls(self): """ - list((Contract, Function)): List of high level calls (external calls). Include library calls + list((Contract, Function|Variable)): + List of high level calls (external calls). + A variable is called in case of call to a public state variable + Include library calls """ return list(self._high_level_calls) diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index c3551cd19..a16438f48 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -251,7 +251,10 @@ class Function(ChildContract, SourceMapping): @property def high_level_calls(self): """ - list((Contract, Function)): List of high level calls (external calls). Include library calls + list((Contract, Function|Variable)): + List of high level calls (external calls). + A variable is called in case of call to a public state variable + Include library calls """ return list(self._high_level_calls) From 90b2f6ed8d9f2fc5c47bf7cc32cc3a053c866864 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 24 Oct 2018 19:50:24 +0100 Subject: [PATCH 196/308] Compact AST: Better support for state mutability --- slither/solc_parsing/declarations/function.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index cf3fed8f0..e8e7f4ba4 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -61,12 +61,14 @@ class FunctionSolc(Function): if 'payable' in attributes: self._payable = attributes['payable'] - elif 'stateMutability' in attributes: + if 'stateMutability' in attributes: if attributes['stateMutability'] == 'payable': self._payable = True elif attributes['stateMutability'] == 'pure': self._pure = True self._view = True + elif attributes['stateMutability'] == 'view': + self._view = True if 'constant' in attributes: self._view = attributes['constant'] From f44fda030a7dd9368764f81b3efd38cec57f24be Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 24 Oct 2018 20:24:19 +0100 Subject: [PATCH 197/308] compact ast: Fix incorrect Return Statement parsing --- slither/solc_parsing/declarations/function.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index e8e7f4ba4..ab565bd4f 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -540,10 +540,14 @@ class FunctionSolc(Function): elif name == 'Return': return_node = self._new_node(NodeType.RETURN) link_nodes(node, return_node) - if self.get_children('children') in statement and statement[self.get_children('children')]: - assert len(statement[self.get_children('children')]) == 1 - expression = statement[self.get_children('children')][0] - return_node.add_unparsed_expression(expression) + if self.is_compact_ast: + if statement['expression']: + return_node.add_unparsed_expression(statement['expression']) + else: + if self.get_children('children') in statement and statement[self.get_children('children')]: + assert len(statement[self.get_children('children')]) == 1 + expression = statement[self.get_children('children')][0] + return_node.add_unparsed_expression(expression) node = return_node elif name == 'Throw': throw_node = self._new_node(NodeType.THROW) From a6c325d40c04d1b0e7ffe07074712bf87f0a88be Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 25 Oct 2018 07:32:58 +0100 Subject: [PATCH 198/308] Fix on ReferenceVariable.points_to --- slither/slithir/variables/reference.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/slither/slithir/variables/reference.py b/slither/slithir/variables/reference.py index e99311886..9763202ef 100644 --- a/slither/slithir/variables/reference.py +++ b/slither/slithir/variables/reference.py @@ -1,6 +1,6 @@ from slither.core.children.child_node import ChildNode -from slither.core.declarations import Contract, Enum, SolidityVariableComposed +from slither.core.declarations import Contract, Enum, SolidityVariable from slither.core.variables.variable import Variable @@ -36,8 +36,7 @@ class ReferenceVariable(ChildNode, Variable): # Member or Index operator from slither.slithir.utils.utils import is_valid_lvalue assert is_valid_lvalue(points_to) \ - or isinstance(points_to, SolidityVariableComposed) \ - or isinstance(points_to, (Contract, Enum)) + or isinstance(points_to, (SolidityVariable, Contract, Enum)) self._points_to = points_to From 62002aa14df1794a0dfa2854f3bdeb4ab7c71181 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 25 Oct 2018 07:49:37 +0100 Subject: [PATCH 199/308] Fix incorrect loop recovery --- slither/solc_parsing/declarations/function.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 407acdd29..44aff75df 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -197,6 +197,11 @@ class FunctionSolc(Function): if len(children) > 2: if children[-2]['name'] == 'ExpressionStatement': node_LoopExpression = self._parse_statement(children[-2], node_statement) + if not hasCondition: + link_nodes(node_LoopExpression, node_endLoop) + + if not hasCondition and not hasLoopExpression: + link_nodes(node, node_endLoop) link_nodes(node_LoopExpression, node_startLoop) @@ -464,7 +469,7 @@ class FunctionSolc(Function): end_node = self._find_end_loop(node, []) if not end_node: - logger.error('Break in no-loop context {}'.format(node.nodeId())) + logger.error('Break in no-loop context {}'.format(node)) exit(-1) for son in node.sons: From f03fa7dfbdea7adf1ae4c013ecef0e14dacbf052 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 25 Oct 2018 08:19:41 +0100 Subject: [PATCH 200/308] Fix incorrect ternary transformation on library --- slither/utils/expression_manipulations.py | 95 ++++++++++++++--------- 1 file changed, 59 insertions(+), 36 deletions(-) diff --git a/slither/utils/expression_manipulations.py b/slither/utils/expression_manipulations.py index 2804235ac..271c6e544 100644 --- a/slither/utils/expression_manipulations.py +++ b/slither/utils/expression_manipulations.py @@ -28,6 +28,9 @@ def f_call(e, x): def f_expression(e, x): e._expression = x +def f_called(e, x): + e._called = x + class SplitTernaryExpression(object): def __init__(self, expression): @@ -57,42 +60,62 @@ class SplitTernaryExpression(object): return True def copy_expression(self, expression, true_expression, false_expression): - if self.condition: - return - - if isinstance(expression, ConditionalExpression): - raise Exception('Nested ternary operator not handled') - - if isinstance(expression, (Literal, Identifier, IndexAccess, MemberAccess)): - return None - - elif isinstance(expression, (AssignmentOperation, BinaryOperation, TupleExpression)): - true_expression._expressions = [] - false_expression._expressions = [] - - for next_expr in expression.expressions: - if self.apply_copy(next_expr, true_expression, false_expression, f_expressions): - # always on last arguments added - self.copy_expression(next_expr, true_expression.expressions[-1], false_expression.expressions[-1]) + if self.condition: + return - elif isinstance(expression, CallExpression): - next_expr = expression.called - true_expression._called = copy.copy(next_expr) - false_expression._called = copy.copy(next_expr) - - true_expression._arguments = [] - false_expression._arguments = [] - - for next_expr in expression.arguments: - if self.apply_copy(next_expr, true_expression, false_expression, f_call): - # always on last arguments added - self.copy_expression(next_expr, true_expression.arguments[-1], false_expression.arguments[-1]) - - elif isinstance(expression, TypeConversion): - next_expr = expression.expression - if self.apply_copy(next_expr, true_expression, false_expression, f_expression): - self.copy_expression(expression.expression, true_expression.expression, false_expression.expression) + if isinstance(expression, ConditionalExpression): + raise Exception('Nested ternary operator not handled') + + if isinstance(expression, (Literal, Identifier, IndexAccess)): + return None + + # case of lib + # (.. ? .. : ..).add + if isinstance(expression, MemberAccess): + next_expr = expression.expression + if self.apply_copy(next_expr, true_expression, false_expression, f_expression): + self.copy_expression(next_expr, + true_expression.expression, + false_expression.expression) + + elif isinstance(expression, (AssignmentOperation, BinaryOperation, TupleExpression)): + true_expression._expressions = [] + false_expression._expressions = [] + + for next_expr in expression.expressions: + if self.apply_copy(next_expr, true_expression, false_expression, f_expressions): + # always on last arguments added + self.copy_expression(next_expr, + true_expression.expressions[-1], + false_expression.expressions[-1]) + + elif isinstance(expression, CallExpression): + next_expr = expression.called + + # case of lib + # (.. ? .. : ..).add + if self.apply_copy(next_expr, true_expression, false_expression, f_called): + self.copy_expression(next_expr, + true_expression.called, + false_expression.called) + + true_expression._arguments = [] + false_expression._arguments = [] + + for next_expr in expression.arguments: + if self.apply_copy(next_expr, true_expression, false_expression, f_call): + # always on last arguments added + self.copy_expression(next_expr, + true_expression.arguments[-1], + false_expression.arguments[-1]) + + elif isinstance(expression, TypeConversion): + next_expr = expression.expression + if self.apply_copy(next_expr, true_expression, false_expression, f_expression): + self.copy_expression(expression.expression, + true_expression.expression, + false_expression.expression) - else: - raise Exception('Ternary operation not handled {}'.format(type(expression))) + else: + raise Exception('Ternary operation not handled {}'.format(type(expression))) From 16aa6c5ac51de59d703f9c05eeb96eac2e20e483 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 25 Oct 2018 08:22:11 +0100 Subject: [PATCH 201/308] Fix incorrect sig on function type --- slither/slithir/convert.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 3fca07f06..8e90c256c 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -283,6 +283,8 @@ def get_sig(ir): for arg in ir.arguments: if isinstance(arg, (list,)): type_arg = '{}[{}]'.format(get_type(arg[0].type), len(arg)) + elif isinstance(arg, Function): + type_arg = arg.signature_str else: type_arg = get_type(arg.type) args.append(type_arg) From a59d8126350c928e89b04833190edf709086dd20 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 25 Oct 2018 08:49:53 +0100 Subject: [PATCH 202/308] Apply early analysis of cst state variables --- slither/solc_parsing/declarations/contract.py | 6 ++++++ slither/solc_parsing/slitherSolc.py | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index 1bfb8d4b0..8efece531 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -189,6 +189,12 @@ class ContractSolc04(Contract): self._variables[var.name] = var + def analyze_constant_state_variables(self): + for var in self.variables: + if var.is_constant: + var.analyze(self) + return + def analyze_state_variables(self): for var in self.variables: var.analyze(self) diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index d0a69cc13..302b4d5c7 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -215,6 +215,9 @@ class SlitherSolc(Slither): contract.set_is_analyzed(True) def _analyze_struct_events(self, contract): + + contract.analyze_constant_state_variables() + # Struct can refer to enum, or state variables contract.analyze_structs() # Event can refer to struct @@ -236,7 +239,7 @@ class SlitherSolc(Slither): contract.analyze_content_functions() contract.set_is_analyzed(True) - + def _convert_to_slithir(self): for contract in self.contracts: for func in contract.functions + contract.modifiers: From 6de9eedbd1bb69b63fc36f081dfbc86ec99b4ef5 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 25 Oct 2018 08:59:44 +0100 Subject: [PATCH 203/308] Partial fix on loop break recovery --- slither/solc_parsing/declarations/function.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 0b9b5d935..273332511 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -216,6 +216,13 @@ class FunctionSolc(Function): else: link_nodes(node_body, node_startLoop) + if not condition: + if not loop_expression: + # TODO: fix case where loop has no expression + link_nodes(node_startLoop, node_endLoop) + else: + link_nodes(node_LoopExpression, node_endLoop) + return node_endLoop From a17e49aa0de88da95970f66a4b96c8c6a4563f83 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 25 Oct 2018 10:21:47 +0100 Subject: [PATCH 204/308] Add hidden --compact-ast cli option --- slither/__main__.py | 10 +++++++++- slither/slither.py | 8 ++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/slither/__main__.py b/slither/__main__.py index aab50cd14..fedfbdfc1 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -53,7 +53,10 @@ def process(filename, args, detector_classes, printer_classes): Returns: list(result), int: Result list and number of contracts analyzed """ - slither = Slither(filename, args.solc, args.disable_solc_warnings, args.solc_args) + ast = '--ast-json' + if args.compact_ast: + ast = '--ast-compact-json' + slither = Slither(filename, args.solc, args.disable_solc_warnings, args.solc_args, ast) return _process(slither, detector_classes, printer_classes) @@ -349,6 +352,11 @@ def parse_args(detector_classes, printer_classes): action="store_true", default=False) + parser.add_argument('--compact-ast', + help=argparse.SUPPRESS, + action='store_true', + default=False) + return parser.parse_args() diff --git a/slither/slither.py b/slither/slither.py index cb443bc86..f4475e7f4 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -17,7 +17,7 @@ logger_printer = logging.getLogger("Printers") class Slither(SlitherSolc): - def __init__(self, contract, solc='solc', disable_solc_warnings=False, solc_arguments=''): + def __init__(self, contract, solc='solc', disable_solc_warnings=False, solc_arguments='', ast_format='-ast-json'): self._detectors = [] self._printers = [] @@ -28,7 +28,7 @@ class Slither(SlitherSolc): self._parse_contracts_from_loaded_json(contract, '') # .json or .sol provided else: - contracts_json = self._run_solc(contract, solc, disable_solc_warnings, solc_arguments) + contracts_json = self._run_solc(contract, solc, disable_solc_warnings, solc_arguments, ast_format) super(Slither, self).__init__(contract) for c in contracts_json: @@ -82,7 +82,7 @@ class Slither(SlitherSolc): "You can't register {!r} twice.".format(cls) ) - def _run_solc(self, filename, solc, disable_solc_warnings, solc_arguments): + def _run_solc(self, filename, solc, disable_solc_warnings, solc_arguments, ast_format): if not os.path.isfile(filename): logger.error('{} does not exist (are you in the correct directory?)'.format(filename)) exit(-1) @@ -99,7 +99,7 @@ class Slither(SlitherSolc): logger.info('Empty AST file: %s', filename) sys.exit(-1) else: - cmd = [solc, filename, '--ast-compact-json'] + cmd = [solc, filename, ast_format] if solc_arguments: # To parse, we first split the string on each '--' solc_args = solc_arguments.split('--') From e18f9437c21e09111c5f18ea1397fa38e075405d Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 25 Oct 2018 10:25:07 +0100 Subject: [PATCH 205/308] Fix typo --- slither/slither.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/slither/slither.py b/slither/slither.py index f4475e7f4..eb9cc64d4 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -17,11 +17,10 @@ logger_printer = logging.getLogger("Printers") class Slither(SlitherSolc): - def __init__(self, contract, solc='solc', disable_solc_warnings=False, solc_arguments='', ast_format='-ast-json'): + def __init__(self, contract, solc='solc', disable_solc_warnings=False, solc_arguments='', ast_format='--ast-json'): self._detectors = [] self._printers = [] - # json text provided if isinstance(contract, dict): super(Slither, self).__init__('') From f973d8634597a0c68c0573410a428e786ad7d4b5 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 25 Oct 2018 10:25:45 +0100 Subject: [PATCH 206/308] Re-add suicidal unit test --- scripts/travis_test.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 0e4e4030a..2c914bd32 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -9,8 +9,8 @@ if [ $? -ne 1 ]; then fi # contains also the test for the suicidal detector -slither tests/backdoor.sol --disable-solc-warnings --detect-backdoor -if [ $? -ne 1 ]; then +slither tests/backdoor.sol --disable-solc-warnings --detect-backdoor --detect-suicidal +if [ $? -ne 2 ]; then exit 1 fi From b031dee943b7a5502bfae365993670ff0259f7ef Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 25 Oct 2018 10:47:08 +0100 Subject: [PATCH 207/308] Update travis to test for normal AST and compact AST --- scripts/travis_test.sh | 107 +++++++++++------------------------------ 1 file changed, 29 insertions(+), 78 deletions(-) diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 2c914bd32..6e788a30c 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -2,84 +2,35 @@ ### Test Detectors - -slither tests/uninitialized.sol --disable-solc-warnings --detect-uninitialized-state -if [ $? -ne 1 ]; then - exit 1 -fi - -# contains also the test for the suicidal detector -slither tests/backdoor.sol --disable-solc-warnings --detect-backdoor --detect-suicidal -if [ $? -ne 2 ]; then - exit 1 -fi - -slither tests/pragma.0.4.24.sol --disable-solc-warnings --detect-pragma -if [ $? -ne 1 ]; then - exit 1 -fi - -slither tests/old_solc.sol.json --solc-ast --detect-solc-version -if [ $? -ne 1 ]; then - exit 1 -fi - -slither tests/reentrancy.sol --disable-solc-warnings --detect-reentrancy -if [ $? -ne 1 ]; then - exit 1 -fi - -slither tests/uninitialized_storage_pointer.sol --disable-solc-warnings --detect-uninitialized-storage -if [ $? -ne 1 ]; then - exit 1 -fi - -slither tests/tx_origin.sol --disable-solc-warnings --detect-tx-origin -if [ $? -ne 2 ]; then - exit 1 -fi - -slither tests/unused_state.sol --detect-unused-state -if [ $? -ne 1 ]; then - exit 1 -fi - -slither tests/locked_ether.sol --detect-locked-ether -if [ $? -ne 1 ]; then - exit 1 -fi - -slither tests/arbitrary_send.sol --disable-solc-warnings --detect-arbitrary-send -if [ $? -ne 2 ]; then - exit 1 -fi - - -slither tests/inline_assembly_contract.sol --disable-solc-warnings --detect-assembly -if [ $? -ne 1 ]; then - exit 1 -fi - -slither tests/inline_assembly_library.sol --disable-solc-warnings --detect-assembly -if [ $? -ne 2 ]; then - exit 1 -fi - -slither tests/naming_convention.sol --disable-solc-warnings --detect-naming-convention -if [ $? -ne 10 ]; then - exit 1 -fi - -slither tests/low_level_calls.sol --disable-solc-warnings --detect-low-level-calls -if [ $? -ne 1 ]; then - exit 1 -fi - -slither tests/const_state_variables.sol --detect-const-candidates-state -if [ $? -ne 2 ]; then - exit 1 -fi - +# test_slither file.sol --detect-detectors number_results +test_slither(){ + slither "$1" --disable-solc-warnings "$2" + if [ $? -ne "$3" ]; then + exit 1 + fi + + slither "$1" --disable-solc-warnings "$2" --compact-ast + if [ $? -ne "$3" ]; then + exit 1 + fi +} + +test_slither tests/uninitialized.sol "--detect-uninitialized-state" 1 +test_slither tests/backdoor.sol "--detect-backdoor" 1 +test_slither tests/backdoor.sol "--detect-suicidal" 1 +test_slither tests/pragma.0.4.24.sol "--detect-pragma" 1 +test_slither tests/old_solc.sol.json "--detect-solc-version" 1 +test_slither tests/reentrancy.sol "--detect-reentrancy" 1 +test_slither tests/uninitialized_storage_pointer.sol "--detect-uninitialized-storage" 1 +test_slither tests/tx_origin.sol "--detect-tx-origin" 2 +test_slither tests/unused_state.sol "--detect-unused-state" 1 +test_slither tests/locked_ether.sol "--detect-locked-ether" 1 +test_slither tests/arbitrary_send.sol "--detect-arbitrary-send" 2 +test_slither tests/inline_assembly_contract.sol "--detect-assembly" 1 +test_slither tests/inline_assembly_library.sol "--detect-assembly" 2 +test_slither tests/naming_convention.sol "--detect-naming-convention" 10 +test_slither tests/low_level_calls.sol "--detect-low-level-calls" 1 +test_slither tests/const_state_variables.sol "--detect-const-candidates-state" 2 ### Test scripts From 5bef2bb1b02d403452a492ec2554c044face1cd6 Mon Sep 17 00:00:00 2001 From: Evgeniy Filatov Date: Tue, 23 Oct 2018 00:55:09 +0300 Subject: [PATCH 208/308] started implementation of call graph printer --- examples/printers/call_graph.sol | 25 ++++++++ examples/printers/call_graph.sol.dot | 20 ++++++ slither/__main__.py | 2 + slither/printers/call/__init__.py | 0 slither/printers/call/call_graph.py | 92 ++++++++++++++++++++++++++++ 5 files changed, 139 insertions(+) create mode 100644 examples/printers/call_graph.sol create mode 100644 examples/printers/call_graph.sol.dot create mode 100644 slither/printers/call/__init__.py create mode 100644 slither/printers/call/call_graph.py diff --git a/examples/printers/call_graph.sol b/examples/printers/call_graph.sol new file mode 100644 index 000000000..36cc7cbb5 --- /dev/null +++ b/examples/printers/call_graph.sol @@ -0,0 +1,25 @@ +contract ContractA { + function my_func_a() { + keccak256(0); + } +} + +contract ContractB { + ContractA a; + + constructor() { + a = new ContractA(); + } + + function my_func_b() { + a.my_func_a(); + my_second_func_b(); + } + + function my_func_a() { + my_second_func_b(); + } + + function my_second_func_b(){ + } +} \ No newline at end of file diff --git a/examples/printers/call_graph.sol.dot b/examples/printers/call_graph.sol.dot new file mode 100644 index 000000000..9e244e6ec --- /dev/null +++ b/examples/printers/call_graph.sol.dot @@ -0,0 +1,20 @@ +digraph { +subgraph "cluster_9_ContractA" { +label = "ContractA" +"9_my_func_a()" [label="my_func_a()"] +} +subgraph "cluster_45_ContractB" { +label = "ContractB" +"45_constructor()" [label="constructor()"] +"45_my_func_b()" [label="my_func_b()"] +"45_my_func_b()" -> "45_my_second_func_b()" +"45_my_func_a()" [label="my_func_a()"] +"45_my_func_a()" -> "45_my_second_func_b()" +"45_my_second_func_b()" [label="my_second_func_b()"] +} +subgraph cluster_solidity { +label = "[Solidity]" +"keccak256()" +} +"9_my_func_a()" -> "keccak256()" +} \ No newline at end of file diff --git a/slither/__main__.py b/slither/__main__.py index fedfbdfc1..085ef8e0f 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -157,6 +157,7 @@ def main(): from slither.printers.summary.contract import ContractSummary from slither.printers.inheritance.inheritance import PrinterInheritance from slither.printers.inheritance.inheritance_graph import PrinterInheritanceGraph + from slither.printers.call.call_graph import PrinterCallGraph from slither.printers.functions.authorization import PrinterWrittenVariablesAndAuthorization from slither.printers.summary.slithir import PrinterSlithIR @@ -164,6 +165,7 @@ def main(): ContractSummary, PrinterInheritance, PrinterInheritanceGraph, + PrinterCallGraph, PrinterWrittenVariablesAndAuthorization, PrinterSlithIR] diff --git a/slither/printers/call/__init__.py b/slither/printers/call/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/slither/printers/call/call_graph.py b/slither/printers/call/call_graph.py new file mode 100644 index 000000000..9c332f72e --- /dev/null +++ b/slither/printers/call/call_graph.py @@ -0,0 +1,92 @@ +""" + Module printing the call graph + + The call graph shows for each function, + what are the contracts/functions called. + The output is a dot file named filename.dot +""" + +from slither.printers.abstract_printer import AbstractPrinter +from slither.core.declarations.solidity_variables import SolidityFunction +from slither.core.declarations.function import Function + + +class PrinterCallGraph(AbstractPrinter): + ARGUMENT = 'call-graph' + HELP = 'the call graph' + + def __init__(self, slither, logger): + super(PrinterCallGraph, self).__init__(slither, logger) + + self.contracts = slither.contracts + + self.solidity_functions = set() + self.solidity_calls = set() + + @staticmethod + def _contract_subgraph_id(contract): + return f'"cluster_{contract.id}_{contract.name}"' + + @staticmethod + def _function_node_id(contract, function): + return f'"{contract.id}_{function.full_name}"' + + def _render_contract(self, contract): + result = f'subgraph {self._contract_subgraph_id(contract)} {{\n' + result += f'label = "{contract.name}"\n' + + for function in contract.functions: + result += self._render_internal_calls(contract, function) + + result += '}\n' + + return result + + def _render_internal_calls(self, contract, function): + result = '' + + # we need to define function nodes with unique ids, + # as it's possible that multiple contracts have same functions + result += f'{self._function_node_id(contract, function)} [label="{function.full_name}"]\n' + + for internal_call in function.internal_calls: + if isinstance(internal_call, (Function)): + result += f'{self._function_node_id(contract, function)} -> {self._function_node_id(contract, internal_call)}\n' + elif isinstance(internal_call, (SolidityFunction)): + self.solidity_functions.add(f'"{internal_call.full_name}"') + self.solidity_calls.add((self._function_node_id(contract, function), f'"{internal_call.full_name}"')) + + return result + + def _render_solidity_calls(self): + result = '' + + result = 'subgraph cluster_solidity {\n' + result += 'label = "[Solidity]"\n' + + for function in self.solidity_functions: + result += f'{function}\n' + + result += '}\n' + + for caller, callee in self.solidity_calls: + result += f'{caller} -> {callee}\n' + + return result + + def output(self, filename): + """ + Output the graph in filename + Args: + filename(string) + """ + if not filename.endswith('.dot'): + filename += ".dot" + info = 'Call Graph: ' + filename + self.info(info) + with open(filename, 'w') as f: + f.write('digraph {\n') + for contract in self.contracts: + f.write(self._render_contract(contract)) + f.write(self._render_solidity_calls()) + f.write('}') From 4a6d8f3495c50d8744fda999a98a09f4acda34c4 Mon Sep 17 00:00:00 2001 From: Evgeniy Filatov Date: Tue, 23 Oct 2018 17:10:05 +0300 Subject: [PATCH 209/308] implemented external call support, refactored code, added image example --- README.md | 1 + examples/printers/call_graph.sol.dot | 25 ++-- examples/printers/call_graph.sol.dot.png | Bin 0 -> 35587 bytes slither/printers/call/call_graph.py | 179 ++++++++++++++++------- 4 files changed, 144 insertions(+), 61 deletions(-) create mode 100644 examples/printers/call_graph.sol.dot.png diff --git a/README.md b/README.md index 03c8712b3..1f24121f2 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ If Slither is run on a directory, it will run on every `.sol` file of the direct * `--printer-quick-summary`: Print a quick summary of the contracts * `--printer-inheritance`: Print the inheritance relations * `--printer-inheritance-graph`: Print the inheritance graph in a file +* `--printer-call-graph`: Print the call graph in a file * `--printer-vars-and-auth`: Print the variables written and the check on `msg.sender` of each function ## Checks available diff --git a/examples/printers/call_graph.sol.dot b/examples/printers/call_graph.sol.dot index 9e244e6ec..a63b24b62 100644 --- a/examples/printers/call_graph.sol.dot +++ b/examples/printers/call_graph.sol.dot @@ -1,20 +1,21 @@ -digraph { -subgraph "cluster_9_ContractA" { +strict digraph { +subgraph cluster_9_ContractA { label = "ContractA" -"9_my_func_a()" [label="my_func_a()"] +"9_my_func_a" [label="my_func_a"] } -subgraph "cluster_45_ContractB" { +subgraph cluster_45_ContractB { label = "ContractB" -"45_constructor()" [label="constructor()"] -"45_my_func_b()" [label="my_func_b()"] -"45_my_func_b()" -> "45_my_second_func_b()" -"45_my_func_a()" [label="my_func_a()"] -"45_my_func_a()" -> "45_my_second_func_b()" -"45_my_second_func_b()" [label="my_second_func_b()"] +"45_my_func_a" [label="my_func_a"] +"45_my_second_func_b" [label="my_second_func_b"] +"45_my_func_b" [label="my_func_b"] +"45_constructor" [label="constructor"] +"45_my_func_b" -> "45_my_second_func_b" +"45_my_func_a" -> "45_my_second_func_b" } subgraph cluster_solidity { label = "[Solidity]" -"keccak256()" +"keccak256()" +"9_my_func_a" -> "keccak256()" } -"9_my_func_a()" -> "keccak256()" +"45_my_func_b" -> "9_my_func_a" } \ No newline at end of file diff --git a/examples/printers/call_graph.sol.dot.png b/examples/printers/call_graph.sol.dot.png new file mode 100644 index 0000000000000000000000000000000000000000..a679acbc488002e04bc3459499304d359b54b6dc GIT binary patch literal 35587 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 Vn4b9s zfc>X{0$Rc9Aw3N{Nhmgd_ds()bKbT#nwp4c?jq>Ci!s1$x*r&{`t{p4lrWHR&K>S) z*`Xe^1F%L6hmMEgdUrE2E>G02wx$cG^!fFFN1`1FZ~?j$nW34L)gcvIF%%k_e(M%$ zLtGf<)0s!{v%Q^`mLBkwL>HtU5WV)Q8;-@t&HzcT7sphM_s_Jgr&F-W4+U{pA9$)! z9GBAJD|dtm=lB^u`fJ$@)9zo{Tj__mHg*rx+KCgm~)nTX(T5l*}_-D)~YIE;cRiq89Ka67~9yc;M zM#?C1Y0&|2{R`SB6bb1(@*Cs%^9!6(n$}m{JgzI1Ljn1bOw$F#C4TqA>{G@zLyv_9 zw^YOk6-l`m=RLnVC|TeJsk3g~lEIrOro6Cvq-+5zR-sn|c5BR}5_>~iU?#HX2Tb0LJ%oE@ zKT-t0SR7{S#Z`YR4PEmpm#m2c`-V2yv>*T0zr(gYz};!c$ezGgW16nqeBviu5u~%` zaJK&r=0$QFNN=Ms8P|Jq)pT#RBgf+dzX;LN5WXgGDE*z$}2I-Iza0wAE)KbB7=hr`QGOBi1^ z%Q2KL!8c-jYSVrL()ac6C0c}{~M{|F;p&;`qJ7V zb8;T!nW&}-U$>c{F3c#btO&Y$mV_c)UHR%By zjT^A2=>WeWyq};Ihc_f`4!cb67%M1^QwZ}!SH=DWKE3PaP@6#PNGCDmWE}V(gT#C; zO1~JhHV&h;>}1lKC~BN@Mo5n=yl*@&l%HHz%)-y6P`|cp+qMy(AH=yMxq$qB5l&K{ z9OOkZ)XbNiV2273xxr0vRz;?)< z#Rmc8p>n{L#J>IdRmFaOcPe~K{~d*u11Anj>h5RStHrd#XE%Cn>^xX2-1y9seU^hY z-cERaEpt|vZ_|^KXWk6()|{lfr@&Y{uzjyl)3WQo4eLGqQnAhE71v*TPY!jR`u59} zJO8fvqoQcniV6EBjYo4c%OD^N1h=ic(QcWZ)D%2X7^^J_x> z#Yrdk@b&ENRkmp8q?0F5c2s=&bjGruqr3ZTw5oqC$zHcJa$wZDA6DcNS&tt5wP4{w zCQ6;8#RIR3Us?5%($bG>lXMy{zS5?J;yH5R^5xS}4t?}6`N0X(A2~9Z9GbbMlR`@S z?7jV(?ES{F{Eevp2M-(ex^(;ctze$qa>a;k4jn!`BYMtb_!8IZs?`LoiS$rUI{h#v zT}!RL%;sVmi|gr5=JSeLE6N*T8DYQMjKbeog~SI+WPRAevDmFakvuNO;eR@zyP=s2sN=8VDG6$t}7?mU0`Raq6T zcsX+$-*^!+`*Fol9O%!xne8vVQL@tD*^?)g#SebpU}Uu4{%mOIulwzL2TXNZwaOBO zS^35hhtv8TG^>v7+gAq5dbm;Ai$s^H1z4SXCI7RJv?jzrkaG5I`Yx@CSYnx z(>vUxtIM);a-gs0 z#*yW}yfIAbttd|(0)S@}hn1!1=0AQi*SbJa6x+e_MwDcE3dOeB|MrJGjdhCh`C{3u zf2&rv-thKso9&v4qV<+jdCubex}7Rjd9X_*@E*n4edSjwX6E^$S!6}8p$m-?ik+ "{to_node}"' + +# return dot language string to add graph node (with optional label) +def _node(node, label=None): + return ' '.join(( + f'"{node}"', + f'[label="{label}"]' if label is not None else '', + )) class PrinterCallGraph(AbstractPrinter): ARGUMENT = 'call-graph' @@ -18,61 +68,89 @@ class PrinterCallGraph(AbstractPrinter): def __init__(self, slither, logger): super(PrinterCallGraph, self).__init__(slither, logger) - self.contracts = slither.contracts - - self.solidity_functions = set() - self.solidity_calls = set() - - @staticmethod - def _contract_subgraph_id(contract): - return f'"cluster_{contract.id}_{contract.name}"' - - @staticmethod - def _function_node_id(contract, function): - return f'"{contract.id}_{function.full_name}"' + self.contract_functions = {} # contract -> contract functions nodes + self.contract_calls = {} # contract -> contract calls edges - def _render_contract(self, contract): - result = f'subgraph {self._contract_subgraph_id(contract)} {{\n' - result += f'label = "{contract.name}"\n' + self.solidity_functions = set() # solidity function nodes + self.solidity_calls = set() # solidity calls edges - for function in contract.functions: - result += self._render_internal_calls(contract, function) + self.external_calls = set() # external calls edges - result += '}\n' + self._process_contracts(slither.contracts) - return result + def _process_contracts(self, contracts): + for contract in contracts: + self.contract_functions[contract] = set() + self.contract_calls[contract] = set() - def _render_internal_calls(self, contract, function): - result = '' + for function in contract.functions: + self._process_function(contract, function) - # we need to define function nodes with unique ids, - # as it's possible that multiple contracts have same functions - result += f'{self._function_node_id(contract, function)} [label="{function.full_name}"]\n' + def _process_function(self, contract, function): + self.contract_functions[contract].add( + _node(_function_node(contract, function), function.name), + ) for internal_call in function.internal_calls: - if isinstance(internal_call, (Function)): - result += f'{self._function_node_id(contract, function)} -> {self._function_node_id(contract, internal_call)}\n' - elif isinstance(internal_call, (SolidityFunction)): - self.solidity_functions.add(f'"{internal_call.full_name}"') - self.solidity_calls.add((self._function_node_id(contract, function), f'"{internal_call.full_name}"')) - - return result + self._process_internal_call(contract, function, internal_call) + for external_call in function.external_calls: + self._process_external_call(contract, function, external_call) + + def _process_internal_call(self, contract, function, internal_call): + if isinstance(internal_call, (Function)): + self.contract_calls[contract].add(_edge( + _function_node(contract, function), + _function_node(contract, internal_call), + )) + elif isinstance(internal_call, (SolidityFunction)): + self.solidity_functions.add( + _node(_solidity_function_node(internal_call)), + ) + self.solidity_calls.add(_edge( + _function_node(contract, function), + _solidity_function_node(internal_call), + )) + + def _process_external_call(self, contract, function, external_call): + if isinstance(external_call.called, MemberAccess): + try: + self.external_calls.add(_edge( + _function_node(contract, function), + _external_function_node(external_call.called), + )) + except TypeError: + # cannot visualize call if we cannot get external contract + pass + + def _render_internal_calls(self): + lines = [] + + for contract in self.contract_functions: + lines.append(f'subgraph {_contract_subgraph(contract)} {{') + lines.append(f'label = "{contract.name}"') + + lines.extend(self.contract_functions[contract]) + lines.extend(self.contract_calls[contract]) + + lines.append('}') + + return '\n'.join(lines) def _render_solidity_calls(self): - result = '' + lines = [] - result = 'subgraph cluster_solidity {\n' - result += 'label = "[Solidity]"\n' + lines.append('subgraph cluster_solidity {') + lines.append('label = "[Solidity]"') - for function in self.solidity_functions: - result += f'{function}\n' + lines.extend(self.solidity_functions) + lines.extend(self.solidity_calls) - result += '}\n' + lines.append('}') - for caller, callee in self.solidity_calls: - result += f'{caller} -> {callee}\n' + return '\n'.join(lines) - return result + def _render_external_calls(self): + return '\n'.join(self.external_calls) def output(self, filename): """ @@ -81,12 +159,15 @@ class PrinterCallGraph(AbstractPrinter): filename(string) """ if not filename.endswith('.dot'): - filename += ".dot" - info = 'Call Graph: ' + filename - self.info(info) + filename += '.dot' + + self.info(f'Call Graph: {filename}') + with open(filename, 'w') as f: - f.write('digraph {\n') - for contract in self.contracts: - f.write(self._render_contract(contract)) - f.write(self._render_solidity_calls()) - f.write('}') + f.write('\n'.join([ + 'strict digraph {', + self._render_internal_calls(), + self._render_solidity_calls(), + self._render_external_calls(), + '}', + ])) From d50b0f01f3159ee42c9c264b8799f23bd341fdfc Mon Sep 17 00:00:00 2001 From: Evgeniy Filatov Date: Thu, 25 Oct 2018 15:17:13 +0300 Subject: [PATCH 210/308] improved support for external calls --- examples/printers/call_graph.sol | 9 +++++ examples/printers/call_graph.sol.dot | 29 +++++++++------ slither/printers/call/call_graph.py | 54 ++++++++++------------------ 3 files changed, 45 insertions(+), 47 deletions(-) diff --git a/examples/printers/call_graph.sol b/examples/printers/call_graph.sol index 36cc7cbb5..182ccbf52 100644 --- a/examples/printers/call_graph.sol +++ b/examples/printers/call_graph.sol @@ -1,6 +1,14 @@ +library Library { + function library_func() { + } +} + contract ContractA { + uint256 public val = 0; + function my_func_a() { keccak256(0); + Library.library_func(); } } @@ -21,5 +29,6 @@ contract ContractB { } function my_second_func_b(){ + a.val(); } } \ No newline at end of file diff --git a/examples/printers/call_graph.sol.dot b/examples/printers/call_graph.sol.dot index a63b24b62..5677b7a40 100644 --- a/examples/printers/call_graph.sol.dot +++ b/examples/printers/call_graph.sol.dot @@ -1,21 +1,28 @@ strict digraph { -subgraph cluster_9_ContractA { +subgraph cluster_5_Library { +label = "Library" +"5_library_func" [label="library_func"] +} +subgraph cluster_22_ContractA { label = "ContractA" -"9_my_func_a" [label="my_func_a"] +"22_my_func_a" [label="my_func_a"] +"22_val" [label="val"] } -subgraph cluster_45_ContractB { +subgraph cluster_63_ContractB { label = "ContractB" -"45_my_func_a" [label="my_func_a"] -"45_my_second_func_b" [label="my_second_func_b"] -"45_my_func_b" [label="my_func_b"] -"45_constructor" [label="constructor"] -"45_my_func_b" -> "45_my_second_func_b" -"45_my_func_a" -> "45_my_second_func_b" +"63_my_second_func_b" [label="my_second_func_b"] +"63_my_func_a" [label="my_func_a"] +"63_constructor" [label="constructor"] +"63_my_func_b" [label="my_func_b"] +"63_my_func_b" -> "63_my_second_func_b" +"63_my_func_a" -> "63_my_second_func_b" } subgraph cluster_solidity { label = "[Solidity]" "keccak256()" -"9_my_func_a" -> "keccak256()" +"22_my_func_a" -> "keccak256()" } -"45_my_func_b" -> "9_my_func_a" +"22_my_func_a" -> "5_library_func" +"63_my_func_b" -> "22_my_func_a" +"63_my_second_func_b" -> "22_val" } \ No newline at end of file diff --git a/slither/printers/call/call_graph.py b/slither/printers/call/call_graph.py index 6fed79c2c..18058c6c6 100644 --- a/slither/printers/call/call_graph.py +++ b/slither/printers/call/call_graph.py @@ -27,29 +27,6 @@ def _function_node(contract, function): def _solidity_function_node(solidity_function): return f'{solidity_function.name}' -# return node for externally called function -def _external_function_node(member_access): - # we have external function name, to get Contract we need to - # check that expression is variable and is of Contract type - expression = member_access.expression - if not isinstance(expression, (Identifier)): - raise TypeError - - value = expression.value - if not isinstance(value, (Variable)): - raise TypeError - - value_type = value.type - if not isinstance(value_type, (UserDefinedType)): - raise TypeError - - contract = value_type.type - if not isinstance(contract, (Contract)): - raise TypeError - - # at this point we have contract instance and function name - return f'{contract.id}_{member_access.member_name}' - # return dot language string to add graph edge def _edge(from_node, to_node): return f'"{from_node}" -> "{to_node}"' @@ -71,6 +48,10 @@ class PrinterCallGraph(AbstractPrinter): self.contract_functions = {} # contract -> contract functions nodes self.contract_calls = {} # contract -> contract calls edges + for contract in slither.contracts: + self.contract_functions[contract] = set() + self.contract_calls[contract] = set() + self.solidity_functions = set() # solidity function nodes self.solidity_calls = set() # solidity calls edges @@ -80,9 +61,6 @@ class PrinterCallGraph(AbstractPrinter): def _process_contracts(self, contracts): for contract in contracts: - self.contract_functions[contract] = set() - self.contract_calls[contract] = set() - for function in contract.functions: self._process_function(contract, function) @@ -93,7 +71,7 @@ class PrinterCallGraph(AbstractPrinter): for internal_call in function.internal_calls: self._process_internal_call(contract, function, internal_call) - for external_call in function.external_calls: + for external_call in function.high_level_calls: self._process_external_call(contract, function, external_call) def _process_internal_call(self, contract, function, internal_call): @@ -112,15 +90,19 @@ class PrinterCallGraph(AbstractPrinter): )) def _process_external_call(self, contract, function, external_call): - if isinstance(external_call.called, MemberAccess): - try: - self.external_calls.add(_edge( - _function_node(contract, function), - _external_function_node(external_call.called), - )) - except TypeError: - # cannot visualize call if we cannot get external contract - pass + external_contract, external_function = external_call + + # add variable as node to respective contract + if isinstance(external_function, (Variable)): + self.contract_functions[external_contract].add(_node( + _function_node(external_contract, external_function), + external_function.name + )) + + self.external_calls.add(_edge( + _function_node(contract, function), + _function_node(external_contract, external_function), + )) def _render_internal_calls(self): lines = [] From 21ff89870cd88f4947828b453d2d2ca3946061cc Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 25 Oct 2018 13:59:47 +0100 Subject: [PATCH 211/308] NamingConvention: Check if parameters are used and if state variables are private --- scripts/travis_test.sh | 2 +- .../naming_convention/naming_convention.py | 19 +++++++++++++++---- tests/naming_convention.sol | 8 ++++++++ 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 2539836d8..7a6c3e5db 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -65,7 +65,7 @@ if [ $? -ne 2 ]; then fi slither tests/naming_convention.sol --disable-solc-warnings --detect-naming-convention -if [ $? -ne 10 ]; then +if [ $? -ne 12 ]; then exit 1 fi diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index 74bf8acb6..5cda7916f 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -9,7 +9,7 @@ class NamingConvention(AbstractDetector): Exceptions: - Allow constant variables name/symbol/decimals to be lowercase (ERC20) - - Allow '_' at the beggining of the mixed_case match, to represent private variable and unused parameters + - Allow '_' at the beggining of the mixed_case match for private variables and unused parameters """ ARGUMENT = 'naming-convention' @@ -23,6 +23,10 @@ class NamingConvention(AbstractDetector): @staticmethod def is_mixed_case(name): + return re.search('^[a-z]([A-Za-z0-9]+)?_?$', name) is not None + + @staticmethod + def is_mixed_case_with_underscore(name): # Allow _ at the beginning to represent private variable # or unused parameters return re.search('^[a-z_]([A-Za-z0-9]+)?_?$', name) is not None @@ -86,8 +90,11 @@ class NamingConvention(AbstractDetector): 'sourceMapping': func.source_mapping}) for argument in func.parameters: - - if self.is_mixed_case(argument.name) is False: + if argument in func.variables_read_or_written: + incorrect_naming = self.is_mixed_case(argument.name) is False + else: + incorrect_naming = self.is_mixed_case_with_underscore(argument.name) is False + if incorrect_naming: info = "Parameter '{}' is not in mixedCase, Contract: '{}', Function: '{}'' " \ .format(argument.name, argument.name, contract.name) self.log(info) @@ -129,7 +136,11 @@ class NamingConvention(AbstractDetector): 'constant': var.name, 'sourceMapping': var.source_mapping}) else: - if self.is_mixed_case(var.name) is False: + if var.visibility == 'private': + incorrect_naming = self.is_mixed_case_with_underscore(var.name) is False + else: + incorrect_naming = self.is_mixed_case(var.name) is False + if incorrect_naming: info = "Variable '{}' is not in mixedCase, Contract: '{}' ".format(var.name, contract.name) self.log(info) diff --git a/tests/naming_convention.sol b/tests/naming_convention.sol index 769739052..48c8b3e44 100644 --- a/tests/naming_convention.sol +++ b/tests/naming_convention.sol @@ -52,6 +52,14 @@ contract Test { } contract T { + uint private _myPrivateVar; + uint _myPublicVar; + + + function test(uint _unused, uint _used){ + _used;} + + uint k = 1; uint constant M = 1; From 16e20508c03057dc4996077eec6f840b7c4863ea Mon Sep 17 00:00:00 2001 From: Cryptomental Date: Thu, 25 Oct 2018 18:34:34 +0200 Subject: [PATCH 212/308] detectors: Use new API for state vars written in uninitialized state detector. Refs: https://github.com/trailofbits/slither/issues/30 --- .../variables/uninitialized_state_variables.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index e24fdf298..152cbbead 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -33,17 +33,9 @@ class UninitializedStateVarsDetection(AbstractDetector): ret = [] for f in contract.all_functions_called + contract.modifiers: for n in f.nodes: + ret += n.state_variables_written for ir in n.irs: - if isinstance(ir, (Index, Member)): - continue # Don't consider Member and Index operations -> ReferenceVariable - elif isinstance(ir, OperationWithLValue) and isinstance(ir.lvalue, StateVariable): - ret.append(ir.lvalue) - elif isinstance(ir, Assignment) and isinstance(ir.lvalue, ReferenceVariable): - dest = ir.lvalue - while isinstance(dest, ReferenceVariable): - dest = dest.points_to - ret.append(dest) - elif isinstance(ir, LibraryCall) \ + if isinstance(ir, LibraryCall) \ or isinstance(ir, InternalCall) \ or isinstance(ir, InternalDynamicCall): for v in ir.arguments: From 6afb9101e96ef25661b47b995188c03d15820cce Mon Sep 17 00:00:00 2001 From: Cryptomental Date: Thu, 25 Oct 2018 18:42:40 +0200 Subject: [PATCH 213/308] travis_test: Increase detect unitialized state findings to three. The test case was extended with new edge cases. Refs: https://github.com/trailofbits/slither/issues/30 --- scripts/travis_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 6e788a30c..2fb3971dc 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -15,7 +15,7 @@ test_slither(){ fi } -test_slither tests/uninitialized.sol "--detect-uninitialized-state" 1 +test_slither tests/uninitialized.sol "--detect-uninitialized-state" 3 test_slither tests/backdoor.sol "--detect-backdoor" 1 test_slither tests/backdoor.sol "--detect-suicidal" 1 test_slither tests/pragma.0.4.24.sol "--detect-pragma" 1 From f6ed3ba68d1a152c5c0e9172b631157308b80464 Mon Sep 17 00:00:00 2001 From: redshark1802 Date: Mon, 22 Oct 2018 21:43:17 +0200 Subject: [PATCH 214/308] cli improvements - help is printed when no arguments are supplied - rework detector arguments: detectors are now a comma-separated list, defaults to all - rework printer arguments: printers are now a comma-separated list, defaults to contract-summary - add version command - add --list-detectors and --list-printers - update README --- README.md | 51 +++++++------ scripts/travis_test.sh | 30 ++++---- slither/__main__.py | 167 +++++++++++++++++++++++++++-------------- 3 files changed, 156 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 03c8712b3..dd26c2e98 100644 --- a/README.md +++ b/README.md @@ -36,32 +36,39 @@ If Slither is run on a directory, it will run on every `.sol` file of the direct ### Printers -* `--printer-summary`: Print a summary of the contracts -* `--printer-quick-summary`: Print a quick summary of the contracts -* `--printer-inheritance`: Print the inheritance relations -* `--printer-inheritance-graph`: Print the inheritance graph in a file -* `--printer-vars-and-auth`: Print the variables written and the check on `msg.sender` of each function +By default, the `contract-summary` printer is used. Use --printers comma-separated list of printers, +or `none` to disable the default printer. -## Checks available +Num | Printer | Description +--- | --- | --- +1 | `contract-summary` | a summary of the contract +2 | `function-summary` | the summary of the functions +3 | `inheritance` | the inheritance relation between contracts +4 | `inheritance-graph` | the inheritance graph +5 | `slithir` | the slithIR +6 | `vars-and-auth` | the state variables written and the authorization of the functions -By default, all the checks are run. Use --detect-_name-of-check_ to run one check at a time. +## Detectors -Num | Check | What it Detects | Impact | Confidence +By default, all the detectors are run. Use --detectors comma-separated list of detectors to run. + +Num | Detector | What it Detects | Impact | Confidence --- | --- | --- | --- | --- -1 | `suicidal` | Suicidal functions | High | High -2 | `uninitialized-state` | Uninitialized state variables | High | High -3 | `uninitialized-storage` | Uninitialized storage variables | High | High -4 | `arbitrary-send` | Functions that send ether to an arbitrary destination | High | Medium -5 | `reentrancy` | Reentrancy vulnerabilities | High | Medium -6 | `locked-ether` | Contracts that lock ether | Medium | High -7 | `tx-origin` | Dangerous usage of `tx.origin` | Medium | Medium -8 | `assembly` | Assembly usage | Informational | High -9 | `const-candidates-state` | State variables that could be declared constant | Informational | High -10 | `low-level-calls` | Low level calls | Informational | High -11 | `naming-convention` | Conformance to Solidity naming conventions | Informational | High -12 | `pragma` | If different pragma directives are used | Informational | High -13 | `solc-version` | If an old version of Solidity used (<0.4.23) | Informational | High -14 | `unused-state` | Unused state variables | Informational | High +1 | `backdoor` | Function named backdoor (detector example) | High | High +2 | `suicidal` | Suicidal functions | High | High +3 | `uninitialized-state` | Uninitialized state variables | High | High +4 | `uninitialized-storage` | Uninitialized storage variables | High | High +5 | `arbitrary-send` | Functions that send ether to an arbitrary destination | High | Medium +6 | `reentrancy` | Reentrancy vulnerabilities | High | Medium +7 | `locked-ether` | Contracts that lock ether | Medium | High +8 | `tx-origin` | Dangerous usage of `tx.origin` | Medium | Medium +9 | `assembly` | Assembly usage | Informational | High +10 | `const-candidates-state` | State variables that could be declared constant | Informational | High +11 | `low-level-calls` | Low level calls | Informational | High +12 | `naming-convention` | Conformance to Solidity naming conventions | Informational | High +13 | `pragma` | If different pragma directives are used | Informational | High +14 | `solc-version` | If an old version of Solidity used (<0.4.23) | Informational | High +15 | `unused-state` | Unused state variables | Informational | High [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 0e4e4030a..0a8db8644 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -3,79 +3,79 @@ ### Test Detectors -slither tests/uninitialized.sol --disable-solc-warnings --detect-uninitialized-state +slither tests/uninitialized.sol --disable-solc-warnings --detectors uninitialized-state if [ $? -ne 1 ]; then exit 1 fi # contains also the test for the suicidal detector -slither tests/backdoor.sol --disable-solc-warnings --detect-backdoor +slither tests/backdoor.sol --disable-solc-warnings --detectors backdoor if [ $? -ne 1 ]; then exit 1 fi -slither tests/pragma.0.4.24.sol --disable-solc-warnings --detect-pragma +slither tests/pragma.0.4.24.sol --disable-solc-warnings --detectors pragma if [ $? -ne 1 ]; then exit 1 fi -slither tests/old_solc.sol.json --solc-ast --detect-solc-version +slither tests/old_solc.sol.json --solc-ast --detectors solc-version if [ $? -ne 1 ]; then exit 1 fi -slither tests/reentrancy.sol --disable-solc-warnings --detect-reentrancy +slither tests/reentrancy.sol --disable-solc-warnings --detectors reentrancy if [ $? -ne 1 ]; then exit 1 fi -slither tests/uninitialized_storage_pointer.sol --disable-solc-warnings --detect-uninitialized-storage +slither tests/uninitialized_storage_pointer.sol --disable-solc-warnings --detectors uninitialized-storage if [ $? -ne 1 ]; then exit 1 fi -slither tests/tx_origin.sol --disable-solc-warnings --detect-tx-origin +slither tests/tx_origin.sol --disable-solc-warnings --detectors tx-origin if [ $? -ne 2 ]; then exit 1 fi -slither tests/unused_state.sol --detect-unused-state +slither tests/unused_state.sol --detectors unused-state if [ $? -ne 1 ]; then exit 1 fi -slither tests/locked_ether.sol --detect-locked-ether +slither tests/locked_ether.sol --detectors locked-ether if [ $? -ne 1 ]; then exit 1 fi -slither tests/arbitrary_send.sol --disable-solc-warnings --detect-arbitrary-send +slither tests/arbitrary_send.sol --disable-solc-warnings --detectors arbitrary-send if [ $? -ne 2 ]; then exit 1 fi -slither tests/inline_assembly_contract.sol --disable-solc-warnings --detect-assembly +slither tests/inline_assembly_contract.sol --disable-solc-warnings --detectors assembly if [ $? -ne 1 ]; then exit 1 fi -slither tests/inline_assembly_library.sol --disable-solc-warnings --detect-assembly +slither tests/inline_assembly_library.sol --disable-solc-warnings --detectors assembly if [ $? -ne 2 ]; then exit 1 fi -slither tests/naming_convention.sol --disable-solc-warnings --detect-naming-convention +slither tests/naming_convention.sol --disable-solc-warnings --detectors naming-convention if [ $? -ne 10 ]; then exit 1 fi -slither tests/low_level_calls.sol --disable-solc-warnings --detect-low-level-calls +slither tests/low_level_calls.sol --disable-solc-warnings --detectors low-level-calls if [ $? -ne 1 ]; then exit 1 fi -slither tests/const_state_variables.sol --detect-const-candidates-state +slither tests/const_state_variables.sol --detectors const-candidates-state if [ $? -ne 2 ]; then exit 1 fi diff --git a/slither/__main__.py b/slither/__main__.py index 9c59f0205..72d83a331 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -8,7 +8,7 @@ import os import sys import traceback -from pkg_resources import iter_entry_points +from pkg_resources import iter_entry_points, require from slither.detectors.abstract_detector import (AbstractDetector, DetectorClassification, @@ -19,7 +19,8 @@ from slither.slither import Slither logging.basicConfig() logger = logging.getLogger("Slither") -def output_to_markdown(detector_classes): + +def output_detectors(detector_classes): """ Pretty print of the detectors to README.md """ @@ -27,8 +28,6 @@ def output_to_markdown(detector_classes): for detector in detector_classes: argument = detector.ARGUMENT # dont show the backdoor example - if argument == 'backdoor': - continue help_info = detector.HELP impact = detector.IMPACT confidence = classification_txt[detector.CONFIDENCE] @@ -43,7 +42,27 @@ def output_to_markdown(detector_classes): help_info, classification_txt[impact], confidence)) - idx = idx +1 + idx = idx + 1 + + +def output_printers(printer_classes): + """ + Pretty print of the printers to README.md + """ + printers_list = [] + for printer in printer_classes: + argument = printer.ARGUMENT + help_info = printer.HELP + printers_list.append((argument, help_info)) + + print(printers_list) + # Sort by name + printers_list = sorted(printers_list, key=lambda element: (element[0])) + idx = 1 + for (argument, help_info) in printers_list: + print('{} | `{}` | {} '.format(idx, argument, help_info)) + idx = idx + 1 + def process(filename, args, detector_classes, printer_classes): """ @@ -64,15 +83,13 @@ def process(filename, args, detector_classes, printer_classes): results = [] - if printer_classes: - slither.run_printers() # Currently printers does not return results + detector_results = slither.run_detectors() + detector_results = [x for x in detector_results if x] # remove empty results + detector_results = [item for sublist in detector_results for item in sublist] # flatten - elif detector_classes: - detector_results = slither.run_detectors() - detector_results = [x for x in detector_results if x] # remove empty results - detector_results = [item for sublist in detector_results for item in sublist] # flatten + results.extend(detector_results) - results.extend(detector_results) + slither.run_printers() # Currently printers does not return results return results, analyzed_contracts_count @@ -164,8 +181,12 @@ def main_impl(all_detector_classes, all_printer_classes): """ args = parse_args(all_detector_classes, all_printer_classes) - if args.markdown: - output_to_markdown(all_detector_classes) + if args.list_detectors: + output_detectors(all_detector_classes) + return + + if args.list_printers: + output_printers(all_printer_classes) return detector_classes = choose_detectors(args, all_detector_classes) @@ -205,9 +226,6 @@ def main_impl(all_detector_classes, all_printer_classes): (results_tmp, number_contracts_tmp) = process(filename, args, detector_classes, printer_classes) number_contracts += number_contracts_tmp results += results_tmp - # if args.json: - # output_json(results, args.json) - # exit(results) else: raise Exception("Unrecognised file/dir path: '#{filename}'".format(filename=filename)) @@ -229,17 +247,48 @@ def main_impl(all_detector_classes, all_printer_classes): def parse_args(detector_classes, printer_classes): parser = argparse.ArgumentParser(description='Slither', - usage="slither.py contract.sol [flag]", - formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=35)) + usage="slither.py contract.sol [flag]") parser.add_argument('filename', help='contract.sol file') + parser.add_argument('--version', + help='displays the current version', + version=require('slither-analyzer')[0].version, + action='version') + parser.add_argument('--solc', help='solc path', action='store', default='solc') + parser.add_argument('--detectors', + help='Comma-separated list of detectors, defaults to all, ' + 'available detectors: {}'.format(', '.join(d.ARGUMENT for d in detector_classes)), + action='store', + dest='detectors_to_run', + default='all') + + parser.add_argument('--printers', + help='Comma-separated list fo contract information printers, ' + 'defaults to contract-summary and can be disabled by using \'none\', ' + 'available printers: {}'.format(', '.join(d.ARGUMENT for d in printer_classes)), + action='store', + dest='printers_to_run', + default='contract-summary') + + parser.add_argument('--output', + help='Define output encoding', + action='store', + choices=['stdout', 'json'], + default='stdout') + + parser.add_argument('--exclude-detectors', + help='Comma-separated list of detectors that should be excluded', + action='store', + dest='detectors_to_exclude', + default='') + parser.add_argument('--solc-args', help='Add custom solc arguments. Example: --solc-args "--allow-path /tmp --evm-version byzantium".', action='store', @@ -280,34 +329,6 @@ def parse_args(detector_classes, printer_classes): action='store_true', default=False) - for detector_cls in detector_classes: - detector_arg = '--detect-{}'.format(detector_cls.ARGUMENT) - detector_help = '{}'.format(detector_cls.HELP) - parser.add_argument(detector_arg, - help=detector_help, - action="append_const", - dest="detectors_to_run", - const=detector_cls.ARGUMENT) - - # Second loop so that the --exclude are shown after all the detectors - for detector_cls in detector_classes: - exclude_detector_arg = '--exclude-{}'.format(detector_cls.ARGUMENT) - exclude_detector_help = 'Exclude {} detector'.format(detector_cls.ARGUMENT) - parser.add_argument(exclude_detector_arg, - help=exclude_detector_help, - action="append_const", - dest="detectors_to_exclude", - const=detector_cls.ARGUMENT) - - for printer_cls in printer_classes: - printer_arg = '--printer-{}'.format(printer_cls.ARGUMENT) - printer_help = 'Print {}'.format(printer_cls.HELP) - parser.add_argument(printer_arg, - help=printer_help, - action="append_const", - dest="printers_to_run", - const=printer_cls.ARGUMENT) - # debugger command parser.add_argument('--debug', help=argparse.SUPPRESS, @@ -319,15 +340,44 @@ def parse_args(detector_classes, printer_classes): action="store_true", default=False) - return parser.parse_args() + parser.add_argument('--list-detectors', + help='List available detectors', + action='store_true', + default=False) + + parser.add_argument('--list-printers', + help='List available printers', + action='store_true', + default=False) + + if len(sys.argv) == 1: + parser.print_help(sys.stderr) + sys.exit(1) + + args = parser.parse_args() + + return args def choose_detectors(args, all_detector_classes): # If detectors are specified, run only these ones - if args.detectors_to_run: - return [d for d in all_detector_classes if d.ARGUMENT in args.detectors_to_run] - detectors_to_run = all_detector_classes + detectors_to_run = [] + detectors = {d.ARGUMENT: d for d in all_detector_classes} + + if args.detectors_to_run == 'all': + detectors_to_run = all_detector_classes + detectors_excluded = args.detectors_to_exclude.split(',') + for d in detectors: + if d in detectors_excluded: + detectors_to_run.remove(detectors[d]) + else: + for d in args.detectors_to_run.split(','): + if d in detectors: + detectors_to_run.append(detectors[d]) + else: + raise Exception('Error: {} is not a detector'.format(d)) + return detectors_to_run if args.exclude_informational: detectors_to_run = [d for d in detectors_to_run if @@ -348,11 +398,18 @@ def choose_detectors(args, all_detector_classes): def choose_printers(args, all_printer_classes): - # by default, dont run any printer printers_to_run = [] - if args.printers_to_run: - printers_to_run = [p for p in all_printer_classes if - p.ARGUMENT in args.printers_to_run] + + # disable default printer + if args.printers_to_run == 'none': + return printers_to_run + + printers = {p.ARGUMENT: p for p in all_printer_classes} + for p in args.printers_to_run.split(','): + if p in printers: + printers_to_run.append(printers[p]) + else: + raise Exception('Error: {} is not a printer'.format(p)) return printers_to_run From d3aa4e069b9b10635a9377d9350863de5b72355c Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 26 Oct 2018 10:28:50 +0100 Subject: [PATCH 215/308] - Group commands - Use PrettyTable for list-detectors/list-printers - Remove default printer - Remove --output --- slither/__main__.py | 257 ++++++++++++++++------------------ slither/utils/command_line.py | 67 +++++++++ 2 files changed, 187 insertions(+), 137 deletions(-) create mode 100644 slither/utils/command_line.py diff --git a/slither/__main__.py b/slither/__main__.py index 72d83a331..b2b075a35 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -11,59 +11,15 @@ import traceback from pkg_resources import iter_entry_points, require from slither.detectors.abstract_detector import (AbstractDetector, - DetectorClassification, - classification_txt) + DetectorClassification) from slither.printers.abstract_printer import AbstractPrinter from slither.slither import Slither +from slither.utils.command_line import output_to_markdown, output_detectors, output_printers logging.basicConfig() logger = logging.getLogger("Slither") -def output_detectors(detector_classes): - """ - Pretty print of the detectors to README.md - """ - detectors_list = [] - for detector in detector_classes: - argument = detector.ARGUMENT - # dont show the backdoor example - help_info = detector.HELP - impact = detector.IMPACT - confidence = classification_txt[detector.CONFIDENCE] - detectors_list.append((argument, help_info, impact, confidence)) - - # Sort by impact, confidence, and name - detectors_list = sorted(detectors_list, key=lambda element: (element[2], element[3], element[0])) - idx = 1 - for (argument, help_info, impact, confidence) in detectors_list: - print('{} | `{}` | {} | {} | {}'.format(idx, - argument, - help_info, - classification_txt[impact], - confidence)) - idx = idx + 1 - - -def output_printers(printer_classes): - """ - Pretty print of the printers to README.md - """ - printers_list = [] - for printer in printer_classes: - argument = printer.ARGUMENT - help_info = printer.HELP - printers_list.append((argument, help_info)) - - print(printers_list) - # Sort by name - printers_list = sorted(printers_list, key=lambda element: (element[0])) - idx = 1 - for (argument, help_info) in printers_list: - print('{} | `{}` | {} '.format(idx, argument, help_info)) - idx = idx + 1 - - def process(filename, args, detector_classes, printer_classes): """ The core high-level code for running Slither static analysis. @@ -105,7 +61,7 @@ def exit(results): sys.exit(len(results)) -def main(): +def get_detectors_and_printers(): """ NOTE: This contains just a few detectors and printers that we made public. """ @@ -171,6 +127,11 @@ def main(): detectors += list(plugin_detectors) printers += list(plugin_printers) + return detectors, printers + +def main(): + detectors, printers = get_detectors_and_printers() + main_impl(all_detector_classes=detectors, all_printer_classes=printers) @@ -181,16 +142,11 @@ def main_impl(all_detector_classes, all_printer_classes): """ args = parse_args(all_detector_classes, all_printer_classes) - if args.list_detectors: - output_detectors(all_detector_classes) - return - - if args.list_printers: - output_printers(all_printer_classes) - return - - detector_classes = choose_detectors(args, all_detector_classes) printer_classes = choose_printers(args, all_printer_classes) + if printer_classes: + detector_classes = [] + else: + detector_classes = choose_detectors(args, all_detector_classes) default_log = logging.INFO if not args.debug else logging.DEBUG @@ -250,84 +206,100 @@ def parse_args(detector_classes, printer_classes): usage="slither.py contract.sol [flag]") parser.add_argument('filename', - help='contract.sol file') + help='contract.sol') parser.add_argument('--version', help='displays the current version', version=require('slither-analyzer')[0].version, action='version') - parser.add_argument('--solc', - help='solc path', - action='store', - default='solc') - - parser.add_argument('--detectors', - help='Comma-separated list of detectors, defaults to all, ' - 'available detectors: {}'.format(', '.join(d.ARGUMENT for d in detector_classes)), - action='store', - dest='detectors_to_run', - default='all') - - parser.add_argument('--printers', - help='Comma-separated list fo contract information printers, ' - 'defaults to contract-summary and can be disabled by using \'none\', ' - 'available printers: {}'.format(', '.join(d.ARGUMENT for d in printer_classes)), - action='store', - dest='printers_to_run', - default='contract-summary') - - parser.add_argument('--output', - help='Define output encoding', - action='store', - choices=['stdout', 'json'], - default='stdout') - - parser.add_argument('--exclude-detectors', - help='Comma-separated list of detectors that should be excluded', - action='store', - dest='detectors_to_exclude', - default='') - - parser.add_argument('--solc-args', - help='Add custom solc arguments. Example: --solc-args "--allow-path /tmp --evm-version byzantium".', - action='store', - default=None) - - parser.add_argument('--disable-solc-warnings', - help='Disable solc warnings', - action='store_true', - default=False) + group_detector = parser.add_argument_group('Detectors') + group_printer = parser.add_argument_group('Printers') + group_solc = parser.add_argument_group('Solc options') + group_misc = parser.add_argument_group('Additional option') + + group_detector.add_argument('--detectors', + help='Comma-separated list of detectors, defaults to all, ' + 'available detectors: {}'.format( + ', '.join(d.ARGUMENT for d in detector_classes)), + action='store', + dest='detectors_to_run', + default='all') + + group_printer.add_argument('--printers', + help='Comma-separated list fo contract information printers, ' + 'available printers: {}'.format( + ', '.join(d.ARGUMENT for d in printer_classes)), + action='store', + dest='printers_to_run', + default='') + + group_detector.add_argument('--list-detectors', + help='List available detectors', + action=ListDetectors, + nargs=0, + default=False) + + group_printer.add_argument('--list-printers', + help='List available printers', + action=ListPrinters, + nargs=0, + default=False) + + + group_detector.add_argument('--exclude-detectors', + help='Comma-separated list of detectors that should be excluded', + action='store', + dest='detectors_to_exclude', + default='') + + group_detector.add_argument('--exclude-informational', + help='Exclude informational impact analyses', + action='store_true', + default=False) + + group_detector.add_argument('--exclude-low', + help='Exclude low impact analyses', + action='store_true', + default=False) + + group_detector.add_argument('--exclude-medium', + help='Exclude medium impact analyses', + action='store_true', + default=False) + + group_detector.add_argument('--exclude-high', + help='Exclude high impact analyses', + action='store_true', + default=False) + + + group_solc.add_argument('--solc', + help='solc path', + action='store', + default='solc') + + group_solc.add_argument('--solc-args', + help='Add custom solc arguments. Example: --solc-args "--allow-path /tmp --evm-version byzantium".', + action='store', + default=None) + + group_solc.add_argument('--disable-solc-warnings', + help='Disable solc warnings', + action='store_true', + default=False) + + group_solc.add_argument('--solc-ast', + help='Provide the ast solc file', + action='store_true', + default=False) + + group_misc.add_argument('--json', + help='Export results as JSON', + action='store', + default=None) - parser.add_argument('--solc-ast', - help='Provide the ast solc file', - action='store_true', - default=False) - parser.add_argument('--json', - help='Export results as JSON', - action='store', - default=None) - - parser.add_argument('--exclude-informational', - help='Exclude informational impact analyses', - action='store_true', - default=False) - - parser.add_argument('--exclude-low', - help='Exclude low impact analyses', - action='store_true', - default=False) - - parser.add_argument('--exclude-medium', - help='Exclude medium impact analyses', - action='store_true', - default=False) - - parser.add_argument('--exclude-high', - help='Exclude high impact analyses', - action='store_true', - default=False) # debugger command parser.add_argument('--debug', @@ -337,18 +309,10 @@ def parse_args(detector_classes, printer_classes): parser.add_argument('--markdown', help=argparse.SUPPRESS, - action="store_true", + action=OutputMarkdown, default=False) - parser.add_argument('--list-detectors', - help='List available detectors', - action='store_true', - default=False) - parser.add_argument('--list-printers', - help='List available printers', - action='store_true', - default=False) if len(sys.argv) == 1: parser.print_help(sys.stderr) @@ -358,6 +322,25 @@ def parse_args(detector_classes, printer_classes): return args +class ListDetectors(argparse.Action): + def __call__(self, parser, *args, **kwargs): + detectors, _ = get_detectors_and_printers() + output_detectors(detectors) + parser.exit() + +class ListPrinters(argparse.Action): + def __call__(self, parser, *args, **kwargs): + _, printers = get_detectors_and_printers() + output_printers(printers) + parser.exit() + +class OutputMarkdown(argparse.Action): + def __call__(self, parser, *args, **kwargs): + detectors, _ = get_detectors_and_printers() + output_to_markdown(detectors) + parser.exit() + + def choose_detectors(args, all_detector_classes): # If detectors are specified, run only these ones @@ -401,8 +384,8 @@ def choose_printers(args, all_printer_classes): printers_to_run = [] # disable default printer - if args.printers_to_run == 'none': - return printers_to_run + if args.printers_to_run == '': + return [] printers = {p.ARGUMENT: p for p in all_printer_classes} for p in args.printers_to_run.split(','): diff --git a/slither/utils/command_line.py b/slither/utils/command_line.py new file mode 100644 index 000000000..90a933fc3 --- /dev/null +++ b/slither/utils/command_line.py @@ -0,0 +1,67 @@ +from prettytable import PrettyTable + +from slither.detectors.abstract_detector import classification_txt + +def output_to_markdown(detector_classes): + detectors_list = [] + for detector in detector_classes: + argument = detector.ARGUMENT + # dont show the backdoor example + if argument == 'backdoor': + continue + help_info = detector.HELP + impact = detector.IMPACT + confidence = classification_txt[detector.CONFIDENCE] + detectors_list.append((argument, help_info, impact, confidence)) + + # Sort by impact, confidence, and name + detectors_list = sorted(detectors_list, key=lambda element: (element[2], element[3], element[0])) + idx = 1 + for (argument, help_info, impact, confidence) in detectors_list: + print('{} | `{}` | {} | {} | {}'.format(idx, + argument, + help_info, + classification_txt[impact], + confidence)) + idx = idx + 1 + + +def output_detectors(detector_classes): + detectors_list = [] + for detector in detector_classes: + argument = detector.ARGUMENT + help_info = detector.HELP + impact = detector.IMPACT + confidence = classification_txt[detector.CONFIDENCE] + detectors_list.append((argument, help_info, impact, confidence)) + table = PrettyTable(["Num", + "Check", + "What it Detects", + "Impact", + "Confidence"]) + + # Sort by impact, confidence, and name + detectors_list = sorted(detectors_list, key=lambda element: (element[2], element[3], element[0])) + idx = 1 + for (argument, help_info, impact, confidence) in detectors_list: + table.add_row([idx, argument, help_info, classification_txt[impact], confidence]) + idx = idx + 1 + print(table) + +def output_printers(printer_classes): + printers_list = [] + for printer in printer_classes: + argument = printer.ARGUMENT + help_info = printer.HELP + printers_list.append((argument, help_info)) + table = PrettyTable(["Num", + "Printer", + "What it Does"]) + + # Sort by impact, confidence, and name + printers_list = sorted(printers_list, key=lambda element: (element[0])) + idx = 1 + for (argument, help_info) in printers_list: + table.add_row([idx, argument, help_info]) + idx = idx + 1 + print(table) From b664ab0b2c0b9ca1a0364f8695bdc3eae7106eeb Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Fri, 26 Oct 2018 10:37:00 +0100 Subject: [PATCH 216/308] Update README.md --- README.md | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index dd26c2e98..b42ab440f 100644 --- a/README.md +++ b/README.md @@ -32,25 +32,10 @@ If Slither is run on a directory, it will run on every `.sol` file of the direct * `--disable-solc-warnings`: Do not print solc warnings * `--solc-ast`: Use the solc AST file as input (`solc file.sol --ast-json > file.ast.json`) * `--json FILE`: Export results as JSON -* `--exclude-name`: Excludes the detector `name` from analysis - -### Printers - -By default, the `contract-summary` printer is used. Use --printers comma-separated list of printers, -or `none` to disable the default printer. - -Num | Printer | Description ---- | --- | --- -1 | `contract-summary` | a summary of the contract -2 | `function-summary` | the summary of the functions -3 | `inheritance` | the inheritance relation between contracts -4 | `inheritance-graph` | the inheritance graph -5 | `slithir` | the slithIR -6 | `vars-and-auth` | the state variables written and the authorization of the functions ## Detectors -By default, all the detectors are run. Use --detectors comma-separated list of detectors to run. +By default, all the detectors are run. Use `--detectors` comma-separated list of detectors to run. Num | Detector | What it Detects | Impact | Confidence --- | --- | --- | --- | --- @@ -72,6 +57,20 @@ Num | Detector | What it Detects | Impact | Confidence [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. +### Printers + +Use `--printers` comma-separated list of printers. + +Num | Printer | Description +--- | --- | --- +1 | `contract-summary` | a summary of the contract +2 | `function-summary` | the summary of the functions +3 | `inheritance` | the inheritance relation between contracts +4 | `inheritance-graph` | the inheritance graph +5 | `slithir` | the slithIR +6 | `vars-and-auth` | the state variables written and the authorization of the functions + + ## How to install Slither requires Python 3.6+ and [solc](https://github.com/ethereum/solidity/), the Solidity compiler. From 5e064be1e65176382612dc6934f24ae86890148c Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 26 Oct 2018 10:53:34 +0100 Subject: [PATCH 217/308] Update readme Add printer to markdown generation --- README.md | 44 +++++++++++++++++------------------ slither/__main__.py | 5 ++-- slither/utils/command_line.py | 15 +++++++++++- 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index b42ab440f..e5cfa6dd9 100644 --- a/README.md +++ b/README.md @@ -39,21 +39,21 @@ By default, all the detectors are run. Use `--detectors` comma-separated list of Num | Detector | What it Detects | Impact | Confidence --- | --- | --- | --- | --- -1 | `backdoor` | Function named backdoor (detector example) | High | High -2 | `suicidal` | Suicidal functions | High | High -3 | `uninitialized-state` | Uninitialized state variables | High | High -4 | `uninitialized-storage` | Uninitialized storage variables | High | High -5 | `arbitrary-send` | Functions that send ether to an arbitrary destination | High | Medium -6 | `reentrancy` | Reentrancy vulnerabilities | High | Medium -7 | `locked-ether` | Contracts that lock ether | Medium | High -8 | `tx-origin` | Dangerous usage of `tx.origin` | Medium | Medium -9 | `assembly` | Assembly usage | Informational | High -10 | `const-candidates-state` | State variables that could be declared constant | Informational | High -11 | `low-level-calls` | Low level calls | Informational | High -12 | `naming-convention` | Conformance to Solidity naming conventions | Informational | High -13 | `pragma` | If different pragma directives are used | Informational | High -14 | `solc-version` | If an old version of Solidity used (<0.4.23) | Informational | High -15 | `unused-state` | Unused state variables | Informational | High +1 | `suicidal` | Suicidal functions | High | High +2 | `uninitialized-state` | Uninitialized state variables | High | High +3 | `uninitialized-storage` | Uninitialized storage variables | High | High +4 | `arbitrary-send` | Functions that send ether to an arbitrary destination | High | Medium +5 | `reentrancy` | Reentrancy vulnerabilities | High | Medium +6 | `locked-ether` | Contracts that lock ether | Medium | High +7 | `tx-origin` | Dangerous usage of `tx.origin` | Medium | Medium +8 | `assembly` | Assembly usage | Informational | High +9 | `const-candidates-state` | State variables that could be declared constant | Informational | High +10 | `low-level-calls` | Low level calls | Informational | High +11 | `naming-convention` | Conformance to Solidity naming conventions | Informational | High +12 | `pragma` | If different pragma directives are used | Informational | High +13 | `solc-version` | If an old version of Solidity used (<0.4.23) | Informational | High +14 | `unused-state` | Unused state variables | Informational | High + [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. @@ -63,13 +63,13 @@ Use `--printers` comma-separated list of printers. Num | Printer | Description --- | --- | --- -1 | `contract-summary` | a summary of the contract -2 | `function-summary` | the summary of the functions -3 | `inheritance` | the inheritance relation between contracts -4 | `inheritance-graph` | the inheritance graph -5 | `slithir` | the slithIR -6 | `vars-and-auth` | the state variables written and the authorization of the functions - +1 | `call-graph` | the call graph +2 | `contract-summary` | a summary of the contract +3 | `function-summary` | the summary of the functions +4 | `inheritance` | the inheritance relation between contracts +5 | `inheritance-graph` | the inheritance graph +6 | `slithir` | the slithIR +7 | `vars-and-auth` | the state variables written and the authorization of the functions ## How to install diff --git a/slither/__main__.py b/slither/__main__.py index 183727ec7..116747c88 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -345,6 +345,7 @@ def parse_args(detector_classes, printer_classes): parser.add_argument('--markdown', help=argparse.SUPPRESS, action=OutputMarkdown, + nargs=0, default=False) parser.add_argument('--compact-ast', @@ -374,8 +375,8 @@ class ListPrinters(argparse.Action): class OutputMarkdown(argparse.Action): def __call__(self, parser, *args, **kwargs): - detectors, _ = get_detectors_and_printers() - output_to_markdown(detectors) + detectors, printers = get_detectors_and_printers() + output_to_markdown(detectors, printers) parser.exit() diff --git a/slither/utils/command_line.py b/slither/utils/command_line.py index 90a933fc3..8ef40df9f 100644 --- a/slither/utils/command_line.py +++ b/slither/utils/command_line.py @@ -2,7 +2,7 @@ from prettytable import PrettyTable from slither.detectors.abstract_detector import classification_txt -def output_to_markdown(detector_classes): +def output_to_markdown(detector_classes, printer_classes): detectors_list = [] for detector in detector_classes: argument = detector.ARGUMENT @@ -25,6 +25,19 @@ def output_to_markdown(detector_classes): confidence)) idx = idx + 1 + print() + printers_list = [] + for printer in printer_classes: + argument = printer.ARGUMENT + help_info = printer.HELP + printers_list.append((argument, help_info)) + + # Sort by impact, confidence, and name + printers_list = sorted(printers_list, key=lambda element: (element[0])) + idx = 1 + for (argument, help_info) in printers_list: + print('{} | `{}` | {}'.format(idx, argument, help_info)) + idx = idx + 1 def output_detectors(detector_classes): detectors_list = [] From dece5ab023f0af1e02f3cde7a29786adf0133fce Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 26 Oct 2018 11:15:59 +0100 Subject: [PATCH 218/308] UninitializedStateVarsDetection: Fix incorrect storage parameters add --- scripts/travis_test.sh | 2 +- .../detectors/variables/uninitialized_state_variables.py | 8 ++++---- tests/uninitialized.sol | 7 ++++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index ccf46d177..b3378aaad 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -15,7 +15,7 @@ test_slither(){ fi } -test_slither tests/uninitialized.sol "uninitialized-state" 3 +test_slither tests/uninitialized.sol "uninitialized-state" 4 test_slither tests/backdoor.sol "backdoor" 1 test_slither tests/backdoor.sol "suicidal" 1 test_slither tests/pragma.0.4.24.sol "pragma" 1 diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index 152cbbead..8ec652782 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -38,11 +38,11 @@ class UninitializedStateVarsDetection(AbstractDetector): if isinstance(ir, LibraryCall) \ or isinstance(ir, InternalCall) \ or isinstance(ir, InternalDynamicCall): - for v in ir.arguments: - ret.append(v) - for param in f.parameters: + idx = 0 + for param in ir.function.parameters: if param.location == 'storage': - ret.append(param) + ret.append(ir.arguments[idx]) + idx = idx+1 return ret diff --git a/tests/uninitialized.sol b/tests/uninitialized.sol index d31eb10bf..2305483e6 100644 --- a/tests/uninitialized.sol +++ b/tests/uninitialized.sol @@ -32,7 +32,7 @@ library Lib{ uint val; } - function set(MyStruct storage st){ + function set(MyStruct storage st, uint v){ st.val = 4; } @@ -44,9 +44,10 @@ contract Test2 { Lib.MyStruct st; Lib.MyStruct stInitiliazed; + uint v; // v is used as parameter of the lib, but is never init function init(){ - stInitiliazed.set(); + stInitiliazed.set(v); } function use(){ @@ -54,4 +55,4 @@ contract Test2 { require(st.val == stInitiliazed.val); } -} \ No newline at end of file +} From cc7ca46d60e67b3cffd11c21bb67fdfbb1b4ba21 Mon Sep 17 00:00:00 2001 From: Omidiora Samuel <8148384+samparsky@users.noreply.github.com> Date: Wed, 24 Oct 2018 07:46:47 +0100 Subject: [PATCH 219/308] added complex func --- .vscode/settings.json | 3 + slither/__main__.py | 1 + .../detectors/functions/complex_function.py | 36 +++++++++ slither/utils/code_complexity.py | 75 +++++++++++++++++++ 4 files changed, 115 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 slither/detectors/functions/complex_function.py create mode 100644 slither/utils/code_complexity.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..5b80df368 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.pythonPath": "venv/bin/python" +} \ No newline at end of file diff --git a/slither/__main__.py b/slither/__main__.py index 116747c88..b53ddbfee 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -101,6 +101,7 @@ def get_detectors_and_printers(): from slither.detectors.attributes.locked_ether import LockedEther from slither.detectors.functions.arbitrary_send import ArbitrarySend from slither.detectors.functions.suicidal import Suicidal + from slither.detectors.functions.complex_function import ComplexFunction from slither.detectors.reentrancy.reentrancy import Reentrancy from slither.detectors.variables.uninitialized_storage_variables import UninitializedStorageVars from slither.detectors.variables.unused_state_variables import UnusedStateVars diff --git a/slither/detectors/functions/complex_function.py b/slither/detectors/functions/complex_function.py new file mode 100644 index 000000000..c27ec0036 --- /dev/null +++ b/slither/detectors/functions/complex_function.py @@ -0,0 +1,36 @@ +from slither.core.declarations.solidity_variables import (SolidityFunction, + SolidityVariableComposed) +from slither.detectors.abstract_detector import (AbstractDetector, + DetectorClassification) +from slither.slithir.operations import (HighLevelCall, Index, LowLevelCall, + Send, SolidityCall, Transfer) +from slither.utils.code_complexity import compute_cyclomatic_complexity + +class ComplexFunction(AbstractDetector): + """ + + """ + + ARGUMENT = 'complex-function' + HELP = 'Complex functions' + IMPACT = DetectorClassification.HIGH + CONFIDENCE = DetectorClassification.MEDIUM + + def detect_complex_func(self): + # check the cyclomatic comlexity + # numerous state vars + # numerious external calls + pass + + def detect_complex(self, contract): + for func in contract.all_functions_called: + pass + + + def detect(self): + + for contract in self.contracts: + + pass + + pass \ No newline at end of file diff --git a/slither/utils/code_complexity.py b/slither/utils/code_complexity.py new file mode 100644 index 000000000..efe94648b --- /dev/null +++ b/slither/utils/code_complexity.py @@ -0,0 +1,75 @@ +# Funciton computing the code complexity + +def compute_number_edges(function): + """ + Compute the number of edges of the CFG + Args: + function (core.declarations.function.Function) + Returns: + int + """ + n = 0 + for node in function.nodes: + n += len(node.sons) + return n + + +def compute_strongly_connected_components(function): + """ + Compute strongly connected components + Based on Kosaraju algo + Implem follows wikipedia algo: https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm#The_algorithm + Args: + function (core.declarations.function.Function) + Returns: + list(list(nodes)) + """ + visited = {n:False for n in function.nodes} + assigned = {n:False for n in function.nodes} + components = [] + l = [] + + def visit(node): + if not visited[node]: + visited[node] = True + for son in node.sons: + visit(son) + l.append(node) + + for n in function.nodes: + visit(n) + + def assign(node, root): + if not assigned[node]: + assigned[node] = True + root.append(node) + for father in node.fathers: + assign(father, root) + + for n in l: + component = [] + assign(n, component) + if component: + components.append(component) + + return components + +def compute_cyclomatic_complexity(function): + """ + Compute the cyclomatic complexity of a function + Args: + function (core.declarations.function.Function) + Returns: + int + """ + # from https://en.wikipedia.org/wiki/Cyclomatic_complexity + # M = E - N + 2P + # where M is the complexity + # E number of edges + # N number of nodes + # P number of connected components + + E = compute_number_edges(function) + N = len(function.nodes) + P = len(compute_strongly_connected_components(function)) + return E - N + 2 * P \ No newline at end of file From 5799bbe09e47f62a9d474779289587a2ff5a5f81 Mon Sep 17 00:00:00 2001 From: Omidiora Samuel <8148384+samparsky@users.noreply.github.com> Date: Wed, 24 Oct 2018 07:47:29 +0100 Subject: [PATCH 220/308] added complex func --- .gitignore | 2 +- .vscode/settings.json | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 51873c6f2..2f5cbe034 100644 --- a/.gitignore +++ b/.gitignore @@ -46,7 +46,7 @@ nosetests.xml coverage.xml *.cover .hypothesis/ - +.vscode/ # Translations *.mo *.pot diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 5b80df368..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "python.pythonPath": "venv/bin/python" -} \ No newline at end of file From d2bca172ae07d6918b65f2a73999f0585babc4a1 Mon Sep 17 00:00:00 2001 From: Omidiora Samuel <8148384+samparsky@users.noreply.github.com> Date: Wed, 24 Oct 2018 09:26:20 +0100 Subject: [PATCH 221/308] added enum --- slither/__main__.py | 3 +- .../detectors/functions/complex_function.py | 63 +++++++++++++++++-- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/slither/__main__.py b/slither/__main__.py index b53ddbfee..a6bf9a96b 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -125,7 +125,8 @@ def get_detectors_and_printers(): Assembly, LowLevelCalls, NamingConvention, - ConstCandidateStateVars] + ConstCandidateStateVars, + ComplexFunction] from slither.printers.summary.function import FunctionSummary from slither.printers.summary.contract import ContractSummary diff --git a/slither/detectors/functions/complex_function.py b/slither/detectors/functions/complex_function.py index c27ec0036..ee114523e 100644 --- a/slither/detectors/functions/complex_function.py +++ b/slither/detectors/functions/complex_function.py @@ -2,9 +2,15 @@ from slither.core.declarations.solidity_variables import (SolidityFunction, SolidityVariableComposed) from slither.detectors.abstract_detector import (AbstractDetector, DetectorClassification) -from slither.slithir.operations import (HighLevelCall, Index, LowLevelCall, +from slither.slithir.operations import (HighLevelCall, Index, LowLevelCall, LibraryCall Send, SolidityCall, Transfer) from slither.utils.code_complexity import compute_cyclomatic_complexity +from enum import Enum + +class COMPLEX(Enum): + HIGH_EXTERNAL_CALLS = 1 + HIGH_STATE_VARIABLES = 2 + HIGH_CYCLOMATIC_COMPLEXITY = 3 class ComplexFunction(AbstractDetector): """ @@ -14,23 +20,68 @@ class ComplexFunction(AbstractDetector): ARGUMENT = 'complex-function' HELP = 'Complex functions' IMPACT = DetectorClassification.HIGH - CONFIDENCE = DetectorClassification.MEDIUM + CONFIDENCE = DetectorClassification.MEDIUM + + MAX_STATE_VARIABLES = 20 + MAX_EXTERNAL_CALLS = 5 + MAX_CYCLOMATIC_COMPLEXITY = 6 - def detect_complex_func(self): + def detect_complex_func(self, func, contract): # check the cyclomatic comlexity # numerous state vars # numerious external calls - pass + + """Detect the cyclomatic complexity of the contract functions + """ + result = [] + code_complexity = compute_cyclomatic_complexity(func) + + if code_complexity > self.MAX_CYCLOMATIC_COMPLEXITY: + result.append({ + contract: contract, + func: func, + type: COMPLEX.HIGH_CYCLOMATIC_COMPLEXITY + }) + + """Detect the number of external calls in the func + shouldn't be greater than 5 + """ + count = 0 + for node in func.nodes: + for ir in node.irs: + if isinstance(ir, (HighLevelCall, LowLevelCall, LibraryCall)): + count += 1 + + if count > self.MAX_EXTERNAL_CALLS: + result.append({ + contract: contract, + func: func, + type: COMPLEX.HIGH_EXTERNAL_CALLS + }) + + return result def detect_complex(self, contract): + ret = [] + + """Checks the number of the contract state variables if its not greater than 20 + """ + if contract.variables > self.MAX_STATE_VARIABLES: + ret.append({ + contract: contract, + type: COMPLEX.HIGH_STATE_VARIABLES + }) + for func in contract.all_functions_called: - pass + result = self.detect_complex_func(func, contract) + ret.extend(result) + return ret def detect(self): for contract in self.contracts: - + pass pass \ No newline at end of file From 16d3b2ad1f49d71b1c4eb905f1323f4982941478 Mon Sep 17 00:00:00 2001 From: Omidiora Samuel <8148384+samparsky@users.noreply.github.com> Date: Wed, 24 Oct 2018 10:08:09 +0100 Subject: [PATCH 222/308] modified func state vars count --- .../detectors/functions/complex_function.py | 85 ++++++++++++------- 1 file changed, 55 insertions(+), 30 deletions(-) diff --git a/slither/detectors/functions/complex_function.py b/slither/detectors/functions/complex_function.py index ee114523e..cdfa50a50 100644 --- a/slither/detectors/functions/complex_function.py +++ b/slither/detectors/functions/complex_function.py @@ -7,10 +7,14 @@ from slither.slithir.operations import (HighLevelCall, Index, LowLevelCall, Libr from slither.utils.code_complexity import compute_cyclomatic_complexity from enum import Enum -class COMPLEX(Enum): - HIGH_EXTERNAL_CALLS = 1 - HIGH_STATE_VARIABLES = 2 - HIGH_CYCLOMATIC_COMPLEXITY = 3 +class Complex(Enum): + HIGH_EXTERNAL_CALLS = 1 + HIGH_STATE_VARIABLES = 2 + HIGH_CYCLOMATIC_COMPLEXITY = 3 + + MAX_STATE_VARIABLES = 20 + MAX_EXTERNAL_CALLS = 5 + MAX_CYCLOMATIC_COMPLEXITY = 6 class ComplexFunction(AbstractDetector): """ @@ -22,25 +26,17 @@ class ComplexFunction(AbstractDetector): IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.MEDIUM - MAX_STATE_VARIABLES = 20 - MAX_EXTERNAL_CALLS = 5 - MAX_CYCLOMATIC_COMPLEXITY = 6 - - def detect_complex_func(self, func, contract): - # check the cyclomatic comlexity - # numerous state vars - # numerious external calls - + def detect_complex_func(self, func, contract): """Detect the cyclomatic complexity of the contract functions """ result = [] code_complexity = compute_cyclomatic_complexity(func) - if code_complexity > self.MAX_CYCLOMATIC_COMPLEXITY: + if code_complexity > Complex.MAX_CYCLOMATIC_COMPLEXITY.value: result.append({ contract: contract, func: func, - type: COMPLEX.HIGH_CYCLOMATIC_COMPLEXITY + type: Complex.HIGH_CYCLOMATIC_COMPLEXITY }) """Detect the number of external calls in the func @@ -51,26 +47,28 @@ class ComplexFunction(AbstractDetector): for ir in node.irs: if isinstance(ir, (HighLevelCall, LowLevelCall, LibraryCall)): count += 1 - - if count > self.MAX_EXTERNAL_CALLS: + + if count > Complex.MAX_EXTERNAL_CALLS.value: result.append({ contract: contract, func: func, - type: COMPLEX.HIGH_EXTERNAL_CALLS + type: Complex.HIGH_EXTERNAL_CALLS }) - - return result - - def detect_complex(self, contract): - ret = [] - """Checks the number of the contract state variables if its not greater than 20 + """Checks the number of the state variables written to isn't + greater than 20 """ - if contract.variables > self.MAX_STATE_VARIABLES: + if func.state_variables_written.length > Complex.MAX_STATE_VARIABLES.value: ret.append({ contract: contract, - type: COMPLEX.HIGH_STATE_VARIABLES + func: func + type: Complex.HIGH_STATE_VARIABLES }) + + return result + + def detect_complex(self, contract): + ret = [] for func in contract.all_functions_called: result = self.detect_complex_func(func, contract) @@ -79,9 +77,36 @@ class ComplexFunction(AbstractDetector): return ret def detect(self): - + result = [] for contract in self.contracts: - - pass + complex_issues = self.detect_complex(contract) + for issue in complex_issues: + txt = "" + + if issue.type == Complex.HIGH_EXTERNAL_CALLS: + txt = "High external calls, complex function in {} Contract: {}, Function: {}" + if issue.type == Complex.HIGH_CYCLOMATIC_COMPLEXITY: + txt = "Too complex function, complex function in {} Contract: {}, Function: {}" + if issue.type == Complex.HIGH_STATE_VARIABLES: + pass + + info = txt.format(self.filename, + c.name, + func_name) + + + txt = "Too many " + info = txt.format(self.filename, + c.name, + func_name) + + self.log(info) + + results.append({'vuln': 'SuicidalFunc', + 'sourceMapping': func.source_mapping, + 'filename': self.filename, + 'contract': c.name, + 'func': func_name}) + + return result - pass \ No newline at end of file From 26aff39b67a0a5167d5365c96db1aa36c17cc5cb Mon Sep 17 00:00:00 2001 From: Omidiora Samuel <8148384+samparsky@users.noreply.github.com> Date: Wed, 24 Oct 2018 10:08:14 +0100 Subject: [PATCH 223/308] modified func state vars count --- slither/detectors/functions/complex_function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/detectors/functions/complex_function.py b/slither/detectors/functions/complex_function.py index cdfa50a50..2bb960ab6 100644 --- a/slither/detectors/functions/complex_function.py +++ b/slither/detectors/functions/complex_function.py @@ -58,7 +58,7 @@ class ComplexFunction(AbstractDetector): """Checks the number of the state variables written to isn't greater than 20 """ - if func.state_variables_written.length > Complex.MAX_STATE_VARIABLES.value: + if func.variables_written.length > Complex.MAX_STATE_VARIABLES.value: ret.append({ contract: contract, func: func From 2a8b3b3568c089671b40be5a44f0e95222cdaa38 Mon Sep 17 00:00:00 2001 From: Omidiora Samuel <8148384+samparsky@users.noreply.github.com> Date: Thu, 25 Oct 2018 15:49:29 +0100 Subject: [PATCH 224/308] added examples added examples added removed uneeded code added complex function tests fixed issues --- .../detectors/functions/complex_function.py | 116 +++++++++--------- tests/complex_func.sol | 88 +++++++++++++ 2 files changed, 147 insertions(+), 57 deletions(-) create mode 100644 tests/complex_func.sol diff --git a/slither/detectors/functions/complex_function.py b/slither/detectors/functions/complex_function.py index 2bb960ab6..355f1865b 100644 --- a/slither/detectors/functions/complex_function.py +++ b/slither/detectors/functions/complex_function.py @@ -2,41 +2,48 @@ from slither.core.declarations.solidity_variables import (SolidityFunction, SolidityVariableComposed) from slither.detectors.abstract_detector import (AbstractDetector, DetectorClassification) -from slither.slithir.operations import (HighLevelCall, Index, LowLevelCall, LibraryCall - Send, SolidityCall, Transfer) +from slither.slithir.operations import (HighLevelCall, + LowLevelCall, + LibraryCall) from slither.utils.code_complexity import compute_cyclomatic_complexity -from enum import Enum -class Complex(Enum): - HIGH_EXTERNAL_CALLS = 1 - HIGH_STATE_VARIABLES = 2 - HIGH_CYCLOMATIC_COMPLEXITY = 3 - - MAX_STATE_VARIABLES = 20 - MAX_EXTERNAL_CALLS = 5 - MAX_CYCLOMATIC_COMPLEXITY = 6 class ComplexFunction(AbstractDetector): """ - + Module detecting complex functions + A complex function is defined by: + - high cyclomatic complexity + - numerous writes to state variables + - numerous external calls """ + ARGUMENT = 'complex-function' HELP = 'Complex functions' - IMPACT = DetectorClassification.HIGH + IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.MEDIUM - def detect_complex_func(self, func, contract): + MAX_STATE_VARIABLES = 10 + MAX_EXTERNAL_CALLS = 5 + MAX_CYCLOMATIC_COMPLEXITY = 7 + + CAUSE_CYCLOMATIC = "cyclomatic" + CAUSE_EXTERNAL_CALL = "external_calls" + CAUSE_STATE_VARS = "state_vars" + + + @staticmethod + def detect_complex_func(func): """Detect the cyclomatic complexity of the contract functions + shouldn't be greater than 7 """ result = [] code_complexity = compute_cyclomatic_complexity(func) - if code_complexity > Complex.MAX_CYCLOMATIC_COMPLEXITY.value: + if code_complexity > ComplexFunction.MAX_CYCLOMATIC_COMPLEXITY: result.append({ - contract: contract, - func: func, - type: Complex.HIGH_CYCLOMATIC_COMPLEXITY + "func": func, + "cause": ComplexFunction.CAUSE_CYCLOMATIC }) """Detect the number of external calls in the func @@ -48,21 +55,19 @@ class ComplexFunction(AbstractDetector): if isinstance(ir, (HighLevelCall, LowLevelCall, LibraryCall)): count += 1 - if count > Complex.MAX_EXTERNAL_CALLS.value: + if count > ComplexFunction.MAX_EXTERNAL_CALLS: result.append({ - contract: contract, - func: func, - type: Complex.HIGH_EXTERNAL_CALLS + "func": func, + "cause": ComplexFunction.CAUSE_EXTERNAL_CALL }) - """Checks the number of the state variables written to isn't - greater than 20 + """Checks the number of the state variables written + shouldn't be greater than 10 """ - if func.variables_written.length > Complex.MAX_STATE_VARIABLES.value: - ret.append({ - contract: contract, - func: func - type: Complex.HIGH_STATE_VARIABLES + if len(func.state_variables_written) > ComplexFunction.MAX_STATE_VARIABLES: + result.append({ + "func": func, + "cause": ComplexFunction.CAUSE_STATE_VARS }) return result @@ -71,42 +76,39 @@ class ComplexFunction(AbstractDetector): ret = [] for func in contract.all_functions_called: - result = self.detect_complex_func(func, contract) + result = self.detect_complex_func(func) ret.extend(result) return ret def detect(self): - result = [] - for contract in self.contracts: - complex_issues = self.detect_complex(contract) - for issue in complex_issues: - txt = "" - - if issue.type == Complex.HIGH_EXTERNAL_CALLS: - txt = "High external calls, complex function in {} Contract: {}, Function: {}" - if issue.type == Complex.HIGH_CYCLOMATIC_COMPLEXITY: - txt = "Too complex function, complex function in {} Contract: {}, Function: {}" - if issue.type == Complex.HIGH_STATE_VARIABLES: - pass + results = [] - info = txt.format(self.filename, - c.name, - func_name) + for contract in self.contracts: + issues = self.detect_complex(contract) + for issue in issues: + func, cause = issue.values() + func_name = func.name - txt = "Too many " - info = txt.format(self.filename, - c.name, - func_name) - - self.log(info) + txt = "Complex function in {} Contract: {}, Function: {}" - results.append({'vuln': 'SuicidalFunc', - 'sourceMapping': func.source_mapping, - 'filename': self.filename, - 'contract': c.name, - 'func': func_name}) + if cause == self.CAUSE_EXTERNAL_CALL: + txt += ", Reason: High number of external calls" + if cause == self.CAUSE_CYCLOMATIC: + txt += ", Reason: High number of branches" + if cause == self.CAUSE_STATE_VARS: + txt += ", Reason: High number of modified state variables" - return result + info = txt.format(self.filename, + contract.name, + func_name) + self.log(info) + + results.append({'vuln': 'ComplexFunc', + 'sourceMapping': func.source_mapping, + 'filename': self.filename, + 'contract': contract.name, + 'func': func_name}) + return results diff --git a/tests/complex_func.sol b/tests/complex_func.sol new file mode 100644 index 000000000..cdb716efd --- /dev/null +++ b/tests/complex_func.sol @@ -0,0 +1,88 @@ +pragma solidity ^0.4.24; + +contract Complex { + int numberOfSides = 7; + string shape; + uint i0 = 0; + uint i1 = 0; + uint i2 = 0; + uint i3 = 0; + uint i4 = 0; + uint i5 = 0; + uint i6 = 0; + uint i7 = 0; + uint i8 = 0; + uint i9 = 0; + uint i10 = 0; + + + function computeShape() external { + if (numberOfSides <= 2) { + shape = "Cant be a shape!"; + } else if (numberOfSides == 3) { + shape = "Triangle"; + } else if (numberOfSides == 4) { + shape = "Square"; + } else if (numberOfSides == 5) { + shape = "Pentagon"; + } else if (numberOfSides == 6) { + shape = "Hexagon"; + } else if (numberOfSides == 7) { + shape = "Heptagon"; + } else if (numberOfSides == 8) { + shape = "Octagon"; + } else if (numberOfSides == 9) { + shape = "Nonagon"; + } else if (numberOfSides == 10) { + shape = "Decagon"; + } else if (numberOfSides == 11) { + shape = "Hendecagon"; + } else { + shape = "Your shape is more than 11 sides."; + } + } + + function complexExternalWrites() external { + Increment test1 = new Increment(); + test1.increaseBy1(); + test1.increaseBy1(); + test1.increaseBy1(); + test1.increaseBy1(); + test1.increaseBy1(); + + Increment test2 = new Increment(); + test2.increaseBy1(); + + address test3 = new Increment(); + test3.call(bytes4(keccak256("increaseBy2()"))); + + address test4 = new Increment(); + test4.call(bytes4(keccak256("increaseBy2()"))); + } + + function complexStateVars() external { + i0 = 1; + i1 = 1; + i2 = 1; + i3 = 1; + i4 = 1; + i5 = 1; + i6 = 1; + i7 = 1; + i8 = 1; + i9 = 1; + i10 = 1; + } +} + +contract Increment { + uint i = 0; + + function increaseBy1() public { + i += 1; + } + + function increaseBy2() public { + i += 2; + } +} \ No newline at end of file From dbe066a66a357258c4e66ec24c011a9d8a41f1bc Mon Sep 17 00:00:00 2001 From: Omidiora Samuel <8148384+samparsky@users.noreply.github.com> Date: Fri, 26 Oct 2018 12:13:10 +0100 Subject: [PATCH 225/308] fixed merge conflict issues --- scripts/travis_test.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index b3378aaad..f3167db43 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -26,6 +26,7 @@ test_slither tests/tx_origin.sol "tx-origin" 2 test_slither tests/unused_state.sol "unused-state" 1 test_slither tests/locked_ether.sol "locked-ether" 1 test_slither tests/arbitrary_send.sol "arbitrary-send" 2 +test_slither tests/complex_func.sol "complex-function" 3 test_slither tests/inline_assembly_contract.sol "assembly" 1 test_slither tests/inline_assembly_library.sol "assembly" 2 test_slither tests/naming_convention.sol "naming-convention" 10 From 5c73e2e29de1fa3c847ecf2c836774029c8a4ab9 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 26 Oct 2018 12:22:31 +0100 Subject: [PATCH 226/308] UninitializedStateVarsDetection: dont consider InternalDynamicCall --- slither/detectors/variables/uninitialized_state_variables.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index 8ec652782..17d60e023 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -36,8 +36,7 @@ class UninitializedStateVarsDetection(AbstractDetector): ret += n.state_variables_written for ir in n.irs: if isinstance(ir, LibraryCall) \ - or isinstance(ir, InternalCall) \ - or isinstance(ir, InternalDynamicCall): + or isinstance(ir, InternalCall): idx = 0 for param in ir.function.parameters: if param.location == 'storage': From 559506c1a68b0a5f75177bc7f3ec30f384f9fcc5 Mon Sep 17 00:00:00 2001 From: Omidiora Samuel <> Date: Sun, 21 Oct 2018 17:28:12 +0100 Subject: [PATCH 227/308] initial commit --- .vscode/settings.json | 3 ++ .../detectors/functions/public_function.py | 34 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 slither/detectors/functions/public_function.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..e5273f67a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.pythonPath": "/usr/local/opt/python/bin/python3.7" +} \ No newline at end of file diff --git a/slither/detectors/functions/public_function.py b/slither/detectors/functions/public_function.py new file mode 100644 index 000000000..2db7328fe --- /dev/null +++ b/slither/detectors/functions/public_function.py @@ -0,0 +1,34 @@ + +from slither.analyses.taint.calls import KEY +from slither.analyses.taint.calls import run_taint as run_taint_calls +from slither.analyses.taint.specific_variable import is_tainted +from slither.analyses.taint.specific_variable import \ + run_taint as run_taint_variable +from slither.core.declarations.solidity_variables import (SolidityFunction, + SolidityVariableComposed) +from slither.detectors.abstract_detector import (AbstractDetector, + DetectorClassification) +from slither.slithir.operations import (HighLevelCall, Index, LowLevelCall, + Send, SolidityCall, Transfer) + +class PublicFunction(AbstractDetector): + """ + Detect public function that could be declared as external + """ + + ARGUMENT = 'detect-possible-external-function' + HELP = 'Detect public function that could be declared as external' + IMPACT = DetectorClassification.HIGH + CONFIDENCE = DetectorClassification.HIGH + + @staticmethod + def detect_public_func_declare_external(func): + pass + + def public_function(): + pass + + def detect(self): + pass + + From b8c117ed0c9b0fa69d5ca6877d944c2009c95013 Mon Sep 17 00:00:00 2001 From: Omidiora Samuel <> Date: Sun, 21 Oct 2018 18:24:36 +0100 Subject: [PATCH 228/308] addeed ir --- .../detectors/functions/external_function.py | 84 +++++++++++++++++++ .../detectors/functions/public_function.py | 34 -------- 2 files changed, 84 insertions(+), 34 deletions(-) create mode 100644 slither/detectors/functions/external_function.py delete mode 100644 slither/detectors/functions/public_function.py diff --git a/slither/detectors/functions/external_function.py b/slither/detectors/functions/external_function.py new file mode 100644 index 000000000..91daa0c18 --- /dev/null +++ b/slither/detectors/functions/external_function.py @@ -0,0 +1,84 @@ + +from slither.analyses.taint.calls import KEY +from slither.analyses.taint.calls import run_taint as run_taint_calls +from slither.analyses.taint.specific_variable import is_tainted +from slither.analyses.taint.specific_variable import \ + run_taint as run_taint_variable +from slither.core.declarations.solidity_variables import (SolidityFunction, + SolidityVariableComposed) +from slither.detectors.abstract_detector import (AbstractDetector, + DetectorClassification) +from slither.slithir.operations import (HighLevelCall, Index, LowLevelCall, + Send, SolidityCall, Transfer) +from slither.slithir.operations import (InternalCall, InternalDynamicCall) + +class ExternalFunction(AbstractDetector): + """ + Detect public function that could be declared as external + """ + + ARGUMENT = 'detect-external-function' + HELP = 'Detect public function that could be declared as external' + IMPACT = DetectorClassification.INFORMATIONAL + CONFIDENCE = DetectorClassification.HIGH + + @staticmethod + def detect_external_func(func): + """ Detect if the function is suicidal + + Detect the public functions that should be declared external + + Checks the following + * The function is never the destination of an InternalCall + * There is no InternalDynamicCall (or ensure that the function is never the destination of an InternalDynamicCall) + * Check if any inherited contracts calls the function + + + * Iterate only over the derived contracts (slither.contracts_derived) to be sure that no inherited contract calls the function + + + Returns: + (bool): True if the function is not called + """ + + # check if the function visibility is public + if func.visibility != 'public': + return False + + # check if the func is not destination of internal call + if func. + + + + + pass + + def detect_external(self, contract): + ret = [] + for f in [f for f in contract.functions if f.contract == contract]: + if self.detect_external_func(f): + ret.append(f) + return ret + + def detect(self): + result = [] + + # check for functions derived contracts call + for contract in self.slither.contracts_derived: + pass + + for c in self.contracts: + functions = self.detect_external(c) + for func in functions: + func_name = func.name + txt = "Public function in {} Contract: {}, Function: {} should be declared external" + info = txt.format(self.filename, + c.name, + func_name) + self.log(info) + results.append({'vuln': 'ExternalFunc', + 'sourceMapping': func.source_mapping, + 'filename': self.filename, + 'contract': c.name, + 'func': func_name}) + return results \ No newline at end of file diff --git a/slither/detectors/functions/public_function.py b/slither/detectors/functions/public_function.py deleted file mode 100644 index 2db7328fe..000000000 --- a/slither/detectors/functions/public_function.py +++ /dev/null @@ -1,34 +0,0 @@ - -from slither.analyses.taint.calls import KEY -from slither.analyses.taint.calls import run_taint as run_taint_calls -from slither.analyses.taint.specific_variable import is_tainted -from slither.analyses.taint.specific_variable import \ - run_taint as run_taint_variable -from slither.core.declarations.solidity_variables import (SolidityFunction, - SolidityVariableComposed) -from slither.detectors.abstract_detector import (AbstractDetector, - DetectorClassification) -from slither.slithir.operations import (HighLevelCall, Index, LowLevelCall, - Send, SolidityCall, Transfer) - -class PublicFunction(AbstractDetector): - """ - Detect public function that could be declared as external - """ - - ARGUMENT = 'detect-possible-external-function' - HELP = 'Detect public function that could be declared as external' - IMPACT = DetectorClassification.HIGH - CONFIDENCE = DetectorClassification.HIGH - - @staticmethod - def detect_public_func_declare_external(func): - pass - - def public_function(): - pass - - def detect(self): - pass - - From add8216f9dc1c4977d7872bfd7c79b823e2d02f6 Mon Sep 17 00:00:00 2001 From: Omidiora Samuel <> Date: Sun, 21 Oct 2018 21:22:51 +0100 Subject: [PATCH 229/308] made changes added test contract fixed call changes clean up --- .vscode/settings.json | 2 +- slither/__main__.py | 4 ++ .../detectors/functions/external_function.py | 72 ++++++++----------- slither/slither.py | 1 + tests/backdoor.sol | 2 +- tests/external_function.sol | 34 +++++++++ tests/external_function1.sol | 5 ++ 7 files changed, 75 insertions(+), 45 deletions(-) create mode 100644 tests/external_function.sol create mode 100644 tests/external_function1.sol diff --git a/.vscode/settings.json b/.vscode/settings.json index e5273f67a..988937c0a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "python.pythonPath": "/usr/local/opt/python/bin/python3.7" + "python.pythonPath": "/usr/local/bin/python3" } \ No newline at end of file diff --git a/slither/__main__.py b/slither/__main__.py index 116747c88..798a45ffd 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -6,6 +6,8 @@ import json import logging import os import sys +sys.path.append("/Users/Samparsky/Sites/python/slither/slither") + import traceback from pkg_resources import iter_entry_points, require @@ -109,6 +111,7 @@ def get_detectors_and_printers(): from slither.detectors.statements.assembly import Assembly from slither.detectors.operations.low_level_calls import LowLevelCalls from slither.detectors.naming_convention.naming_convention import NamingConvention + from slither.detectors.functions.external_function import ExternalFunction detectors = [Backdoor, UninitializedStateVarsDetection, @@ -125,6 +128,7 @@ def get_detectors_and_printers(): LowLevelCalls, NamingConvention, ConstCandidateStateVars] + ExternalFunction] from slither.printers.summary.function import FunctionSummary from slither.printers.summary.contract import ContractSummary diff --git a/slither/detectors/functions/external_function.py b/slither/detectors/functions/external_function.py index 91daa0c18..25f3b4014 100644 --- a/slither/detectors/functions/external_function.py +++ b/slither/detectors/functions/external_function.py @@ -1,15 +1,6 @@ - -from slither.analyses.taint.calls import KEY -from slither.analyses.taint.calls import run_taint as run_taint_calls -from slither.analyses.taint.specific_variable import is_tainted -from slither.analyses.taint.specific_variable import \ - run_taint as run_taint_variable -from slither.core.declarations.solidity_variables import (SolidityFunction, - SolidityVariableComposed) from slither.detectors.abstract_detector import (AbstractDetector, DetectorClassification) -from slither.slithir.operations import (HighLevelCall, Index, LowLevelCall, - Send, SolidityCall, Transfer) +from slither.slithir.operations import (HighLevelCall, SolidityCall ) from slither.slithir.operations import (InternalCall, InternalDynamicCall) class ExternalFunction(AbstractDetector): @@ -17,58 +8,53 @@ class ExternalFunction(AbstractDetector): Detect public function that could be declared as external """ - ARGUMENT = 'detect-external-function' + ARGUMENT = 'external-function' HELP = 'Detect public function that could be declared as external' IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH @staticmethod - def detect_external_func(func): - """ Detect if the function is suicidal - - Detect the public functions that should be declared external - - Checks the following - * The function is never the destination of an InternalCall - * There is no InternalDynamicCall (or ensure that the function is never the destination of an InternalDynamicCall) - * Check if any inherited contracts calls the function - - - * Iterate only over the derived contracts (slither.contracts_derived) to be sure that no inherited contract calls the function - + def detect_function_calls(func): + """ Returns a list of all InternallCall, InternalDynamicCall, SolidityCall + calls made in a function Returns: - (bool): True if the function is not called + (list): List of all InternallCall, InternalDynamicCall, SolidityCall """ - - # check if the function visibility is public - if func.visibility != 'public': - return False - - # check if the func is not destination of internal call - if func. - - - - - pass + result = [] + for node in func.nodes: + for ir in node.irs: + if isinstance(ir, ( InternalCall, InternalDynamicCall, HighLevelCall, SolidityCall )): + print(ir.function) + result.append(ir.function) + return result def detect_external(self, contract): ret = [] for f in [f for f in contract.functions if f.contract == contract]: - if self.detect_external_func(f): - ret.append(f) + calls = self.detect_function_calls(f) + ret = [ret.append(f) for f in calls] return ret def detect(self): - result = [] + results = [] - # check for functions derived contracts call + public_function_calls = [] for contract in self.slither.contracts_derived: - pass + # get the contract functions + func_list = self.detect_external(contract) + public_function_calls = [public_function_calls.append(f) for f in func_list] + + print(public_function_calls_list) for c in self.contracts: - functions = self.detect_external(c) + """ + Returns the list of functions with public visibility in contract doesn't exist in the public_function_calls_list + This means that the public function doesn't have any InternallCall, InternalDynamicCall, SolidityCall call + attached to it hence + """ + functions = [f for f in c.functions if f.visibility == "public" and f.contract == c and f not in public_function_calls_list] + for func in functions: func_name = func.name txt = "Public function in {} Contract: {}, Function: {} should be declared external" diff --git a/slither/slither.py b/slither/slither.py index eb9cc64d4..172ffce66 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -2,6 +2,7 @@ import logging import os import subprocess import sys +import sys from slither.detectors.abstract_detector import AbstractDetector from slither.printers.abstract_printer import AbstractPrinter diff --git a/tests/backdoor.sol b/tests/backdoor.sol index 449eedc07..43f38421f 100644 --- a/tests/backdoor.sol +++ b/tests/backdoor.sol @@ -1,4 +1,4 @@ -pragma solidity 0.4.24; +pragma solidity 0.4.25; contract C{ diff --git a/tests/external_function.sol b/tests/external_function.sol new file mode 100644 index 000000000..04660a12f --- /dev/null +++ b/tests/external_function.sol @@ -0,0 +1,34 @@ +pragma solidity 0.4.25; + +import "./external_function1.sol"; + +contract ItemTwo is ItemFour { + function helloTwo() public { + uint256 i = 0; + } +} + +contract ItemThree { + + function helloThree() { + } + + function helloTwo() internal { + + } + + function helloOne() public { + + } +} + +contract ItemOne is ItemTwo { + function helloOne() public { + uint256 i = 0; + address three = new ItemThree(); + three.call(bytes4(keccak256("helloTwo()"))); + super.helloTwo(); + ItemFour four = new ItemFour(); + four.helloFour(); + } +} \ No newline at end of file diff --git a/tests/external_function1.sol b/tests/external_function1.sol new file mode 100644 index 000000000..8708be54d --- /dev/null +++ b/tests/external_function1.sol @@ -0,0 +1,5 @@ +contract ItemFour { + function helloFour() { + uint256 i = 0; + } +} \ No newline at end of file From 0bb9a3c0c0bdb682f28ab1fd47ed3e775c1fe98e Mon Sep 17 00:00:00 2001 From: Omidiora Samuel <8148384+samparsky@users.noreply.github.com> Date: Mon, 22 Oct 2018 09:29:48 +0100 Subject: [PATCH 230/308] added to readme removed false changes remove folder added more changed wording --- .vscode/settings.json | 2 +- README.md | 1 + slither/__main__.py | 4 +-- .../detectors/functions/external_function.py | 26 +++++++++++-------- slither/slither.py | 1 - tests/backdoor.sol | 2 +- tests/external_function.sol | 7 ++--- ...tion1.sol => external_function_test_2.sol} | 2 ++ 8 files changed, 25 insertions(+), 20 deletions(-) rename tests/{external_function1.sol => external_function_test_2.sol} (74%) diff --git a/.vscode/settings.json b/.vscode/settings.json index 988937c0a..5b80df368 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "python.pythonPath": "/usr/local/bin/python3" + "python.pythonPath": "venv/bin/python" } \ No newline at end of file diff --git a/README.md b/README.md index e5cfa6dd9..8a21c3159 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ Num | Detector | What it Detects | Impact | Confidence 12 | `pragma` | If different pragma directives are used | Informational | High 13 | `solc-version` | If an old version of Solidity used (<0.4.23) | Informational | High 14 | `unused-state` | Unused state variables | Informational | High +15 | `external-function` | Public functions that could be declared as external | Informational | High [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. diff --git a/slither/__main__.py b/slither/__main__.py index 798a45ffd..567811444 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -6,8 +6,6 @@ import json import logging import os import sys -sys.path.append("/Users/Samparsky/Sites/python/slither/slither") - import traceback from pkg_resources import iter_entry_points, require @@ -127,7 +125,7 @@ def get_detectors_and_printers(): Assembly, LowLevelCalls, NamingConvention, - ConstCandidateStateVars] + ConstCandidateStateVars, ExternalFunction] from slither.printers.summary.function import FunctionSummary diff --git a/slither/detectors/functions/external_function.py b/slither/detectors/functions/external_function.py index 25f3b4014..b5086894f 100644 --- a/slither/detectors/functions/external_function.py +++ b/slither/detectors/functions/external_function.py @@ -15,7 +15,7 @@ class ExternalFunction(AbstractDetector): @staticmethod def detect_function_calls(func): - """ Returns a list of all InternallCall, InternalDynamicCall, SolidityCall + """ Returns a list of InternallCall, InternalDynamicCall, SolidityCall calls made in a function Returns: @@ -25,7 +25,6 @@ class ExternalFunction(AbstractDetector): for node in func.nodes: for ir in node.irs: if isinstance(ir, ( InternalCall, InternalDynamicCall, HighLevelCall, SolidityCall )): - print(ir.function) result.append(ir.function) return result @@ -33,7 +32,7 @@ class ExternalFunction(AbstractDetector): ret = [] for f in [f for f in contract.functions if f.contract == contract]: calls = self.detect_function_calls(f) - ret = [ret.append(f) for f in calls] + ret.extend(calls) return ret def detect(self): @@ -41,19 +40,24 @@ class ExternalFunction(AbstractDetector): public_function_calls = [] for contract in self.slither.contracts_derived: - # get the contract functions func_list = self.detect_external(contract) - public_function_calls = [public_function_calls.append(f) for f in func_list] - - print(public_function_calls_list) + public_function_calls.extend(func_list) for c in self.contracts: """ - Returns the list of functions with public visibility in contract doesn't exist in the public_function_calls_list - This means that the public function doesn't have any InternallCall, InternalDynamicCall, SolidityCall call - attached to it hence + Returns a list of functions with public visibility in contract that doesn't + exist in the public_function_calls_list + + This means that the public function doesn't have any + InternallCall, InternalDynamicCall, SolidityCall call + attached to it hence it can be declared as external + """ - functions = [f for f in c.functions if f.visibility == "public" and f.contract == c and f not in public_function_calls_list] + functions = [ f for f in c.functions if f.visibility == "public" + and + f.contract == c + and + f not in public_function_calls ] for func in functions: func_name = func.name diff --git a/slither/slither.py b/slither/slither.py index 172ffce66..eb9cc64d4 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -2,7 +2,6 @@ import logging import os import subprocess import sys -import sys from slither.detectors.abstract_detector import AbstractDetector from slither.printers.abstract_printer import AbstractPrinter diff --git a/tests/backdoor.sol b/tests/backdoor.sol index 43f38421f..449eedc07 100644 --- a/tests/backdoor.sol +++ b/tests/backdoor.sol @@ -1,4 +1,4 @@ -pragma solidity 0.4.25; +pragma solidity 0.4.24; contract C{ diff --git a/tests/external_function.sol b/tests/external_function.sol index 04660a12f..02f32bdd6 100644 --- a/tests/external_function.sol +++ b/tests/external_function.sol @@ -1,6 +1,6 @@ -pragma solidity 0.4.25; +pragma solidity ^0.4.24; -import "./external_function1.sol"; +import "./external_function_test_2.sol"; contract ItemTwo is ItemFour { function helloTwo() public { @@ -11,9 +11,10 @@ contract ItemTwo is ItemFour { contract ItemThree { function helloThree() { + } - function helloTwo() internal { + function helloTwo() internal { } diff --git a/tests/external_function1.sol b/tests/external_function_test_2.sol similarity index 74% rename from tests/external_function1.sol rename to tests/external_function_test_2.sol index 8708be54d..7a99a7ef5 100644 --- a/tests/external_function1.sol +++ b/tests/external_function_test_2.sol @@ -1,3 +1,5 @@ +pragma solidity ^0.4.24; + contract ItemFour { function helloFour() { uint256 i = 0; From 5e49c233be30c44619cb5ce680e7bf3f2c49ee0c Mon Sep 17 00:00:00 2001 From: Omidiora Samuel <8148384+samparsky@users.noreply.github.com> Date: Mon, 22 Oct 2018 16:16:15 +0100 Subject: [PATCH 231/308] removed vscode folder --- .gitignore | 2 +- .vscode/settings.json | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 51873c6f2..2f5cbe034 100644 --- a/.gitignore +++ b/.gitignore @@ -46,7 +46,7 @@ nosetests.xml coverage.xml *.cover .hypothesis/ - +.vscode/ # Translations *.mo *.pot diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 5b80df368..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "python.pythonPath": "venv/bin/python" -} \ No newline at end of file From a608e2d85cddb25337743f1bb806f5fad51e7ac0 Mon Sep 17 00:00:00 2001 From: Omidiora Samuel <8148384+samparsky@users.noreply.github.com> Date: Mon, 22 Oct 2018 16:21:17 +0100 Subject: [PATCH 232/308] added more doc --- slither/detectors/functions/external_function.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/slither/detectors/functions/external_function.py b/slither/detectors/functions/external_function.py index b5086894f..41a02af7c 100644 --- a/slither/detectors/functions/external_function.py +++ b/slither/detectors/functions/external_function.py @@ -15,11 +15,11 @@ class ExternalFunction(AbstractDetector): @staticmethod def detect_function_calls(func): - """ Returns a list of InternallCall, InternalDynamicCall, SolidityCall + """ Returns a list of InternallCall, InternalDynamicCall, SolidityCall, HighLevelCall calls made in a function Returns: - (list): List of all InternallCall, InternalDynamicCall, SolidityCall + (list): List of all InternallCall, InternalDynamicCall, SolidityCall, HighLevelCall """ result = [] for node in func.nodes: @@ -40,7 +40,12 @@ class ExternalFunction(AbstractDetector): public_function_calls = [] for contract in self.slither.contracts_derived: + """ + Returns list of InternallCall, InternalDynamicCall, HighLevelCall, SolidityCall calls + in contract functions + """ func_list = self.detect_external(contract) + # appends the list to public function calls public_function_calls.extend(func_list) for c in self.contracts: From dd1db0a076fb9345c15d8707bf6b8471534b8f94 Mon Sep 17 00:00:00 2001 From: Omidiora Samuel <8148384+samparsky@users.noreply.github.com> Date: Mon, 22 Oct 2018 16:23:36 +0100 Subject: [PATCH 233/308] added more doc added more doc change func name fixed supposed changes ignore contract with internal dynamic call --- .../detectors/functions/external_function.py | 54 +++++++++++++------ tests/external_function.sol | 39 +++++++++++++- tests/external_function_test_2.sol | 2 +- 3 files changed, 75 insertions(+), 20 deletions(-) diff --git a/slither/detectors/functions/external_function.py b/slither/detectors/functions/external_function.py index 41a02af7c..4ca4cb0c8 100644 --- a/slither/detectors/functions/external_function.py +++ b/slither/detectors/functions/external_function.py @@ -6,6 +6,9 @@ from slither.slithir.operations import (InternalCall, InternalDynamicCall) class ExternalFunction(AbstractDetector): """ Detect public function that could be declared as external + + IMPROVEMENT: Add InternalDynamicCall check + https://github.com/trailofbits/slither/pull/53#issuecomment-432809950 """ ARGUMENT = 'external-function' @@ -15,54 +18,71 @@ class ExternalFunction(AbstractDetector): @staticmethod def detect_function_calls(func): - """ Returns a list of InternallCall, InternalDynamicCall, SolidityCall, HighLevelCall + """ Returns a list of InternallCall, InternalDynamicCall, SolidityCall calls made in a function Returns: - (list): List of all InternallCall, InternalDynamicCall, SolidityCall, HighLevelCall + (list): List of all InternallCall, InternalDynamicCall, SolidityCall """ result = [] + containsInternalDynamicCall = False for node in func.nodes: for ir in node.irs: - if isinstance(ir, ( InternalCall, InternalDynamicCall, HighLevelCall, SolidityCall )): + if isinstance(ir, (InternalDynamicCall)): + containsInternalDynamicCall = True + break + if isinstance(ir, (InternalCall, SolidityCall)): result.append(ir.function) - return result + return result, containsInternalDynamicCall - def detect_external(self, contract): + def detect_public(self, contract): ret = [] - for f in [f for f in contract.functions if f.contract == contract]: - calls = self.detect_function_calls(f) - ret.extend(calls) - return ret + containsInternalDynamicCall = False + for f in contract.all_functions_called: + calls, containsInternalDynamicCall = self.detect_function_calls(f) + if containsInternalDynamicCall: + break + else: + ret.extend(calls) + return ret, containsInternalDynamicCall def detect(self): results = [] public_function_calls = [] + + """ Exclude contracts with InternalDynamicCall + """ + excluded_contracts = [] + for contract in self.slither.contracts_derived: """ Returns list of InternallCall, InternalDynamicCall, HighLevelCall, SolidityCall calls in contract functions """ - func_list = self.detect_external(contract) - # appends the list to public function calls - public_function_calls.extend(func_list) + func_list, exclude_contract = self.detect_public(contract) + + if exclude_contract: + excluded_contracts.append(contract) + else: + # appends the list to public function calls + public_function_calls.extend(func_list) - for c in self.contracts: + for c in [ contract for contract in self.contracts if contract not in excluded_contracts ]: """ Returns a list of functions with public visibility in contract that doesn't - exist in the public_function_calls_list + exist in the public_function_calls This means that the public function doesn't have any InternallCall, InternalDynamicCall, SolidityCall call attached to it hence it can be declared as external """ - functions = [ f for f in c.functions if f.visibility == "public" + functions = [ func for func in c.functions if func.visibility == "public" and - f.contract == c + func.contract == c and - f not in public_function_calls ] + func not in public_function_calls ] for func in functions: func_name = func.name diff --git a/tests/external_function.sol b/tests/external_function.sol index 02f32bdd6..30745e194 100644 --- a/tests/external_function.sol +++ b/tests/external_function.sol @@ -10,17 +10,22 @@ contract ItemTwo is ItemFour { contract ItemThree { - function helloThree() { + function helloThree() public { } - function helloTwo() internal { + function helloTwo() public { } function helloOne() public { } + + function my_func() internal returns(bool){ + return true; + } + } contract ItemOne is ItemTwo { @@ -32,4 +37,34 @@ contract ItemOne is ItemTwo { ItemFour four = new ItemFour(); four.helloFour(); } +} + +contract InternalCall { + + function() returns(uint) ptr; + + function set_test1() external{ + ptr = test1; + } + + function set_test2() external{ + ptr = test2; + } + + function test1() public returns(uint){ + return 1; + } + + function test2() public returns(uint){ + return 2; + } + + function test3() public returns(uint){ + return 3; + } + + function exec() external returns(uint){ + return ptr(); + } + } \ No newline at end of file diff --git a/tests/external_function_test_2.sol b/tests/external_function_test_2.sol index 7a99a7ef5..08332edbf 100644 --- a/tests/external_function_test_2.sol +++ b/tests/external_function_test_2.sol @@ -1,7 +1,7 @@ pragma solidity ^0.4.24; contract ItemFour { - function helloFour() { + function helloFour() external { uint256 i = 0; } } \ No newline at end of file From 8ca9e2353ead977552284603cadd95a20ed85eb0 Mon Sep 17 00:00:00 2001 From: Omidiora Samuel <8148384+samparsky@users.noreply.github.com> Date: Fri, 26 Oct 2018 12:35:12 +0100 Subject: [PATCH 234/308] added: ignore contracts with internal dynamic calls --- scripts/travis_test.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index b3378aaad..e42e14027 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -31,6 +31,7 @@ test_slither tests/inline_assembly_library.sol "assembly" 2 test_slither tests/naming_convention.sol "naming-convention" 10 test_slither tests/low_level_calls.sol "low-level-calls" 1 test_slither tests/const_state_variables.sol "const-candidates-state" 2 +test_slither tests/external_function.sol "external-function" 4 ### Test scripts From d5f10b49d47a9333e0f588915ad9b98f439cf300 Mon Sep 17 00:00:00 2001 From: Omidiora Samuel <8148384+samparsky@users.noreply.github.com> Date: Fri, 26 Oct 2018 12:57:42 +0100 Subject: [PATCH 235/308] fixed test issues --- slither/detectors/functions/external_function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/detectors/functions/external_function.py b/slither/detectors/functions/external_function.py index 4ca4cb0c8..83f60b2cd 100644 --- a/slither/detectors/functions/external_function.py +++ b/slither/detectors/functions/external_function.py @@ -67,7 +67,7 @@ class ExternalFunction(AbstractDetector): else: # appends the list to public function calls public_function_calls.extend(func_list) - + for c in [ contract for contract in self.contracts if contract not in excluded_contracts ]: """ Returns a list of functions with public visibility in contract that doesn't From 87b43c38c056092afa9d882944f756dce4fa484a Mon Sep 17 00:00:00 2001 From: Omidiora Samuel <8148384+samparsky@users.noreply.github.com> Date: Fri, 26 Oct 2018 13:01:10 +0100 Subject: [PATCH 236/308] fixed proper doc --- slither/detectors/functions/external_function.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/slither/detectors/functions/external_function.py b/slither/detectors/functions/external_function.py index 83f60b2cd..44fea2ce2 100644 --- a/slither/detectors/functions/external_function.py +++ b/slither/detectors/functions/external_function.py @@ -57,8 +57,10 @@ class ExternalFunction(AbstractDetector): for contract in self.slither.contracts_derived: """ - Returns list of InternallCall, InternalDynamicCall, HighLevelCall, SolidityCall calls + Returns list of InternallCall, SolidityCall calls in contract functions + + And excludes contract with InternalDynamicCall """ func_list, exclude_contract = self.detect_public(contract) @@ -67,14 +69,14 @@ class ExternalFunction(AbstractDetector): else: # appends the list to public function calls public_function_calls.extend(func_list) - + for c in [ contract for contract in self.contracts if contract not in excluded_contracts ]: """ Returns a list of functions with public visibility in contract that doesn't exist in the public_function_calls This means that the public function doesn't have any - InternallCall, InternalDynamicCall, SolidityCall call + InternallCall, SolidityCall call attached to it hence it can be declared as external """ From 84bda253ff40ddeb0be83dbaa277926f01b858ba Mon Sep 17 00:00:00 2001 From: Omidiora Samuel <8148384+samparsky@users.noreply.github.com> Date: Fri, 26 Oct 2018 13:03:54 +0100 Subject: [PATCH 237/308] fixed proper doc --- slither/detectors/functions/external_function.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/slither/detectors/functions/external_function.py b/slither/detectors/functions/external_function.py index 44fea2ce2..6dbea9039 100644 --- a/slither/detectors/functions/external_function.py +++ b/slither/detectors/functions/external_function.py @@ -18,11 +18,12 @@ class ExternalFunction(AbstractDetector): @staticmethod def detect_function_calls(func): - """ Returns a list of InternallCall, InternalDynamicCall, SolidityCall + """ Returns a list of InternallCall, SolidityCall calls made in a function Returns: - (list): List of all InternallCall, InternalDynamicCall, SolidityCall + (list): List of all InternallCall, SolidityCall + (bool): exclude the contract if it contains InternalDynamicCall """ result = [] containsInternalDynamicCall = False From 32ba0c094d18938755ba243b231f799944d7ba2d Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Fri, 26 Oct 2018 08:53:29 -0400 Subject: [PATCH 238/308] Update README.md --- README.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e5cfa6dd9..1f98a195f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Slither, the Solidity source analyzer [![Build Status](https://travis-ci.com/trailofbits/slither.svg?token=JEF97dFy1QsDCfQ2Wusd&branch=master)](https://travis-ci.com/trailofbits/slither) [![Slack Status](https://empireslacking.herokuapp.com/badge.svg)](https://empireslacking.herokuapp.com) +[![PyPI version](https://badge.fury.io/py/slither-analyzer.svg)](https://badge.fury.io/py/slither-analyzer) Slither is a Solidity static analysis framework written in Python 3. It runs a suite of vulnerability detectors, prints visual information about contract details, and provides an API to easily write custom analyses. Slither enables developers to find vulnerabilities, enhance their code comphrehension, and quickly prototype custom analyses. @@ -8,7 +9,7 @@ Slither is a Solidity static analysis framework written in Python 3. It runs a s * Detects vulnerable Solidity code with low false positives * Identifies where the error condition occurs in the source code -* Easy integration into continuous integration pipelines +* Easy integration into continuous integration and Truffle builds * Built-in 'printers' quickly report crucial contract information * Detector API to write custom analyses in Python * Ability to analyze contracts written with Solidity >= 0.4 @@ -16,6 +17,13 @@ Slither is a Solidity static analysis framework written in Python 3. It runs a s ## Usage +Run Slither on a Truffle application: +``` +truffle compile +slither . +``` + +Run Slither on a single file: ``` $ slither tests/uninitialized.sol # argument can be file, folder or glob, be sure to quote the argument when using a glob [..] @@ -23,7 +31,7 @@ INFO:Detectors:Uninitialized state variables in tests/uninitialized.sol, Contrac [..] ``` -If Slither is run on a directory, it will run on every `.sol` file of the directory. All vulnerability checks are run by default. +If Slither is run on a directory, it will run on every `.sol` file in the directory. ### Configuration @@ -35,14 +43,14 @@ If Slither is run on a directory, it will run on every `.sol` file of the direct ## Detectors -By default, all the detectors are run. Use `--detectors` comma-separated list of detectors to run. +By default, all the detectors are run. Num | Detector | What it Detects | Impact | Confidence --- | --- | --- | --- | --- 1 | `suicidal` | Suicidal functions | High | High 2 | `uninitialized-state` | Uninitialized state variables | High | High 3 | `uninitialized-storage` | Uninitialized storage variables | High | High -4 | `arbitrary-send` | Functions that send ether to an arbitrary destination | High | Medium +4 | `arbitrary-send` | Functions that send ether to arbitrary destinations | High | Medium 5 | `reentrancy` | Reentrancy vulnerabilities | High | Medium 6 | `locked-ether` | Contracts that lock ether | Medium | High 7 | `tx-origin` | Dangerous usage of `tx.origin` | Medium | Medium @@ -51,10 +59,9 @@ Num | Detector | What it Detects | Impact | Confidence 10 | `low-level-calls` | Low level calls | Informational | High 11 | `naming-convention` | Conformance to Solidity naming conventions | Informational | High 12 | `pragma` | If different pragma directives are used | Informational | High -13 | `solc-version` | If an old version of Solidity used (<0.4.23) | Informational | High +13 | `solc-version` | Old versions of Solidity (< 0.4.23) | Informational | High 14 | `unused-state` | Unused state variables | Informational | High - [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. ### Printers @@ -74,6 +81,7 @@ Num | Printer | Description ## How to install Slither requires Python 3.6+ and [solc](https://github.com/ethereum/solidity/), the Solidity compiler. + ### Using Pip ``` From dab823e8f4ec1f07d83171afc2b48c6d37a9c6e1 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Fri, 26 Oct 2018 08:57:23 -0400 Subject: [PATCH 239/308] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1f98a195f..b75ac79ee 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Num | Detector | What it Detects | Impact | Confidence ### Printers -Use `--printers` comma-separated list of printers. +To run a printer, use `--printers` and a comma-separated list of printers. Num | Printer | Description --- | --- | --- @@ -76,7 +76,7 @@ Num | Printer | Description 4 | `inheritance` | the inheritance relation between contracts 5 | `inheritance-graph` | the inheritance graph 6 | `slithir` | the slithIR -7 | `vars-and-auth` | the state variables written and the authorization of the functions +7 | `vars-and-auth` | state variables written and the authorization of the functions ## How to install From 44583294f2b83f9fe497d529fc152c19fcc72e4b Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 26 Oct 2018 16:27:02 +0100 Subject: [PATCH 240/308] ExternalFunction: Simplify codebase and example --- README.md | 13 +-- .../detectors/functions/external_function.py | 81 ++++++------------- tests/external_function.sol | 26 +++--- tests/external_function_test_2.sol | 6 +- 4 files changed, 47 insertions(+), 79 deletions(-) diff --git a/README.md b/README.md index 8a21c3159..123f5ab2f 100644 --- a/README.md +++ b/README.md @@ -48,12 +48,13 @@ Num | Detector | What it Detects | Impact | Confidence 7 | `tx-origin` | Dangerous usage of `tx.origin` | Medium | Medium 8 | `assembly` | Assembly usage | Informational | High 9 | `const-candidates-state` | State variables that could be declared constant | Informational | High -10 | `low-level-calls` | Low level calls | Informational | High -11 | `naming-convention` | Conformance to Solidity naming conventions | Informational | High -12 | `pragma` | If different pragma directives are used | Informational | High -13 | `solc-version` | If an old version of Solidity used (<0.4.23) | Informational | High -14 | `unused-state` | Unused state variables | Informational | High -15 | `external-function` | Public functions that could be declared as external | Informational | High +10 | `external-function` | Public functions that could be declared as external | Informational | High +11 | `low-level-calls` | Low level calls | Informational | High +12 | `naming-convention` | Conformance to Solidity naming conventions | Informational | High +13 | `pragma` | If different pragma directives are used | Informational | High +14 | `solc-version` | If an old version of Solidity used (<0.4.23) | Informational | High +15 | `unused-state` | Unused state variables | Informational | High + [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. diff --git a/slither/detectors/functions/external_function.py b/slither/detectors/functions/external_function.py index 6dbea9039..d1b08a417 100644 --- a/slither/detectors/functions/external_function.py +++ b/slither/detectors/functions/external_function.py @@ -17,86 +17,53 @@ class ExternalFunction(AbstractDetector): CONFIDENCE = DetectorClassification.HIGH @staticmethod - def detect_function_calls(func): + def detect_functions_called(contract): """ Returns a list of InternallCall, SolidityCall calls made in a function Returns: (list): List of all InternallCall, SolidityCall - (bool): exclude the contract if it contains InternalDynamicCall """ result = [] - containsInternalDynamicCall = False - for node in func.nodes: - for ir in node.irs: - if isinstance(ir, (InternalDynamicCall)): - containsInternalDynamicCall = True - break - if isinstance(ir, (InternalCall, SolidityCall)): - result.append(ir.function) - return result, containsInternalDynamicCall + for func in contract.all_functions_called: + for node in func.nodes: + for ir in node.irs: + if isinstance(ir, (InternalCall, SolidityCall)): + result.append(ir.function) + return result - def detect_public(self, contract): - ret = [] - containsInternalDynamicCall = False - for f in contract.all_functions_called: - calls, containsInternalDynamicCall = self.detect_function_calls(f) - if containsInternalDynamicCall: - break - else: - ret.extend(calls) - return ret, containsInternalDynamicCall + @staticmethod + def _contains_internal_dynamic_call(contract): + for func in contract.all_functions_called: + for node in func.nodes: + for ir in node.irs: + if isinstance(ir, (InternalDynamicCall)): + return True + return False def detect(self): results = [] public_function_calls = [] - """ Exclude contracts with InternalDynamicCall - """ - excluded_contracts = [] - for contract in self.slither.contracts_derived: - """ - Returns list of InternallCall, SolidityCall calls - in contract functions - - And excludes contract with InternalDynamicCall - """ - func_list, exclude_contract = self.detect_public(contract) - - if exclude_contract: - excluded_contracts.append(contract) - else: - # appends the list to public function calls - public_function_calls.extend(func_list) - - for c in [ contract for contract in self.contracts if contract not in excluded_contracts ]: - """ - Returns a list of functions with public visibility in contract that doesn't - exist in the public_function_calls - - This means that the public function doesn't have any - InternallCall, SolidityCall call - attached to it hence it can be declared as external + if self._contains_internal_dynamic_call(contract): + continue - """ - functions = [ func for func in c.functions if func.visibility == "public" - and - func.contract == c - and - func not in public_function_calls ] + func_list = self.detect_functions_called(contract) + public_function_calls.extend(func_list) - for func in functions: + for func in [f for f in contract.functions if f.visibility == 'public' and\ + not f in public_function_calls]: func_name = func.name txt = "Public function in {} Contract: {}, Function: {} should be declared external" info = txt.format(self.filename, - c.name, + contract.name, func_name) self.log(info) results.append({'vuln': 'ExternalFunc', 'sourceMapping': func.source_mapping, 'filename': self.filename, - 'contract': c.name, + 'contract': contract.name, 'func': func_name}) - return results \ No newline at end of file + return results diff --git a/tests/external_function.sol b/tests/external_function.sol index 30745e194..040bb9329 100644 --- a/tests/external_function.sol +++ b/tests/external_function.sol @@ -2,23 +2,23 @@ pragma solidity ^0.4.24; import "./external_function_test_2.sol"; -contract ItemTwo is ItemFour { - function helloTwo() public { +contract ContractWithFunctionCalledSuper is ContractWithFunctionCalled { + function callWithSuper() public { uint256 i = 0; } } -contract ItemThree { +contract ContractWithFunctionNotCalled { - function helloThree() public { + function funcNotCalled3() public { } - function helloTwo() public { + function funcNotCalled2() public { } - function helloOne() public { + function funcNotCalled() public { } @@ -28,14 +28,14 @@ contract ItemThree { } -contract ItemOne is ItemTwo { - function helloOne() public { +contract ContractWithFunctionNotCalled2 is ContractWithFunctionCalledSuper { + function funcNotCalled() public { uint256 i = 0; - address three = new ItemThree(); + address three = new ContractWithFunctionNotCalled(); three.call(bytes4(keccak256("helloTwo()"))); - super.helloTwo(); - ItemFour four = new ItemFour(); - four.helloFour(); + super.callWithSuper(); + ContractWithFunctionCalled c = new ContractWithFunctionCalled(); + c.funcCalled(); } } @@ -67,4 +67,4 @@ contract InternalCall { return ptr(); } -} \ No newline at end of file +} diff --git a/tests/external_function_test_2.sol b/tests/external_function_test_2.sol index 08332edbf..406494631 100644 --- a/tests/external_function_test_2.sol +++ b/tests/external_function_test_2.sol @@ -1,7 +1,7 @@ pragma solidity ^0.4.24; -contract ItemFour { - function helloFour() external { +contract ContractWithFunctionCalled { + function funcCalled() external { uint256 i = 0; } -} \ No newline at end of file +} From f28f3704c4c505c3f904899d8a3f0ca3939b390e Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 26 Oct 2018 16:52:25 +0100 Subject: [PATCH 241/308] NamingConvention: Clean code --- .../naming_convention/naming_convention.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index 02aafd4c8..f3312c1f0 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -44,7 +44,7 @@ class NamingConvention(AbstractDetector): results = [] for contract in self.contracts: - if self.is_cap_words(contract.name) is False: + if not self.is_cap_words(contract.name): info = "Contract '{}' is not in CapWords".format(contract.name) self.log(info) @@ -57,7 +57,7 @@ class NamingConvention(AbstractDetector): if struct.contract != contract: continue - if self.is_cap_words(struct.name) is False: + if not self.is_cap_words(struct.name): info = "Struct '{}' is not in CapWords, Contract: '{}' ".format(struct.name, contract.name) self.log(info) @@ -71,7 +71,7 @@ class NamingConvention(AbstractDetector): if event.contract != contract: continue - if self.is_cap_words(event.name) is False: + if not self.is_cap_words(event.name): info = "Event '{}' is not in CapWords, Contract: '{}' ".format(event.name, contract.name) self.log(info) @@ -85,7 +85,7 @@ class NamingConvention(AbstractDetector): if func.contract != contract: continue - if self.is_mixed_case(func.name) is False: + if not self.is_mixed_case(func.name): info = "Function '{}' is not in mixedCase, Contract: '{}' ".format(func.name, contract.name) self.log(info) @@ -97,10 +97,10 @@ class NamingConvention(AbstractDetector): for argument in func.parameters: if argument in func.variables_read_or_written: - incorrect_naming = self.is_mixed_case(argument.name) is False + correct_naming = self.is_mixed_case(argument.name) else: - incorrect_naming = self.is_mixed_case_with_underscore(argument.name) is False - if incorrect_naming: + correct_naming = self.is_mixed_case_with_underscore(argument.name) + if not correct_naming: info = "Parameter '{}' is not in mixedCase, Contract: '{}', Function: '{}'' " \ .format(argument.name, argument.name, contract.name) self.log(info) @@ -117,7 +117,7 @@ class NamingConvention(AbstractDetector): continue if self.should_avoid_name(var.name): - if self.is_upper_case_with_underscores(var.name) is False: + if not self.is_upper_case_with_underscores(var.name): info = "Variable '{}' l, O, I should not be used, Contract: '{}' " \ .format(var.name, contract.name) self.log(info) @@ -133,7 +133,7 @@ class NamingConvention(AbstractDetector): if var.name in ['symbol', 'name', 'decimals']: continue - if self.is_upper_case_with_underscores(var.name) is False: + if not self.is_upper_case_with_underscores(var.name): info = "Constant '{}' is not in UPPER_CASE_WITH_UNDERSCORES, Contract: '{}' " \ .format(var.name, contract.name) self.log(info) @@ -145,10 +145,10 @@ class NamingConvention(AbstractDetector): 'sourceMapping': var.source_mapping}) else: if var.visibility == 'private': - incorrect_naming = self.is_mixed_case_with_underscore(var.name) is False + correct_naming = self.is_mixed_case_with_underscore(var.name) else: - incorrect_naming = self.is_mixed_case(var.name) is False - if incorrect_naming: + correct_naming = self.is_mixed_case(var.name) + if not correct_naming: info = "Variable '{}' is not in mixedCase, Contract: '{}' ".format(var.name, contract.name) self.log(info) @@ -162,7 +162,7 @@ class NamingConvention(AbstractDetector): if enum.contract != contract: continue - if self.is_cap_words(enum.name) is False: + if not self.is_cap_words(enum.name): info = "Enum '{}' is not in CapWords, Contract: '{}' ".format(enum.name, contract.name) self.log(info) @@ -176,7 +176,7 @@ class NamingConvention(AbstractDetector): if modifier.contract != contract: continue - if self.is_mixed_case(modifier.name) is False: + if not self.is_mixed_case(modifier.name): info = "Modifier '{}' is not in mixedCase, Contract: '{}' ".format(modifier.name, contract.name) self.log(info) From c2187307836feb9a2c713d22a3066a28b785bde8 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 26 Oct 2018 17:07:17 +0100 Subject: [PATCH 242/308] Update printers description --- README.md | 15 ++++++++------- slither/printers/call/call_graph.py | 2 +- slither/printers/functions/authorization.py | 2 +- slither/printers/inheritance/inheritance.py | 2 +- slither/printers/inheritance/inheritance_graph.py | 2 +- slither/printers/summary/contract.py | 2 +- slither/printers/summary/function.py | 2 +- slither/printers/summary/slithir.py | 2 +- 8 files changed, 15 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index ceb053040..f78def51e 100644 --- a/README.md +++ b/README.md @@ -71,13 +71,14 @@ To run a printer, use `--printers` and a comma-separated list of printers. Num | Printer | Description --- | --- | --- -1 | `call-graph` | the call graph -2 | `contract-summary` | a summary of the contract -3 | `function-summary` | the summary of the functions -4 | `inheritance` | the inheritance relation between contracts -5 | `inheritance-graph` | the inheritance graph -6 | `slithir` | the slithIR -7 | `vars-and-auth` | state variables written and the authorization of the functions +1 | `call-graph` | Export the call-graph of the contracts to a dot file +2 | `contract-summary` | Print a summary of the contracts +3 | `function-summary` | Print a summary of the functions +4 | `inheritance` | Print the inheritance relations between contracts +5 | `inheritance-graph` | Export the inheritance graph of each contract to a dot file +6 | `slithir` | Print the slithIR representation of the functions +7 | `vars-and-auth` | Print the state variables written and the authorization of the functions + ## How to install diff --git a/slither/printers/call/call_graph.py b/slither/printers/call/call_graph.py index 18058c6c6..bc446180d 100644 --- a/slither/printers/call/call_graph.py +++ b/slither/printers/call/call_graph.py @@ -40,7 +40,7 @@ def _node(node, label=None): class PrinterCallGraph(AbstractPrinter): ARGUMENT = 'call-graph' - HELP = 'the call graph' + HELP = 'Export the call-graph of the contracts to a dot file' def __init__(self, slither, logger): super(PrinterCallGraph, self).__init__(slither, logger) diff --git a/slither/printers/functions/authorization.py b/slither/printers/functions/authorization.py index 5880d6c11..ff1666331 100644 --- a/slither/printers/functions/authorization.py +++ b/slither/printers/functions/authorization.py @@ -9,7 +9,7 @@ from slither.core.declarations.function import Function class PrinterWrittenVariablesAndAuthorization(AbstractPrinter): ARGUMENT = 'vars-and-auth' - HELP = 'the state variables written and the authorization of the functions' + HELP = 'Print the state variables written and the authorization of the functions' @staticmethod def get_msg_sender_checks(function): diff --git a/slither/printers/inheritance/inheritance.py b/slither/printers/inheritance/inheritance.py index 3d8c0ad63..d7b4a6b11 100644 --- a/slither/printers/inheritance/inheritance.py +++ b/slither/printers/inheritance/inheritance.py @@ -10,7 +10,7 @@ from slither.utils.colors import blue, green class PrinterInheritance(AbstractPrinter): ARGUMENT = 'inheritance' - HELP = 'the inheritance relation between contracts' + HELP = 'Print the inheritance relations between contracts' def _get_child_contracts(self, base): # Generate function to get all child contracts of a base contract diff --git a/slither/printers/inheritance/inheritance_graph.py b/slither/printers/inheritance/inheritance_graph.py index 680bb8ed8..f76e9fb07 100644 --- a/slither/printers/inheritance/inheritance_graph.py +++ b/slither/printers/inheritance/inheritance_graph.py @@ -12,7 +12,7 @@ from slither.printers.abstract_printer import AbstractPrinter class PrinterInheritanceGraph(AbstractPrinter): ARGUMENT = 'inheritance-graph' - HELP = 'the inheritance graph' + HELP = 'Export the inheritance graph of each contract to a dot file' def __init__(self, slither, logger): super(PrinterInheritanceGraph, self).__init__(slither, logger) diff --git a/slither/printers/summary/contract.py b/slither/printers/summary/contract.py index bbcf1e9b3..51409ce91 100644 --- a/slither/printers/summary/contract.py +++ b/slither/printers/summary/contract.py @@ -8,7 +8,7 @@ from slither.utils.colors import blue, green, magenta class ContractSummary(AbstractPrinter): ARGUMENT = 'contract-summary' - HELP = 'a summary of the contract' + HELP = 'Print a summary of the contracts' def output(self, _filename): """ diff --git a/slither/printers/summary/function.py b/slither/printers/summary/function.py index 1fca36b68..a56552035 100644 --- a/slither/printers/summary/function.py +++ b/slither/printers/summary/function.py @@ -8,7 +8,7 @@ from slither.printers.abstract_printer import AbstractPrinter class FunctionSummary(AbstractPrinter): ARGUMENT = 'function-summary' - HELP = 'the summary of the functions' + HELP = 'Print a summary of the functions' @staticmethod def _convert(l): diff --git a/slither/printers/summary/slithir.py b/slither/printers/summary/slithir.py index 472bfbda6..755dfd169 100644 --- a/slither/printers/summary/slithir.py +++ b/slither/printers/summary/slithir.py @@ -8,7 +8,7 @@ from slither.utils.colors import blue, green, magenta class PrinterSlithIR(AbstractPrinter): ARGUMENT = 'slithir' - HELP = 'the slithIR' + HELP = 'Print the slithIR representation of the functions' def output(self, _filename): """ From 519a8698db4a627540c6d4da32c0142a86419b66 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 26 Oct 2018 17:09:08 +0100 Subject: [PATCH 243/308] Dont show the backdoor detector example in --list-detectors --- slither/utils/command_line.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/slither/utils/command_line.py b/slither/utils/command_line.py index 8ef40df9f..7233b541e 100644 --- a/slither/utils/command_line.py +++ b/slither/utils/command_line.py @@ -43,6 +43,9 @@ def output_detectors(detector_classes): detectors_list = [] for detector in detector_classes: argument = detector.ARGUMENT + # dont show the backdoor example + if argument == 'backdoor': + continue help_info = detector.HELP impact = detector.IMPACT confidence = classification_txt[detector.CONFIDENCE] From 452811e1ee79ce7af6f4dac2f23d66cdb1f9bf2b Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 26 Oct 2018 17:17:29 +0100 Subject: [PATCH 244/308] ExternalFunction: exclude constructor --- slither/detectors/functions/external_function.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/slither/detectors/functions/external_function.py b/slither/detectors/functions/external_function.py index d1b08a417..9cb19909b 100644 --- a/slither/detectors/functions/external_function.py +++ b/slither/detectors/functions/external_function.py @@ -54,7 +54,8 @@ class ExternalFunction(AbstractDetector): public_function_calls.extend(func_list) for func in [f for f in contract.functions if f.visibility == 'public' and\ - not f in public_function_calls]: + not f in public_function_calls and\ + not f.is_constructor]: func_name = func.name txt = "Public function in {} Contract: {}, Function: {} should be declared external" info = txt.format(self.filename, From 517a73ca33c824278ea915842486b097cace8f60 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 26 Oct 2018 17:34:52 +0100 Subject: [PATCH 245/308] Uninitialized state variable: remove var init at declaration --- slither/detectors/variables/uninitialized_state_variables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index 17d60e023..a7c6111ba 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -48,7 +48,7 @@ class UninitializedStateVarsDetection(AbstractDetector): def detect_uninitialized(self, contract): written_variables = self.written_variables(contract) return [(variable, contract.get_functions_reading_from_variable(variable)) - for variable in contract.state_variables if variable not in written_variables] + for variable in contract.state_variables if variable not in written_variables and not variable.expression] def detect(self): """ Detect uninitialized state variables From 12d0013095f8a49f7b13e7c9e31249d6fdaf37d0 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 26 Oct 2018 18:41:33 +0100 Subject: [PATCH 246/308] UninitializedStateVarsDetection: dont report unused variables SlithIr Printer: printer modifiers IRs SlithIR: fix missing condition conversion --- .../variables/uninitialized_state_variables.py | 12 +++++++++++- slither/printers/summary/slithir.py | 10 ++++++++++ slither/slithir/convert.py | 6 ++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index a7c6111ba..427e3ad4a 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -45,10 +45,20 @@ class UninitializedStateVarsDetection(AbstractDetector): return ret + @staticmethod + def read_variables(contract): + ret = [] + for f in contract.all_functions_called + contract.modifiers: + ret += f.state_variables_read + return ret + def detect_uninitialized(self, contract): written_variables = self.written_variables(contract) + read_variables = self.read_variables(contract) return [(variable, contract.get_functions_reading_from_variable(variable)) - for variable in contract.state_variables if variable not in written_variables and not variable.expression] + for variable in contract.state_variables if variable not in written_variables and\ + not variable.expression and\ + variable in read_variables] def detect(self): """ Detect uninitialized state variables diff --git a/slither/printers/summary/slithir.py b/slither/printers/summary/slithir.py index 755dfd169..d48d3bf87 100644 --- a/slither/printers/summary/slithir.py +++ b/slither/printers/summary/slithir.py @@ -29,4 +29,14 @@ class PrinterSlithIR(AbstractPrinter): print('\t\tIRs:') for ir in node.irs: print('\t\t\t{}'.format(ir)) + for modifier in contract.modifiers: + if modifier.contract == contract: + print('\tModifier {}'.format(modifier.full_name)) + for node in modifier.nodes: + print(node) + if node.expression: + print('\t\tExpression: {}'.format(node.expression)) + print('\t\tIRs:') + for ir in node.irs: + print('\t\t\t{}'.format(ir)) self.info(txt) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 8e90c256c..ecffadfdd 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -636,6 +636,12 @@ def convert_expression(expression, node): if isinstance(expression, Identifier) and node.type == NodeType.RETURN: result = [Return(expression.value)] return result + if isinstance(expression, Literal) and node.type in [NodeType.IF, NodeType.IFLOOP]: + result = [Condition(Constant(expression.value))] + return result + if isinstance(expression, Identifier) and node.type in [NodeType.IF, NodeType.IFLOOP]: + result = [Condition(expression.value)] + return result visitor = ExpressionToSlithIR(expression) result = visitor.result() From 6689a544763c30f2c4b4840dd0f166dfd722c4e3 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 26 Oct 2018 19:05:33 +0100 Subject: [PATCH 247/308] Rename const-candidates-state to constable-states --- README.md | 2 +- slither/detectors/variables/possible_const_state_variables.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f78def51e..97522379f 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Num | Detector | What it Detects | Impact | Confidence 6 | `locked-ether` | Contracts that lock ether | Medium | High 7 | `tx-origin` | Dangerous usage of `tx.origin` | Medium | Medium 8 | `assembly` | Assembly usage | Informational | High -9 | `const-candidates-state` | State variables that could be declared constant | Informational | High +9 | `constable-states` | State variables that could be declared constant | Informational | High 10 | `external-function` | Public functions that could be declared as external | Informational | High 11 | `low-level-calls` | Low level calls | Informational | High 12 | `naming-convention` | Conformance to Solidity naming conventions | Informational | High diff --git a/slither/detectors/variables/possible_const_state_variables.py b/slither/detectors/variables/possible_const_state_variables.py index 53f4ca697..e19a389d1 100644 --- a/slither/detectors/variables/possible_const_state_variables.py +++ b/slither/detectors/variables/possible_const_state_variables.py @@ -18,7 +18,7 @@ class ConstCandidateStateVars(AbstractDetector): Reference: https://solidity.readthedocs.io/en/latest/contracts.html#constant-state-variables """ - ARGUMENT = 'const-candidates-state' + ARGUMENT = 'constable-states' HELP = 'State variables that could be declared constant' IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH From 9b818aab16e0c082e61ba491d0ce58bab0461ebf Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 26 Oct 2018 19:05:33 +0100 Subject: [PATCH 248/308] Rename const-candidates-state to constable-states --- README.md | 2 +- slither/detectors/variables/possible_const_state_variables.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f78def51e..97522379f 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Num | Detector | What it Detects | Impact | Confidence 6 | `locked-ether` | Contracts that lock ether | Medium | High 7 | `tx-origin` | Dangerous usage of `tx.origin` | Medium | Medium 8 | `assembly` | Assembly usage | Informational | High -9 | `const-candidates-state` | State variables that could be declared constant | Informational | High +9 | `constable-states` | State variables that could be declared constant | Informational | High 10 | `external-function` | Public functions that could be declared as external | Informational | High 11 | `low-level-calls` | Low level calls | Informational | High 12 | `naming-convention` | Conformance to Solidity naming conventions | Informational | High diff --git a/slither/detectors/variables/possible_const_state_variables.py b/slither/detectors/variables/possible_const_state_variables.py index 53f4ca697..e19a389d1 100644 --- a/slither/detectors/variables/possible_const_state_variables.py +++ b/slither/detectors/variables/possible_const_state_variables.py @@ -18,7 +18,7 @@ class ConstCandidateStateVars(AbstractDetector): Reference: https://solidity.readthedocs.io/en/latest/contracts.html#constant-state-variables """ - ARGUMENT = 'const-candidates-state' + ARGUMENT = 'constable-states' HELP = 'State variables that could be declared constant' IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH From 00dad1335a696c094d4a0ac015169688167be6dc Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Fri, 26 Oct 2018 19:10:10 +0100 Subject: [PATCH 249/308] Update travis_test.sh --- scripts/travis_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index bbaa88376..d16679bdb 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -29,7 +29,7 @@ test_slither tests/arbitrary_send.sol "arbitrary-send" 2 test_slither tests/inline_assembly_contract.sol "assembly" 1 test_slither tests/inline_assembly_library.sol "assembly" 2 test_slither tests/low_level_calls.sol "low-level-calls" 1 -test_slither tests/const_state_variables.sol "const-candidates-state" 2 +test_slither tests/const_state_variables.sol "constable-state" 2 test_slither tests/external_function.sol "external-function" 4 test_slither tests/naming_convention.sol "naming-convention" 12 From 0d14c342f7dcb20a3f1e1d3115aae70b521bd448 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Fri, 26 Oct 2018 19:29:51 +0100 Subject: [PATCH 250/308] Update travis_test.sh --- scripts/travis_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index d16679bdb..6ccaf0ef6 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -29,7 +29,7 @@ test_slither tests/arbitrary_send.sol "arbitrary-send" 2 test_slither tests/inline_assembly_contract.sol "assembly" 1 test_slither tests/inline_assembly_library.sol "assembly" 2 test_slither tests/low_level_calls.sol "low-level-calls" 1 -test_slither tests/const_state_variables.sol "constable-state" 2 +test_slither tests/const_state_variables.sol "constable-states" 2 test_slither tests/external_function.sol "external-function" 4 test_slither tests/naming_convention.sol "naming-convention" 12 From 82a5461ed94a62549beb89800f04a2a868ad6f8c Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 26 Oct 2018 19:54:59 +0100 Subject: [PATCH 251/308] More strict rules on variable type --- slither/core/variables/variable.py | 6 +++++- slither/slithir/convert.py | 9 ++++++--- slither/slithir/operations/binary.py | 3 ++- slither/slithir/variables/tuple.py | 5 +++++ slither/solc_parsing/expressions/expression_parsing.py | 10 +++++----- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/slither/core/variables/variable.py b/slither/core/variables/variable.py index 64373b33a..d4e218173 100644 --- a/slither/core/variables/variable.py +++ b/slither/core/variables/variable.py @@ -3,7 +3,8 @@ """ from slither.core.source_mapping.source_mapping import SourceMapping - +from slither.core.solidity_types.type import Type +from slither.core.solidity_types.elementary_type import ElementaryType class Variable(SourceMapping): @@ -73,6 +74,9 @@ class Variable(SourceMapping): return self._visibility def set_type(self, t): + if isinstance(t, str): + t = ElementaryType(t) + assert isinstance(t, Type) or t is None self._type = t def __str__(self): diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index ecffadfdd..eec9dc0d9 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -189,7 +189,10 @@ def convert_to_low_level(ir): call = SolidityFunction('abi.{}()'.format(ir.function_name)) new_ir = SolidityCall(call, ir.nbr_arguments, ir.lvalue, ir.type_call) new_ir.arguments = ir.arguments - new_ir.lvalue.set_type(call.return_type) + if isinstance(call.return_type, list) and len(call.return_type) == 1: + new_ir.lvalue.set_type(call.return_type[0]) + else: + new_ir.lvalue.set_type(call.return_type) return new_ir elif ir.function_name in ['call', 'delegatecall', 'callcode']: new_ir = LowLevelCall(ir.destination, @@ -427,7 +430,7 @@ def propagate_types(ir, node): if return_type: if len(return_type) == 1: ir.lvalue.set_type(return_type[0]) - else: + elif len(return_type)>1: ir.lvalue.set_type(return_type) else: ir.lvalue = None @@ -483,7 +486,7 @@ def propagate_types(ir, node): return_type = ir.function.return_type if len(return_type) == 1: ir.lvalue.set_type(return_type[0]) - else: + elif len(return_type)>1: ir.lvalue.set_type(return_type) elif isinstance(ir, TypeConversion): ir.lvalue.set_type(ir.type) diff --git a/slither/slithir/operations/binary.py b/slither/slithir/operations/binary.py index 1485f01b0..733b6596b 100644 --- a/slither/slithir/operations/binary.py +++ b/slither/slithir/operations/binary.py @@ -2,6 +2,7 @@ import logging from slither.slithir.operations.lvalue import OperationWithLValue from slither.core.variables.variable import Variable from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue +from slither.core.solidity_types import ElementaryType logger = logging.getLogger("BinaryOperationIR") @@ -135,7 +136,7 @@ class Binary(OperationWithLValue): self._type = operation_type self._lvalue = result if BinaryType.return_bool(operation_type): - result.set_type('bool') + result.set_type(ElementaryType('bool')) else: result.set_type(left_variable.type) diff --git a/slither/slithir/variables/tuple.py b/slither/slithir/variables/tuple.py index 7abe34a25..74091a301 100644 --- a/slither/slithir/variables/tuple.py +++ b/slither/slithir/variables/tuple.py @@ -1,6 +1,7 @@ from slither.core.variables.variable import Variable +from slither.core.solidity_types.type import Type class TupleVariable(Variable): COUNTER = 0 @@ -22,5 +23,9 @@ class TupleVariable(Variable): def name(self): return 'TUPLE_{}'.format(self.index) + def set_type(self, t): + assert all(isinstance(x, Type) or x is None for x in t) + self._type = t + def __str__(self): return self.name diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index 688482177..02d095cc8 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -152,11 +152,11 @@ def parse_call(expression, caller_context): type_info = children[0] expression_to_parse = children[1] assert type_info['name'] in ['ElementaryTypenameExpression', - 'ElementaryTypeNameExpression', - 'Identifier', - 'TupleExpression', - 'IndexAccess', - 'MemberAccess'] + 'ElementaryTypeNameExpression', + 'Identifier', + 'TupleExpression', + 'IndexAccess', + 'MemberAccess'] expression = parse_expression(expression_to_parse, caller_context) t = TypeConversion(expression, type_call) From 5c87654c97596d5201644c23088c7bcf62e0097b Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 26 Oct 2018 21:48:39 +0100 Subject: [PATCH 252/308] SlithIR: add Balance and Lenght Operator Improve HighLevelCall -> LibraryCall conversion Call.read -> unroll parameters if nested arrays --- slither/analyses/taint/common.py | 5 ++- .../analyses/write/are_variables_written.py | 4 ++- slither/core/cfg/node.py | 31 ++++++++----------- slither/core/variables/variable.py | 2 +- .../uninitialized_state_variables.py | 9 +++--- slither/slithir/convert.py | 16 +++++++--- slither/slithir/operations/__init__.py | 2 ++ slither/slithir/operations/balance.py | 26 ++++++++++++++++ slither/slithir/operations/high_level_call.py | 11 ++++++- slither/slithir/operations/internal_call.py | 11 ++++++- .../operations/internal_dynamic_call.py | 12 ++++++- slither/slithir/operations/length.py | 26 ++++++++++++++++ slither/slithir/variables/constant.py | 5 +-- slither/slithir/variables/tuple.py | 4 --- 14 files changed, 125 insertions(+), 39 deletions(-) create mode 100644 slither/slithir/operations/balance.py create mode 100644 slither/slithir/operations/length.py diff --git a/slither/analyses/taint/common.py b/slither/analyses/taint/common.py index 484f5f2d2..944f65bab 100644 --- a/slither/analyses/taint/common.py +++ b/slither/analyses/taint/common.py @@ -1,4 +1,4 @@ -from slither.slithir.operations import (Index, Member) +from slither.slithir.operations import (Index, Member, Length, Balance) def iterate_over_irs(irs, transfer_func, taints): refs = {} @@ -6,6 +6,9 @@ def iterate_over_irs(irs, transfer_func, taints): if isinstance(ir, (Index, Member)): refs[ir.lvalue] = ir.variable_left + if isinstance(ir, (Length, Balance)): + refs[ir.lvalue] = ir.value + if isinstance(ir, Index): read = [ir.variable_left] else: diff --git a/slither/analyses/write/are_variables_written.py b/slither/analyses/write/are_variables_written.py index f76ca4874..d644e3ac2 100644 --- a/slither/analyses/write/are_variables_written.py +++ b/slither/analyses/write/are_variables_written.py @@ -4,7 +4,7 @@ from slither.core.cfg.node import NodeType from slither.core.declarations import SolidityFunction from slither.slithir.operations import (Index, Member, OperationWithLValue, - SolidityCall) + SolidityCall, Length, Balance) from slither.slithir.variables import ReferenceVariable @@ -27,6 +27,8 @@ def _visit(node, visited, variables_written, variables_to_write): continue if isinstance(ir, (Index, Member)): refs[ir.lvalue] = ir.variable_left + if isinstance(ir, (Length, Balance)): + refs[ir.lvalue] = ir.value variables_written = variables_written + [ir.lvalue] lvalue = ir.lvalue diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index 03aec416f..66194a2a1 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -3,27 +3,24 @@ """ import logging +from slither.core.children.child_function import ChildFunction +from slither.core.declarations import Contract +from slither.core.declarations.solidity_variables import (SolidityFunction, + SolidityVariable) from slither.core.source_mapping.source_mapping import SourceMapping -from slither.core.variables.variable import Variable from slither.core.variables.state_variable import StateVariable - +from slither.core.variables.variable import Variable +from slither.slithir.convert import convert_expression +from slither.slithir.operations import (Balance, HighLevelCall, Index, + InternalCall, Length, LibraryCall, + LowLevelCall, Member, + OperationWithLValue, SolidityCall) +from slither.slithir.variables import (Constant, ReferenceVariable, + TemporaryVariable, TupleVariable) from slither.visitors.expression.expression_printer import ExpressionPrinter from slither.visitors.expression.read_var import ReadVar from slither.visitors.expression.write_var import WriteVar -from slither.core.children.child_function import ChildFunction - -from slither.core.declarations.solidity_variables import SolidityVariable, SolidityFunction - -from slither.slithir.convert import convert_expression - -from slither.slithir.operations import OperationWithLValue, Index, Member, LowLevelCall, SolidityCall, HighLevelCall, InternalCall, LibraryCall - - -from slither.slithir.variables import Constant, ReferenceVariable, TemporaryVariable, TupleVariable - -from slither.core.declarations import Contract - logger = logging.getLogger("Node") class NodeType: @@ -374,7 +371,7 @@ class Node(SourceMapping, ChildFunction): for ir in self.irs: self._vars_read += [v for v in ir.read if not is_slithir_var(v)] if isinstance(ir, OperationWithLValue): - if isinstance(ir, (Index, Member)): + if isinstance(ir, (Index, Member, Length, Balance)): continue # Don't consider Member and Index operations -> ReferenceVariable var = ir.lvalue # If its a reference, we loop until finding the origin @@ -413,5 +410,3 @@ class Node(SourceMapping, ChildFunction): self._high_level_calls = list(set(self._high_level_calls)) self._low_level_calls = list(set(self._low_level_calls)) - - diff --git a/slither/core/variables/variable.py b/slither/core/variables/variable.py index d4e218173..0ab0a99ca 100644 --- a/slither/core/variables/variable.py +++ b/slither/core/variables/variable.py @@ -76,7 +76,7 @@ class Variable(SourceMapping): def set_type(self, t): if isinstance(t, str): t = ElementaryType(t) - assert isinstance(t, Type) or t is None + assert isinstance(t, (Type, list)) or t is None self._type = t def __str__(self): diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index 427e3ad4a..86f54b048 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -38,10 +38,11 @@ class UninitializedStateVarsDetection(AbstractDetector): if isinstance(ir, LibraryCall) \ or isinstance(ir, InternalCall): idx = 0 - for param in ir.function.parameters: - if param.location == 'storage': - ret.append(ir.arguments[idx]) - idx = idx+1 + if ir.function: + for param in ir.function.parameters: + if param.location == 'storage': + ret.append(ir.arguments[idx]) + idx = idx+1 return ret diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index eec9dc0d9..43175e1be 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -14,7 +14,7 @@ from slither.slithir.operations import (Assignment, Binary, BinaryType, Call, NewStructure, OperationWithLValue, Push, Return, Send, SolidityCall, Transfer, TypeConversion, Unary, - Unpack) + Unpack, Length, Balance) from slither.slithir.tmp_operations.argument import Argument, ArgumentType from slither.slithir.tmp_operations.tmp_call import TmpCall from slither.slithir.tmp_operations.tmp_new_array import TmpNewArray @@ -257,9 +257,10 @@ def convert_to_library(ir, node, using_for): contract = node.function.contract t = ir.destination.type - new_ir = look_for_library(contract, ir, node, using_for, t) - if new_ir: - return new_ir + if t in using_for: + new_ir = look_for_library(contract, ir, node, using_for, t) + if new_ir: + return new_ir if '*' in using_for: new_ir = look_for_library(contract, ir, node, using_for, '*') @@ -389,7 +390,7 @@ def propagate_types(ir, node): return # convert library - if t in using_for: + if t in using_for or '*' in using_for: new_ir = convert_to_library(ir, node, using_for) if new_ir: return new_ir @@ -449,6 +450,11 @@ def propagate_types(ir, node): # This should not happen assert False elif isinstance(ir, Member): + # TODO we should convert the reference to a temporary if the member is a length or a balance + if ir.variable_right == 'length' and isinstance(ir.variable_left.type, ElementaryType): + return Length(ir.variable_left, ir.lvalue) + if ir.variable_right == 'balance' and isinstance(ir.variable_left.type, ElementaryType): + return Balance(ir.variable_left, ir.lvalue) left = ir.variable_left if isinstance(left, (Variable, SolidityVariable)): t = ir.variable_left.type diff --git a/slither/slithir/operations/__init__.py b/slither/slithir/operations/__init__.py index 13200f273..ed59260e2 100644 --- a/slither/slithir/operations/__init__.py +++ b/slither/slithir/operations/__init__.py @@ -26,3 +26,5 @@ from .transfer import Transfer from .type_conversion import TypeConversion from .unary import Unary, UnaryType from .unpack import Unpack +from .length import Length +from .balance import Balance diff --git a/slither/slithir/operations/balance.py b/slither/slithir/operations/balance.py new file mode 100644 index 000000000..6c42e6b3c --- /dev/null +++ b/slither/slithir/operations/balance.py @@ -0,0 +1,26 @@ +import logging +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.declarations import Function +from slither.core.variables.variable import Variable +from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue +from slither.core.solidity_types.elementary_type import ElementaryType + +class Balance(OperationWithLValue): + + def __init__(self, value, lvalue): + assert is_valid_rvalue(value) + assert is_valid_lvalue(lvalue) + self._value = value + self._lvalue = lvalue + lvalue.set_type(ElementaryType('uint256')) + + @property + def read(self): + return [self._value] + + @property + def value(self): + return self._value + + def __str__(self): + return "{} -> Balance {}".format(self.lvalue, self.value) diff --git a/slither/slithir/operations/high_level_call.py b/slither/slithir/operations/high_level_call.py index 1681bb263..fd54a1113 100644 --- a/slither/slithir/operations/high_level_call.py +++ b/slither/slithir/operations/high_level_call.py @@ -58,7 +58,16 @@ class HighLevelCall(Call, OperationWithLValue): @property def read(self): - all_read = [self.destination, self.call_gas, self.call_value] + self.arguments + # if array inside the parameters + def unroll(l): + ret = [] + for x in l: + if not isinstance(x, list): + ret += [x] + else: + ret += unroll(x) + return ret + all_read = [self.destination, self.call_gas, self.call_value] + unroll(self.arguments) # remove None return [x for x in all_read if x] diff --git a/slither/slithir/operations/internal_call.py b/slither/slithir/operations/internal_call.py index e8bbb3db7..606059ca6 100644 --- a/slither/slithir/operations/internal_call.py +++ b/slither/slithir/operations/internal_call.py @@ -16,7 +16,16 @@ class InternalCall(Call, OperationWithLValue): @property def read(self): - return list(self.arguments) + # if array inside the parameters + def unroll(l): + ret = [] + for x in l: + if not isinstance(x, list): + ret += [x] + else: + ret += unroll(x) + return ret + return list(unroll(self.arguments)) @property def function(self): diff --git a/slither/slithir/operations/internal_dynamic_call.py b/slither/slithir/operations/internal_dynamic_call.py index e39fc9d65..5e40d3e57 100644 --- a/slither/slithir/operations/internal_dynamic_call.py +++ b/slither/slithir/operations/internal_dynamic_call.py @@ -19,7 +19,17 @@ class InternalDynamicCall(Call, OperationWithLValue): @property def read(self): - return list(self.arguments) + [self.function] + # if array inside the parameters + def unroll(l): + ret = [] + for x in l: + if not isinstance(x, list): + ret += [x] + else: + ret += unroll(x) + return ret + + return unroll(self.arguments) + [self.function] @property def function(self): diff --git a/slither/slithir/operations/length.py b/slither/slithir/operations/length.py new file mode 100644 index 000000000..10b16ff80 --- /dev/null +++ b/slither/slithir/operations/length.py @@ -0,0 +1,26 @@ +import logging +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.declarations import Function +from slither.core.variables.variable import Variable +from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue +from slither.core.solidity_types.elementary_type import ElementaryType + +class Length(OperationWithLValue): + + def __init__(self, value, lvalue): + assert is_valid_rvalue(value) + assert is_valid_lvalue(lvalue) + self._value = value + self._lvalue = lvalue + lvalue.set_type(ElementaryType('uint256')) + + @property + def read(self): + return [self._value] + + @property + def value(self): + return self._value + + def __str__(self): + return "{} -> LENGTH {}".format(self.lvalue, self.value) diff --git a/slither/slithir/variables/constant.py b/slither/slithir/variables/constant.py index 296dd7f25..f16d686a6 100644 --- a/slither/slithir/variables/constant.py +++ b/slither/slithir/variables/constant.py @@ -1,4 +1,5 @@ from slither.core.variables.variable import Variable +from slither.core.solidity_types.elementary_type import ElementaryType class Constant(Variable): @@ -6,10 +7,10 @@ class Constant(Variable): super(Constant, self).__init__() assert isinstance(val, str) if val.isdigit(): - self._type = 'uint256' + self._type = ElementaryType('uint256') self._val = int(val) else: - self._type = 'string' + self._type = ElementaryType('string') self._val = val @property diff --git a/slither/slithir/variables/tuple.py b/slither/slithir/variables/tuple.py index 74091a301..1b9e73353 100644 --- a/slither/slithir/variables/tuple.py +++ b/slither/slithir/variables/tuple.py @@ -23,9 +23,5 @@ class TupleVariable(Variable): def name(self): return 'TUPLE_{}'.format(self.index) - def set_type(self, t): - assert all(isinstance(x, Type) or x is None for x in t) - self._type = t - def __str__(self): return self.name From b6460dd235ca142839b03d44d78e707d5d2d0d4e Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 26 Oct 2018 21:52:46 +0100 Subject: [PATCH 253/308] Apply unrolling on new operators --- slither/slithir/operations/new_array.py | 11 ++++++++++- slither/slithir/operations/new_contract.py | 12 +++++++++++- slither/slithir/operations/new_structure.py | 11 ++++++++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/slither/slithir/operations/new_array.py b/slither/slithir/operations/new_array.py index 91383b00c..213a5c487 100644 --- a/slither/slithir/operations/new_array.py +++ b/slither/slithir/operations/new_array.py @@ -18,7 +18,16 @@ class NewArray(Call, OperationWithLValue): @property def read(self): - return list(self.arguments) + # if array inside the parameters + def unroll(l): + ret = [] + for x in l: + if not isinstance(x, list): + ret += [x] + else: + ret += unroll(x) + return ret + return unroll(self.arguments) @property def depth(self): diff --git a/slither/slithir/operations/new_contract.py b/slither/slithir/operations/new_contract.py index 2299351ef..0646a70db 100644 --- a/slither/slithir/operations/new_contract.py +++ b/slither/slithir/operations/new_contract.py @@ -39,7 +39,17 @@ class NewContract(Call, OperationWithLValue): @property def read(self): - return list(self.arguments) + # if array inside the parameters + def unroll(l): + ret = [] + for x in l: + if not isinstance(x, list): + ret += [x] + else: + ret += unroll(x) + return ret + return unroll(self.arguments) + def __str__(self): value = '' if self.call_value: diff --git a/slither/slithir/operations/new_structure.py b/slither/slithir/operations/new_structure.py index 6c41aaaac..302ac7ff6 100644 --- a/slither/slithir/operations/new_structure.py +++ b/slither/slithir/operations/new_structure.py @@ -17,7 +17,16 @@ class NewStructure(Call, OperationWithLValue): @property def read(self): - return list(self.arguments) + # if array inside the parameters + def unroll(l): + ret = [] + for x in l: + if not isinstance(x, list): + ret += [x] + else: + ret += unroll(x) + return ret + return unroll(self.arguments) @property def structure(self): From cfc3988ed16edd8381cfb97bf755716b812b3beb Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 26 Oct 2018 22:20:50 +0100 Subject: [PATCH 254/308] Remove this.balance (automatically converted to BALANCE) --- slither/core/declarations/solidity_variables.py | 3 +-- slither/slithir/operations/balance.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py index 777b5ce83..36f0795f1 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -22,8 +22,7 @@ SOLIDITY_VARIABLES_COMPOSED = {"block.coinbase":"address", "msg.sig":"bytes4", "msg.value":"uint256", "tx.gasprice":"uint256", - "tx.origin":"address", - "this.balance":"uint256"} + "tx.origin":"address"} SOLIDITY_FUNCTIONS = {"gasleft()":['uint256'], diff --git a/slither/slithir/operations/balance.py b/slither/slithir/operations/balance.py index 6c42e6b3c..3dd6560fd 100644 --- a/slither/slithir/operations/balance.py +++ b/slither/slithir/operations/balance.py @@ -23,4 +23,4 @@ class Balance(OperationWithLValue): return self._value def __str__(self): - return "{} -> Balance {}".format(self.lvalue, self.value) + return "{} -> BALANCE {}".format(self.lvalue, self.value) From e1981a29840383be24491dc605a42e42621faacc Mon Sep 17 00:00:00 2001 From: "Simon @redshark1802" Date: Sat, 27 Oct 2018 19:38:16 +0200 Subject: [PATCH 255/308] update detector descriptions witht txt from README (#62) --- slither/detectors/attributes/old_solc.py | 2 +- slither/detectors/functions/arbitrary_send.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/slither/detectors/attributes/old_solc.py b/slither/detectors/attributes/old_solc.py index f322deb1c..a14a1df50 100644 --- a/slither/detectors/attributes/old_solc.py +++ b/slither/detectors/attributes/old_solc.py @@ -12,7 +12,7 @@ class OldSolc(AbstractDetector): """ ARGUMENT = 'solc-version' - HELP = 'If an old version of Solidity used (<0.4.23)' + HELP = 'Old versions of Solidity (< 0.4.23)' IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH diff --git a/slither/detectors/functions/arbitrary_send.py b/slither/detectors/functions/arbitrary_send.py index ae115ede3..920351d41 100644 --- a/slither/detectors/functions/arbitrary_send.py +++ b/slither/detectors/functions/arbitrary_send.py @@ -27,7 +27,7 @@ class ArbitrarySend(AbstractDetector): """ ARGUMENT = 'arbitrary-send' - HELP = 'Functions that send ether to an arbitrary destination' + HELP = 'Functions that send ether to arbitrary destinations' IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.MEDIUM From 545841659bdc4ba1967d54acea80e63460a1f7ed Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 29 Oct 2018 06:36:19 +0000 Subject: [PATCH 256/308] SlithIR: fix minor bugs --- slither/core/cfg/node.py | 1 - slither/slithir/convert.py | 14 +++++++++++--- slither/slithir/operations/assignment.py | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index 66194a2a1..dbbbb522d 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -367,7 +367,6 @@ class Node(SourceMapping, ChildFunction): def is_slithir_var(var): return isinstance(var, (Constant, ReferenceVariable, TemporaryVariable, TupleVariable)) - for ir in self.irs: self._vars_read += [v for v in ir.read if not is_slithir_var(v)] if isinstance(ir, OperationWithLValue): diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 43175e1be..9e0e19989 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -357,7 +357,12 @@ def convert_type_of_high_level_call(ir, contract): return_type = return_type[0] else: # otherwise its a variable (getter) - return_type = func.type + if isinstance(func.type, MappingType): + return_type = func.type.type_to + elif isinstance(func.type, ArrayType): + return_type = func.type.type + else: + return_type = func.type if return_type: ir.lvalue.set_type(return_type) else: @@ -369,9 +374,12 @@ def propagate_types(ir, node): # propagate the type using_for = node.function.contract.using_for if isinstance(ir, OperationWithLValue): + # Force assignment in case of missing previous correct type + if isinstance(ir, Assignment): + ir.lvalue.set_type(ir.rvalue.type) if not ir.lvalue.type: if isinstance(ir, Assignment): - ir.lvalue.set_type(ir.rvalue.type) + pass elif isinstance(ir, Binary): if BinaryType.return_bool(ir.type): ir.lvalue.set_type(ElementaryType('bool')) @@ -451,7 +459,7 @@ def propagate_types(ir, node): assert False elif isinstance(ir, Member): # TODO we should convert the reference to a temporary if the member is a length or a balance - if ir.variable_right == 'length' and isinstance(ir.variable_left.type, ElementaryType): + if ir.variable_right == 'length' and isinstance(ir.variable_left.type, (ElementaryType, ArrayType)): return Length(ir.variable_left, ir.lvalue) if ir.variable_right == 'balance' and isinstance(ir.variable_left.type, ElementaryType): return Balance(ir.variable_left, ir.lvalue) diff --git a/slither/slithir/operations/assignment.py b/slither/slithir/operations/assignment.py index ad4b9d7fe..0440a1697 100644 --- a/slither/slithir/operations/assignment.py +++ b/slither/slithir/operations/assignment.py @@ -36,4 +36,4 @@ class Assignment(OperationWithLValue): return self._rvalue def __str__(self): - return '{} := {}'.format(self.lvalue, self.rvalue) + return '{}({}) := {}({})'.format(self.lvalue, self.lvalue.type, self.rvalue, self.rvalue.type) From 9648c99dd84535c7f029fd189ab781b9b004bfff Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 29 Oct 2018 06:50:31 +0000 Subject: [PATCH 257/308] Minor --- slither/slithir/convert.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 9e0e19989..3748f4403 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -375,11 +375,9 @@ def propagate_types(ir, node): using_for = node.function.contract.using_for if isinstance(ir, OperationWithLValue): # Force assignment in case of missing previous correct type - if isinstance(ir, Assignment): - ir.lvalue.set_type(ir.rvalue.type) if not ir.lvalue.type: if isinstance(ir, Assignment): - pass + ir.lvalue.set_type(ir.rvalue.type) elif isinstance(ir, Binary): if BinaryType.return_bool(ir.type): ir.lvalue.set_type(ElementaryType('bool')) From 64cb06dfe5fe039cfc924e11d643c83aa13adfe3 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 29 Oct 2018 07:33:29 +0000 Subject: [PATCH 258/308] SlithIR: add unroll to InitArray op --- slither/slithir/operations/init_array.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/slither/slithir/operations/init_array.py b/slither/slithir/operations/init_array.py index bbd939c65..e85299dba 100644 --- a/slither/slithir/operations/init_array.py +++ b/slither/slithir/operations/init_array.py @@ -23,7 +23,16 @@ class InitArray(OperationWithLValue): @property def read(self): - return list(self.init_values) + # if array inside the init values + def unroll(l): + ret = [] + for x in l: + if not isinstance(x, list): + ret += [x] + else: + ret += unroll(x) + return ret + return unroll(self.init_values) @property def init_values(self): From 5809eb91882191a332643068d5ecb72747e4fad2 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 29 Oct 2018 09:52:06 +0000 Subject: [PATCH 259/308] SlithIR: add unroll to EventCall op --- slither/slithir/operations/event_call.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/slither/slithir/operations/event_call.py b/slither/slithir/operations/event_call.py index acffe5970..0c7214d60 100644 --- a/slither/slithir/operations/event_call.py +++ b/slither/slithir/operations/event_call.py @@ -14,7 +14,15 @@ class EventCall(Call): @property def read(self): - return list(self.arguments) + def unroll(l): + ret = [] + for x in l: + if not isinstance(x, list): + ret += [x] + else: + ret += unroll(x) + return ret + return unroll(self.arguments) def __str__(self): args = [str(a) for a in self.arguments] From 30bb43906ea1c8fb7853d1cb6470b15a55dd06a3 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 29 Oct 2018 09:57:11 +0000 Subject: [PATCH 260/308] Update README --- README.md | 4 ++-- slither/detectors/functions/external_function.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 97522379f..1c654acd5 100644 --- a/README.md +++ b/README.md @@ -56,11 +56,11 @@ Num | Detector | What it Detects | Impact | Confidence 7 | `tx-origin` | Dangerous usage of `tx.origin` | Medium | Medium 8 | `assembly` | Assembly usage | Informational | High 9 | `constable-states` | State variables that could be declared constant | Informational | High -10 | `external-function` | Public functions that could be declared as external | Informational | High +10 | `external-function` | Public function that could be declared as external | Informational | High 11 | `low-level-calls` | Low level calls | Informational | High 12 | `naming-convention` | Conformance to Solidity naming conventions | Informational | High 13 | `pragma` | If different pragma directives are used | Informational | High -14 | `solc-version` | If an old version of Solidity used (<0.4.23) | Informational | High +14 | `solc-version` | Old versions of Solidity (< 0.4.23) | Informational | High 15 | `unused-state` | Unused state variables | Informational | High [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. diff --git a/slither/detectors/functions/external_function.py b/slither/detectors/functions/external_function.py index 9cb19909b..99ffe40fe 100644 --- a/slither/detectors/functions/external_function.py +++ b/slither/detectors/functions/external_function.py @@ -12,7 +12,7 @@ class ExternalFunction(AbstractDetector): """ ARGUMENT = 'external-function' - HELP = 'Detect public function that could be declared as external' + HELP = 'Public function that could be declared as external' IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH From d95147851ddd5ce74e7e13070ef0a7a70bbd70fb Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 29 Oct 2018 13:11:05 +0000 Subject: [PATCH 261/308] Add node ref to Temporary/Reference Remove filter to slithir var in specific taint --- slither/analyses/taint/specific_variable.py | 4 +-- slither/core/children/child_node.py | 8 +++++ slither/slithir/convert.py | 8 ++--- slither/slithir/variables/reference.py | 3 +- slither/slithir/variables/temporary.py | 3 +- .../visitors/slithir/expression_to_slithir.py | 31 ++++++++++--------- 6 files changed, 33 insertions(+), 24 deletions(-) diff --git a/slither/analyses/taint/specific_variable.py b/slither/analyses/taint/specific_variable.py index 304b89b4d..34f90e4c0 100644 --- a/slither/analyses/taint/specific_variable.py +++ b/slither/analyses/taint/specific_variable.py @@ -60,8 +60,6 @@ def _visit_node(node, visited, key): key) taints = iterate_over_irs(node.irs, _transfer_func_, taints) - taints = [v for v in taints if not isinstance(v, (TemporaryVariable, ReferenceVariable))] - node.function.slither.context[key] = list(set(taints)) for son in node.sons: @@ -101,7 +99,7 @@ def is_tainted(variable, taint): if not isinstance(variable, (Variable, SolidityVariable)): return False key = make_key(taint) - return key in variable.context and variable.context[key] + return (key in variable.context and variable.context[key]) or variable == taint def is_tainted_from_key(variable, key): """ diff --git a/slither/core/children/child_node.py b/slither/core/children/child_node.py index 747b7d285..8c16e3106 100644 --- a/slither/core/children/child_node.py +++ b/slither/core/children/child_node.py @@ -10,3 +10,11 @@ class ChildNode(object): @property def node(self): return self._node + + @property + def function(self): + return self.node.function + + @property + def contract(self): + return self.node.function.contract diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 3748f4403..010ffbb1c 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -208,7 +208,7 @@ def convert_to_low_level(ir): logger.error('Incorrect conversion to low level {}'.format(ir)) exit(-1) -def convert_to_push(ir): +def convert_to_push(ir, node): """ Convert a call to a PUSH operaiton @@ -221,7 +221,7 @@ def convert_to_push(ir): if isinstance(ir.arguments[0], list): ret = [] - val = TemporaryVariable() + val = TemporaryVariable(node) operation = InitArray(ir.arguments[0], val) ret.append(operation) @@ -419,7 +419,7 @@ def propagate_types(ir, node): # Which leads to return a list of operation if isinstance(t, ArrayType): if ir.function_name == 'push' and len(ir.arguments) == 1: - return convert_to_push(ir) + return convert_to_push(ir, node) elif isinstance(ir, Index): if isinstance(ir.variable_left.type, MappingType): @@ -657,7 +657,7 @@ def convert_expression(expression, node): if isinstance(expression, Identifier) and node.type in [NodeType.IF, NodeType.IFLOOP]: result = [Condition(expression.value)] return result - visitor = ExpressionToSlithIR(expression) + visitor = ExpressionToSlithIR(expression, node) result = visitor.result() result = apply_ir_heuristics(result, node) diff --git a/slither/slithir/variables/reference.py b/slither/slithir/variables/reference.py index 9763202ef..defccaaea 100644 --- a/slither/slithir/variables/reference.py +++ b/slither/slithir/variables/reference.py @@ -8,11 +8,12 @@ class ReferenceVariable(ChildNode, Variable): COUNTER = 0 - def __init__(self): + def __init__(self, node): super(ReferenceVariable, self).__init__() self._index = ReferenceVariable.COUNTER ReferenceVariable.COUNTER += 1 self._points_to = None + self._node = node @property def index(self): diff --git a/slither/slithir/variables/temporary.py b/slither/slithir/variables/temporary.py index 910ff51b1..a736a3dd1 100644 --- a/slither/slithir/variables/temporary.py +++ b/slither/slithir/variables/temporary.py @@ -6,10 +6,11 @@ class TemporaryVariable(ChildNode, Variable): COUNTER = 0 - def __init__(self): + def __init__(self, node): super(TemporaryVariable, self).__init__() self._index = TemporaryVariable.COUNTER TemporaryVariable.COUNTER += 1 + self._node = node @property def index(self): diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 3dfe07ce9..e1c90c595 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -61,8 +61,9 @@ def convert_assignment(left, right, t, return_type): class ExpressionToSlithIR(ExpressionVisitor): - def __init__(self, expression): + def __init__(self, expression, node): self._expression = expression + self._node = node self._result = [] self._visit_expression(self.expression) @@ -104,7 +105,7 @@ class ExpressionToSlithIR(ExpressionVisitor): def _post_binary_operation(self, expression): left = get(expression.expression_left) right = get(expression.expression_right) - val = TemporaryVariable() + val = TemporaryVariable(self._node) operation = Binary(val, left, right, expression.type) self._result.append(operation) @@ -123,18 +124,18 @@ class ExpressionToSlithIR(ExpressionVisitor): if expression.type_call.startswith('tuple(') and expression.type_call != 'tuple()': val = TupleVariable() else: - val = TemporaryVariable() + val = TemporaryVariable(self._node) internal_call = InternalCall(called, len(args), val, expression.type_call) self._result.append(internal_call) set_val(expression, val) else: - val = TemporaryVariable() + val = TemporaryVariable(self._node) # If tuple if expression.type_call.startswith('tuple(') and expression.type_call != 'tuple()': val = TupleVariable() else: - val = TemporaryVariable() + val = TemporaryVariable(self._node) message_call = TmpCall(called, len(args), val, expression.type_call) self._result.append(message_call) @@ -152,7 +153,7 @@ class ExpressionToSlithIR(ExpressionVisitor): def _post_index_access(self, expression): left = get(expression.expression_left) right = get(expression.expression_right) - val = ReferenceVariable() + val = ReferenceVariable(self._node) operation = Index(val, left, right, expression.type) self._result.append(operation) set_val(expression, val) @@ -162,26 +163,26 @@ class ExpressionToSlithIR(ExpressionVisitor): def _post_member_access(self, expression): expr = get(expression.expression) - val = ReferenceVariable() + val = ReferenceVariable(self._node) member = Member(expr, Constant(expression.member_name), val) self._result.append(member) set_val(expression, val) def _post_new_array(self, expression): - val = TemporaryVariable() + val = TemporaryVariable(self._node) operation = TmpNewArray(expression.depth, expression.array_type, val) self._result.append(operation) set_val(expression, val) def _post_new_contract(self, expression): - val = TemporaryVariable() + val = TemporaryVariable(self._node) operation = TmpNewContract(expression.contract_name, val) self._result.append(operation) set_val(expression, val) def _post_new_elementary_type(self, expression): # TODO unclear if this is ever used? - val = TemporaryVariable() + val = TemporaryVariable(self._node) operation = TmpNewElementaryType(expression.type, val) self._result.append(operation) set_val(expression, val) @@ -196,7 +197,7 @@ class ExpressionToSlithIR(ExpressionVisitor): def _post_type_conversion(self, expression): expr = get(expression.expression) - val = TemporaryVariable() + val = TemporaryVariable(self._node) operation = TypeConversion(val, expr, expression.type) self._result.append(operation) set_val(expression, val) @@ -204,7 +205,7 @@ class ExpressionToSlithIR(ExpressionVisitor): def _post_unary_operation(self, expression): value = get(expression.expression) if expression.type in [UnaryOperationType.BANG, UnaryOperationType.TILD]: - lvalue = TemporaryVariable() + lvalue = TemporaryVariable(self._node) operation = Unary(lvalue, value, expression.type) self._result.append(operation) set_val(expression, lvalue) @@ -221,14 +222,14 @@ class ExpressionToSlithIR(ExpressionVisitor): self._result.append(operation) set_val(expression, value) elif expression.type in [UnaryOperationType.PLUSPLUS_POST]: - lvalue = TemporaryVariable() + lvalue = TemporaryVariable(self._node) operation = Assignment(lvalue, value, value.type) self._result.append(operation) operation = Binary(value, value, Constant("1"), BinaryType.ADDITION) self._result.append(operation) set_val(expression, lvalue) elif expression.type in [UnaryOperationType.MINUSMINUS_POST]: - lvalue = TemporaryVariable() + lvalue = TemporaryVariable(self._node) operation = Assignment(lvalue, value, value.type) self._result.append(operation) operation = Binary(value, value, Constant("1"), BinaryType.SUBTRACTION) @@ -237,7 +238,7 @@ class ExpressionToSlithIR(ExpressionVisitor): elif expression.type in [UnaryOperationType.PLUS_PRE]: set_val(expression, value) elif expression.type in [UnaryOperationType.MINUS_PRE]: - lvalue = TemporaryVariable() + lvalue = TemporaryVariable(self._node) operation = Binary(lvalue, Constant("0"), value, BinaryType.SUBTRACTION) self._result.append(operation) set_val(expression, lvalue) From 9cfc57eb774f5909d8e96f8fbc9327330262f1a1 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 29 Oct 2018 13:22:16 +0000 Subject: [PATCH 262/308] update readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f7267339c..6244c6938 100644 --- a/README.md +++ b/README.md @@ -56,12 +56,13 @@ Num | Detector | What it Detects | Impact | Confidence 7 | `tx-origin` | Dangerous usage of `tx.origin` | Medium | Medium 8 | `assembly` | Assembly usage | Informational | High 9 | `constable-states` | State variables that could be declared constant | Informational | High -10 | `external-function` | Public functions that could be declared as external | Informational | High +10 | `external-function` | Public function that could be declared as external | Informational | High 11 | `low-level-calls` | Low level calls | Informational | High 12 | `naming-convention` | Conformance to Solidity naming conventions | Informational | High 13 | `pragma` | If different pragma directives are used | Informational | High 14 | `solc-version` | Old versions of Solidity (< 0.4.23) | Informational | High 15 | `unused-state` | Unused state variables | Informational | High +16 | `complex-function` | Complex functions | Informational | Medium [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. From 8fafb75cc70900a832c86ddffa7abf19a47793d9 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 29 Oct 2018 16:26:54 +0000 Subject: [PATCH 263/308] SlithIR: Improve Length op support --- slither/slithir/convert.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 010ffbb1c..9b4a98e9c 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -458,7 +458,9 @@ def propagate_types(ir, node): elif isinstance(ir, Member): # TODO we should convert the reference to a temporary if the member is a length or a balance if ir.variable_right == 'length' and isinstance(ir.variable_left.type, (ElementaryType, ArrayType)): - return Length(ir.variable_left, ir.lvalue) + length = Length(ir.variable_left, ir.lvalue) + ir.lvalue.points_to = ir.variable_left + return ir if ir.variable_right == 'balance' and isinstance(ir.variable_left.type, ElementaryType): return Balance(ir.variable_left, ir.lvalue) left = ir.variable_left From 39f560bf973acbc31c908991f94d4cb57a7e4828 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 30 Oct 2018 09:18:49 +0100 Subject: [PATCH 264/308] Version 0.2.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8de27eb00..99368e649 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( description='Slither is a Solidity static analysis framework written in Python 3.', url='https://github.com/trailofbits/slither', author='Trail of Bits', - version='0.1.0', + version='0.2.0', packages=find_packages(), python_requires='>=3.6', install_requires=['prettytable>=0.7.2'], From 3700283c8588762df1beab660f7e3c067fde8455 Mon Sep 17 00:00:00 2001 From: Rene Date: Tue, 30 Oct 2018 14:35:25 +0100 Subject: [PATCH 265/308] small typos in print statement --- examples/scripts/functions_called.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/scripts/functions_called.py b/examples/scripts/functions_called.py index f3e63f003..5f25477d0 100644 --- a/examples/scripts/functions_called.py +++ b/examples/scripts/functions_called.py @@ -2,7 +2,7 @@ import sys from slither.slither import Slither if len(sys.argv) != 2: - print('python.py function_called.py functions_called.sol') + print('python functions_called.py functions_called.sol') exit(-1) # Init slither From 72ab14003ceb1e7f255d23ada5de3feb21dd73a8 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 30 Oct 2018 15:29:59 +0100 Subject: [PATCH 266/308] Add raw source_code to slither when possible Use raw source_code to generate sourceMapping Update pragma detector output --- slither/core/declarations/pragma_directive.py | 2 +- slither/core/slither_core.py | 6 +++ slither/core/source_mapping/source_mapping.py | 40 ++++++++++++++++++- .../detectors/attributes/constant_pragma.py | 4 +- slither/solc_parsing/slitherSolc.py | 8 ++++ 5 files changed, 57 insertions(+), 3 deletions(-) diff --git a/slither/core/declarations/pragma_directive.py b/slither/core/declarations/pragma_directive.py index 4a949cd0d..1747b9229 100644 --- a/slither/core/declarations/pragma_directive.py +++ b/slither/core/declarations/pragma_directive.py @@ -18,4 +18,4 @@ class Pragma(SourceMapping): return ''.join(self.directive[1:]) def __str__(self): - return 'pragma '+str(self.directive) + return 'pragma '+''.join(self.directive) diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index ba8342237..cd8f8a113 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -17,6 +17,7 @@ class Slither(Context): self._solc_version = None # '0.3' or '0.4':! self._pragma_directives = [] self._import_directives = [] + self._raw_source_code = {} @property def source_units(self): @@ -58,6 +59,11 @@ class Slither(Context): """ list(str): Import directives""" return self._import_directives + @property + def source_code(self): + """ {filename: source_code}: source code """ + return self._raw_source_code + def get_contract_from_name(self, contract_name): """ Return a contract from a name diff --git a/slither/core/source_mapping/source_mapping.py b/slither/core/source_mapping/source_mapping.py index fc546d425..f49308672 100644 --- a/slither/core/source_mapping/source_mapping.py +++ b/slither/core/source_mapping/source_mapping.py @@ -11,6 +11,26 @@ class SourceMapping(Context): def source_mapping(self): return self._source_mapping + @staticmethod + def _compute_line(source_code, start, end): + """ + Compute line(s) number from a start/end offset + Not done in an efficient way + """ + total_length = len(source_code) + source_code = source_code.split('\n') + counter = 0 + i = 0 + lines = [] + while counter < total_length: + counter += len(source_code[i]) +1 + i = i+1 + if counter > start: + lines.append(i) + if counter > end: + break + return lines + @staticmethod def _convert_source_mapping(offset, slither): ''' @@ -33,8 +53,26 @@ class SourceMapping(Context): if f not in sourceUnits: return {'start':s, 'length':l} filename = sourceUnits[f] - return {'start':s, 'length':l, 'filename': filename} + + lines = [] + + if filename in slither.source_code: + lines = SourceMapping._compute_line(slither.source_code[filename], s, l) + + return {'start':s, 'length':l, 'filename': filename, 'lines' : lines } def set_offset(self, offset, slither): self._source_mapping = self._convert_source_mapping(offset, slither) + + @property + def source_mapping_str(self): + lines = self.source_mapping['lines'] + if not lines: + lines = '[source code not found]' + elif len(lines) == 1: + lines = 'line {}'.format(lines[0]) + else: + lines = 'lines: {}'.format(','.join(lines)) + return '{} {}'.format(self.source_mapping['filename'], lines) + diff --git a/slither/detectors/attributes/constant_pragma.py b/slither/detectors/attributes/constant_pragma.py index 7a1e272c0..c567103fd 100644 --- a/slither/detectors/attributes/constant_pragma.py +++ b/slither/detectors/attributes/constant_pragma.py @@ -22,7 +22,9 @@ class ConstantPragma(AbstractDetector): versions = list(set(versions)) if len(versions) > 1: - info = "Different version of Solidity used in {}: {}".format(self.filename, versions) + info = "\nDifferent version of Solidity used in {}:\n".format(self.filename) + for p in pragma: + info += "\t- {} uses {}\n".format(p.source_mapping_str, str(p)) self.log(info) source = [p.source_mapping for p in pragma] diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index 28108f3fd..487afe61c 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -1,3 +1,4 @@ +import os import json import re import logging @@ -115,6 +116,13 @@ class SlitherSolc(Slither): self._source_units[sourceUnit] = name + if os.path.isfile(name): + with open(name) as f: + source_code = f.read() + self.source_code[name] = source_code + + + def _analyze_contracts(self): if self._analyzed: raise Exception('Contract analysis can be run only once!') From 0fa07b31948ad1c203f4d309ac2392bf715ecabe Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 30 Oct 2018 15:40:48 +0100 Subject: [PATCH 267/308] Improve offset-lines number conversion Improve locked ether output --- slither/core/source_mapping/source_mapping.py | 10 +++++----- slither/detectors/abstract_detector.py | 2 +- slither/detectors/attributes/constant_pragma.py | 4 ++-- slither/detectors/attributes/locked_ether.py | 6 +++++- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/slither/core/source_mapping/source_mapping.py b/slither/core/source_mapping/source_mapping.py index f49308672..a47ffa635 100644 --- a/slither/core/source_mapping/source_mapping.py +++ b/slither/core/source_mapping/source_mapping.py @@ -12,7 +12,7 @@ class SourceMapping(Context): return self._source_mapping @staticmethod - def _compute_line(source_code, start, end): + def _compute_line(source_code, start, length): """ Compute line(s) number from a start/end offset Not done in an efficient way @@ -27,7 +27,7 @@ class SourceMapping(Context): i = i+1 if counter > start: lines.append(i) - if counter > end: + if counter > start+length: break return lines @@ -71,8 +71,8 @@ class SourceMapping(Context): if not lines: lines = '[source code not found]' elif len(lines) == 1: - lines = 'line {}'.format(lines[0]) + lines = '{}'.format(lines[0]) else: - lines = 'lines: {}'.format(','.join(lines)) - return '{} {}'.format(self.source_mapping['filename'], lines) + lines = '{}-{}'.format(lines[0], lines[-1]) + return '{}#{}'.format(self.source_mapping['filename'], lines) diff --git a/slither/detectors/abstract_detector.py b/slither/detectors/abstract_detector.py index 2575bc652..94de03228 100644 --- a/slither/detectors/abstract_detector.py +++ b/slither/detectors/abstract_detector.py @@ -64,7 +64,7 @@ class AbstractDetector(metaclass=abc.ABCMeta): def log(self, info): if self.logger: - info = " "+info + info = "\n"+info self.logger.info(self.color(info)) @abc.abstractmethod diff --git a/slither/detectors/attributes/constant_pragma.py b/slither/detectors/attributes/constant_pragma.py index c567103fd..faf65d988 100644 --- a/slither/detectors/attributes/constant_pragma.py +++ b/slither/detectors/attributes/constant_pragma.py @@ -22,9 +22,9 @@ class ConstantPragma(AbstractDetector): versions = list(set(versions)) if len(versions) > 1: - info = "\nDifferent version of Solidity used in {}:\n".format(self.filename) + info = "Different version of Solidity used in {}:\n".format(self.filename) for p in pragma: - info += "\t- {} uses {}\n".format(p.source_mapping_str, str(p)) + info += "\t- {} declares {}\n".format(p.source_mapping_str, str(p)) self.log(info) source = [p.source_mapping for p in pragma] diff --git a/slither/detectors/attributes/locked_ether.py b/slither/detectors/attributes/locked_ether.py index 682224f02..9436e4003 100644 --- a/slither/detectors/attributes/locked_ether.py +++ b/slither/detectors/attributes/locked_ether.py @@ -44,7 +44,11 @@ class LockedEther(AbstractDetector): funcs_payable = [function for function in contract.functions if function.payable] if funcs_payable: if self.do_no_send_ether(contract): - txt = "Contract locked ether in {}, Contract {}, Functions {}" + txt = "Contract locking ether found in {}:\n".format(self.filename) + txt += "\tContract {} has payable functions:\n".format(contract.name) + for function in funcs_payable: + txt += "\t - {} ({})\n".format(function.name, function.source_mapping_str) + txt += "\tBut has not function to withdraw the ether" info = txt.format(self.filename, contract.name, [f.name for f in funcs_payable]) From eb2c2a560a40a5aa8eb806c7a68f28068b342283 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 30 Oct 2018 15:46:01 +0100 Subject: [PATCH 268/308] Impropve source mapping printing Improve old solc output --- slither/core/source_mapping/source_mapping.py | 8 ++++---- slither/detectors/attributes/old_solc.py | 13 ++++++++----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/slither/core/source_mapping/source_mapping.py b/slither/core/source_mapping/source_mapping.py index a47ffa635..64dd7fcc4 100644 --- a/slither/core/source_mapping/source_mapping.py +++ b/slither/core/source_mapping/source_mapping.py @@ -69,10 +69,10 @@ class SourceMapping(Context): def source_mapping_str(self): lines = self.source_mapping['lines'] if not lines: - lines = '[source code not found]' + lines = '' elif len(lines) == 1: - lines = '{}'.format(lines[0]) + lines = '#{}'.format(lines[0]) else: - lines = '{}-{}'.format(lines[0], lines[-1]) - return '{}#{}'.format(self.source_mapping['filename'], lines) + lines = '#{}-{}'.format(lines[0], lines[-1]) + return '{}{}'.format(self.source_mapping['filename'], lines) diff --git a/slither/detectors/attributes/old_solc.py b/slither/detectors/attributes/old_solc.py index a14a1df50..94b39a5f7 100644 --- a/slither/detectors/attributes/old_solc.py +++ b/slither/detectors/attributes/old_solc.py @@ -16,16 +16,19 @@ class OldSolc(AbstractDetector): IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH + @staticmethod + def _convert_pragma(version): + return version.replace('solidity', '').replace('^', '') + def detect(self): results = [] pragma = self.slither.pragma_directives - versions = [p.version for p in pragma] - versions = [p.replace('solidity', '').replace('^', '') for p in versions] - versions = list(set(versions)) - old_pragma = [p for p in versions if p not in ['0.4.23', '0.4.24']] + old_pragma = [p for p in pragma if self._convert_pragma(p.version) not in ['0.4.23', '0.4.24']] if old_pragma: - info = "Old version of Solidity used in {}: {}".format(self.filename, old_pragma) + info = "Old version (<0.4.23) of Solidity used in {}:\n".format(self.filename) + for p in old_pragma: + info += "\t- {} declares {}\n".format(p.source_mapping_str, str(p)) self.log(info) source = [p.source_mapping for p in pragma] From 30c76ffa41cfb5b174fbc9b2516f49ffc35e14b7 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 30 Oct 2018 15:56:09 +0100 Subject: [PATCH 269/308] Improve complex, external, suicidal --- slither/detectors/functions/complex_function.py | 13 +++++++------ slither/detectors/functions/external_function.py | 13 ++++++------- slither/detectors/functions/suicidal.py | 13 ++++++------- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/slither/detectors/functions/complex_function.py b/slither/detectors/functions/complex_function.py index 355f1865b..f2521ad57 100644 --- a/slither/detectors/functions/complex_function.py +++ b/slither/detectors/functions/complex_function.py @@ -90,19 +90,20 @@ class ComplexFunction(AbstractDetector): for issue in issues: func, cause = issue.values() func_name = func.name - - txt = "Complex function in {} Contract: {}, Function: {}" + + txt = "Complex function in {}\n\t- {}.{} ({})\n" if cause == self.CAUSE_EXTERNAL_CALL: - txt += ", Reason: High number of external calls" + txt += "\t- Reason: High number of external calls" if cause == self.CAUSE_CYCLOMATIC: - txt += ", Reason: High number of branches" + txt += "\t- Reason: High number of branches" if cause == self.CAUSE_STATE_VARS: - txt += ", Reason: High number of modified state variables" + txt += "\t- Reason: High number of modified state variables" info = txt.format(self.filename, contract.name, - func_name) + func_name, + func.source_mapping_str) self.log(info) results.append({'vuln': 'ComplexFunc', diff --git a/slither/detectors/functions/external_function.py b/slither/detectors/functions/external_function.py index 99ffe40fe..929414c71 100644 --- a/slither/detectors/functions/external_function.py +++ b/slither/detectors/functions/external_function.py @@ -56,15 +56,14 @@ class ExternalFunction(AbstractDetector): for func in [f for f in contract.functions if f.visibility == 'public' and\ not f in public_function_calls and\ not f.is_constructor]: - func_name = func.name - txt = "Public function in {} Contract: {}, Function: {} should be declared external" - info = txt.format(self.filename, - contract.name, - func_name) + txt = "{}.{} ({}) should be declared external" + info = txt.format(func.contract.name, + func.name, + func.source_mapping_str) self.log(info) results.append({'vuln': 'ExternalFunc', 'sourceMapping': func.source_mapping, 'filename': self.filename, - 'contract': contract.name, - 'func': func_name}) + 'contract': func.contract.name, + 'func': func.name}) return results diff --git a/slither/detectors/functions/suicidal.py b/slither/detectors/functions/suicidal.py index 41b6f9bc5..8cc21cefe 100644 --- a/slither/detectors/functions/suicidal.py +++ b/slither/detectors/functions/suicidal.py @@ -12,7 +12,7 @@ class Suicidal(AbstractDetector): """ ARGUMENT = 'suicidal' - HELP = 'Suicidal functions' + HELP = 'Functions allowing anyone to destruct the contract' IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.HIGH @@ -54,12 +54,11 @@ class Suicidal(AbstractDetector): for c in self.contracts: functions = self.detect_suicidal(c) for func in functions: - func_name = func.name - txt = "Suicidal function in {} Contract: {}, Function: {}" - info = txt.format(self.filename, - c.name, - func_name) + txt = "{}.{} ({}) allows anyone to destruct the contract" + info = txt.format(func.contract.name, + func.name, + func.source_mapping_str) self.log(info) @@ -67,6 +66,6 @@ class Suicidal(AbstractDetector): 'sourceMapping': func.source_mapping, 'filename': self.filename, 'contract': c.name, - 'func': func_name}) + 'func': func.name}) return results From 6dc9faf496fceddf8998641bad8398d2e6dc3877 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 30 Oct 2018 17:17:35 +0100 Subject: [PATCH 270/308] Update --- slither/detectors/functions/arbitrary_send.py | 18 ++--- .../detectors/functions/external_function.py | 2 +- .../naming_convention/naming_convention.py | 35 ++++++---- .../detectors/operations/low_level_calls.py | 12 ++-- slither/detectors/reentrancy/reentrancy.py | 49 +++++++------- slither/detectors/statements/assembly.py | 10 ++- slither/detectors/statements/tx_origin.py | 14 ++-- slither/solc_parsing/declarations/contract.py | 1 + slither/solc_parsing/declarations/function.py | 65 ++++++++++--------- 9 files changed, 110 insertions(+), 96 deletions(-) diff --git a/slither/detectors/functions/arbitrary_send.py b/slither/detectors/functions/arbitrary_send.py index 920351d41..c72cc84c0 100644 --- a/slither/detectors/functions/arbitrary_send.py +++ b/slither/detectors/functions/arbitrary_send.py @@ -97,24 +97,24 @@ class ArbitrarySend(AbstractDetector): for c in self.contracts: arbitrary_send = self.detect_arbitrary_send(c) for (func, nodes) in arbitrary_send: - func_name = func.name calls_str = [str(node.expression) for node in nodes] - txt = "Arbitrary send in {} Contract: {}, Function: {}, Calls: {}" - info = txt.format(self.filename, - c.name, - func_name, - calls_str) + info = "{}{} sends eth to arbirary user\n" + info = info.format(func.contract.name, + func.name) + info += '\tDangerous calls:\n' + for node in nodes: + info += '- {} ({})'.format(node.expression, node.source_mapping_str) self.log(info) source_mapping = [node.source_mapping for node in nodes] - results.append({'vuln': 'SuicidalFunc', + results.append({'vuln': 'ArbitrarySend', 'sourceMapping': source_mapping, 'filename': self.filename, - 'contract': c.name, - 'func': func_name, + 'contract': func.contract.name, + 'func': func.name, 'calls': calls_str}) return results diff --git a/slither/detectors/functions/external_function.py b/slither/detectors/functions/external_function.py index 929414c71..595634a88 100644 --- a/slither/detectors/functions/external_function.py +++ b/slither/detectors/functions/external_function.py @@ -56,7 +56,7 @@ class ExternalFunction(AbstractDetector): for func in [f for f in contract.functions if f.visibility == 'public' and\ not f in public_function_calls and\ not f.is_constructor]: - txt = "{}.{} ({}) should be declared external" + txt = "{}.{} ({}) should be declared external\n" info = txt.format(func.contract.name, func.name, func.source_mapping_str) diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index f3312c1f0..ebf5e8c0b 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -45,7 +45,7 @@ class NamingConvention(AbstractDetector): for contract in self.contracts: if not self.is_cap_words(contract.name): - info = "Contract '{}' is not in CapWords".format(contract.name) + info = "Contract '{}' ({}) is not in CapWords\n".format(contract.name, contract.source_mapping_str) self.log(info) results.append({'vuln': 'NamingConvention', @@ -58,7 +58,8 @@ class NamingConvention(AbstractDetector): continue if not self.is_cap_words(struct.name): - info = "Struct '{}' is not in CapWords, Contract: '{}' ".format(struct.name, contract.name) + info = "Struct '{}.{}' ({}) is not in CapWords\n" + info = info.format(struct.contract.name, struct.name, struct.source_mapping_str) self.log(info) results.append({'vuln': 'NamingConvention', @@ -72,7 +73,8 @@ class NamingConvention(AbstractDetector): continue if not self.is_cap_words(event.name): - info = "Event '{}' is not in CapWords, Contract: '{}' ".format(event.name, contract.name) + info = "Event '{}.{}' ({}) is not in CapWords\n" + info = info.format(event.contract.name, event.name, event.source_mapping_str) self.log(info) results.append({'vuln': 'NamingConvention', @@ -86,7 +88,8 @@ class NamingConvention(AbstractDetector): continue if not self.is_mixed_case(func.name): - info = "Function '{}' is not in mixedCase, Contract: '{}' ".format(func.name, contract.name) + info = "Function '{}.{}' ({}) is not in mixedCase\n" + info = info.format(func.contract.name, func.name, func.source_mapping_str) self.log(info) results.append({'vuln': 'NamingConvention', @@ -101,8 +104,11 @@ class NamingConvention(AbstractDetector): else: correct_naming = self.is_mixed_case_with_underscore(argument.name) if not correct_naming: - info = "Parameter '{}' is not in mixedCase, Contract: '{}', Function: '{}'' " \ - .format(argument.name, argument.name, contract.name) + info = "Parameter '{}' of {}.{} ({}) is not in mixedCase\n" + info = info.format(argument.name, + argument.function.contract.name, + argument.function, + argument.source_mapping_str) self.log(info) results.append({'vuln': 'NamingConvention', @@ -118,8 +124,8 @@ class NamingConvention(AbstractDetector): if self.should_avoid_name(var.name): if not self.is_upper_case_with_underscores(var.name): - info = "Variable '{}' l, O, I should not be used, Contract: '{}' " \ - .format(var.name, contract.name) + info = "Variable '{}.{}' ({}) used l, O, I, which should not be used\n" + info = info.format(var.contract.name, var.name, var.source_mapping_str) self.log(info) results.append({'vuln': 'NamingConvention', @@ -134,8 +140,8 @@ class NamingConvention(AbstractDetector): continue if not self.is_upper_case_with_underscores(var.name): - info = "Constant '{}' is not in UPPER_CASE_WITH_UNDERSCORES, Contract: '{}' " \ - .format(var.name, contract.name) + info = "Constant '{}.{}' ({}) is not in UPPER_CASE_WITH_UNDERSCORES\n" + info = info.format(var.contract.name, var.name, var.source_mapping_str) self.log(info) results.append({'vuln': 'NamingConvention', @@ -149,7 +155,8 @@ class NamingConvention(AbstractDetector): else: correct_naming = self.is_mixed_case(var.name) if not correct_naming: - info = "Variable '{}' is not in mixedCase, Contract: '{}' ".format(var.name, contract.name) + info = "Variable '{}.{}' ({}) is not in mixedCase" + info = info.format(var.contract.name, var.name, var.source_mapping_str) self.log(info) results.append({'vuln': 'NamingConvention', @@ -163,7 +170,8 @@ class NamingConvention(AbstractDetector): continue if not self.is_cap_words(enum.name): - info = "Enum '{}' is not in CapWords, Contract: '{}' ".format(enum.name, contract.name) + info = "Enum '{}.{}' ({}) is not in CapWords\n" + info = info.format(enum.contract.name, enum.name, enum.source_mapping_str) self.log(info) results.append({'vuln': 'NamingConvention', @@ -177,7 +185,8 @@ class NamingConvention(AbstractDetector): continue if not self.is_mixed_case(modifier.name): - info = "Modifier '{}' is not in mixedCase, Contract: '{}' ".format(modifier.name, contract.name) + info = "Modifier '{}.{}' ({}) is not in mixedCase\n" + info = info.format(modifier.contract.name, modifier.name, modifier.source_mapping_str) self.log(info) results.append({'vuln': 'NamingConvention', diff --git a/slither/detectors/operations/low_level_calls.py b/slither/detectors/operations/low_level_calls.py index ec0f546a7..a5a7e828a 100644 --- a/slither/detectors/operations/low_level_calls.py +++ b/slither/detectors/operations/low_level_calls.py @@ -27,7 +27,7 @@ class LowLevelCalls(AbstractDetector): def detect_low_level_calls(self, contract): ret = [] - for f in contract.functions: + for f in [f for f in contract.functions if contract == f.contract]: nodes = f.nodes assembly_nodes = [n for n in nodes if self._contains_low_level_calls(n)] @@ -42,10 +42,8 @@ class LowLevelCalls(AbstractDetector): for c in self.contracts: values = self.detect_low_level_calls(c) for func, nodes in values: - func_name = func.name - info = "Low level call in %s, Contract: %s, Function: %s" % (self.filename, - c.name, - func_name) + info = "Low level call in {}.{} ({})" + info = info.format(func.contract.name, func.name, func.source_mapping_str) self.log(info) sourceMapping = [n.source_mapping for n in nodes] @@ -53,7 +51,7 @@ class LowLevelCalls(AbstractDetector): results.append({'vuln': 'Low level call', 'sourceMapping': sourceMapping, 'filename': self.filename, - 'contract': c.name, - 'function_name': func_name}) + 'contract': func.contract.name, + 'function_name': func.name}) return results diff --git a/slither/detectors/reentrancy/reentrancy.py b/slither/detectors/reentrancy/reentrancy.py index e5c9966a7..2d6c63cb7 100644 --- a/slither/detectors/reentrancy/reentrancy.py +++ b/slither/detectors/reentrancy/reentrancy.py @@ -126,7 +126,7 @@ class Reentrancy(AbstractDetector): if isinstance(internal_call, Function): state_vars_written += internal_call.all_state_variables_written() - read_then_written = [v for v in state_vars_written if v in node.context[self.key]['read']] + read_then_written = [(v, node.source_mapping_str) for v in state_vars_written if v in node.context[self.key]['read']] node.context[self.key]['read'] = list(set(node.context[self.key]['read'] + node.state_variables_read)) # If a state variables was read and is then written, there is a dangerous call and @@ -136,8 +136,7 @@ class Reentrancy(AbstractDetector): node.context[self.key]['calls'] and node.context[self.key]['send_eth']): # calls are ordered - finding_key = (node.function.contract.name, - node.function.full_name, + finding_key = (node.function, tuple(set(node.context[self.key]['calls'])), tuple(set(node.context[self.key]['send_eth']))) finding_vars = read_then_written @@ -176,32 +175,38 @@ class Reentrancy(AbstractDetector): results = [] - for (contract, func, calls, send_eth), varsWritten in self.result.items(): - varsWritten_str = list(set([str(x) for x in list(varsWritten)])) - calls_str = list(set([str(x.expression) for x in list(calls)])) - send_eth_str = list(set([str(x.expression) for x in list(send_eth)])) - - if calls == send_eth: - call_info = 'Call: {},'.format(calls_str) - else: - call_info = 'Call: {}, Ether sent: {},'.format(calls_str, send_eth_str) - info = 'Reentrancy in {}, Contract: {}, '.format(self.filename, contract) + \ - 'Func: {}, '.format(func) + \ - '{}'.format(call_info) + \ - 'Vars Written: {}'.format(str(varsWritten_str)) + for (func, calls, send_eth), varsWritten in self.result.items(): + calls = list(set(calls)) + send_eth = list(set(send_eth)) +# if calls == send_eth: +# calls_info = 'Call: {},'.format(calls_str) +# else: +# calls_info = 'Call: {}, Ether sent: {},'.format(calls_str, send_eth_str) + info = 'Reentrancy in {}.{} ({}):\n' + info = info.format(func.contract.name, func.name, func.source_mapping_str) + info += '\tExternal calls:\n' + for call_info in calls: + info += '\t- {} ({})\n'.format(call_info.expression, call_info.source_mapping_str) + if calls != send_eth: + info += '\tExternal calls sending eth:\n' + for call_info in send_eth: + info += '\t- {} ({})\n'.format(call_info.expression, call_info.source_mapping_str) + info += '\tState variables written after the call(s):\n' + for (v, mapping) in varsWritten: + info += '\t- {} ({})\n'.format(v, mapping) self.log(info) - source = [v.source_mapping for v in varsWritten] + source = [v.source_mapping for (v,_) in varsWritten] source += [node.source_mapping for node in calls] source += [node.source_mapping for node in send_eth] results.append({'vuln': 'Reentrancy', 'sourceMapping': source, 'filename': self.filename, - 'contract': contract, - 'function_name': func, - 'calls': calls_str, - 'send_eth': send_eth_str, - 'varsWritten': varsWritten_str}) + 'contract': func.contract.name, + 'function_name': func.name, + 'calls': [str(x.expression) for x in calls], + 'send_eth': [str(x.expression) for x in send_eth], + 'varsWritten': [str(x) for (x,_) in varsWritten]}) return results diff --git a/slither/detectors/statements/assembly.py b/slither/detectors/statements/assembly.py index 93e9ea598..f2bd62a8b 100644 --- a/slither/detectors/statements/assembly.py +++ b/slither/detectors/statements/assembly.py @@ -42,10 +42,8 @@ class Assembly(AbstractDetector): for c in self.contracts: values = self.detect_assembly(c) for func, nodes in values: - func_name = func.name - info = "Assembly in %s, Contract: %s, Function: %s" % (self.filename, - c.name, - func_name) + info = "{}.{} uses assembly ({})\n" + info = info.format(func.contract.name, func.name, func.source_mapping_str) self.log(info) sourceMapping = [n.source_mapping for n in nodes] @@ -53,7 +51,7 @@ class Assembly(AbstractDetector): results.append({'vuln': 'Assembly', 'sourceMapping': sourceMapping, 'filename': self.filename, - 'contract': c.name, - 'function_name': func_name}) + 'contract': func.contract.name, + 'function_name': func.name}) return results diff --git a/slither/detectors/statements/tx_origin.py b/slither/detectors/statements/tx_origin.py index f5c474f8f..fb14bdfba 100644 --- a/slither/detectors/statements/tx_origin.py +++ b/slither/detectors/statements/tx_origin.py @@ -48,10 +48,12 @@ class TxOrigin(AbstractDetector): for c in self.contracts: values = self.detect_tx_origin(c) for func, nodes in values: - func_name = func.name - info = "tx.origin in %s, Contract: %s, Function: %s" % (self.filename, - c.name, - func_name) + info = "{}.{} uses tx.origin for authorization:\n" + info = info.format(func.contract.name, func.name) + + for node in nodes: + info += "\t- {} ({})\n".format(node.expression, node.source_mapping_str) + self.log(info) sourceMapping = [n.source_mapping for n in nodes] @@ -59,7 +61,7 @@ class TxOrigin(AbstractDetector): results.append({'vuln': 'TxOrigin', 'sourceMapping': sourceMapping, 'filename': self.filename, - 'contract': c.name, - 'function_name': func_name}) + 'contract': func.contract.name, + 'function_name': func.name}) return results diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index 087750577..92445e03e 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -218,6 +218,7 @@ class ContractSolc04(Contract): event = EventSolc(event_to_parse, self) event.analyze(self) event.set_contract(self) + event.set_offset(event_to_parse['src'], self.slither) self._events[event.full_name] = event self._eventsNotParsed = None diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 4a2e5d926..b138f0d06 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -92,8 +92,9 @@ class FunctionSolc(Function): if 'payable' in attributes: self._payable = attributes['payable'] - def _new_node(self, node_type): + def _new_node(self, node_type, src): node = NodeSolc(node_type, self._counter_nodes) + node.set_offset(src, self.slither) self._counter_nodes += 1 node.set_function(self) self._nodes.append(node) @@ -107,7 +108,7 @@ class FunctionSolc(Function): condition = ifStatement['condition'] # Note: check if the expression could be directly # parsed here - condition_node = self._new_node(NodeType.IF) + condition_node = self._new_node(NodeType.IF, ifStatement['src']) condition_node.add_unparsed_expression(condition) link_nodes(node, condition_node) trueStatement = self._parse_statement(ifStatement['trueBody'], condition_node) @@ -118,14 +119,14 @@ class FunctionSolc(Function): condition = children[0] # Note: check if the expression could be directly # parsed here - condition_node = self._new_node(NodeType.IF) + condition_node = self._new_node(NodeType.IF, ifStatement['src']) condition_node.add_unparsed_expression(condition) link_nodes(node, condition_node) trueStatement = self._parse_statement(children[1], condition_node) if len(children) == 3: falseStatement = self._parse_statement(children[2], condition_node) - endIf_node = self._new_node(NodeType.ENDIF) + endIf_node = self._new_node(NodeType.ENDIF, ifStatement['src']) link_nodes(trueStatement, endIf_node) if falseStatement: @@ -164,8 +165,8 @@ class FunctionSolc(Function): def _parse_while(self, whileStatement, node): # WhileStatement = 'while' '(' Expression ')' Statement - node_startWhile = self._new_node(NodeType.STARTLOOP) - node_condition = self._new_node(NodeType.IFLOOP) + node_startWhile = self._new_node(NodeType.STARTLOOP, whileStatement['src']) + node_condition = self._new_node(NodeType.IFLOOP, whileStatement['src']) if self.is_compact_ast: node_condition.add_unparsed_expression(whileStatement['condition']) @@ -176,7 +177,7 @@ class FunctionSolc(Function): node_condition.add_unparsed_expression(expression) statement = self._parse_statement(children[1], node_condition) - node_endWhile = self._new_node(NodeType.ENDLOOP) + node_endWhile = self._new_node(NodeType.ENDLOOP, whileStatement['src']) link_nodes(node, node_startWhile) link_nodes(node_startWhile, node_condition) @@ -191,8 +192,8 @@ class FunctionSolc(Function): condition = statement['condition'] loop_expression = statement['loopExpression'] - node_startLoop = self._new_node(NodeType.STARTLOOP) - node_endLoop = self._new_node(NodeType.ENDLOOP) + node_startLoop = self._new_node(NodeType.STARTLOOP, statement['src']) + node_endLoop = self._new_node(NodeType.ENDLOOP, statement['src']) if init_expression: node_init_expression = self._parse_statement(init_expression, node) @@ -201,7 +202,7 @@ class FunctionSolc(Function): link_nodes(node, node_startLoop) if condition: - node_condition = self._new_node(NodeType.IFLOOP) + node_condition = self._new_node(NodeType.IFLOOP, statement['src']) node_condition.add_unparsed_expression(condition) link_nodes(node_startLoop, node_condition) link_nodes(node_condition, node_endLoop) @@ -254,8 +255,8 @@ class FunctionSolc(Function): hasLoopExpression = False - node_startLoop = self._new_node(NodeType.STARTLOOP) - node_endLoop = self._new_node(NodeType.ENDLOOP) + node_startLoop = self._new_node(NodeType.STARTLOOP, statement['src']) + node_endLoop = self._new_node(NodeType.ENDLOOP, statement['src']) children = statement[self.get_children('children')] @@ -283,7 +284,7 @@ class FunctionSolc(Function): if candidate[self.get_key()] not in ['VariableDefinitionStatement', 'VariableDeclarationStatement', 'ExpressionStatement']: - node_condition = self._new_node(NodeType.IFLOOP) + node_condition = self._new_node(NodeType.IFLOOP, statement['src']) #expression = parse_expression(candidate, self) expression = candidate node_condition.add_unparsed_expression(expression) @@ -313,8 +314,8 @@ class FunctionSolc(Function): def _parse_dowhile(self, doWhilestatement, node): - node_startDoWhile = self._new_node(NodeType.STARTLOOP) - node_condition = self._new_node(NodeType.IFLOOP) + node_startDoWhile = self._new_node(NodeType.STARTLOOP, doWhilestatement['src']) + node_condition = self._new_node(NodeType.IFLOOP, doWhilestatement['src']) if self.is_compact_ast: node_condition.add_unparsed_expression(doWhilestatement['condition']) @@ -326,7 +327,7 @@ class FunctionSolc(Function): node_condition.add_unparsed_expression(expression) statement = self._parse_statement(children[1], node_condition) - node_endDoWhile = self._new_node(NodeType.ENDLOOP) + node_endDoWhile = self._new_node(NodeType.ENDLOOP, doWhilestatement['src']) link_nodes(node, node_startDoWhile) link_nodes(node_startDoWhile, statement) @@ -344,7 +345,7 @@ class FunctionSolc(Function): self._variables[local_var.name] = local_var #local_var.analyze(self) - new_node = self._new_node(NodeType.VARIABLE) + new_node = self._new_node(NodeType.VARIABLE, statement['src']) new_node.add_variable_declaration(local_var) link_nodes(node, new_node) return new_node @@ -418,7 +419,7 @@ class FunctionSolc(Function): 'typeDescriptions': {'typeString':'tuple()'} } node = new_node - new_node = self._new_node(NodeType.EXPRESSION) + new_node = self._new_node(NodeType.EXPRESSION, statement['src']) new_node.add_unparsed_expression(expression) link_nodes(node, new_node) @@ -490,7 +491,7 @@ class FunctionSolc(Function): self.get_children('children'): var_identifiers}, tuple_vars]} node = new_node - new_node = self._new_node(NodeType.EXPRESSION) + new_node = self._new_node(NodeType.EXPRESSION, statement['src']) new_node.add_unparsed_expression(expression) link_nodes(node, new_node) @@ -506,7 +507,7 @@ class FunctionSolc(Function): self._variables[local_var.name] = local_var # local_var.analyze(self) - new_node = self._new_node(NodeType.VARIABLE) + new_node = self._new_node(NodeType.VARIABLE, statement['src']) new_node.add_variable_declaration(local_var) link_nodes(node, new_node) return new_node @@ -534,7 +535,7 @@ class FunctionSolc(Function): elif name == 'Block': node = self._parse_block(statement, node) elif name == 'InlineAssembly': - break_node = self._new_node(NodeType.ASSEMBLY) + break_node = self._new_node(NodeType.ASSEMBLY, statement['src']) link_nodes(node, break_node) node = break_node elif name == 'DoWhileStatement': @@ -542,15 +543,15 @@ class FunctionSolc(Function): # For Continue / Break / Return / Throw # The is fixed later elif name == 'Continue': - continue_node = self._new_node(NodeType.CONTINUE) + continue_node = self._new_node(NodeType.CONTINUE, statement['src']) link_nodes(node, continue_node) node = continue_node elif name == 'Break': - break_node = self._new_node(NodeType.BREAK) + break_node = self._new_node(NodeType.BREAK, statement['src']) link_nodes(node, break_node) node = break_node elif name == 'Return': - return_node = self._new_node(NodeType.RETURN) + return_node = self._new_node(NodeType.RETURN, statement['src']) link_nodes(node, return_node) if self.is_compact_ast: if statement['expression']: @@ -562,7 +563,7 @@ class FunctionSolc(Function): return_node.add_unparsed_expression(expression) node = return_node elif name == 'Throw': - throw_node = self._new_node(NodeType.THROW) + throw_node = self._new_node(NodeType.THROW, statement['src']) link_nodes(node, throw_node) node = throw_node elif name == 'EmitStatement': @@ -571,7 +572,7 @@ class FunctionSolc(Function): expression = statement['eventCall'] else: expression = statement[self.get_children('children')][0] - new_node = self._new_node(NodeType.EXPRESSION) + new_node = self._new_node(NodeType.EXPRESSION, statement['src']) new_node.add_unparsed_expression(expression) link_nodes(node, new_node) node = new_node @@ -585,7 +586,7 @@ class FunctionSolc(Function): expression = statement[self.get_children('expression')] else: expression = statement[self.get_children('expression')][0] - new_node = self._new_node(NodeType.EXPRESSION) + new_node = self._new_node(NodeType.EXPRESSION, statement['src']) new_node.add_unparsed_expression(expression) link_nodes(node, new_node) node = new_node @@ -615,7 +616,7 @@ class FunctionSolc(Function): assert cfg[self.get_key()] == 'Block' - node = self._new_node(NodeType.ENTRYPOINT) + node = self._new_node(NodeType.ENTRYPOINT, cfg['src']) self._entry_point = node if self.is_compact_ast: @@ -866,23 +867,23 @@ class FunctionSolc(Function): def split_ternary_node(self, node, condition, true_expr, false_expr): - condition_node = self._new_node(NodeType.IF) + condition_node = self._new_node(NodeType.IF, condition['src']) condition_node.add_expression(condition) condition_node.analyze_expressions(self) - true_node = self._new_node(node.type) + true_node = self._new_node(node.type, true_expr['src']) if node.type == NodeType.VARIABLE: true_node.add_variable_declaration(node.variable_declaration) true_node.add_expression(true_expr) true_node.analyze_expressions(self) - false_node = self._new_node(node.type) + false_node = self._new_node(node.type, false_expr['src']) if node.type == NodeType.VARIABLE: false_node.add_variable_declaration(node.variable_declaration) false_node.add_expression(false_expr) false_node.analyze_expressions(self) - endif_node = self._new_node(NodeType.ENDIF) + endif_node = self._new_node(NodeType.ENDIF, condition['src']) for father in node.fathers: father.remove_son(node) From c08c3b56e7f986bcdac4b9624d0ca1159a1859e6 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 30 Oct 2018 17:32:40 +0100 Subject: [PATCH 271/308] Udapte --- slither/detectors/attributes/locked_ether.py | 2 +- slither/detectors/examples/backdoor.py | 3 ++- slither/detectors/functions/arbitrary_send.py | 2 +- slither/detectors/functions/complex_function.py | 1 + slither/detectors/functions/suicidal.py | 2 +- slither/detectors/naming_convention/naming_convention.py | 2 +- slither/detectors/operations/low_level_calls.py | 2 +- .../detectors/variables/possible_const_state_variables.py | 6 +++--- .../detectors/variables/uninitialized_state_variables.py | 8 ++++---- .../variables/uninitialized_storage_variables.py | 7 +++---- slither/detectors/variables/unused_state_variables.py | 7 ++++--- slither/solc_parsing/declarations/modifier.py | 2 +- 12 files changed, 23 insertions(+), 21 deletions(-) diff --git a/slither/detectors/attributes/locked_ether.py b/slither/detectors/attributes/locked_ether.py index 9436e4003..dc17ea6b9 100644 --- a/slither/detectors/attributes/locked_ether.py +++ b/slither/detectors/attributes/locked_ether.py @@ -48,7 +48,7 @@ class LockedEther(AbstractDetector): txt += "\tContract {} has payable functions:\n".format(contract.name) for function in funcs_payable: txt += "\t - {} ({})\n".format(function.name, function.source_mapping_str) - txt += "\tBut has not function to withdraw the ether" + txt += "\tBut has not function to withdraw the ether\n" info = txt.format(self.filename, contract.name, [f.name for f in funcs_payable]) diff --git a/slither/detectors/examples/backdoor.py b/slither/detectors/examples/backdoor.py index 9e29724f9..76fe19fcc 100644 --- a/slither/detectors/examples/backdoor.py +++ b/slither/detectors/examples/backdoor.py @@ -19,7 +19,8 @@ class Backdoor(AbstractDetector): for f in contract.functions: if 'backdoor' in f.name: # Info to be printed - info = 'Backdoor function found in {}.{}'.format(contract.name, f.name) + info = 'Backdoor function found in {}.{} ({})\n' + info = info.format(contract.name, f.name, f.source_mapping_str) # Print the info self.log(info) # Add the result in ret diff --git a/slither/detectors/functions/arbitrary_send.py b/slither/detectors/functions/arbitrary_send.py index c72cc84c0..0561fe61c 100644 --- a/slither/detectors/functions/arbitrary_send.py +++ b/slither/detectors/functions/arbitrary_send.py @@ -104,7 +104,7 @@ class ArbitrarySend(AbstractDetector): func.name) info += '\tDangerous calls:\n' for node in nodes: - info += '- {} ({})'.format(node.expression, node.source_mapping_str) + info += '\t- {} ({})\n'.format(node.expression, node.source_mapping_str) self.log(info) diff --git a/slither/detectors/functions/complex_function.py b/slither/detectors/functions/complex_function.py index f2521ad57..ab1260e21 100644 --- a/slither/detectors/functions/complex_function.py +++ b/slither/detectors/functions/complex_function.py @@ -104,6 +104,7 @@ class ComplexFunction(AbstractDetector): contract.name, func_name, func.source_mapping_str) + info = info + "\n" self.log(info) results.append({'vuln': 'ComplexFunc', diff --git a/slither/detectors/functions/suicidal.py b/slither/detectors/functions/suicidal.py index 8cc21cefe..aef4a9721 100644 --- a/slither/detectors/functions/suicidal.py +++ b/slither/detectors/functions/suicidal.py @@ -55,7 +55,7 @@ class Suicidal(AbstractDetector): functions = self.detect_suicidal(c) for func in functions: - txt = "{}.{} ({}) allows anyone to destruct the contract" + txt = "{}.{} ({}) allows anyone to destruct the contract\n" info = txt.format(func.contract.name, func.name, func.source_mapping_str) diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index ebf5e8c0b..7e64a01b2 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -155,7 +155,7 @@ class NamingConvention(AbstractDetector): else: correct_naming = self.is_mixed_case(var.name) if not correct_naming: - info = "Variable '{}.{}' ({}) is not in mixedCase" + info = "Variable '{}.{}' ({}) is not in mixedCase\n" info = info.format(var.contract.name, var.name, var.source_mapping_str) self.log(info) diff --git a/slither/detectors/operations/low_level_calls.py b/slither/detectors/operations/low_level_calls.py index a5a7e828a..2bc74099f 100644 --- a/slither/detectors/operations/low_level_calls.py +++ b/slither/detectors/operations/low_level_calls.py @@ -42,7 +42,7 @@ class LowLevelCalls(AbstractDetector): for c in self.contracts: values = self.detect_low_level_calls(c) for func, nodes in values: - info = "Low level call in {}.{} ({})" + info = "Low level call in {}.{} ({})\n" info = info.format(func.contract.name, func.name, func.source_mapping_str) self.log(info) diff --git a/slither/detectors/variables/possible_const_state_variables.py b/slither/detectors/variables/possible_const_state_variables.py index e19a389d1..9edcc8703 100644 --- a/slither/detectors/variables/possible_const_state_variables.py +++ b/slither/detectors/variables/possible_const_state_variables.py @@ -64,9 +64,9 @@ class ConstCandidateStateVars(AbstractDetector): for contract, variables in variables_by_contract.items(): variable_names = [v.name for v in variables] - info = "State variables that could be const in %s, Contract: %s, Vars %s" % (self.filename, - contract, - str(variable_names)) + info = "{} has state variables that should be constant:\n".format(contract) + for v in variables: + info += "\t- {} ({})\n".format(v.name, v.source_mapping_str) self.log(info) sourceMapping = [v.source_mapping for v in const_candidates] diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index 86f54b048..a1650e6b7 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -72,10 +72,10 @@ class UninitializedStateVarsDetection(AbstractDetector): for c in self.slither.contracts_derived: ret = self.detect_uninitialized(c) for variable, functions in ret: - info = "Uninitialized state variable in %s, " % self.filename + \ - "Contract: %s, Variable: %s, Used in %s" % (c.name, - str(variable), - [str(f) for f in functions]) + info = "{}.{} ({}) is never initialized. It is used in:\n" + info = info.format(variable.contract.name, variable.name, variable.source_mapping_str) + for f in functions: + info += "\t- {} ({})\n".format(f.name, f.source_mapping_str) self.log(info) source = [variable.source_mapping] diff --git a/slither/detectors/variables/uninitialized_storage_variables.py b/slither/detectors/variables/uninitialized_storage_variables.py index 37201b33c..be861a1ef 100644 --- a/slither/detectors/variables/uninitialized_storage_variables.py +++ b/slither/detectors/variables/uninitialized_storage_variables.py @@ -82,10 +82,9 @@ class UninitializedStorageVars(AbstractDetector): for(function, uninitialized_storage_variable) in self.results: var_name = uninitialized_storage_variable.name - info = "Uninitialized storage variables in %s, " % self.filename + \ - "Contract: %s, Function: %s, Variable %s" % (function.contract.name, - function.name, - var_name) + info = "{} in {}.{} ({}) is a storage variable never initialiazed\n" + info = info.format(var_name, function.contract.name, function.name, uninitialized_storage_variable.source_mapping_str) + self.log(info) source = [function.source_mapping, uninitialized_storage_variable.source_mapping] diff --git a/slither/detectors/variables/unused_state_variables.py b/slither/detectors/variables/unused_state_variables.py index 5be36163e..621e4d8fa 100644 --- a/slither/detectors/variables/unused_state_variables.py +++ b/slither/detectors/variables/unused_state_variables.py @@ -34,9 +34,10 @@ class UnusedStateVars(AbstractDetector): unusedVars = self.detect_unused(c) if unusedVars: unusedVarsName = [v.name for v in unusedVars] - info = "Unused state variables in %s, Contract: %s, Vars %s" % (self.filename, - c.name, - str(unusedVarsName)) + info = '' + for var in unusedVars: + info += "{}.{} ({}) is never used\n".format(var.contract.name, var.name, var.source_mapping_str) + self.log(info) sourceMapping = [v.source_mapping for v in unusedVars] diff --git a/slither/solc_parsing/declarations/modifier.py b/slither/solc_parsing/declarations/modifier.py index 527c439e8..1341779c3 100644 --- a/slither/solc_parsing/declarations/modifier.py +++ b/slither/solc_parsing/declarations/modifier.py @@ -65,7 +65,7 @@ class ModifierSolc(Modifier, FunctionSolc): def _parse_statement(self, statement, node): name = statement[self.get_key()] if name == 'PlaceholderStatement': - placeholder_node = self._new_node(NodeType.PLACEHOLDER) + placeholder_node = self._new_node(NodeType.PLACEHOLDER, statement['src']) link_nodes(node, placeholder_node) return placeholder_node return super(ModifierSolc, self)._parse_statement(statement, node) From cc64d36486c7186566d4eb8307c2b76430fdc954 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 30 Oct 2018 18:57:46 +0100 Subject: [PATCH 272/308] Fix bug --- slither/core/source_mapping/source_mapping.py | 5 ++++- slither/solc_parsing/declarations/function.py | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/slither/core/source_mapping/source_mapping.py b/slither/core/source_mapping/source_mapping.py index 64dd7fcc4..642a2fb4c 100644 --- a/slither/core/source_mapping/source_mapping.py +++ b/slither/core/source_mapping/source_mapping.py @@ -62,7 +62,10 @@ class SourceMapping(Context): return {'start':s, 'length':l, 'filename': filename, 'lines' : lines } def set_offset(self, offset, slither): - self._source_mapping = self._convert_source_mapping(offset, slither) + if isinstance(offset, dict): + self._source_mapping = offset + else: + self._source_mapping = self._convert_source_mapping(offset, slither) @property diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index b138f0d06..5b3952917 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -867,23 +867,23 @@ class FunctionSolc(Function): def split_ternary_node(self, node, condition, true_expr, false_expr): - condition_node = self._new_node(NodeType.IF, condition['src']) + condition_node = self._new_node(NodeType.IF, node.source_mapping) condition_node.add_expression(condition) condition_node.analyze_expressions(self) - true_node = self._new_node(node.type, true_expr['src']) + true_node = self._new_node(node.type, node.source_mapping) if node.type == NodeType.VARIABLE: true_node.add_variable_declaration(node.variable_declaration) true_node.add_expression(true_expr) true_node.analyze_expressions(self) - false_node = self._new_node(node.type, false_expr['src']) + false_node = self._new_node(node.type, node.source_mapping) if node.type == NodeType.VARIABLE: false_node.add_variable_declaration(node.variable_declaration) false_node.add_expression(false_expr) false_node.analyze_expressions(self) - endif_node = self._new_node(NodeType.ENDIF, condition['src']) + endif_node = self._new_node(NodeType.ENDIF, node.source_mapping) for father in node.fathers: father.remove_son(node) From a1758a1421094cd82c357a01402b924bc4b9d0e0 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 30 Oct 2018 19:00:39 +0100 Subject: [PATCH 273/308] Fix typos in print statement --- examples/scripts/convert_to_ir.py | 9 ++++----- examples/scripts/export_to_dot.py | 2 +- examples/scripts/functions_writing.py | 2 +- examples/scripts/slithIR.py | 2 +- examples/scripts/taint_mapping.py | 2 +- examples/scripts/variable_in_condition.py | 2 +- 6 files changed, 9 insertions(+), 10 deletions(-) diff --git a/examples/scripts/convert_to_ir.py b/examples/scripts/convert_to_ir.py index fe71f1bd1..72f79ba64 100644 --- a/examples/scripts/convert_to_ir.py +++ b/examples/scripts/convert_to_ir.py @@ -3,19 +3,18 @@ from slither.slither import Slither from slither.slithir.convert import convert_expression -if len(sys.argv) != 4: - print('python.py function_called.py functions_called.sol Contract function()') +if len(sys.argv) != 2: + print('python function_called.py functions_called.sol') exit(-1) # Init slither slither = Slither(sys.argv[1]) # Get the contract -contract = slither.get_contract_from_name(sys.argv[2]) +contract = slither.get_contract_from_name('Test') # Get the variable -test = contract.get_function_from_signature(sys.argv[3]) -#test = contract.get_function_from_signature('two()') +test = contract.get_function_from_signature('one()') nodes = test.nodes diff --git a/examples/scripts/export_to_dot.py b/examples/scripts/export_to_dot.py index ef4e79779..628747515 100644 --- a/examples/scripts/export_to_dot.py +++ b/examples/scripts/export_to_dot.py @@ -3,7 +3,7 @@ from slither.slither import Slither if len(sys.argv) != 2: - print('python.py function_called.py') + print('python function_called.py contract.sol') exit(-1) # Init slither diff --git a/examples/scripts/functions_writing.py b/examples/scripts/functions_writing.py index e545c6f22..4609e9f6c 100644 --- a/examples/scripts/functions_writing.py +++ b/examples/scripts/functions_writing.py @@ -2,7 +2,7 @@ import sys from slither.slither import Slither if len(sys.argv) != 2: - print('python.py function_writing.py functions_writing.sol') + print('python function_writing.py functions_writing.sol') exit(-1) # Init slither diff --git a/examples/scripts/slithIR.py b/examples/scripts/slithIR.py index f676e6cd5..04fe255c8 100644 --- a/examples/scripts/slithIR.py +++ b/examples/scripts/slithIR.py @@ -2,7 +2,7 @@ import sys from slither import Slither if len(sys.argv) != 2: - print('python.py slithIR.py contract.sol') + print('python slithIR.py contract.sol') exit(-1) # Init slither diff --git a/examples/scripts/taint_mapping.py b/examples/scripts/taint_mapping.py index 64e46b49a..db88b0d91 100644 --- a/examples/scripts/taint_mapping.py +++ b/examples/scripts/taint_mapping.py @@ -56,7 +56,7 @@ def check_call(func, taints): if __name__ == "__main__": if len(sys.argv) != 2: - print('python.py taint.py taint.sol') + print('python taint_mapping.py taint.sol') exit(-1) # Init slither diff --git a/examples/scripts/variable_in_condition.py b/examples/scripts/variable_in_condition.py index 90463c25e..d931a744b 100644 --- a/examples/scripts/variable_in_condition.py +++ b/examples/scripts/variable_in_condition.py @@ -2,7 +2,7 @@ import sys from slither.slither import Slither if len(sys.argv) != 2: - print('python.py variable_in_condition.py variable_in_condition.sol') + print('python variable_in_condition.py variable_in_condition.sol') exit(-1) # Init slither From 8ca89eda96191393567876bfae0a50c55549f573 Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Tue, 30 Oct 2018 23:13:39 +0100 Subject: [PATCH 274/308] fix vars-and-auth print bug --- slither/printers/functions/authorization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/printers/functions/authorization.py b/slither/printers/functions/authorization.py index ff1666331..b2e2a1086 100644 --- a/slither/printers/functions/authorization.py +++ b/slither/printers/functions/authorization.py @@ -39,4 +39,4 @@ class PrinterWrittenVariablesAndAuthorization(AbstractPrinter): state_variables_written = [v.name for v in function.all_state_variables_written()] msg_sender_condition = self.get_msg_sender_checks(function) table.add_row([function.name, str(state_variables_written), str(msg_sender_condition)]) - self.info(txt + str(table)) + self.info(txt + str(table)) From db84aeca1f0a957245efc374f569669fce9532c3 Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Wed, 31 Oct 2018 02:36:19 +0100 Subject: [PATCH 275/308] add shell script to generate the expected (beautified) json output for each test --- scripts/tests_generate_expected_json.sh | 36 +++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100755 scripts/tests_generate_expected_json.sh diff --git a/scripts/tests_generate_expected_json.sh b/scripts/tests_generate_expected_json.sh new file mode 100755 index 000000000..7ebe43107 --- /dev/null +++ b/scripts/tests_generate_expected_json.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +# generate_expected_json file.sol detectors +generate_expected_json(){ + # generate output filename + # e.g. file: uninitialized.sol detector: uninitialized-state + # ---> uninitialized.uninitialized-state.json + output_filename="$(basename $1 .sol).$2.json" + + # run slither detector on input file and save output as json + slither "$1" --disable-solc-warnings --detectors "$2" --json "$output_filename" + + # beautify json and move to test/ + cat "$output_filename" | python -m json.tool > tests/$output_filename + + # rm original un-beautified json file + rm $output_filename +} + +generate_expected_json tests/uninitialized.sol "uninitialized-state" +generate_expected_json tests/backdoor.sol "backdoor" +generate_expected_json tests/backdoor.sol "suicidal" +generate_expected_json tests/pragma.0.4.24.sol "pragma" +generate_expected_json tests/old_solc.sol.json "solc-version" +generate_expected_json tests/reentrancy.sol "reentrancy" +generate_expected_json tests/uninitialized_storage_pointer.sol "uninitialized-storage" +generate_expected_json tests/tx_origin.sol "tx-origin" +generate_expected_json tests/unused_state.sol "unused-state" +generate_expected_json tests/locked_ether.sol "locked-ether" +generate_expected_json tests/arbitrary_send.sol "arbitrary-send" +generate_expected_json tests/inline_assembly_contract.sol "assembly" +generate_expected_json tests/inline_assembly_library.sol "assembly" +generate_expected_json tests/low_level_calls.sol "low-level-calls" +generate_expected_json tests/const_state_variables.sol "constable-states" +generate_expected_json tests/external_function.sol "external-function" +generate_expected_json tests/naming_convention.sol "naming-convention" From b7119f0460fe0f0bedcadfc26ccfaf35ebb550ca Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Wed, 31 Oct 2018 02:37:14 +0100 Subject: [PATCH 276/308] update travis_test.sh to compare json output instead of counting output lines --- scripts/travis_test.sh | 73 ++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 24 deletions(-) diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 6ccaf0ef6..548fc9324 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -2,36 +2,61 @@ ### Test Detectors -# test_slither file.sol detectors number_results +# test_slither file.sol detectors test_slither(){ - slither "$1" --disable-solc-warnings --detectors "$2" - if [ $? -ne "$3" ]; then - exit 1 + expected="tests/$(basename $1 .sol).$2.json" + actual="$(basename $1 .sol).$2.json" + + slither "$1" --disable-solc-warnings --detectors "$2" --json tmp.json + + cat tmp.json | python -m json.tool > "$actual" + rm tmp.json + + result=$(diff "$expected" "$actual") + + if [ "$result" != "" ]; then + rm "$actual" + echo "\nfailed test of file: $1, detector: $2\n" + echo "$result\n" + exit 1 + else + rm "$actual" fi - slither "$1" --disable-solc-warnings --detectors "$2" --compact-ast - if [ $? -ne "$3" ]; then - exit 1 + slither "$1" --disable-solc-warnings --detectors "$2" --compact-ast --json tmp.json + + cat tmp.json | python -m json.tool > "$actual" + rm tmp.json + + result=$(diff "$expected" "$actual") + + if [ "$result" != "" ]; then + rm "$actual" + echo "\nfailed test of file: $1, detector: $2\n" + echo "$result\n" + exit 1 + else + rm "$actual" fi } -test_slither tests/uninitialized.sol "uninitialized-state" 4 -test_slither tests/backdoor.sol "backdoor" 1 -test_slither tests/backdoor.sol "suicidal" 1 -test_slither tests/pragma.0.4.24.sol "pragma" 1 -test_slither tests/old_solc.sol.json "solc-version" 1 -test_slither tests/reentrancy.sol "reentrancy" 1 -test_slither tests/uninitialized_storage_pointer.sol "uninitialized-storage" 1 -test_slither tests/tx_origin.sol "tx-origin" 2 -test_slither tests/unused_state.sol "unused-state" 1 -test_slither tests/locked_ether.sol "locked-ether" 1 -test_slither tests/arbitrary_send.sol "arbitrary-send" 2 -test_slither tests/inline_assembly_contract.sol "assembly" 1 -test_slither tests/inline_assembly_library.sol "assembly" 2 -test_slither tests/low_level_calls.sol "low-level-calls" 1 -test_slither tests/const_state_variables.sol "constable-states" 2 -test_slither tests/external_function.sol "external-function" 4 -test_slither tests/naming_convention.sol "naming-convention" 12 +test_slither tests/uninitialized.sol "uninitialized-state" +test_slither tests/backdoor.sol "backdoor" +test_slither tests/backdoor.sol "suicidal" +test_slither tests/pragma.0.4.24.sol "pragma" +test_slither tests/old_solc.sol.json "solc-version" +test_slither tests/reentrancy.sol "reentrancy" +test_slither tests/uninitialized_storage_pointer.sol "uninitialized-storage" +test_slither tests/tx_origin.sol "tx-origin" +test_slither tests/unused_state.sol "unused-state" +test_slither tests/locked_ether.sol "locked-ether" +test_slither tests/arbitrary_send.sol "arbitrary-send" +test_slither tests/inline_assembly_contract.sol "assembly" +test_slither tests/inline_assembly_library.sol "assembly" +test_slither tests/low_level_calls.sol "low-level-calls" +test_slither tests/const_state_variables.sol "constable-states" +test_slither tests/external_function.sol "external-function" +test_slither tests/naming_convention.sol "naming-convention" ### Test scripts From 014bce3a8552eb8da6deced86a103674f73e9302 Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Wed, 31 Oct 2018 02:37:47 +0100 Subject: [PATCH 277/308] add generated expected json output of each test --- tests/arbitrary_send.arbitrary-send.json | 26 ++++ tests/backdoor.backdoor.json | 1 + tests/backdoor.suicidal.json | 1 + ...onst_state_variables.constable-states.json | 64 +++++++++ .../external_function.external-function.json | 46 ++++++ tests/inline_assembly_contract.assembly.json | 11 ++ tests/inline_assembly_library.assembly.json | 20 +++ tests/locked_ether.locked-ether.json | 1 + tests/low_level_calls.low-level-calls.json | 11 ++ .../naming_convention.naming-convention.json | 131 ++++++++++++++++++ tests/old_solc.sol.json.solc-version.json | 15 ++ tests/pragma.0.4.24.pragma.json | 1 + tests/reentrancy.reentrancy.json | 26 ++++ tests/tx_origin.tx-origin.json | 20 +++ tests/uninitialized.uninitialized-state.json | 86 ++++++++++++ ...storage_pointer.uninitialized-storage.json | 21 +++ tests/unused_state.unused-state.json | 1 + 17 files changed, 482 insertions(+) create mode 100644 tests/arbitrary_send.arbitrary-send.json create mode 100644 tests/backdoor.backdoor.json create mode 100644 tests/backdoor.suicidal.json create mode 100644 tests/const_state_variables.constable-states.json create mode 100644 tests/external_function.external-function.json create mode 100644 tests/inline_assembly_contract.assembly.json create mode 100644 tests/inline_assembly_library.assembly.json create mode 100644 tests/locked_ether.locked-ether.json create mode 100644 tests/low_level_calls.low-level-calls.json create mode 100644 tests/naming_convention.naming-convention.json create mode 100644 tests/old_solc.sol.json.solc-version.json create mode 100644 tests/pragma.0.4.24.pragma.json create mode 100644 tests/reentrancy.reentrancy.json create mode 100644 tests/tx_origin.tx-origin.json create mode 100644 tests/uninitialized.uninitialized-state.json create mode 100644 tests/uninitialized_storage_pointer.uninitialized-storage.json create mode 100644 tests/unused_state.unused-state.json diff --git a/tests/arbitrary_send.arbitrary-send.json b/tests/arbitrary_send.arbitrary-send.json new file mode 100644 index 000000000..9a1da6ca1 --- /dev/null +++ b/tests/arbitrary_send.arbitrary-send.json @@ -0,0 +1,26 @@ +[ + { + "vuln": "SuicidalFunc", + "sourceMapping": [ + null + ], + "filename": "tests/arbitrary_send.sol", + "contract": "Test", + "func": "direct", + "calls": [ + "msg.sender.send(this.balance)" + ] + }, + { + "vuln": "SuicidalFunc", + "sourceMapping": [ + null + ], + "filename": "tests/arbitrary_send.sol", + "contract": "Test", + "func": "indirect", + "calls": [ + "destination.send(this.balance)" + ] + } +] diff --git a/tests/backdoor.backdoor.json b/tests/backdoor.backdoor.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/tests/backdoor.backdoor.json @@ -0,0 +1 @@ +[] diff --git a/tests/backdoor.suicidal.json b/tests/backdoor.suicidal.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/tests/backdoor.suicidal.json @@ -0,0 +1 @@ +[] diff --git a/tests/const_state_variables.constable-states.json b/tests/const_state_variables.constable-states.json new file mode 100644 index 000000000..a02aa98bb --- /dev/null +++ b/tests/const_state_variables.constable-states.json @@ -0,0 +1,64 @@ +[ + { + "vuln": "ConstStateVariableCandidates", + "sourceMapping": [ + { + "start": 130, + "length": 76, + "filename": "tests/const_state_variables.sol" + }, + { + "start": 235, + "length": 20, + "filename": "tests/const_state_variables.sol" + }, + { + "start": 331, + "length": 20, + "filename": "tests/const_state_variables.sol" + }, + { + "start": 494, + "length": 76, + "filename": "tests/const_state_variables.sol" + } + ], + "filename": "tests/const_state_variables.sol", + "contract": "B", + "unusedVars": [ + "myFriendsAddress", + "test", + "text2" + ] + }, + { + "vuln": "ConstStateVariableCandidates", + "sourceMapping": [ + { + "start": 130, + "length": 76, + "filename": "tests/const_state_variables.sol" + }, + { + "start": 235, + "length": 20, + "filename": "tests/const_state_variables.sol" + }, + { + "start": 331, + "length": 20, + "filename": "tests/const_state_variables.sol" + }, + { + "start": 494, + "length": 76, + "filename": "tests/const_state_variables.sol" + } + ], + "filename": "tests/const_state_variables.sol", + "contract": "B", + "unusedVars": [ + "mySistersAddress" + ] + } +] diff --git a/tests/external_function.external-function.json b/tests/external_function.external-function.json new file mode 100644 index 000000000..829c6e800 --- /dev/null +++ b/tests/external_function.external-function.json @@ -0,0 +1,46 @@ +[ + { + "vuln": "ExternalFunc", + "sourceMapping": { + "start": 257, + "length": 41, + "filename": "tests/external_function.sol" + }, + "filename": "tests/external_function.sol", + "contract": "ContractWithFunctionNotCalled", + "func": "funcNotCalled3" + }, + { + "vuln": "ExternalFunc", + "sourceMapping": { + "start": 304, + "length": 41, + "filename": "tests/external_function.sol" + }, + "filename": "tests/external_function.sol", + "contract": "ContractWithFunctionNotCalled", + "func": "funcNotCalled2" + }, + { + "vuln": "ExternalFunc", + "sourceMapping": { + "start": 351, + "length": 40, + "filename": "tests/external_function.sol" + }, + "filename": "tests/external_function.sol", + "contract": "ContractWithFunctionNotCalled", + "func": "funcNotCalled" + }, + { + "vuln": "ExternalFunc", + "sourceMapping": { + "start": 552, + "length": 304, + "filename": "tests/external_function.sol" + }, + "filename": "tests/external_function.sol", + "contract": "ContractWithFunctionNotCalled2", + "func": "funcNotCalled" + } +] diff --git a/tests/inline_assembly_contract.assembly.json b/tests/inline_assembly_contract.assembly.json new file mode 100644 index 000000000..0333b2f0a --- /dev/null +++ b/tests/inline_assembly_contract.assembly.json @@ -0,0 +1,11 @@ +[ + { + "vuln": "Assembly", + "sourceMapping": [ + null + ], + "filename": "tests/inline_assembly_contract.sol", + "contract": "GetCode", + "function_name": "at" + } +] diff --git a/tests/inline_assembly_library.assembly.json b/tests/inline_assembly_library.assembly.json new file mode 100644 index 000000000..62bbf9fae --- /dev/null +++ b/tests/inline_assembly_library.assembly.json @@ -0,0 +1,20 @@ +[ + { + "vuln": "Assembly", + "sourceMapping": [ + null + ], + "filename": "tests/inline_assembly_library.sol", + "contract": "VectorSum", + "function_name": "sumAsm" + }, + { + "vuln": "Assembly", + "sourceMapping": [ + null + ], + "filename": "tests/inline_assembly_library.sol", + "contract": "VectorSum", + "function_name": "sumPureAsm" + } +] diff --git a/tests/locked_ether.locked-ether.json b/tests/locked_ether.locked-ether.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/tests/locked_ether.locked-ether.json @@ -0,0 +1 @@ +[] diff --git a/tests/low_level_calls.low-level-calls.json b/tests/low_level_calls.low-level-calls.json new file mode 100644 index 000000000..7f30bf684 --- /dev/null +++ b/tests/low_level_calls.low-level-calls.json @@ -0,0 +1,11 @@ +[ + { + "vuln": "Low level call", + "sourceMapping": [ + null + ], + "filename": "tests/low_level_calls.sol", + "contract": "Sender", + "function_name": "send" + } +] diff --git a/tests/naming_convention.naming-convention.json b/tests/naming_convention.naming-convention.json new file mode 100644 index 000000000..2410e66f3 --- /dev/null +++ b/tests/naming_convention.naming-convention.json @@ -0,0 +1,131 @@ +[ + { + "vuln": "NamingConvention", + "filename": "tests/naming_convention.sol", + "contract": "naming", + "sourceMapping": { + "start": 26, + "length": 598, + "filename": "tests/naming_convention.sol" + } + }, + { + "vuln": "NamingConvention", + "filename": "tests/naming_convention.sol", + "contract": "naming", + "struct": "test", + "sourceMapping": { + "start": 227, + "length": 20, + "filename": "tests/naming_convention.sol" + } + }, + { + "vuln": "NamingConvention", + "filename": "tests/naming_convention.sol", + "contract": "naming", + "event": "event_", + "sourceMapping": null + }, + { + "vuln": "NamingConvention", + "filename": "tests/naming_convention.sol", + "contract": "naming", + "function": "GetOne", + "sourceMapping": { + "start": 405, + "length": 71, + "filename": "tests/naming_convention.sol" + } + }, + { + "vuln": "NamingConvention", + "filename": "tests/naming_convention.sol", + "contract": "naming", + "function": "setInt", + "argument": "Number2", + "sourceMapping": { + "start": 512, + "length": 12, + "filename": "tests/naming_convention.sol" + } + }, + { + "vuln": "NamingConvention", + "filename": "tests/naming_convention.sol", + "contract": "naming", + "constant": "MY_other_CONSTANT", + "sourceMapping": { + "start": 141, + "length": 35, + "filename": "tests/naming_convention.sol" + } + }, + { + "vuln": "NamingConvention", + "filename": "tests/naming_convention.sol", + "contract": "naming", + "variable": "Var_One", + "sourceMapping": { + "start": 183, + "length": 16, + "filename": "tests/naming_convention.sol" + } + }, + { + "vuln": "NamingConvention", + "filename": "tests/naming_convention.sol", + "contract": "naming", + "enum": "numbers", + "sourceMapping": { + "start": 77, + "length": 23, + "filename": "tests/naming_convention.sol" + } + }, + { + "vuln": "NamingConvention", + "filename": "tests/naming_convention.sol", + "contract": "naming", + "modifier": "CantDo", + "sourceMapping": { + "start": 545, + "length": 36, + "filename": "tests/naming_convention.sol" + } + }, + { + "vuln": "NamingConvention", + "filename": "tests/naming_convention.sol", + "contract": "T", + "function": "test", + "argument": "_used", + "sourceMapping": { + "start": 748, + "length": 10, + "filename": "tests/naming_convention.sol" + } + }, + { + "vuln": "NamingConvention", + "filename": "tests/naming_convention.sol", + "contract": "T", + "variable": "_myPublicVar", + "sourceMapping": { + "start": 695, + "length": 17, + "filename": "tests/naming_convention.sol" + } + }, + { + "vuln": "NamingConvention", + "filename": "tests/naming_convention.sol", + "contract": "T", + "constant": "l", + "sourceMapping": { + "start": 847, + "length": 10, + "filename": "tests/naming_convention.sol" + } + } +] diff --git a/tests/old_solc.sol.json.solc-version.json b/tests/old_solc.sol.json.solc-version.json new file mode 100644 index 000000000..8c349b7c2 --- /dev/null +++ b/tests/old_solc.sol.json.solc-version.json @@ -0,0 +1,15 @@ +[ + { + "vuln": "OldPragma", + "pragma": [ + "0.4.21" + ], + "sourceMapping": [ + { + "start": 0, + "length": 23, + "filename": "old_solc.sol" + } + ] + } +] diff --git a/tests/pragma.0.4.24.pragma.json b/tests/pragma.0.4.24.pragma.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/tests/pragma.0.4.24.pragma.json @@ -0,0 +1 @@ +[] diff --git a/tests/reentrancy.reentrancy.json b/tests/reentrancy.reentrancy.json new file mode 100644 index 000000000..d4163f6aa --- /dev/null +++ b/tests/reentrancy.reentrancy.json @@ -0,0 +1,26 @@ +[ + { + "vuln": "Reentrancy", + "sourceMapping": [ + { + "start": 52, + "length": 37, + "filename": "tests/reentrancy.sol" + }, + null, + null + ], + "filename": "tests/reentrancy.sol", + "contract": "Reentrancy", + "function_name": "withdrawBalance()", + "calls": [ + "! (msg.sender.call.value(userBalance[msg.sender])())" + ], + "send_eth": [ + "! (msg.sender.call.value(userBalance[msg.sender])())" + ], + "varsWritten": [ + "userBalance" + ] + } +] diff --git a/tests/tx_origin.tx-origin.json b/tests/tx_origin.tx-origin.json new file mode 100644 index 000000000..fcc426df0 --- /dev/null +++ b/tests/tx_origin.tx-origin.json @@ -0,0 +1,20 @@ +[ + { + "vuln": "TxOrigin", + "sourceMapping": [ + null + ], + "filename": "tests/tx_origin.sol", + "contract": "TxOrigin", + "function_name": "bug0" + }, + { + "vuln": "TxOrigin", + "sourceMapping": [ + null + ], + "filename": "tests/tx_origin.sol", + "contract": "TxOrigin", + "function_name": "bug2" + } +] diff --git a/tests/uninitialized.uninitialized-state.json b/tests/uninitialized.uninitialized-state.json new file mode 100644 index 000000000..856dac396 --- /dev/null +++ b/tests/uninitialized.uninitialized-state.json @@ -0,0 +1,86 @@ +[ + { + "vuln": "UninitializedStateVars", + "sourceMapping": [ + { + "start": 55, + "length": 19, + "filename": "tests/uninitialized.sol" + }, + { + "start": 81, + "length": 82, + "filename": "tests/uninitialized.sol" + } + ], + "filename": "tests/uninitialized.sol", + "contract": "Uninitialized", + "functions": [ + "transfer" + ], + "variable": "destination" + }, + { + "vuln": "UninitializedStateVars", + "sourceMapping": [ + { + "start": 189, + "length": 34, + "filename": "tests/uninitialized.sol" + }, + { + "start": 356, + "length": 143, + "filename": "tests/uninitialized.sol" + } + ], + "filename": "tests/uninitialized.sol", + "contract": "Test", + "functions": [ + "use" + ], + "variable": "balances" + }, + { + "vuln": "UninitializedStateVars", + "sourceMapping": [ + { + "start": 695, + "length": 15, + "filename": "tests/uninitialized.sol" + }, + { + "start": 875, + "length": 117, + "filename": "tests/uninitialized.sol" + } + ], + "filename": "tests/uninitialized.sol", + "contract": "Test2", + "functions": [ + "use" + ], + "variable": "st" + }, + { + "vuln": "UninitializedStateVars", + "sourceMapping": [ + { + "start": 748, + "length": 6, + "filename": "tests/uninitialized.sol" + }, + { + "start": 817, + "length": 52, + "filename": "tests/uninitialized.sol" + } + ], + "filename": "tests/uninitialized.sol", + "contract": "Test2", + "functions": [ + "init" + ], + "variable": "v" + } +] diff --git a/tests/uninitialized_storage_pointer.uninitialized-storage.json b/tests/uninitialized_storage_pointer.uninitialized-storage.json new file mode 100644 index 000000000..81fc04e74 --- /dev/null +++ b/tests/uninitialized_storage_pointer.uninitialized-storage.json @@ -0,0 +1,21 @@ +[ + { + "vuln": "UninitializedStorageVars", + "sourceMapping": [ + { + "start": 67, + "length": 138, + "filename": "tests/uninitialized_storage_pointer.sol" + }, + { + "start": 171, + "length": 9, + "filename": "tests/uninitialized_storage_pointer.sol" + } + ], + "filename": "tests/uninitialized_storage_pointer.sol", + "contract": "Uninitialized", + "function": "func", + "variable": "st_bug" + } +] diff --git a/tests/unused_state.unused-state.json b/tests/unused_state.unused-state.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/tests/unused_state.unused-state.json @@ -0,0 +1 @@ +[] From 1b3b29a8088f5df46bff998870b2aa9e6158e148 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 31 Oct 2018 11:50:19 +0100 Subject: [PATCH 278/308] Improve truffle integration --- slither/__main__.py | 7 +++++-- slither/core/source_mapping/source_mapping.py | 2 ++ slither/detectors/attributes/constant_pragma.py | 2 +- .../variables/uninitialized_state_variables.py | 1 + slither/solc_parsing/slitherSolc.py | 12 ++++++++++-- 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/slither/__main__.py b/slither/__main__.py index 21bd5ec3e..00247efbb 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -65,15 +65,18 @@ def process_truffle(dirname, args, detector_classes, printer_classes): filenames = glob.glob(os.path.join(dirname,'build','contracts', '*.json')) all_contracts = [] + all_filenames = [] for filename in filenames: with open(filename) as f: contract_loaded = json.load(f) - all_contracts += contract_loaded['ast']['nodes'] + all_contracts += contract_loaded['ast']['nodes'] + all_filenames.append(contract_loaded['sourcePath']) contract = { "nodeType": "SourceUnit", - "nodes" : all_contracts} + "nodes" : all_contracts, + "sourcePaths": all_filenames} slither = Slither(contract, args.solc, args.disable_solc_warnings, args.solc_args) return _process(slither, detector_classes, printer_classes) diff --git a/slither/core/source_mapping/source_mapping.py b/slither/core/source_mapping/source_mapping.py index 642a2fb4c..7c67c5283 100644 --- a/slither/core/source_mapping/source_mapping.py +++ b/slither/core/source_mapping/source_mapping.py @@ -51,6 +51,8 @@ class SourceMapping(Context): f = int(f) if f not in sourceUnits: + print(f) + print(sourceUnits) return {'start':s, 'length':l} filename = sourceUnits[f] diff --git a/slither/detectors/attributes/constant_pragma.py b/slither/detectors/attributes/constant_pragma.py index faf65d988..4fc531a0e 100644 --- a/slither/detectors/attributes/constant_pragma.py +++ b/slither/detectors/attributes/constant_pragma.py @@ -22,7 +22,7 @@ class ConstantPragma(AbstractDetector): versions = list(set(versions)) if len(versions) > 1: - info = "Different version of Solidity used in {}:\n".format(self.filename) + info = "Different versions of Solidity is used in {}:\n".format(self.filename) for p in pragma: info += "\t- {} declares {}\n".format(p.source_mapping_str, str(p)) self.log(info) diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index a1650e6b7..bc4fb3759 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -73,6 +73,7 @@ class UninitializedStateVarsDetection(AbstractDetector): ret = self.detect_uninitialized(c) for variable, functions in ret: info = "{}.{} ({}) is never initialized. It is used in:\n" + print(variable.source_mapping) info = info.format(variable.contract.name, variable.name, variable.source_mapping_str) for f in functions: info += "\t- {} ({})\n".format(f.name, f.source_mapping_str) diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index 487afe61c..247fb9d5d 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -54,9 +54,18 @@ class SlitherSolc(Slither): return False def _parse_contracts_from_loaded_json(self, data_loaded, filename): + print(filename) if 'nodeType' in data_loaded: self._is_compact_ast = True + if 'sourcePaths' in data_loaded: + for sourcePath in data_loaded['sourcePaths']: + if os.path.isfile(sourcePath): + with open(sourcePath) as f: + source_code = f.read() + print(sourcePath) + self.source_code[sourcePath] = source_code + if data_loaded[self.get_key()] == 'root': self._solc_version = '0.3' logger.error('solc <0.4 is not supported') @@ -106,7 +115,7 @@ class SlitherSolc(Slither): assert len(name) == 1 name = name[0] else: - name =filename + name = filename sourceUnit = -1 # handle old solc, or error if 'src' in data: @@ -115,7 +124,6 @@ class SlitherSolc(Slither): sourceUnit = int(sourceUnit[0]) self._source_units[sourceUnit] = name - if os.path.isfile(name): with open(name) as f: source_code = f.read() From aa562c7a8bdb7913649b3b11d986933d607a9b3d Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Thu, 1 Nov 2018 02:24:34 +0100 Subject: [PATCH 279/308] force sorted list of pragmas, otherwise order is non-deterministic and output json does nto always get same pragma versions array order --- slither/detectors/attributes/constant_pragma.py | 2 +- slither/detectors/attributes/old_solc.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/slither/detectors/attributes/constant_pragma.py b/slither/detectors/attributes/constant_pragma.py index 7a1e272c0..763f3d04a 100644 --- a/slither/detectors/attributes/constant_pragma.py +++ b/slither/detectors/attributes/constant_pragma.py @@ -19,7 +19,7 @@ class ConstantPragma(AbstractDetector): results = [] pragma = self.slither.pragma_directives versions = [p.version for p in pragma] - versions = list(set(versions)) + versions = sorted(list(set(versions))) if len(versions) > 1: info = "Different version of Solidity used in {}: {}".format(self.filename, versions) diff --git a/slither/detectors/attributes/old_solc.py b/slither/detectors/attributes/old_solc.py index a14a1df50..b733ba1a7 100644 --- a/slither/detectors/attributes/old_solc.py +++ b/slither/detectors/attributes/old_solc.py @@ -21,7 +21,7 @@ class OldSolc(AbstractDetector): pragma = self.slither.pragma_directives versions = [p.version for p in pragma] versions = [p.replace('solidity', '').replace('^', '') for p in versions] - versions = list(set(versions)) + versions = sorted(list(set(versions))) old_pragma = [p for p in versions if p not in ['0.4.23', '0.4.24']] if old_pragma: From 6dc5cbe70ee635b86e319ab10d3a18179bd959d0 Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Thu, 1 Nov 2018 02:26:43 +0100 Subject: [PATCH 280/308] improve expected-json-generation and test script + use new tests/expected_json/ dir to store expected json files --- scripts/tests_generate_expected_json.sh | 12 ++++---- scripts/travis_test.sh | 40 +++++++++++++++++-------- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/scripts/tests_generate_expected_json.sh b/scripts/tests_generate_expected_json.sh index 7ebe43107..aa92fa555 100755 --- a/scripts/tests_generate_expected_json.sh +++ b/scripts/tests_generate_expected_json.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +DIR="$(cd "$(dirname "$0")" && pwd)" + # generate_expected_json file.sol detectors generate_expected_json(){ # generate output filename @@ -8,13 +10,13 @@ generate_expected_json(){ output_filename="$(basename $1 .sol).$2.json" # run slither detector on input file and save output as json - slither "$1" --disable-solc-warnings --detectors "$2" --json "$output_filename" + slither "$1" --disable-solc-warnings --detectors "$2" --json "$DIR/tmp-gen.json" - # beautify json and move to test/ - cat "$output_filename" | python -m json.tool > tests/$output_filename + # convert json file to pretty print and write to destination folder + cat "$DIR/tmp-gen.json" | python -m json.tool > "$DIR/../tests/expected_json/$output_filename" - # rm original un-beautified json file - rm $output_filename + # remove the raw un-prettified json file + rm "$DIR/tmp-gen.json" } generate_expected_json tests/uninitialized.sol "uninitialized-state" diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 548fc9324..444343ee0 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -2,38 +2,54 @@ ### Test Detectors +DIR="$(cd "$(dirname "$0")" && pwd)" + # test_slither file.sol detectors test_slither(){ - expected="tests/$(basename $1 .sol).$2.json" - actual="$(basename $1 .sol).$2.json" + expected="$DIR/../tests/expected_json/$(basename $1 .sol).$2.json" + actual="$DIR/$(basename $1 .sol).$2.json" + + # run slither detector on input file and save output as json + slither "$1" --disable-solc-warnings --detectors "$2" --json "$DIR/tmp-test.json" - slither "$1" --disable-solc-warnings --detectors "$2" --json tmp.json + # convert json file to pretty print and write to destination folder + cat "$DIR/tmp-test.json" | python -m json.tool > "$actual" - cat tmp.json | python -m json.tool > "$actual" - rm tmp.json + # remove the raw un-prettified json file + rm "$DIR/tmp-test.json" result=$(diff "$expected" "$actual") if [ "$result" != "" ]; then rm "$actual" - echo "\nfailed test of file: $1, detector: $2\n" - echo "$result\n" + echo "" + echo "failed test of file: $1, detector: $2" + echo "" + echo "$result" + echo "" exit 1 else rm "$actual" fi - slither "$1" --disable-solc-warnings --detectors "$2" --compact-ast --json tmp.json + # run slither detector on input file and save output as json + slither "$1" --disable-solc-warnings --detectors "$2" --compact-ast --json "$DIR/tmp-test.json" + + # convert json file to pretty print and write to destination folder + cat "$DIR/tmp-test.json" | python -m json.tool > "$actual" - cat tmp.json | python -m json.tool > "$actual" - rm tmp.json + # remove the raw un-prettified json file + rm "$DIR/tmp-test.json" result=$(diff "$expected" "$actual") if [ "$result" != "" ]; then rm "$actual" - echo "\nfailed test of file: $1, detector: $2\n" - echo "$result\n" + echo "" + echo "failed test of file: $1, detector: $2" + echo "" + echo "$result" + echo "" exit 1 else rm "$actual" From a7a933ff9b9fb9609c8dcd851269eb693be5468f Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Thu, 1 Nov 2018 02:28:00 +0100 Subject: [PATCH 281/308] regenerated expected json output files and moved them to tests/expected_json/ --- tests/backdoor.backdoor.json | 1 - tests/backdoor.suicidal.json | 1 - .../arbitrary_send.arbitrary-send.json | 28 ++-- tests/expected_json/backdoor.backdoor.json | 11 ++ tests/expected_json/backdoor.suicidal.json | 13 ++ ...onst_state_variables.constable-states.json | 48 +++---- .../external_function.external-function.json | 48 +++---- .../inline_assembly_contract.assembly.json | 8 +- .../inline_assembly_library.assembly.json | 16 +-- .../locked_ether.locked-ether.json | 16 +++ .../low_level_calls.low-level-calls.json | 8 +- .../naming_convention.naming-convention.json | 130 +++++++++--------- .../old_solc.sol.json.solc-version.json | 8 +- tests/expected_json/pragma.0.4.24.pragma.json | 21 +++ .../reentrancy.reentrancy.json | 26 ++-- .../tx_origin.tx-origin.json | 16 +-- .../uninitialized.uninitialized-state.json | 80 +++++------ ...storage_pointer.uninitialized-storage.json | 18 +-- .../unused_state.unused-state.json | 17 +++ tests/locked_ether.locked-ether.json | 1 - tests/pragma.0.4.24.pragma.json | 1 - tests/unused_state.unused-state.json | 1 - 22 files changed, 295 insertions(+), 222 deletions(-) delete mode 100644 tests/backdoor.backdoor.json delete mode 100644 tests/backdoor.suicidal.json rename tests/{ => expected_json}/arbitrary_send.arbitrary-send.json (84%) create mode 100644 tests/expected_json/backdoor.backdoor.json create mode 100644 tests/expected_json/backdoor.suicidal.json rename tests/{ => expected_json}/const_state_variables.constable-states.json (50%) rename tests/{ => expected_json}/external_function.external-function.json (55%) rename tests/{ => expected_json}/inline_assembly_contract.assembly.json (73%) rename tests/{ => expected_json}/inline_assembly_library.assembly.json (71%) create mode 100644 tests/expected_json/locked_ether.locked-ether.json rename tests/{ => expected_json}/low_level_calls.low-level-calls.json (69%) rename tests/{ => expected_json}/naming_convention.naming-convention.json (59%) rename tests/{ => expected_json}/old_solc.sol.json.solc-version.json (58%) create mode 100644 tests/expected_json/pragma.0.4.24.pragma.json rename tests/{ => expected_json}/reentrancy.reentrancy.json (81%) rename tests/{ => expected_json}/tx_origin.tx-origin.json (70%) rename tests/{ => expected_json}/uninitialized.uninitialized-state.json (55%) rename tests/{ => expected_json}/uninitialized_storage_pointer.uninitialized-storage.json (74%) create mode 100644 tests/expected_json/unused_state.unused-state.json delete mode 100644 tests/locked_ether.locked-ether.json delete mode 100644 tests/pragma.0.4.24.pragma.json delete mode 100644 tests/unused_state.unused-state.json diff --git a/tests/backdoor.backdoor.json b/tests/backdoor.backdoor.json deleted file mode 100644 index fe51488c7..000000000 --- a/tests/backdoor.backdoor.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/tests/backdoor.suicidal.json b/tests/backdoor.suicidal.json deleted file mode 100644 index fe51488c7..000000000 --- a/tests/backdoor.suicidal.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/tests/arbitrary_send.arbitrary-send.json b/tests/expected_json/arbitrary_send.arbitrary-send.json similarity index 84% rename from tests/arbitrary_send.arbitrary-send.json rename to tests/expected_json/arbitrary_send.arbitrary-send.json index 9a1da6ca1..abb73a3a3 100644 --- a/tests/arbitrary_send.arbitrary-send.json +++ b/tests/expected_json/arbitrary_send.arbitrary-send.json @@ -1,26 +1,26 @@ [ { - "vuln": "SuicidalFunc", - "sourceMapping": [ - null + "calls": [ + "msg.sender.send(this.balance)" ], - "filename": "tests/arbitrary_send.sol", "contract": "Test", + "filename": "tests/arbitrary_send.sol", "func": "direct", - "calls": [ - "msg.sender.send(this.balance)" - ] - }, - { - "vuln": "SuicidalFunc", "sourceMapping": [ null ], - "filename": "tests/arbitrary_send.sol", - "contract": "Test", - "func": "indirect", + "vuln": "SuicidalFunc" + }, + { "calls": [ "destination.send(this.balance)" - ] + ], + "contract": "Test", + "filename": "tests/arbitrary_send.sol", + "func": "indirect", + "sourceMapping": [ + null + ], + "vuln": "SuicidalFunc" } ] diff --git a/tests/expected_json/backdoor.backdoor.json b/tests/expected_json/backdoor.backdoor.json new file mode 100644 index 000000000..87ae303b3 --- /dev/null +++ b/tests/expected_json/backdoor.backdoor.json @@ -0,0 +1,11 @@ +[ + { + "contract": "C", + "sourceMapping": { + "filename": "tests/backdoor.sol", + "length": 74, + "start": 42 + }, + "vuln": "backdoor" + } +] diff --git a/tests/expected_json/backdoor.suicidal.json b/tests/expected_json/backdoor.suicidal.json new file mode 100644 index 000000000..e8dd6d8c8 --- /dev/null +++ b/tests/expected_json/backdoor.suicidal.json @@ -0,0 +1,13 @@ +[ + { + "contract": "C", + "filename": "tests/backdoor.sol", + "func": "i_am_a_backdoor", + "sourceMapping": { + "filename": "tests/backdoor.sol", + "length": 74, + "start": 42 + }, + "vuln": "SuicidalFunc" + } +] diff --git a/tests/const_state_variables.constable-states.json b/tests/expected_json/const_state_variables.constable-states.json similarity index 50% rename from tests/const_state_variables.constable-states.json rename to tests/expected_json/const_state_variables.constable-states.json index a02aa98bb..2060fe3d7 100644 --- a/tests/const_state_variables.constable-states.json +++ b/tests/expected_json/const_state_variables.constable-states.json @@ -1,64 +1,64 @@ [ { - "vuln": "ConstStateVariableCandidates", + "contract": "B", + "filename": "tests/const_state_variables.sol", "sourceMapping": [ { - "start": 130, + "filename": "tests/const_state_variables.sol", "length": 76, - "filename": "tests/const_state_variables.sol" + "start": 130 }, { - "start": 235, + "filename": "tests/const_state_variables.sol", "length": 20, - "filename": "tests/const_state_variables.sol" + "start": 235 }, { - "start": 331, + "filename": "tests/const_state_variables.sol", "length": 20, - "filename": "tests/const_state_variables.sol" + "start": 331 }, { - "start": 494, + "filename": "tests/const_state_variables.sol", "length": 76, - "filename": "tests/const_state_variables.sol" + "start": 494 } ], - "filename": "tests/const_state_variables.sol", - "contract": "B", "unusedVars": [ "myFriendsAddress", "test", "text2" - ] + ], + "vuln": "ConstStateVariableCandidates" }, { - "vuln": "ConstStateVariableCandidates", + "contract": "B", + "filename": "tests/const_state_variables.sol", "sourceMapping": [ { - "start": 130, + "filename": "tests/const_state_variables.sol", "length": 76, - "filename": "tests/const_state_variables.sol" + "start": 130 }, { - "start": 235, + "filename": "tests/const_state_variables.sol", "length": 20, - "filename": "tests/const_state_variables.sol" + "start": 235 }, { - "start": 331, + "filename": "tests/const_state_variables.sol", "length": 20, - "filename": "tests/const_state_variables.sol" + "start": 331 }, { - "start": 494, + "filename": "tests/const_state_variables.sol", "length": 76, - "filename": "tests/const_state_variables.sol" + "start": 494 } ], - "filename": "tests/const_state_variables.sol", - "contract": "B", "unusedVars": [ "mySistersAddress" - ] + ], + "vuln": "ConstStateVariableCandidates" } ] diff --git a/tests/external_function.external-function.json b/tests/expected_json/external_function.external-function.json similarity index 55% rename from tests/external_function.external-function.json rename to tests/expected_json/external_function.external-function.json index 829c6e800..c31400a86 100644 --- a/tests/external_function.external-function.json +++ b/tests/expected_json/external_function.external-function.json @@ -1,46 +1,46 @@ [ { - "vuln": "ExternalFunc", + "contract": "ContractWithFunctionNotCalled", + "filename": "tests/external_function.sol", + "func": "funcNotCalled3", "sourceMapping": { - "start": 257, + "filename": "tests/external_function.sol", "length": 41, - "filename": "tests/external_function.sol" + "start": 257 }, - "filename": "tests/external_function.sol", - "contract": "ContractWithFunctionNotCalled", - "func": "funcNotCalled3" + "vuln": "ExternalFunc" }, { - "vuln": "ExternalFunc", + "contract": "ContractWithFunctionNotCalled", + "filename": "tests/external_function.sol", + "func": "funcNotCalled2", "sourceMapping": { - "start": 304, + "filename": "tests/external_function.sol", "length": 41, - "filename": "tests/external_function.sol" + "start": 304 }, - "filename": "tests/external_function.sol", - "contract": "ContractWithFunctionNotCalled", - "func": "funcNotCalled2" + "vuln": "ExternalFunc" }, { - "vuln": "ExternalFunc", + "contract": "ContractWithFunctionNotCalled", + "filename": "tests/external_function.sol", + "func": "funcNotCalled", "sourceMapping": { - "start": 351, + "filename": "tests/external_function.sol", "length": 40, - "filename": "tests/external_function.sol" + "start": 351 }, - "filename": "tests/external_function.sol", - "contract": "ContractWithFunctionNotCalled", - "func": "funcNotCalled" + "vuln": "ExternalFunc" }, { - "vuln": "ExternalFunc", + "contract": "ContractWithFunctionNotCalled2", + "filename": "tests/external_function.sol", + "func": "funcNotCalled", "sourceMapping": { - "start": 552, + "filename": "tests/external_function.sol", "length": 304, - "filename": "tests/external_function.sol" + "start": 552 }, - "filename": "tests/external_function.sol", - "contract": "ContractWithFunctionNotCalled2", - "func": "funcNotCalled" + "vuln": "ExternalFunc" } ] diff --git a/tests/inline_assembly_contract.assembly.json b/tests/expected_json/inline_assembly_contract.assembly.json similarity index 73% rename from tests/inline_assembly_contract.assembly.json rename to tests/expected_json/inline_assembly_contract.assembly.json index 0333b2f0a..0d02458c6 100644 --- a/tests/inline_assembly_contract.assembly.json +++ b/tests/expected_json/inline_assembly_contract.assembly.json @@ -1,11 +1,11 @@ [ { - "vuln": "Assembly", + "contract": "GetCode", + "filename": "tests/inline_assembly_contract.sol", + "function_name": "at", "sourceMapping": [ null ], - "filename": "tests/inline_assembly_contract.sol", - "contract": "GetCode", - "function_name": "at" + "vuln": "Assembly" } ] diff --git a/tests/inline_assembly_library.assembly.json b/tests/expected_json/inline_assembly_library.assembly.json similarity index 71% rename from tests/inline_assembly_library.assembly.json rename to tests/expected_json/inline_assembly_library.assembly.json index 62bbf9fae..5c62a4e1d 100644 --- a/tests/inline_assembly_library.assembly.json +++ b/tests/expected_json/inline_assembly_library.assembly.json @@ -1,20 +1,20 @@ [ { - "vuln": "Assembly", + "contract": "VectorSum", + "filename": "tests/inline_assembly_library.sol", + "function_name": "sumAsm", "sourceMapping": [ null ], - "filename": "tests/inline_assembly_library.sol", - "contract": "VectorSum", - "function_name": "sumAsm" + "vuln": "Assembly" }, { - "vuln": "Assembly", + "contract": "VectorSum", + "filename": "tests/inline_assembly_library.sol", + "function_name": "sumPureAsm", "sourceMapping": [ null ], - "filename": "tests/inline_assembly_library.sol", - "contract": "VectorSum", - "function_name": "sumPureAsm" + "vuln": "Assembly" } ] diff --git a/tests/expected_json/locked_ether.locked-ether.json b/tests/expected_json/locked_ether.locked-ether.json new file mode 100644 index 000000000..bdab33b8f --- /dev/null +++ b/tests/expected_json/locked_ether.locked-ether.json @@ -0,0 +1,16 @@ +[ + { + "contract": "OnlyLocked", + "functions_payable": [ + "receive" + ], + "sourceMapping": [ + { + "filename": "tests/locked_ether.sol", + "length": 72, + "start": 46 + } + ], + "vuln": "LockedEther" + } +] diff --git a/tests/low_level_calls.low-level-calls.json b/tests/expected_json/low_level_calls.low-level-calls.json similarity index 69% rename from tests/low_level_calls.low-level-calls.json rename to tests/expected_json/low_level_calls.low-level-calls.json index 7f30bf684..e3b7ce1e2 100644 --- a/tests/low_level_calls.low-level-calls.json +++ b/tests/expected_json/low_level_calls.low-level-calls.json @@ -1,11 +1,11 @@ [ { - "vuln": "Low level call", + "contract": "Sender", + "filename": "tests/low_level_calls.sol", + "function_name": "send", "sourceMapping": [ null ], - "filename": "tests/low_level_calls.sol", - "contract": "Sender", - "function_name": "send" + "vuln": "Low level call" } ] diff --git a/tests/naming_convention.naming-convention.json b/tests/expected_json/naming_convention.naming-convention.json similarity index 59% rename from tests/naming_convention.naming-convention.json rename to tests/expected_json/naming_convention.naming-convention.json index 2410e66f3..1a4032780 100644 --- a/tests/naming_convention.naming-convention.json +++ b/tests/expected_json/naming_convention.naming-convention.json @@ -1,131 +1,131 @@ [ { - "vuln": "NamingConvention", - "filename": "tests/naming_convention.sol", "contract": "naming", + "filename": "tests/naming_convention.sol", "sourceMapping": { - "start": 26, + "filename": "tests/naming_convention.sol", "length": 598, - "filename": "tests/naming_convention.sol" - } + "start": 26 + }, + "vuln": "NamingConvention" }, { - "vuln": "NamingConvention", - "filename": "tests/naming_convention.sol", "contract": "naming", - "struct": "test", + "filename": "tests/naming_convention.sol", "sourceMapping": { - "start": 227, + "filename": "tests/naming_convention.sol", "length": 20, - "filename": "tests/naming_convention.sol" - } + "start": 227 + }, + "struct": "test", + "vuln": "NamingConvention" }, { - "vuln": "NamingConvention", - "filename": "tests/naming_convention.sol", "contract": "naming", "event": "event_", - "sourceMapping": null + "filename": "tests/naming_convention.sol", + "sourceMapping": null, + "vuln": "NamingConvention" }, { - "vuln": "NamingConvention", - "filename": "tests/naming_convention.sol", "contract": "naming", + "filename": "tests/naming_convention.sol", "function": "GetOne", "sourceMapping": { - "start": 405, + "filename": "tests/naming_convention.sol", "length": 71, - "filename": "tests/naming_convention.sol" - } + "start": 405 + }, + "vuln": "NamingConvention" }, { - "vuln": "NamingConvention", - "filename": "tests/naming_convention.sol", + "argument": "Number2", "contract": "naming", + "filename": "tests/naming_convention.sol", "function": "setInt", - "argument": "Number2", "sourceMapping": { - "start": 512, + "filename": "tests/naming_convention.sol", "length": 12, - "filename": "tests/naming_convention.sol" - } + "start": 512 + }, + "vuln": "NamingConvention" }, { - "vuln": "NamingConvention", - "filename": "tests/naming_convention.sol", - "contract": "naming", "constant": "MY_other_CONSTANT", + "contract": "naming", + "filename": "tests/naming_convention.sol", "sourceMapping": { - "start": 141, + "filename": "tests/naming_convention.sol", "length": 35, - "filename": "tests/naming_convention.sol" - } + "start": 141 + }, + "vuln": "NamingConvention" }, { - "vuln": "NamingConvention", - "filename": "tests/naming_convention.sol", "contract": "naming", - "variable": "Var_One", + "filename": "tests/naming_convention.sol", "sourceMapping": { - "start": 183, + "filename": "tests/naming_convention.sol", "length": 16, - "filename": "tests/naming_convention.sol" - } + "start": 183 + }, + "variable": "Var_One", + "vuln": "NamingConvention" }, { - "vuln": "NamingConvention", - "filename": "tests/naming_convention.sol", "contract": "naming", "enum": "numbers", + "filename": "tests/naming_convention.sol", "sourceMapping": { - "start": 77, + "filename": "tests/naming_convention.sol", "length": 23, - "filename": "tests/naming_convention.sol" - } + "start": 77 + }, + "vuln": "NamingConvention" }, { - "vuln": "NamingConvention", - "filename": "tests/naming_convention.sol", "contract": "naming", + "filename": "tests/naming_convention.sol", "modifier": "CantDo", "sourceMapping": { - "start": 545, + "filename": "tests/naming_convention.sol", "length": 36, - "filename": "tests/naming_convention.sol" - } + "start": 545 + }, + "vuln": "NamingConvention" }, { - "vuln": "NamingConvention", - "filename": "tests/naming_convention.sol", + "argument": "_used", "contract": "T", + "filename": "tests/naming_convention.sol", "function": "test", - "argument": "_used", "sourceMapping": { - "start": 748, + "filename": "tests/naming_convention.sol", "length": 10, - "filename": "tests/naming_convention.sol" - } + "start": 748 + }, + "vuln": "NamingConvention" }, { - "vuln": "NamingConvention", - "filename": "tests/naming_convention.sol", "contract": "T", - "variable": "_myPublicVar", + "filename": "tests/naming_convention.sol", "sourceMapping": { - "start": 695, + "filename": "tests/naming_convention.sol", "length": 17, - "filename": "tests/naming_convention.sol" - } + "start": 695 + }, + "variable": "_myPublicVar", + "vuln": "NamingConvention" }, { - "vuln": "NamingConvention", - "filename": "tests/naming_convention.sol", - "contract": "T", "constant": "l", + "contract": "T", + "filename": "tests/naming_convention.sol", "sourceMapping": { - "start": 847, + "filename": "tests/naming_convention.sol", "length": 10, - "filename": "tests/naming_convention.sol" - } + "start": 847 + }, + "vuln": "NamingConvention" } ] diff --git a/tests/old_solc.sol.json.solc-version.json b/tests/expected_json/old_solc.sol.json.solc-version.json similarity index 58% rename from tests/old_solc.sol.json.solc-version.json rename to tests/expected_json/old_solc.sol.json.solc-version.json index 8c349b7c2..4d950e0c4 100644 --- a/tests/old_solc.sol.json.solc-version.json +++ b/tests/expected_json/old_solc.sol.json.solc-version.json @@ -1,15 +1,15 @@ [ { - "vuln": "OldPragma", "pragma": [ "0.4.21" ], "sourceMapping": [ { - "start": 0, + "filename": "old_solc.sol", "length": 23, - "filename": "old_solc.sol" + "start": 0 } - ] + ], + "vuln": "OldPragma" } ] diff --git a/tests/expected_json/pragma.0.4.24.pragma.json b/tests/expected_json/pragma.0.4.24.pragma.json new file mode 100644 index 000000000..74922475c --- /dev/null +++ b/tests/expected_json/pragma.0.4.24.pragma.json @@ -0,0 +1,21 @@ +[ + { + "sourceMapping": [ + { + "filename": "tests/pragma.0.4.23.sol", + "length": 24, + "start": 0 + }, + { + "filename": "tests/pragma.0.4.24.sol", + "length": 23, + "start": 0 + } + ], + "versions": [ + "0.4.24", + "^0.4.23" + ], + "vuln": "ConstantPragma" + } +] diff --git a/tests/reentrancy.reentrancy.json b/tests/expected_json/reentrancy.reentrancy.json similarity index 81% rename from tests/reentrancy.reentrancy.json rename to tests/expected_json/reentrancy.reentrancy.json index d4163f6aa..e5c1b4597 100644 --- a/tests/reentrancy.reentrancy.json +++ b/tests/expected_json/reentrancy.reentrancy.json @@ -1,26 +1,26 @@ [ { - "vuln": "Reentrancy", + "calls": [ + "! (msg.sender.call.value(userBalance[msg.sender])())" + ], + "contract": "Reentrancy", + "filename": "tests/reentrancy.sol", + "function_name": "withdrawBalance()", + "send_eth": [ + "! (msg.sender.call.value(userBalance[msg.sender])())" + ], "sourceMapping": [ { - "start": 52, + "filename": "tests/reentrancy.sol", "length": 37, - "filename": "tests/reentrancy.sol" + "start": 52 }, null, null ], - "filename": "tests/reentrancy.sol", - "contract": "Reentrancy", - "function_name": "withdrawBalance()", - "calls": [ - "! (msg.sender.call.value(userBalance[msg.sender])())" - ], - "send_eth": [ - "! (msg.sender.call.value(userBalance[msg.sender])())" - ], "varsWritten": [ "userBalance" - ] + ], + "vuln": "Reentrancy" } ] diff --git a/tests/tx_origin.tx-origin.json b/tests/expected_json/tx_origin.tx-origin.json similarity index 70% rename from tests/tx_origin.tx-origin.json rename to tests/expected_json/tx_origin.tx-origin.json index fcc426df0..0be0cdad4 100644 --- a/tests/tx_origin.tx-origin.json +++ b/tests/expected_json/tx_origin.tx-origin.json @@ -1,20 +1,20 @@ [ { - "vuln": "TxOrigin", + "contract": "TxOrigin", + "filename": "tests/tx_origin.sol", + "function_name": "bug0", "sourceMapping": [ null ], - "filename": "tests/tx_origin.sol", - "contract": "TxOrigin", - "function_name": "bug0" + "vuln": "TxOrigin" }, { - "vuln": "TxOrigin", + "contract": "TxOrigin", + "filename": "tests/tx_origin.sol", + "function_name": "bug2", "sourceMapping": [ null ], - "filename": "tests/tx_origin.sol", - "contract": "TxOrigin", - "function_name": "bug2" + "vuln": "TxOrigin" } ] diff --git a/tests/uninitialized.uninitialized-state.json b/tests/expected_json/uninitialized.uninitialized-state.json similarity index 55% rename from tests/uninitialized.uninitialized-state.json rename to tests/expected_json/uninitialized.uninitialized-state.json index 856dac396..345ce93b8 100644 --- a/tests/uninitialized.uninitialized-state.json +++ b/tests/expected_json/uninitialized.uninitialized-state.json @@ -1,86 +1,86 @@ [ { - "vuln": "UninitializedStateVars", + "contract": "Uninitialized", + "filename": "tests/uninitialized.sol", + "functions": [ + "transfer" + ], "sourceMapping": [ { - "start": 55, + "filename": "tests/uninitialized.sol", "length": 19, - "filename": "tests/uninitialized.sol" + "start": 55 }, { - "start": 81, + "filename": "tests/uninitialized.sol", "length": 82, - "filename": "tests/uninitialized.sol" + "start": 81 } ], + "variable": "destination", + "vuln": "UninitializedStateVars" + }, + { + "contract": "Test", "filename": "tests/uninitialized.sol", - "contract": "Uninitialized", "functions": [ - "transfer" + "use" ], - "variable": "destination" - }, - { - "vuln": "UninitializedStateVars", "sourceMapping": [ { - "start": 189, + "filename": "tests/uninitialized.sol", "length": 34, - "filename": "tests/uninitialized.sol" + "start": 189 }, { - "start": 356, + "filename": "tests/uninitialized.sol", "length": 143, - "filename": "tests/uninitialized.sol" + "start": 356 } ], + "variable": "balances", + "vuln": "UninitializedStateVars" + }, + { + "contract": "Test2", "filename": "tests/uninitialized.sol", - "contract": "Test", "functions": [ "use" ], - "variable": "balances" - }, - { - "vuln": "UninitializedStateVars", "sourceMapping": [ { - "start": 695, + "filename": "tests/uninitialized.sol", "length": 15, - "filename": "tests/uninitialized.sol" + "start": 695 }, { - "start": 875, + "filename": "tests/uninitialized.sol", "length": 117, - "filename": "tests/uninitialized.sol" + "start": 875 } ], - "filename": "tests/uninitialized.sol", + "variable": "st", + "vuln": "UninitializedStateVars" + }, + { "contract": "Test2", + "filename": "tests/uninitialized.sol", "functions": [ - "use" + "init" ], - "variable": "st" - }, - { - "vuln": "UninitializedStateVars", "sourceMapping": [ { - "start": 748, + "filename": "tests/uninitialized.sol", "length": 6, - "filename": "tests/uninitialized.sol" + "start": 748 }, { - "start": 817, + "filename": "tests/uninitialized.sol", "length": 52, - "filename": "tests/uninitialized.sol" + "start": 817 } ], - "filename": "tests/uninitialized.sol", - "contract": "Test2", - "functions": [ - "init" - ], - "variable": "v" + "variable": "v", + "vuln": "UninitializedStateVars" } ] diff --git a/tests/uninitialized_storage_pointer.uninitialized-storage.json b/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json similarity index 74% rename from tests/uninitialized_storage_pointer.uninitialized-storage.json rename to tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json index 81fc04e74..50fd5c7f8 100644 --- a/tests/uninitialized_storage_pointer.uninitialized-storage.json +++ b/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json @@ -1,21 +1,21 @@ [ { - "vuln": "UninitializedStorageVars", + "contract": "Uninitialized", + "filename": "tests/uninitialized_storage_pointer.sol", + "function": "func", "sourceMapping": [ { - "start": 67, + "filename": "tests/uninitialized_storage_pointer.sol", "length": 138, - "filename": "tests/uninitialized_storage_pointer.sol" + "start": 67 }, { - "start": 171, + "filename": "tests/uninitialized_storage_pointer.sol", "length": 9, - "filename": "tests/uninitialized_storage_pointer.sol" + "start": 171 } ], - "filename": "tests/uninitialized_storage_pointer.sol", - "contract": "Uninitialized", - "function": "func", - "variable": "st_bug" + "variable": "st_bug", + "vuln": "UninitializedStorageVars" } ] diff --git a/tests/expected_json/unused_state.unused-state.json b/tests/expected_json/unused_state.unused-state.json new file mode 100644 index 000000000..ab82de585 --- /dev/null +++ b/tests/expected_json/unused_state.unused-state.json @@ -0,0 +1,17 @@ +[ + { + "contract": "B", + "filename": "tests/unused_state.sol", + "sourceMapping": [ + { + "filename": "tests/unused_state.sol", + "length": 14, + "start": 41 + } + ], + "unusedVars": [ + "unused" + ], + "vuln": "unusedStateVars" + } +] diff --git a/tests/locked_ether.locked-ether.json b/tests/locked_ether.locked-ether.json deleted file mode 100644 index fe51488c7..000000000 --- a/tests/locked_ether.locked-ether.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/tests/pragma.0.4.24.pragma.json b/tests/pragma.0.4.24.pragma.json deleted file mode 100644 index fe51488c7..000000000 --- a/tests/pragma.0.4.24.pragma.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/tests/unused_state.unused-state.json b/tests/unused_state.unused-state.json deleted file mode 100644 index fe51488c7..000000000 --- a/tests/unused_state.unused-state.json +++ /dev/null @@ -1 +0,0 @@ -[] From 4c1ff2e68efe79a34f387c8b1d3be6113ddd560d Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Thu, 1 Nov 2018 03:34:59 +0100 Subject: [PATCH 282/308] make output order of every detector deterministic --- slither/detectors/attributes/locked_ether.py | 4 ++-- slither/detectors/examples/backdoor.py | 4 ++-- slither/detectors/functions/arbitrary_send.py | 6 +++--- slither/detectors/functions/external_function.py | 4 ++-- slither/detectors/functions/suicidal.py | 4 ++-- .../naming_convention/naming_convention.py | 16 ++++++++-------- slither/detectors/operations/low_level_calls.py | 4 ++-- slither/detectors/reentrancy/reentrancy.py | 2 +- .../detectors/shadowing/shadowing_functions.py | 4 ++-- slither/detectors/statements/assembly.py | 4 ++-- slither/detectors/statements/tx_origin.py | 4 ++-- .../variables/possible_const_state_variables.py | 4 ++-- .../variables/uninitialized_state_variables.py | 4 ++-- .../variables/uninitialized_storage_variables.py | 2 +- .../variables/unused_state_variables.py | 2 +- 15 files changed, 34 insertions(+), 34 deletions(-) diff --git a/slither/detectors/attributes/locked_ether.py b/slither/detectors/attributes/locked_ether.py index 682224f02..c0ea08953 100644 --- a/slither/detectors/attributes/locked_ether.py +++ b/slither/detectors/attributes/locked_ether.py @@ -38,10 +38,10 @@ class LockedEther(AbstractDetector): def detect(self): results = [] - for contract in self.slither.contracts_derived: + for contract in sorted(self.slither.contracts_derived, key=lambda c: c.name): if contract.is_signature_only(): continue - funcs_payable = [function for function in contract.functions if function.payable] + funcs_payable = [function for function in sorted(contract.functions, key=lambda x: x.name) if function.payable] if funcs_payable: if self.do_no_send_ether(contract): txt = "Contract locked ether in {}, Contract {}, Functions {}" diff --git a/slither/detectors/examples/backdoor.py b/slither/detectors/examples/backdoor.py index 9e29724f9..1543e70f6 100644 --- a/slither/detectors/examples/backdoor.py +++ b/slither/detectors/examples/backdoor.py @@ -14,9 +14,9 @@ class Backdoor(AbstractDetector): def detect(self): ret = [] - for contract in self.slither.contracts_derived: + for contract in sorted(self.slither.contracts_derived, key=lambda c: c.name): # Check if a function has 'backdoor' in its name - for f in contract.functions: + for f in sorted(contract.functions, key=lambda x: x.name): if 'backdoor' in f.name: # Info to be printed info = 'Backdoor function found in {}.{}'.format(contract.name, f.name) diff --git a/slither/detectors/functions/arbitrary_send.py b/slither/detectors/functions/arbitrary_send.py index 920351d41..594c6ce78 100644 --- a/slither/detectors/functions/arbitrary_send.py +++ b/slither/detectors/functions/arbitrary_send.py @@ -33,7 +33,7 @@ class ArbitrarySend(AbstractDetector): @staticmethod def arbitrary_send(func): - """ + """ """ if func.is_protected(): return [] @@ -94,9 +94,9 @@ class ArbitrarySend(AbstractDetector): taint = SolidityVariableComposed('msg.sender') run_taint_variable(self.slither, taint) - for c in self.contracts: + for c in sorted(self.contracts, key=lambda c: c.name): arbitrary_send = self.detect_arbitrary_send(c) - for (func, nodes) in arbitrary_send: + for (func, nodes) in sorted(arbitrary_send, key=lambda v: v[0].name): func_name = func.name calls_str = [str(node.expression) for node in nodes] diff --git a/slither/detectors/functions/external_function.py b/slither/detectors/functions/external_function.py index 99ffe40fe..c5d860201 100644 --- a/slither/detectors/functions/external_function.py +++ b/slither/detectors/functions/external_function.py @@ -46,14 +46,14 @@ class ExternalFunction(AbstractDetector): public_function_calls = [] - for contract in self.slither.contracts_derived: + for contract in sorted(self.slither.contracts_derived, key=lambda c: c.name): if self._contains_internal_dynamic_call(contract): continue func_list = self.detect_functions_called(contract) public_function_calls.extend(func_list) - for func in [f for f in contract.functions if f.visibility == 'public' and\ + for func in [f for f in sorted(contract.functions, key=lambda x: x.name) if f.visibility == 'public' and\ not f in public_function_calls and\ not f.is_constructor]: func_name = func.name diff --git a/slither/detectors/functions/suicidal.py b/slither/detectors/functions/suicidal.py index 41b6f9bc5..156aa569f 100644 --- a/slither/detectors/functions/suicidal.py +++ b/slither/detectors/functions/suicidal.py @@ -51,9 +51,9 @@ class Suicidal(AbstractDetector): """ Detect the suicidal functions """ results = [] - for c in self.contracts: + for c in sorted(self.contracts, key=lambda c: c.name): functions = self.detect_suicidal(c) - for func in functions: + for func in sorted(functions, key=lambda x: x.name): func_name = func.name txt = "Suicidal function in {} Contract: {}, Function: {}" diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index f3312c1f0..36d928983 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -42,7 +42,7 @@ class NamingConvention(AbstractDetector): def detect(self): results = [] - for contract in self.contracts: + for contract in sorted(self.contracts, key=lambda c: c.name): if not self.is_cap_words(contract.name): info = "Contract '{}' is not in CapWords".format(contract.name) @@ -53,7 +53,7 @@ class NamingConvention(AbstractDetector): 'contract': contract.name, 'sourceMapping': contract.source_mapping}) - for struct in contract.structures: + for struct in sorted(contract.structures, key=lambda x: x.name): if struct.contract != contract: continue @@ -67,7 +67,7 @@ class NamingConvention(AbstractDetector): 'struct': struct.name, 'sourceMapping': struct.source_mapping}) - for event in contract.events: + for event in sorted(contract.events, key=lambda x: x.name): if event.contract != contract: continue @@ -81,7 +81,7 @@ class NamingConvention(AbstractDetector): 'event': event.name, 'sourceMapping': event.source_mapping}) - for func in contract.functions: + for func in sorted(contract.functions, key=lambda x: x.name): if func.contract != contract: continue @@ -95,7 +95,7 @@ class NamingConvention(AbstractDetector): 'function': func.name, 'sourceMapping': func.source_mapping}) - for argument in func.parameters: + for argument in sorted(func.parameters, key=lambda x: x.name): if argument in func.variables_read_or_written: correct_naming = self.is_mixed_case(argument.name) else: @@ -112,7 +112,7 @@ class NamingConvention(AbstractDetector): 'argument': argument.name, 'sourceMapping': argument.source_mapping}) - for var in contract.state_variables: + for var in sorted(contract.state_variables, key=lambda x: x.name): if var.contract != contract: continue @@ -158,7 +158,7 @@ class NamingConvention(AbstractDetector): 'variable': var.name, 'sourceMapping': var.source_mapping}) - for enum in contract.enums: + for enum in sorted(contract.enums, key=lambda x: x.name): if enum.contract != contract: continue @@ -172,7 +172,7 @@ class NamingConvention(AbstractDetector): 'enum': enum.name, 'sourceMapping': enum.source_mapping}) - for modifier in contract.modifiers: + for modifier in sorted(contract.modifiers, key=lambda x: x.name): if modifier.contract != contract: continue diff --git a/slither/detectors/operations/low_level_calls.py b/slither/detectors/operations/low_level_calls.py index ec0f546a7..11ce2c89b 100644 --- a/slither/detectors/operations/low_level_calls.py +++ b/slither/detectors/operations/low_level_calls.py @@ -39,9 +39,9 @@ class LowLevelCalls(AbstractDetector): """ Detect the functions that use low level calls """ results = [] - for c in self.contracts: + for c in sorted(self.contracts, key=lambda c: c.name): values = self.detect_low_level_calls(c) - for func, nodes in values: + for func, nodes in sorted(values, key=lambda v: v[0].name): func_name = func.name info = "Low level call in %s, Contract: %s, Function: %s" % (self.filename, c.name, diff --git a/slither/detectors/reentrancy/reentrancy.py b/slither/detectors/reentrancy/reentrancy.py index e5c9966a7..15325feb2 100644 --- a/slither/detectors/reentrancy/reentrancy.py +++ b/slither/detectors/reentrancy/reentrancy.py @@ -176,7 +176,7 @@ class Reentrancy(AbstractDetector): results = [] - for (contract, func, calls, send_eth), varsWritten in self.result.items(): + for (contract, func, calls, send_eth), varsWritten in sorted(self.result.items(), key=lambda x: (x[0][0], x[0][1], str(list(x[0][2])) )): varsWritten_str = list(set([str(x) for x in list(varsWritten)])) calls_str = list(set([str(x.expression) for x in list(calls)])) send_eth_str = list(set([str(x.expression) for x in list(send_eth)])) diff --git a/slither/detectors/shadowing/shadowing_functions.py b/slither/detectors/shadowing/shadowing_functions.py index 60d9677df..cecdc4b57 100644 --- a/slither/detectors/shadowing/shadowing_functions.py +++ b/slither/detectors/shadowing/shadowing_functions.py @@ -37,10 +37,10 @@ class ShadowingFunctionsDetection(AbstractDetector): """ results = [] - for c in self.contracts: + for c in sorted(self.contracts, key=lambda c: c.name): shadowing = self.detect_shadowing(c) if shadowing: - for contract, funcs in shadowing.items(): + for contract, funcs in sorted(shadowing.items(), key=lambda x: (x[0].name, str(list(x[1])))): results.append({'vuln': self.vuln_name, 'filename': self.filename, 'contractShadower': c.name, diff --git a/slither/detectors/statements/assembly.py b/slither/detectors/statements/assembly.py index 93e9ea598..85ffbe934 100644 --- a/slither/detectors/statements/assembly.py +++ b/slither/detectors/statements/assembly.py @@ -39,9 +39,9 @@ class Assembly(AbstractDetector): """ Detect the functions that use inline assembly """ results = [] - for c in self.contracts: + for c in sorted(self.contracts, key=lambda c: c.name): values = self.detect_assembly(c) - for func, nodes in values: + for func, nodes in sorted(values, key=lambda v: v[0].name): func_name = func.name info = "Assembly in %s, Contract: %s, Function: %s" % (self.filename, c.name, diff --git a/slither/detectors/statements/tx_origin.py b/slither/detectors/statements/tx_origin.py index f5c474f8f..fe2f5dc53 100644 --- a/slither/detectors/statements/tx_origin.py +++ b/slither/detectors/statements/tx_origin.py @@ -45,9 +45,9 @@ class TxOrigin(AbstractDetector): """ Detect the functions that use tx.origin in a conditional node """ results = [] - for c in self.contracts: + for c in sorted(self.contracts, key=lambda c: c.name): values = self.detect_tx_origin(c) - for func, nodes in values: + for func, nodes in sorted(values, key=lambda v: v[0].name): func_name = func.name info = "tx.origin in %s, Contract: %s, Function: %s" % (self.filename, c.name, diff --git a/slither/detectors/variables/possible_const_state_variables.py b/slither/detectors/variables/possible_const_state_variables.py index e19a389d1..4bcb0f3f4 100644 --- a/slither/detectors/variables/possible_const_state_variables.py +++ b/slither/detectors/variables/possible_const_state_variables.py @@ -54,12 +54,12 @@ class ConstCandidateStateVars(AbstractDetector): """ Detect state variables that could be const """ results = [] - for c in self.slither.contracts_derived: + for c in sorted(self.slither.contracts_derived, key=lambda c: c.name): const_candidates = self.detect_const_candidates(c) if const_candidates: variables_by_contract = defaultdict(list) - for state_var in const_candidates: + for state_var in sorted(const_candidates, key=lambda x: (x.contract.name, x.name)): variables_by_contract[state_var.contract.name].append(state_var) for contract, variables in variables_by_contract.items(): diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index 86f54b048..17abb4a28 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -69,9 +69,9 @@ class UninitializedStateVarsDetection(AbstractDetector): dict: [contract name] = set(state variable uninitialized) """ results = [] - for c in self.slither.contracts_derived: + for c in sorted(self.slither.contracts_derived, key=lambda c: c.name): ret = self.detect_uninitialized(c) - for variable, functions in ret: + for variable, functions in sorted(ret, key=lambda r: str(r[0])): info = "Uninitialized state variable in %s, " % self.filename + \ "Contract: %s, Variable: %s, Used in %s" % (c.name, str(variable), diff --git a/slither/detectors/variables/uninitialized_storage_variables.py b/slither/detectors/variables/uninitialized_storage_variables.py index 37201b33c..eef4ef4d6 100644 --- a/slither/detectors/variables/uninitialized_storage_variables.py +++ b/slither/detectors/variables/uninitialized_storage_variables.py @@ -79,7 +79,7 @@ class UninitializedStorageVars(AbstractDetector): function.entry_point.context[self.key] = uninitialized_storage_variables self._detect_uninitialized(function, function.entry_point, []) - for(function, uninitialized_storage_variable) in self.results: + for(function, uninitialized_storage_variable) in sorted(self.results, key=lambda r: (r[0].contract.name, r[0].name, r[1].name)): var_name = uninitialized_storage_variable.name info = "Uninitialized storage variables in %s, " % self.filename + \ diff --git a/slither/detectors/variables/unused_state_variables.py b/slither/detectors/variables/unused_state_variables.py index 5be36163e..c9ec84c03 100644 --- a/slither/detectors/variables/unused_state_variables.py +++ b/slither/detectors/variables/unused_state_variables.py @@ -30,7 +30,7 @@ class UnusedStateVars(AbstractDetector): """ Detect unused state variables """ results = [] - for c in self.slither.contracts_derived: + for c in sorted(self.slither.contracts_derived, key=lambda c: c.name): unusedVars = self.detect_unused(c) if unusedVars: unusedVarsName = [v.name for v in unusedVars] From 42415bf917cc776123da9559c6bcd85df6093585 Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Thu, 1 Nov 2018 03:35:45 +0100 Subject: [PATCH 283/308] regenerated (now deterministic ordered) json output for some tests --- .../external_function.external-function.json | 12 ++-- .../naming_convention.naming-convention.json | 68 +++++++++---------- .../uninitialized.uninitialized-state.json | 42 ++++++------ 3 files changed, 61 insertions(+), 61 deletions(-) diff --git a/tests/expected_json/external_function.external-function.json b/tests/expected_json/external_function.external-function.json index c31400a86..d6cfdbed9 100644 --- a/tests/expected_json/external_function.external-function.json +++ b/tests/expected_json/external_function.external-function.json @@ -2,11 +2,11 @@ { "contract": "ContractWithFunctionNotCalled", "filename": "tests/external_function.sol", - "func": "funcNotCalled3", + "func": "funcNotCalled", "sourceMapping": { "filename": "tests/external_function.sol", - "length": 41, - "start": 257 + "length": 40, + "start": 351 }, "vuln": "ExternalFunc" }, @@ -24,11 +24,11 @@ { "contract": "ContractWithFunctionNotCalled", "filename": "tests/external_function.sol", - "func": "funcNotCalled", + "func": "funcNotCalled3", "sourceMapping": { "filename": "tests/external_function.sol", - "length": 40, - "start": 351 + "length": 41, + "start": 257 }, "vuln": "ExternalFunc" }, diff --git a/tests/expected_json/naming_convention.naming-convention.json b/tests/expected_json/naming_convention.naming-convention.json index 1a4032780..f7cb8026c 100644 --- a/tests/expected_json/naming_convention.naming-convention.json +++ b/tests/expected_json/naming_convention.naming-convention.json @@ -1,4 +1,38 @@ [ + { + "argument": "_used", + "contract": "T", + "filename": "tests/naming_convention.sol", + "function": "test", + "sourceMapping": { + "filename": "tests/naming_convention.sol", + "length": 10, + "start": 748 + }, + "vuln": "NamingConvention" + }, + { + "contract": "T", + "filename": "tests/naming_convention.sol", + "sourceMapping": { + "filename": "tests/naming_convention.sol", + "length": 17, + "start": 695 + }, + "variable": "_myPublicVar", + "vuln": "NamingConvention" + }, + { + "constant": "l", + "contract": "T", + "filename": "tests/naming_convention.sol", + "sourceMapping": { + "filename": "tests/naming_convention.sol", + "length": 10, + "start": 847 + }, + "vuln": "NamingConvention" + }, { "contract": "naming", "filename": "tests/naming_convention.sol", @@ -93,39 +127,5 @@ "start": 545 }, "vuln": "NamingConvention" - }, - { - "argument": "_used", - "contract": "T", - "filename": "tests/naming_convention.sol", - "function": "test", - "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 10, - "start": 748 - }, - "vuln": "NamingConvention" - }, - { - "contract": "T", - "filename": "tests/naming_convention.sol", - "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 17, - "start": 695 - }, - "variable": "_myPublicVar", - "vuln": "NamingConvention" - }, - { - "constant": "l", - "contract": "T", - "filename": "tests/naming_convention.sol", - "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 10, - "start": 847 - }, - "vuln": "NamingConvention" } ] diff --git a/tests/expected_json/uninitialized.uninitialized-state.json b/tests/expected_json/uninitialized.uninitialized-state.json index 345ce93b8..9981962dc 100644 --- a/tests/expected_json/uninitialized.uninitialized-state.json +++ b/tests/expected_json/uninitialized.uninitialized-state.json @@ -1,25 +1,4 @@ [ - { - "contract": "Uninitialized", - "filename": "tests/uninitialized.sol", - "functions": [ - "transfer" - ], - "sourceMapping": [ - { - "filename": "tests/uninitialized.sol", - "length": 19, - "start": 55 - }, - { - "filename": "tests/uninitialized.sol", - "length": 82, - "start": 81 - } - ], - "variable": "destination", - "vuln": "UninitializedStateVars" - }, { "contract": "Test", "filename": "tests/uninitialized.sol", @@ -82,5 +61,26 @@ ], "variable": "v", "vuln": "UninitializedStateVars" + }, + { + "contract": "Uninitialized", + "filename": "tests/uninitialized.sol", + "functions": [ + "transfer" + ], + "sourceMapping": [ + { + "filename": "tests/uninitialized.sol", + "length": 19, + "start": 55 + }, + { + "filename": "tests/uninitialized.sol", + "length": 82, + "start": 81 + } + ], + "variable": "destination", + "vuln": "UninitializedStateVars" } ] From 4cb3a13eb17c36d71bd97bcd91d4dac39b518947 Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Thu, 1 Nov 2018 03:40:35 +0100 Subject: [PATCH 284/308] use custom python script to pretty print json output instead of python -e json.tool --- scripts/pretty_print_json.py | 11 +++++++++++ scripts/tests_generate_expected_json.sh | 2 +- scripts/travis_test.sh | 4 ++-- 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 scripts/pretty_print_json.py diff --git a/scripts/pretty_print_json.py b/scripts/pretty_print_json.py new file mode 100644 index 000000000..9de71f14e --- /dev/null +++ b/scripts/pretty_print_json.py @@ -0,0 +1,11 @@ +#!/usr/bin/python3 + +import sys +import json + +raw_json_file = sys.argv[1] +pretty_json_file = sys.argv[2] + +with open(raw_json_file, 'r') as json_data: + with open(pretty_json_file, 'w') as out_file: + out_file.write(json.dumps(json.load(json_data), sort_keys=True, indent=4)) \ No newline at end of file diff --git a/scripts/tests_generate_expected_json.sh b/scripts/tests_generate_expected_json.sh index aa92fa555..2e024a106 100755 --- a/scripts/tests_generate_expected_json.sh +++ b/scripts/tests_generate_expected_json.sh @@ -13,7 +13,7 @@ generate_expected_json(){ slither "$1" --disable-solc-warnings --detectors "$2" --json "$DIR/tmp-gen.json" # convert json file to pretty print and write to destination folder - cat "$DIR/tmp-gen.json" | python -m json.tool > "$DIR/../tests/expected_json/$output_filename" + python "$DIR/pretty_print_json.py" "$DIR/tmp-gen.json" "$DIR/../tests/expected_json/$output_filename" # remove the raw un-prettified json file rm "$DIR/tmp-gen.json" diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 444343ee0..5e70083b1 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -13,7 +13,7 @@ test_slither(){ slither "$1" --disable-solc-warnings --detectors "$2" --json "$DIR/tmp-test.json" # convert json file to pretty print and write to destination folder - cat "$DIR/tmp-test.json" | python -m json.tool > "$actual" + python "$DIR/pretty_print_json.py" "$DIR/tmp-test.json" "$actual" # remove the raw un-prettified json file rm "$DIR/tmp-test.json" @@ -36,7 +36,7 @@ test_slither(){ slither "$1" --disable-solc-warnings --detectors "$2" --compact-ast --json "$DIR/tmp-test.json" # convert json file to pretty print and write to destination folder - cat "$DIR/tmp-test.json" | python -m json.tool > "$actual" + python "$DIR/pretty_print_json.py" "$DIR/tmp-test.json" "$actual" # remove the raw un-prettified json file rm "$DIR/tmp-test.json" From d8c21daf45074e6bc9c6696bc738d256388ce16d Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Thu, 1 Nov 2018 03:40:56 +0100 Subject: [PATCH 285/308] regenrate expected json for tests --- .../arbitrary_send.arbitrary-send.json | 24 +-- tests/expected_json/backdoor.backdoor.json | 10 +- tests/expected_json/backdoor.suicidal.json | 14 +- ...onst_state_variables.constable-states.json | 68 +++---- .../external_function.external-function.json | 56 +++--- .../inline_assembly_contract.assembly.json | 10 +- .../inline_assembly_library.assembly.json | 20 +-- .../locked_ether.locked-ether.json | 12 +- .../low_level_calls.low-level-calls.json | 10 +- .../naming_convention.naming-convention.json | 166 +++++++++--------- .../old_solc.sol.json.solc-version.json | 10 +- tests/expected_json/pragma.0.4.24.pragma.json | 18 +- .../expected_json/reentrancy.reentrancy.json | 24 +-- tests/expected_json/tx_origin.tx-origin.json | 20 +-- .../uninitialized.uninitialized-state.json | 88 +++++----- ...storage_pointer.uninitialized-storage.json | 22 +-- .../unused_state.unused-state.json | 14 +- 17 files changed, 293 insertions(+), 293 deletions(-) diff --git a/tests/expected_json/arbitrary_send.arbitrary-send.json b/tests/expected_json/arbitrary_send.arbitrary-send.json index abb73a3a3..fa6299fa1 100644 --- a/tests/expected_json/arbitrary_send.arbitrary-send.json +++ b/tests/expected_json/arbitrary_send.arbitrary-send.json @@ -2,25 +2,25 @@ { "calls": [ "msg.sender.send(this.balance)" - ], - "contract": "Test", - "filename": "tests/arbitrary_send.sol", - "func": "direct", + ], + "contract": "Test", + "filename": "tests/arbitrary_send.sol", + "func": "direct", "sourceMapping": [ null - ], + ], "vuln": "SuicidalFunc" - }, + }, { "calls": [ "destination.send(this.balance)" - ], - "contract": "Test", - "filename": "tests/arbitrary_send.sol", - "func": "indirect", + ], + "contract": "Test", + "filename": "tests/arbitrary_send.sol", + "func": "indirect", "sourceMapping": [ null - ], + ], "vuln": "SuicidalFunc" } -] +] \ No newline at end of file diff --git a/tests/expected_json/backdoor.backdoor.json b/tests/expected_json/backdoor.backdoor.json index 87ae303b3..5a68afb9b 100644 --- a/tests/expected_json/backdoor.backdoor.json +++ b/tests/expected_json/backdoor.backdoor.json @@ -1,11 +1,11 @@ [ { - "contract": "C", + "contract": "C", "sourceMapping": { - "filename": "tests/backdoor.sol", - "length": 74, + "filename": "tests/backdoor.sol", + "length": 74, "start": 42 - }, + }, "vuln": "backdoor" } -] +] \ No newline at end of file diff --git a/tests/expected_json/backdoor.suicidal.json b/tests/expected_json/backdoor.suicidal.json index e8dd6d8c8..16099e748 100644 --- a/tests/expected_json/backdoor.suicidal.json +++ b/tests/expected_json/backdoor.suicidal.json @@ -1,13 +1,13 @@ [ { - "contract": "C", - "filename": "tests/backdoor.sol", - "func": "i_am_a_backdoor", + "contract": "C", + "filename": "tests/backdoor.sol", + "func": "i_am_a_backdoor", "sourceMapping": { - "filename": "tests/backdoor.sol", - "length": 74, + "filename": "tests/backdoor.sol", + "length": 74, "start": 42 - }, + }, "vuln": "SuicidalFunc" } -] +] \ No newline at end of file diff --git a/tests/expected_json/const_state_variables.constable-states.json b/tests/expected_json/const_state_variables.constable-states.json index 2060fe3d7..3f8bd9552 100644 --- a/tests/expected_json/const_state_variables.constable-states.json +++ b/tests/expected_json/const_state_variables.constable-states.json @@ -1,64 +1,64 @@ [ { - "contract": "B", - "filename": "tests/const_state_variables.sol", + "contract": "B", + "filename": "tests/const_state_variables.sol", "sourceMapping": [ { - "filename": "tests/const_state_variables.sol", - "length": 76, + "filename": "tests/const_state_variables.sol", + "length": 76, "start": 130 - }, + }, { - "filename": "tests/const_state_variables.sol", - "length": 20, + "filename": "tests/const_state_variables.sol", + "length": 20, "start": 235 - }, + }, { - "filename": "tests/const_state_variables.sol", - "length": 20, + "filename": "tests/const_state_variables.sol", + "length": 20, "start": 331 - }, + }, { - "filename": "tests/const_state_variables.sol", - "length": 76, + "filename": "tests/const_state_variables.sol", + "length": 76, "start": 494 } - ], + ], "unusedVars": [ - "myFriendsAddress", - "test", + "myFriendsAddress", + "test", "text2" - ], + ], "vuln": "ConstStateVariableCandidates" - }, + }, { - "contract": "B", - "filename": "tests/const_state_variables.sol", + "contract": "B", + "filename": "tests/const_state_variables.sol", "sourceMapping": [ { - "filename": "tests/const_state_variables.sol", - "length": 76, + "filename": "tests/const_state_variables.sol", + "length": 76, "start": 130 - }, + }, { - "filename": "tests/const_state_variables.sol", - "length": 20, + "filename": "tests/const_state_variables.sol", + "length": 20, "start": 235 - }, + }, { - "filename": "tests/const_state_variables.sol", - "length": 20, + "filename": "tests/const_state_variables.sol", + "length": 20, "start": 331 - }, + }, { - "filename": "tests/const_state_variables.sol", - "length": 76, + "filename": "tests/const_state_variables.sol", + "length": 76, "start": 494 } - ], + ], "unusedVars": [ "mySistersAddress" - ], + ], "vuln": "ConstStateVariableCandidates" } -] +] \ No newline at end of file diff --git a/tests/expected_json/external_function.external-function.json b/tests/expected_json/external_function.external-function.json index d6cfdbed9..6e5023fcd 100644 --- a/tests/expected_json/external_function.external-function.json +++ b/tests/expected_json/external_function.external-function.json @@ -1,46 +1,46 @@ [ { - "contract": "ContractWithFunctionNotCalled", - "filename": "tests/external_function.sol", - "func": "funcNotCalled", + "contract": "ContractWithFunctionNotCalled", + "filename": "tests/external_function.sol", + "func": "funcNotCalled", "sourceMapping": { - "filename": "tests/external_function.sol", - "length": 40, + "filename": "tests/external_function.sol", + "length": 40, "start": 351 - }, + }, "vuln": "ExternalFunc" - }, + }, { - "contract": "ContractWithFunctionNotCalled", - "filename": "tests/external_function.sol", - "func": "funcNotCalled2", + "contract": "ContractWithFunctionNotCalled", + "filename": "tests/external_function.sol", + "func": "funcNotCalled2", "sourceMapping": { - "filename": "tests/external_function.sol", - "length": 41, + "filename": "tests/external_function.sol", + "length": 41, "start": 304 - }, + }, "vuln": "ExternalFunc" - }, + }, { - "contract": "ContractWithFunctionNotCalled", - "filename": "tests/external_function.sol", - "func": "funcNotCalled3", + "contract": "ContractWithFunctionNotCalled", + "filename": "tests/external_function.sol", + "func": "funcNotCalled3", "sourceMapping": { - "filename": "tests/external_function.sol", - "length": 41, + "filename": "tests/external_function.sol", + "length": 41, "start": 257 - }, + }, "vuln": "ExternalFunc" - }, + }, { - "contract": "ContractWithFunctionNotCalled2", - "filename": "tests/external_function.sol", - "func": "funcNotCalled", + "contract": "ContractWithFunctionNotCalled2", + "filename": "tests/external_function.sol", + "func": "funcNotCalled", "sourceMapping": { - "filename": "tests/external_function.sol", - "length": 304, + "filename": "tests/external_function.sol", + "length": 304, "start": 552 - }, + }, "vuln": "ExternalFunc" } -] +] \ No newline at end of file diff --git a/tests/expected_json/inline_assembly_contract.assembly.json b/tests/expected_json/inline_assembly_contract.assembly.json index 0d02458c6..f51ae2918 100644 --- a/tests/expected_json/inline_assembly_contract.assembly.json +++ b/tests/expected_json/inline_assembly_contract.assembly.json @@ -1,11 +1,11 @@ [ { - "contract": "GetCode", - "filename": "tests/inline_assembly_contract.sol", - "function_name": "at", + "contract": "GetCode", + "filename": "tests/inline_assembly_contract.sol", + "function_name": "at", "sourceMapping": [ null - ], + ], "vuln": "Assembly" } -] +] \ No newline at end of file diff --git a/tests/expected_json/inline_assembly_library.assembly.json b/tests/expected_json/inline_assembly_library.assembly.json index 5c62a4e1d..0e7bc1df8 100644 --- a/tests/expected_json/inline_assembly_library.assembly.json +++ b/tests/expected_json/inline_assembly_library.assembly.json @@ -1,20 +1,20 @@ [ { - "contract": "VectorSum", - "filename": "tests/inline_assembly_library.sol", - "function_name": "sumAsm", + "contract": "VectorSum", + "filename": "tests/inline_assembly_library.sol", + "function_name": "sumAsm", "sourceMapping": [ null - ], + ], "vuln": "Assembly" - }, + }, { - "contract": "VectorSum", - "filename": "tests/inline_assembly_library.sol", - "function_name": "sumPureAsm", + "contract": "VectorSum", + "filename": "tests/inline_assembly_library.sol", + "function_name": "sumPureAsm", "sourceMapping": [ null - ], + ], "vuln": "Assembly" } -] +] \ No newline at end of file diff --git a/tests/expected_json/locked_ether.locked-ether.json b/tests/expected_json/locked_ether.locked-ether.json index bdab33b8f..50b0dbbf4 100644 --- a/tests/expected_json/locked_ether.locked-ether.json +++ b/tests/expected_json/locked_ether.locked-ether.json @@ -1,16 +1,16 @@ [ { - "contract": "OnlyLocked", + "contract": "OnlyLocked", "functions_payable": [ "receive" - ], + ], "sourceMapping": [ { - "filename": "tests/locked_ether.sol", - "length": 72, + "filename": "tests/locked_ether.sol", + "length": 72, "start": 46 } - ], + ], "vuln": "LockedEther" } -] +] \ No newline at end of file diff --git a/tests/expected_json/low_level_calls.low-level-calls.json b/tests/expected_json/low_level_calls.low-level-calls.json index e3b7ce1e2..460535875 100644 --- a/tests/expected_json/low_level_calls.low-level-calls.json +++ b/tests/expected_json/low_level_calls.low-level-calls.json @@ -1,11 +1,11 @@ [ { - "contract": "Sender", - "filename": "tests/low_level_calls.sol", - "function_name": "send", + "contract": "Sender", + "filename": "tests/low_level_calls.sol", + "function_name": "send", "sourceMapping": [ null - ], + ], "vuln": "Low level call" } -] +] \ No newline at end of file diff --git a/tests/expected_json/naming_convention.naming-convention.json b/tests/expected_json/naming_convention.naming-convention.json index f7cb8026c..52e34ddc8 100644 --- a/tests/expected_json/naming_convention.naming-convention.json +++ b/tests/expected_json/naming_convention.naming-convention.json @@ -1,131 +1,131 @@ [ { - "argument": "_used", - "contract": "T", - "filename": "tests/naming_convention.sol", - "function": "test", + "argument": "_used", + "contract": "T", + "filename": "tests/naming_convention.sol", + "function": "test", "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 10, + "filename": "tests/naming_convention.sol", + "length": 10, "start": 748 - }, + }, "vuln": "NamingConvention" - }, + }, { - "contract": "T", - "filename": "tests/naming_convention.sol", + "contract": "T", + "filename": "tests/naming_convention.sol", "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 17, + "filename": "tests/naming_convention.sol", + "length": 17, "start": 695 - }, - "variable": "_myPublicVar", + }, + "variable": "_myPublicVar", "vuln": "NamingConvention" - }, + }, { - "constant": "l", - "contract": "T", - "filename": "tests/naming_convention.sol", + "constant": "l", + "contract": "T", + "filename": "tests/naming_convention.sol", "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 10, + "filename": "tests/naming_convention.sol", + "length": 10, "start": 847 - }, + }, "vuln": "NamingConvention" - }, + }, { - "contract": "naming", - "filename": "tests/naming_convention.sol", + "contract": "naming", + "filename": "tests/naming_convention.sol", "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 598, + "filename": "tests/naming_convention.sol", + "length": 598, "start": 26 - }, + }, "vuln": "NamingConvention" - }, + }, { - "contract": "naming", - "filename": "tests/naming_convention.sol", + "contract": "naming", + "filename": "tests/naming_convention.sol", "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 20, + "filename": "tests/naming_convention.sol", + "length": 20, "start": 227 - }, - "struct": "test", + }, + "struct": "test", "vuln": "NamingConvention" - }, + }, { - "contract": "naming", - "event": "event_", - "filename": "tests/naming_convention.sol", - "sourceMapping": null, + "contract": "naming", + "event": "event_", + "filename": "tests/naming_convention.sol", + "sourceMapping": null, "vuln": "NamingConvention" - }, + }, { - "contract": "naming", - "filename": "tests/naming_convention.sol", - "function": "GetOne", + "contract": "naming", + "filename": "tests/naming_convention.sol", + "function": "GetOne", "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 71, + "filename": "tests/naming_convention.sol", + "length": 71, "start": 405 - }, + }, "vuln": "NamingConvention" - }, + }, { - "argument": "Number2", - "contract": "naming", - "filename": "tests/naming_convention.sol", - "function": "setInt", + "argument": "Number2", + "contract": "naming", + "filename": "tests/naming_convention.sol", + "function": "setInt", "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 12, + "filename": "tests/naming_convention.sol", + "length": 12, "start": 512 - }, + }, "vuln": "NamingConvention" - }, + }, { - "constant": "MY_other_CONSTANT", - "contract": "naming", - "filename": "tests/naming_convention.sol", + "constant": "MY_other_CONSTANT", + "contract": "naming", + "filename": "tests/naming_convention.sol", "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 35, + "filename": "tests/naming_convention.sol", + "length": 35, "start": 141 - }, + }, "vuln": "NamingConvention" - }, + }, { - "contract": "naming", - "filename": "tests/naming_convention.sol", + "contract": "naming", + "filename": "tests/naming_convention.sol", "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 16, + "filename": "tests/naming_convention.sol", + "length": 16, "start": 183 - }, - "variable": "Var_One", + }, + "variable": "Var_One", "vuln": "NamingConvention" - }, + }, { - "contract": "naming", - "enum": "numbers", - "filename": "tests/naming_convention.sol", + "contract": "naming", + "enum": "numbers", + "filename": "tests/naming_convention.sol", "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 23, + "filename": "tests/naming_convention.sol", + "length": 23, "start": 77 - }, + }, "vuln": "NamingConvention" - }, + }, { - "contract": "naming", - "filename": "tests/naming_convention.sol", - "modifier": "CantDo", + "contract": "naming", + "filename": "tests/naming_convention.sol", + "modifier": "CantDo", "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 36, + "filename": "tests/naming_convention.sol", + "length": 36, "start": 545 - }, + }, "vuln": "NamingConvention" } -] +] \ No newline at end of file diff --git a/tests/expected_json/old_solc.sol.json.solc-version.json b/tests/expected_json/old_solc.sol.json.solc-version.json index 4d950e0c4..44e536156 100644 --- a/tests/expected_json/old_solc.sol.json.solc-version.json +++ b/tests/expected_json/old_solc.sol.json.solc-version.json @@ -2,14 +2,14 @@ { "pragma": [ "0.4.21" - ], + ], "sourceMapping": [ { - "filename": "old_solc.sol", - "length": 23, + "filename": "old_solc.sol", + "length": 23, "start": 0 } - ], + ], "vuln": "OldPragma" } -] +] \ No newline at end of file diff --git a/tests/expected_json/pragma.0.4.24.pragma.json b/tests/expected_json/pragma.0.4.24.pragma.json index 74922475c..e70a6253c 100644 --- a/tests/expected_json/pragma.0.4.24.pragma.json +++ b/tests/expected_json/pragma.0.4.24.pragma.json @@ -2,20 +2,20 @@ { "sourceMapping": [ { - "filename": "tests/pragma.0.4.23.sol", - "length": 24, + "filename": "tests/pragma.0.4.23.sol", + "length": 24, "start": 0 - }, + }, { - "filename": "tests/pragma.0.4.24.sol", - "length": 23, + "filename": "tests/pragma.0.4.24.sol", + "length": 23, "start": 0 } - ], + ], "versions": [ - "0.4.24", + "0.4.24", "^0.4.23" - ], + ], "vuln": "ConstantPragma" } -] +] \ No newline at end of file diff --git a/tests/expected_json/reentrancy.reentrancy.json b/tests/expected_json/reentrancy.reentrancy.json index e5c1b4597..aefa64c1a 100644 --- a/tests/expected_json/reentrancy.reentrancy.json +++ b/tests/expected_json/reentrancy.reentrancy.json @@ -2,25 +2,25 @@ { "calls": [ "! (msg.sender.call.value(userBalance[msg.sender])())" - ], - "contract": "Reentrancy", - "filename": "tests/reentrancy.sol", - "function_name": "withdrawBalance()", + ], + "contract": "Reentrancy", + "filename": "tests/reentrancy.sol", + "function_name": "withdrawBalance()", "send_eth": [ "! (msg.sender.call.value(userBalance[msg.sender])())" - ], + ], "sourceMapping": [ { - "filename": "tests/reentrancy.sol", - "length": 37, + "filename": "tests/reentrancy.sol", + "length": 37, "start": 52 - }, - null, + }, + null, null - ], + ], "varsWritten": [ "userBalance" - ], + ], "vuln": "Reentrancy" } -] +] \ No newline at end of file diff --git a/tests/expected_json/tx_origin.tx-origin.json b/tests/expected_json/tx_origin.tx-origin.json index 0be0cdad4..53b387f22 100644 --- a/tests/expected_json/tx_origin.tx-origin.json +++ b/tests/expected_json/tx_origin.tx-origin.json @@ -1,20 +1,20 @@ [ { - "contract": "TxOrigin", - "filename": "tests/tx_origin.sol", - "function_name": "bug0", + "contract": "TxOrigin", + "filename": "tests/tx_origin.sol", + "function_name": "bug0", "sourceMapping": [ null - ], + ], "vuln": "TxOrigin" - }, + }, { - "contract": "TxOrigin", - "filename": "tests/tx_origin.sol", - "function_name": "bug2", + "contract": "TxOrigin", + "filename": "tests/tx_origin.sol", + "function_name": "bug2", "sourceMapping": [ null - ], + ], "vuln": "TxOrigin" } -] +] \ No newline at end of file diff --git a/tests/expected_json/uninitialized.uninitialized-state.json b/tests/expected_json/uninitialized.uninitialized-state.json index 9981962dc..fd0f88386 100644 --- a/tests/expected_json/uninitialized.uninitialized-state.json +++ b/tests/expected_json/uninitialized.uninitialized-state.json @@ -1,86 +1,86 @@ [ { - "contract": "Test", - "filename": "tests/uninitialized.sol", + "contract": "Test", + "filename": "tests/uninitialized.sol", "functions": [ "use" - ], + ], "sourceMapping": [ { - "filename": "tests/uninitialized.sol", - "length": 34, + "filename": "tests/uninitialized.sol", + "length": 34, "start": 189 - }, + }, { - "filename": "tests/uninitialized.sol", - "length": 143, + "filename": "tests/uninitialized.sol", + "length": 143, "start": 356 } - ], - "variable": "balances", + ], + "variable": "balances", "vuln": "UninitializedStateVars" - }, + }, { - "contract": "Test2", - "filename": "tests/uninitialized.sol", + "contract": "Test2", + "filename": "tests/uninitialized.sol", "functions": [ "use" - ], + ], "sourceMapping": [ { - "filename": "tests/uninitialized.sol", - "length": 15, + "filename": "tests/uninitialized.sol", + "length": 15, "start": 695 - }, + }, { - "filename": "tests/uninitialized.sol", - "length": 117, + "filename": "tests/uninitialized.sol", + "length": 117, "start": 875 } - ], - "variable": "st", + ], + "variable": "st", "vuln": "UninitializedStateVars" - }, + }, { - "contract": "Test2", - "filename": "tests/uninitialized.sol", + "contract": "Test2", + "filename": "tests/uninitialized.sol", "functions": [ "init" - ], + ], "sourceMapping": [ { - "filename": "tests/uninitialized.sol", - "length": 6, + "filename": "tests/uninitialized.sol", + "length": 6, "start": 748 - }, + }, { - "filename": "tests/uninitialized.sol", - "length": 52, + "filename": "tests/uninitialized.sol", + "length": 52, "start": 817 } - ], - "variable": "v", + ], + "variable": "v", "vuln": "UninitializedStateVars" - }, + }, { - "contract": "Uninitialized", - "filename": "tests/uninitialized.sol", + "contract": "Uninitialized", + "filename": "tests/uninitialized.sol", "functions": [ "transfer" - ], + ], "sourceMapping": [ { - "filename": "tests/uninitialized.sol", - "length": 19, + "filename": "tests/uninitialized.sol", + "length": 19, "start": 55 - }, + }, { - "filename": "tests/uninitialized.sol", - "length": 82, + "filename": "tests/uninitialized.sol", + "length": 82, "start": 81 } - ], - "variable": "destination", + ], + "variable": "destination", "vuln": "UninitializedStateVars" } -] +] \ No newline at end of file diff --git a/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json b/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json index 50fd5c7f8..442c2bcdf 100644 --- a/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json +++ b/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json @@ -1,21 +1,21 @@ [ { - "contract": "Uninitialized", - "filename": "tests/uninitialized_storage_pointer.sol", - "function": "func", + "contract": "Uninitialized", + "filename": "tests/uninitialized_storage_pointer.sol", + "function": "func", "sourceMapping": [ { - "filename": "tests/uninitialized_storage_pointer.sol", - "length": 138, + "filename": "tests/uninitialized_storage_pointer.sol", + "length": 138, "start": 67 - }, + }, { - "filename": "tests/uninitialized_storage_pointer.sol", - "length": 9, + "filename": "tests/uninitialized_storage_pointer.sol", + "length": 9, "start": 171 } - ], - "variable": "st_bug", + ], + "variable": "st_bug", "vuln": "UninitializedStorageVars" } -] +] \ No newline at end of file diff --git a/tests/expected_json/unused_state.unused-state.json b/tests/expected_json/unused_state.unused-state.json index ab82de585..0b2854aef 100644 --- a/tests/expected_json/unused_state.unused-state.json +++ b/tests/expected_json/unused_state.unused-state.json @@ -1,17 +1,17 @@ [ { - "contract": "B", - "filename": "tests/unused_state.sol", + "contract": "B", + "filename": "tests/unused_state.sol", "sourceMapping": [ { - "filename": "tests/unused_state.sol", - "length": 14, + "filename": "tests/unused_state.sol", + "length": 14, "start": 41 } - ], + ], "unusedVars": [ "unused" - ], + ], "vuln": "unusedStateVars" } -] +] \ No newline at end of file From 3b35b5a14b3ae233935480be023618f09492f6a7 Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Thu, 1 Nov 2018 03:46:26 +0100 Subject: [PATCH 286/308] force set json.dumps separators --- scripts/pretty_print_json.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/pretty_print_json.py b/scripts/pretty_print_json.py index 9de71f14e..d86974e84 100644 --- a/scripts/pretty_print_json.py +++ b/scripts/pretty_print_json.py @@ -8,4 +8,4 @@ pretty_json_file = sys.argv[2] with open(raw_json_file, 'r') as json_data: with open(pretty_json_file, 'w') as out_file: - out_file.write(json.dumps(json.load(json_data), sort_keys=True, indent=4)) \ No newline at end of file + out_file.write(json.dumps(json.load(json_data), sort_keys=True, indent=4, separators=(',',': '))) \ No newline at end of file From 15cdf2accb1a2d55a0ce1d8f70d1928dfd0baf6d Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Thu, 1 Nov 2018 03:46:45 +0100 Subject: [PATCH 287/308] regenrate expected json for tests --- .../arbitrary_send.arbitrary-send.json | 22 +-- tests/expected_json/backdoor.backdoor.json | 8 +- tests/expected_json/backdoor.suicidal.json | 12 +- ...onst_state_variables.constable-states.json | 66 +++---- .../external_function.external-function.json | 54 +++--- .../inline_assembly_contract.assembly.json | 8 +- .../inline_assembly_library.assembly.json | 18 +- .../locked_ether.locked-ether.json | 10 +- .../low_level_calls.low-level-calls.json | 8 +- .../naming_convention.naming-convention.json | 164 +++++++++--------- .../old_solc.sol.json.solc-version.json | 8 +- tests/expected_json/pragma.0.4.24.pragma.json | 16 +- .../expected_json/reentrancy.reentrancy.json | 22 +-- tests/expected_json/tx_origin.tx-origin.json | 18 +- .../uninitialized.uninitialized-state.json | 86 ++++----- ...storage_pointer.uninitialized-storage.json | 20 +-- .../unused_state.unused-state.json | 12 +- 17 files changed, 276 insertions(+), 276 deletions(-) diff --git a/tests/expected_json/arbitrary_send.arbitrary-send.json b/tests/expected_json/arbitrary_send.arbitrary-send.json index fa6299fa1..d8a46428b 100644 --- a/tests/expected_json/arbitrary_send.arbitrary-send.json +++ b/tests/expected_json/arbitrary_send.arbitrary-send.json @@ -2,25 +2,25 @@ { "calls": [ "msg.sender.send(this.balance)" - ], - "contract": "Test", - "filename": "tests/arbitrary_send.sol", - "func": "direct", + ], + "contract": "Test", + "filename": "tests/arbitrary_send.sol", + "func": "direct", "sourceMapping": [ null - ], + ], "vuln": "SuicidalFunc" - }, + }, { "calls": [ "destination.send(this.balance)" - ], - "contract": "Test", - "filename": "tests/arbitrary_send.sol", - "func": "indirect", + ], + "contract": "Test", + "filename": "tests/arbitrary_send.sol", + "func": "indirect", "sourceMapping": [ null - ], + ], "vuln": "SuicidalFunc" } ] \ No newline at end of file diff --git a/tests/expected_json/backdoor.backdoor.json b/tests/expected_json/backdoor.backdoor.json index 5a68afb9b..9b51501b2 100644 --- a/tests/expected_json/backdoor.backdoor.json +++ b/tests/expected_json/backdoor.backdoor.json @@ -1,11 +1,11 @@ [ { - "contract": "C", + "contract": "C", "sourceMapping": { - "filename": "tests/backdoor.sol", - "length": 74, + "filename": "tests/backdoor.sol", + "length": 74, "start": 42 - }, + }, "vuln": "backdoor" } ] \ No newline at end of file diff --git a/tests/expected_json/backdoor.suicidal.json b/tests/expected_json/backdoor.suicidal.json index 16099e748..28ec9d278 100644 --- a/tests/expected_json/backdoor.suicidal.json +++ b/tests/expected_json/backdoor.suicidal.json @@ -1,13 +1,13 @@ [ { - "contract": "C", - "filename": "tests/backdoor.sol", - "func": "i_am_a_backdoor", + "contract": "C", + "filename": "tests/backdoor.sol", + "func": "i_am_a_backdoor", "sourceMapping": { - "filename": "tests/backdoor.sol", - "length": 74, + "filename": "tests/backdoor.sol", + "length": 74, "start": 42 - }, + }, "vuln": "SuicidalFunc" } ] \ No newline at end of file diff --git a/tests/expected_json/const_state_variables.constable-states.json b/tests/expected_json/const_state_variables.constable-states.json index 3f8bd9552..947425c07 100644 --- a/tests/expected_json/const_state_variables.constable-states.json +++ b/tests/expected_json/const_state_variables.constable-states.json @@ -1,64 +1,64 @@ [ { - "contract": "B", - "filename": "tests/const_state_variables.sol", + "contract": "B", + "filename": "tests/const_state_variables.sol", "sourceMapping": [ { - "filename": "tests/const_state_variables.sol", - "length": 76, + "filename": "tests/const_state_variables.sol", + "length": 76, "start": 130 - }, + }, { - "filename": "tests/const_state_variables.sol", - "length": 20, + "filename": "tests/const_state_variables.sol", + "length": 20, "start": 235 - }, + }, { - "filename": "tests/const_state_variables.sol", - "length": 20, + "filename": "tests/const_state_variables.sol", + "length": 20, "start": 331 - }, + }, { - "filename": "tests/const_state_variables.sol", - "length": 76, + "filename": "tests/const_state_variables.sol", + "length": 76, "start": 494 } - ], + ], "unusedVars": [ - "myFriendsAddress", - "test", + "myFriendsAddress", + "test", "text2" - ], + ], "vuln": "ConstStateVariableCandidates" - }, + }, { - "contract": "B", - "filename": "tests/const_state_variables.sol", + "contract": "B", + "filename": "tests/const_state_variables.sol", "sourceMapping": [ { - "filename": "tests/const_state_variables.sol", - "length": 76, + "filename": "tests/const_state_variables.sol", + "length": 76, "start": 130 - }, + }, { - "filename": "tests/const_state_variables.sol", - "length": 20, + "filename": "tests/const_state_variables.sol", + "length": 20, "start": 235 - }, + }, { - "filename": "tests/const_state_variables.sol", - "length": 20, + "filename": "tests/const_state_variables.sol", + "length": 20, "start": 331 - }, + }, { - "filename": "tests/const_state_variables.sol", - "length": 76, + "filename": "tests/const_state_variables.sol", + "length": 76, "start": 494 } - ], + ], "unusedVars": [ "mySistersAddress" - ], + ], "vuln": "ConstStateVariableCandidates" } ] \ No newline at end of file diff --git a/tests/expected_json/external_function.external-function.json b/tests/expected_json/external_function.external-function.json index 6e5023fcd..fdf8f8b67 100644 --- a/tests/expected_json/external_function.external-function.json +++ b/tests/expected_json/external_function.external-function.json @@ -1,46 +1,46 @@ [ { - "contract": "ContractWithFunctionNotCalled", - "filename": "tests/external_function.sol", - "func": "funcNotCalled", + "contract": "ContractWithFunctionNotCalled", + "filename": "tests/external_function.sol", + "func": "funcNotCalled", "sourceMapping": { - "filename": "tests/external_function.sol", - "length": 40, + "filename": "tests/external_function.sol", + "length": 40, "start": 351 - }, + }, "vuln": "ExternalFunc" - }, + }, { - "contract": "ContractWithFunctionNotCalled", - "filename": "tests/external_function.sol", - "func": "funcNotCalled2", + "contract": "ContractWithFunctionNotCalled", + "filename": "tests/external_function.sol", + "func": "funcNotCalled2", "sourceMapping": { - "filename": "tests/external_function.sol", - "length": 41, + "filename": "tests/external_function.sol", + "length": 41, "start": 304 - }, + }, "vuln": "ExternalFunc" - }, + }, { - "contract": "ContractWithFunctionNotCalled", - "filename": "tests/external_function.sol", - "func": "funcNotCalled3", + "contract": "ContractWithFunctionNotCalled", + "filename": "tests/external_function.sol", + "func": "funcNotCalled3", "sourceMapping": { - "filename": "tests/external_function.sol", - "length": 41, + "filename": "tests/external_function.sol", + "length": 41, "start": 257 - }, + }, "vuln": "ExternalFunc" - }, + }, { - "contract": "ContractWithFunctionNotCalled2", - "filename": "tests/external_function.sol", - "func": "funcNotCalled", + "contract": "ContractWithFunctionNotCalled2", + "filename": "tests/external_function.sol", + "func": "funcNotCalled", "sourceMapping": { - "filename": "tests/external_function.sol", - "length": 304, + "filename": "tests/external_function.sol", + "length": 304, "start": 552 - }, + }, "vuln": "ExternalFunc" } ] \ No newline at end of file diff --git a/tests/expected_json/inline_assembly_contract.assembly.json b/tests/expected_json/inline_assembly_contract.assembly.json index f51ae2918..3176781ef 100644 --- a/tests/expected_json/inline_assembly_contract.assembly.json +++ b/tests/expected_json/inline_assembly_contract.assembly.json @@ -1,11 +1,11 @@ [ { - "contract": "GetCode", - "filename": "tests/inline_assembly_contract.sol", - "function_name": "at", + "contract": "GetCode", + "filename": "tests/inline_assembly_contract.sol", + "function_name": "at", "sourceMapping": [ null - ], + ], "vuln": "Assembly" } ] \ No newline at end of file diff --git a/tests/expected_json/inline_assembly_library.assembly.json b/tests/expected_json/inline_assembly_library.assembly.json index 0e7bc1df8..7169cf959 100644 --- a/tests/expected_json/inline_assembly_library.assembly.json +++ b/tests/expected_json/inline_assembly_library.assembly.json @@ -1,20 +1,20 @@ [ { - "contract": "VectorSum", - "filename": "tests/inline_assembly_library.sol", - "function_name": "sumAsm", + "contract": "VectorSum", + "filename": "tests/inline_assembly_library.sol", + "function_name": "sumAsm", "sourceMapping": [ null - ], + ], "vuln": "Assembly" - }, + }, { - "contract": "VectorSum", - "filename": "tests/inline_assembly_library.sol", - "function_name": "sumPureAsm", + "contract": "VectorSum", + "filename": "tests/inline_assembly_library.sol", + "function_name": "sumPureAsm", "sourceMapping": [ null - ], + ], "vuln": "Assembly" } ] \ No newline at end of file diff --git a/tests/expected_json/locked_ether.locked-ether.json b/tests/expected_json/locked_ether.locked-ether.json index 50b0dbbf4..eef591ddc 100644 --- a/tests/expected_json/locked_ether.locked-ether.json +++ b/tests/expected_json/locked_ether.locked-ether.json @@ -1,16 +1,16 @@ [ { - "contract": "OnlyLocked", + "contract": "OnlyLocked", "functions_payable": [ "receive" - ], + ], "sourceMapping": [ { - "filename": "tests/locked_ether.sol", - "length": 72, + "filename": "tests/locked_ether.sol", + "length": 72, "start": 46 } - ], + ], "vuln": "LockedEther" } ] \ No newline at end of file diff --git a/tests/expected_json/low_level_calls.low-level-calls.json b/tests/expected_json/low_level_calls.low-level-calls.json index 460535875..2c3b9e355 100644 --- a/tests/expected_json/low_level_calls.low-level-calls.json +++ b/tests/expected_json/low_level_calls.low-level-calls.json @@ -1,11 +1,11 @@ [ { - "contract": "Sender", - "filename": "tests/low_level_calls.sol", - "function_name": "send", + "contract": "Sender", + "filename": "tests/low_level_calls.sol", + "function_name": "send", "sourceMapping": [ null - ], + ], "vuln": "Low level call" } ] \ No newline at end of file diff --git a/tests/expected_json/naming_convention.naming-convention.json b/tests/expected_json/naming_convention.naming-convention.json index 52e34ddc8..2e9212e93 100644 --- a/tests/expected_json/naming_convention.naming-convention.json +++ b/tests/expected_json/naming_convention.naming-convention.json @@ -1,131 +1,131 @@ [ { - "argument": "_used", - "contract": "T", - "filename": "tests/naming_convention.sol", - "function": "test", + "argument": "_used", + "contract": "T", + "filename": "tests/naming_convention.sol", + "function": "test", "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 10, + "filename": "tests/naming_convention.sol", + "length": 10, "start": 748 - }, + }, "vuln": "NamingConvention" - }, + }, { - "contract": "T", - "filename": "tests/naming_convention.sol", + "contract": "T", + "filename": "tests/naming_convention.sol", "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 17, + "filename": "tests/naming_convention.sol", + "length": 17, "start": 695 - }, - "variable": "_myPublicVar", + }, + "variable": "_myPublicVar", "vuln": "NamingConvention" - }, + }, { - "constant": "l", - "contract": "T", - "filename": "tests/naming_convention.sol", + "constant": "l", + "contract": "T", + "filename": "tests/naming_convention.sol", "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 10, + "filename": "tests/naming_convention.sol", + "length": 10, "start": 847 - }, + }, "vuln": "NamingConvention" - }, + }, { - "contract": "naming", - "filename": "tests/naming_convention.sol", + "contract": "naming", + "filename": "tests/naming_convention.sol", "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 598, + "filename": "tests/naming_convention.sol", + "length": 598, "start": 26 - }, + }, "vuln": "NamingConvention" - }, + }, { - "contract": "naming", - "filename": "tests/naming_convention.sol", + "contract": "naming", + "filename": "tests/naming_convention.sol", "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 20, + "filename": "tests/naming_convention.sol", + "length": 20, "start": 227 - }, - "struct": "test", + }, + "struct": "test", "vuln": "NamingConvention" - }, + }, { - "contract": "naming", - "event": "event_", - "filename": "tests/naming_convention.sol", - "sourceMapping": null, + "contract": "naming", + "event": "event_", + "filename": "tests/naming_convention.sol", + "sourceMapping": null, "vuln": "NamingConvention" - }, + }, { - "contract": "naming", - "filename": "tests/naming_convention.sol", - "function": "GetOne", + "contract": "naming", + "filename": "tests/naming_convention.sol", + "function": "GetOne", "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 71, + "filename": "tests/naming_convention.sol", + "length": 71, "start": 405 - }, + }, "vuln": "NamingConvention" - }, + }, { - "argument": "Number2", - "contract": "naming", - "filename": "tests/naming_convention.sol", - "function": "setInt", + "argument": "Number2", + "contract": "naming", + "filename": "tests/naming_convention.sol", + "function": "setInt", "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 12, + "filename": "tests/naming_convention.sol", + "length": 12, "start": 512 - }, + }, "vuln": "NamingConvention" - }, + }, { - "constant": "MY_other_CONSTANT", - "contract": "naming", - "filename": "tests/naming_convention.sol", + "constant": "MY_other_CONSTANT", + "contract": "naming", + "filename": "tests/naming_convention.sol", "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 35, + "filename": "tests/naming_convention.sol", + "length": 35, "start": 141 - }, + }, "vuln": "NamingConvention" - }, + }, { - "contract": "naming", - "filename": "tests/naming_convention.sol", + "contract": "naming", + "filename": "tests/naming_convention.sol", "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 16, + "filename": "tests/naming_convention.sol", + "length": 16, "start": 183 - }, - "variable": "Var_One", + }, + "variable": "Var_One", "vuln": "NamingConvention" - }, + }, { - "contract": "naming", - "enum": "numbers", - "filename": "tests/naming_convention.sol", + "contract": "naming", + "enum": "numbers", + "filename": "tests/naming_convention.sol", "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 23, + "filename": "tests/naming_convention.sol", + "length": 23, "start": 77 - }, + }, "vuln": "NamingConvention" - }, + }, { - "contract": "naming", - "filename": "tests/naming_convention.sol", - "modifier": "CantDo", + "contract": "naming", + "filename": "tests/naming_convention.sol", + "modifier": "CantDo", "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 36, + "filename": "tests/naming_convention.sol", + "length": 36, "start": 545 - }, + }, "vuln": "NamingConvention" } ] \ No newline at end of file diff --git a/tests/expected_json/old_solc.sol.json.solc-version.json b/tests/expected_json/old_solc.sol.json.solc-version.json index 44e536156..842ca0f8a 100644 --- a/tests/expected_json/old_solc.sol.json.solc-version.json +++ b/tests/expected_json/old_solc.sol.json.solc-version.json @@ -2,14 +2,14 @@ { "pragma": [ "0.4.21" - ], + ], "sourceMapping": [ { - "filename": "old_solc.sol", - "length": 23, + "filename": "old_solc.sol", + "length": 23, "start": 0 } - ], + ], "vuln": "OldPragma" } ] \ No newline at end of file diff --git a/tests/expected_json/pragma.0.4.24.pragma.json b/tests/expected_json/pragma.0.4.24.pragma.json index e70a6253c..2fbd07f39 100644 --- a/tests/expected_json/pragma.0.4.24.pragma.json +++ b/tests/expected_json/pragma.0.4.24.pragma.json @@ -2,20 +2,20 @@ { "sourceMapping": [ { - "filename": "tests/pragma.0.4.23.sol", - "length": 24, + "filename": "tests/pragma.0.4.23.sol", + "length": 24, "start": 0 - }, + }, { - "filename": "tests/pragma.0.4.24.sol", - "length": 23, + "filename": "tests/pragma.0.4.24.sol", + "length": 23, "start": 0 } - ], + ], "versions": [ - "0.4.24", + "0.4.24", "^0.4.23" - ], + ], "vuln": "ConstantPragma" } ] \ No newline at end of file diff --git a/tests/expected_json/reentrancy.reentrancy.json b/tests/expected_json/reentrancy.reentrancy.json index aefa64c1a..b78d91c3c 100644 --- a/tests/expected_json/reentrancy.reentrancy.json +++ b/tests/expected_json/reentrancy.reentrancy.json @@ -2,25 +2,25 @@ { "calls": [ "! (msg.sender.call.value(userBalance[msg.sender])())" - ], - "contract": "Reentrancy", - "filename": "tests/reentrancy.sol", - "function_name": "withdrawBalance()", + ], + "contract": "Reentrancy", + "filename": "tests/reentrancy.sol", + "function_name": "withdrawBalance()", "send_eth": [ "! (msg.sender.call.value(userBalance[msg.sender])())" - ], + ], "sourceMapping": [ { - "filename": "tests/reentrancy.sol", - "length": 37, + "filename": "tests/reentrancy.sol", + "length": 37, "start": 52 - }, - null, + }, + null, null - ], + ], "varsWritten": [ "userBalance" - ], + ], "vuln": "Reentrancy" } ] \ No newline at end of file diff --git a/tests/expected_json/tx_origin.tx-origin.json b/tests/expected_json/tx_origin.tx-origin.json index 53b387f22..105614ad4 100644 --- a/tests/expected_json/tx_origin.tx-origin.json +++ b/tests/expected_json/tx_origin.tx-origin.json @@ -1,20 +1,20 @@ [ { - "contract": "TxOrigin", - "filename": "tests/tx_origin.sol", - "function_name": "bug0", + "contract": "TxOrigin", + "filename": "tests/tx_origin.sol", + "function_name": "bug0", "sourceMapping": [ null - ], + ], "vuln": "TxOrigin" - }, + }, { - "contract": "TxOrigin", - "filename": "tests/tx_origin.sol", - "function_name": "bug2", + "contract": "TxOrigin", + "filename": "tests/tx_origin.sol", + "function_name": "bug2", "sourceMapping": [ null - ], + ], "vuln": "TxOrigin" } ] \ No newline at end of file diff --git a/tests/expected_json/uninitialized.uninitialized-state.json b/tests/expected_json/uninitialized.uninitialized-state.json index fd0f88386..d15bc3eb6 100644 --- a/tests/expected_json/uninitialized.uninitialized-state.json +++ b/tests/expected_json/uninitialized.uninitialized-state.json @@ -1,86 +1,86 @@ [ { - "contract": "Test", - "filename": "tests/uninitialized.sol", + "contract": "Test", + "filename": "tests/uninitialized.sol", "functions": [ "use" - ], + ], "sourceMapping": [ { - "filename": "tests/uninitialized.sol", - "length": 34, + "filename": "tests/uninitialized.sol", + "length": 34, "start": 189 - }, + }, { - "filename": "tests/uninitialized.sol", - "length": 143, + "filename": "tests/uninitialized.sol", + "length": 143, "start": 356 } - ], - "variable": "balances", + ], + "variable": "balances", "vuln": "UninitializedStateVars" - }, + }, { - "contract": "Test2", - "filename": "tests/uninitialized.sol", + "contract": "Test2", + "filename": "tests/uninitialized.sol", "functions": [ "use" - ], + ], "sourceMapping": [ { - "filename": "tests/uninitialized.sol", - "length": 15, + "filename": "tests/uninitialized.sol", + "length": 15, "start": 695 - }, + }, { - "filename": "tests/uninitialized.sol", - "length": 117, + "filename": "tests/uninitialized.sol", + "length": 117, "start": 875 } - ], - "variable": "st", + ], + "variable": "st", "vuln": "UninitializedStateVars" - }, + }, { - "contract": "Test2", - "filename": "tests/uninitialized.sol", + "contract": "Test2", + "filename": "tests/uninitialized.sol", "functions": [ "init" - ], + ], "sourceMapping": [ { - "filename": "tests/uninitialized.sol", - "length": 6, + "filename": "tests/uninitialized.sol", + "length": 6, "start": 748 - }, + }, { - "filename": "tests/uninitialized.sol", - "length": 52, + "filename": "tests/uninitialized.sol", + "length": 52, "start": 817 } - ], - "variable": "v", + ], + "variable": "v", "vuln": "UninitializedStateVars" - }, + }, { - "contract": "Uninitialized", - "filename": "tests/uninitialized.sol", + "contract": "Uninitialized", + "filename": "tests/uninitialized.sol", "functions": [ "transfer" - ], + ], "sourceMapping": [ { - "filename": "tests/uninitialized.sol", - "length": 19, + "filename": "tests/uninitialized.sol", + "length": 19, "start": 55 - }, + }, { - "filename": "tests/uninitialized.sol", - "length": 82, + "filename": "tests/uninitialized.sol", + "length": 82, "start": 81 } - ], - "variable": "destination", + ], + "variable": "destination", "vuln": "UninitializedStateVars" } ] \ No newline at end of file diff --git a/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json b/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json index 442c2bcdf..ce783152c 100644 --- a/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json +++ b/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json @@ -1,21 +1,21 @@ [ { - "contract": "Uninitialized", - "filename": "tests/uninitialized_storage_pointer.sol", - "function": "func", + "contract": "Uninitialized", + "filename": "tests/uninitialized_storage_pointer.sol", + "function": "func", "sourceMapping": [ { - "filename": "tests/uninitialized_storage_pointer.sol", - "length": 138, + "filename": "tests/uninitialized_storage_pointer.sol", + "length": 138, "start": 67 - }, + }, { - "filename": "tests/uninitialized_storage_pointer.sol", - "length": 9, + "filename": "tests/uninitialized_storage_pointer.sol", + "length": 9, "start": 171 } - ], - "variable": "st_bug", + ], + "variable": "st_bug", "vuln": "UninitializedStorageVars" } ] \ No newline at end of file diff --git a/tests/expected_json/unused_state.unused-state.json b/tests/expected_json/unused_state.unused-state.json index 0b2854aef..f65be6eba 100644 --- a/tests/expected_json/unused_state.unused-state.json +++ b/tests/expected_json/unused_state.unused-state.json @@ -1,17 +1,17 @@ [ { - "contract": "B", - "filename": "tests/unused_state.sol", + "contract": "B", + "filename": "tests/unused_state.sol", "sourceMapping": [ { - "filename": "tests/unused_state.sol", - "length": 14, + "filename": "tests/unused_state.sol", + "length": 14, "start": 41 } - ], + ], "unusedVars": [ "unused" - ], + ], "vuln": "unusedStateVars" } ] \ No newline at end of file From a94292ceed5590704fa1959cb7021040289f4167 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 1 Nov 2018 08:56:19 +0100 Subject: [PATCH 288/308] Disable ComplexFunction detector until benchmark is performed --- README.md | 1 - slither/__main__.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 6244c6938..1c654acd5 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,6 @@ Num | Detector | What it Detects | Impact | Confidence 13 | `pragma` | If different pragma directives are used | Informational | High 14 | `solc-version` | Old versions of Solidity (< 0.4.23) | Informational | High 15 | `unused-state` | Unused state variables | Informational | High -16 | `complex-function` | Complex functions | Informational | Medium [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. diff --git a/slither/__main__.py b/slither/__main__.py index 21bd5ec3e..017bebaa6 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -127,7 +127,7 @@ def get_detectors_and_printers(): LowLevelCalls, NamingConvention, ConstCandidateStateVars, - ComplexFunction, + #ComplexFunction, ExternalFunction] from slither.printers.summary.function import FunctionSummary From 4c14041942e8978c3b5609e9635b1e412df6a5b5 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 1 Nov 2018 08:57:25 +0100 Subject: [PATCH 289/308] Update travis --- scripts/travis_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 918342a97..a6b4dcbc9 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -26,7 +26,7 @@ test_slither tests/tx_origin.sol "tx-origin" 2 test_slither tests/unused_state.sol "unused-state" 1 test_slither tests/locked_ether.sol "locked-ether" 1 test_slither tests/arbitrary_send.sol "arbitrary-send" 2 -test_slither tests/complex_func.sol "complex-function" 3 +#test_slither tests/complex_func.sol "complex-function" 3 test_slither tests/inline_assembly_contract.sol "assembly" 1 test_slither tests/inline_assembly_library.sol "assembly" 2 test_slither tests/low_level_calls.sol "low-level-calls" 1 From c40776121a331b0ec859277984c7a089258fe07b Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 1 Nov 2018 09:03:01 +0100 Subject: [PATCH 290/308] Improve specific taint --- slither/analyses/taint/specific_variable.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/slither/analyses/taint/specific_variable.py b/slither/analyses/taint/specific_variable.py index 34f90e4c0..fc508c963 100644 --- a/slither/analyses/taint/specific_variable.py +++ b/slither/analyses/taint/specific_variable.py @@ -15,9 +15,7 @@ from .common import iterate_over_irs def make_key(variable): if isinstance(variable, Variable): - key = 'TAINT_{}{}{}'.format(variable.contract.name, - variable.name, - str(type(variable))) + key = 'TAINT_{}'.format(id(variable)) else: assert isinstance(variable, SolidityVariable) key = 'TAINT_{}{}'.format(variable.name, From 916cbf7acfb0a3263269bd7326719615053ebb64 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 4 Nov 2018 10:39:51 +0100 Subject: [PATCH 291/308] Improve support for constant variables init --- slither/solc_parsing/declarations/contract.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index 087750577..f3e7c3c15 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -234,9 +234,14 @@ class ContractSolc04(Contract): self._variables[var.name] = var def analyze_constant_state_variables(self): + from slither.solc_parsing.expressions.expression_parsing import VariableNotFound for var in self.variables: if var.is_constant: - var.analyze(self) + # cant parse constant expression based on function calls + try: + var.analyze(self) + except VariableNotFound: + pass return def analyze_state_variables(self): From 563d5118298e4cae7f0ea5f2a531f0dcdcebd64d Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 5 Nov 2018 11:34:29 +0000 Subject: [PATCH 292/308] Improve output format Group info of informational issues into one log action per detector Add WIKI information --- README.md | 30 +++++++++---------- slither/__main__.py | 9 ++---- slither/core/declarations/import_directive.py | 2 +- slither/core/source_mapping/source_mapping.py | 12 ++++++-- slither/detectors/abstract_detector.py | 4 +++ .../detectors/attributes/constant_pragma.py | 2 ++ slither/detectors/attributes/locked_ether.py | 2 ++ slither/detectors/attributes/old_solc.py | 2 ++ slither/detectors/functions/arbitrary_send.py | 2 ++ .../detectors/functions/external_function.py | 7 ++++- slither/detectors/functions/suicidal.py | 2 ++ .../naming_convention/naming_convention.py | 26 +++++++++------- .../detectors/operations/low_level_calls.py | 7 ++++- slither/detectors/reentrancy/reentrancy.py | 2 ++ slither/detectors/statements/assembly.py | 7 ++++- slither/detectors/statements/tx_origin.py | 2 ++ .../possible_const_state_variables.py | 9 ++++-- .../uninitialized_state_variables.py | 3 +- .../uninitialized_storage_variables.py | 1 + .../variables/unused_state_variables.py | 7 ++++- slither/slither.py | 5 ++-- slither/solc_parsing/slitherSolc.py | 4 +-- slither/utils/command_line.py | 8 ++++- 23 files changed, 105 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 1c654acd5..8fe629cd7 100644 --- a/README.md +++ b/README.md @@ -47,21 +47,21 @@ By default, all the detectors are run. Num | Detector | What it Detects | Impact | Confidence --- | --- | --- | --- | --- -1 | `suicidal` | Suicidal functions | High | High -2 | `uninitialized-state` | Uninitialized state variables | High | High -3 | `uninitialized-storage` | Uninitialized storage variables | High | High -4 | `arbitrary-send` | Functions that send ether to arbitrary destinations | High | Medium -5 | `reentrancy` | Reentrancy vulnerabilities | High | Medium -6 | `locked-ether` | Contracts that lock ether | Medium | High -7 | `tx-origin` | Dangerous usage of `tx.origin` | Medium | Medium -8 | `assembly` | Assembly usage | Informational | High -9 | `constable-states` | State variables that could be declared constant | Informational | High -10 | `external-function` | Public function that could be declared as external | Informational | High -11 | `low-level-calls` | Low level calls | Informational | High -12 | `naming-convention` | Conformance to Solidity naming conventions | Informational | High -13 | `pragma` | If different pragma directives are used | Informational | High -14 | `solc-version` | Old versions of Solidity (< 0.4.23) | Informational | High -15 | `unused-state` | Unused state variables | Informational | High +1 | `suicidal` | [Functions allowing anyone to destruct the contract](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#suicidal) | High | High +2 | `uninitialized-state` | [Uninitialized state variables](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#uninitialized-state-variables) | High | High +3 | `uninitialized-storage` | [Uninitialized storage variables](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#uninitialized-storage-variables) | High | High +4 | `arbitrary-send` | [Functions that send ether to arbitrary destinations](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#functions-that-send-ether-to-arbitrary-destinations) | High | Medium +5 | `reentrancy` | [Reentrancy vulnerabilities](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#reentrancy-vulnerabilities) | High | Medium +6 | `locked-ether` | [Contracts that lock ether](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#contracts-that-lock-ether) | Medium | High +7 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#dangerous-usage-of-txorigin) | Medium | Medium +8 | `assembly` | [Assembly usage](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#assembly-usage) | Informational | High +9 | `constable-states` | [State variables that could be declared constant](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#state-variables-that-could-be-declared-constant) | Informational | High +10 | `external-function` | [Public function that could be declared as external](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#public-function-that-could-be-declared-as-external) | Informational | High +11 | `low-level-calls` | [Low level calls](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#low-level-calls) | Informational | High +12 | `naming-convention` | [Conformance to Solidity naming conventions](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#conformance-to-solidity-naming-conventions) | Informational | High +13 | `pragma` | [If different pragma directives are used](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#state-variables-that-could-be-declared-constant) | Informational | High +14 | `solc-version` | [Old versions of Solidity (< 0.4.23)](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#old-versions-of-solidity) | Informational | High +15 | `unused-state` | [Unused state variables](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#unused-state-variables) | Informational | High [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. diff --git a/slither/__main__.py b/slither/__main__.py index 88230791e..f287ae679 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -70,15 +70,10 @@ def process_truffle(dirname, args, detector_classes, printer_classes): for filename in filenames: with open(filename) as f: contract_loaded = json.load(f) - all_contracts += contract_loaded['ast']['nodes'] + all_contracts.append(contract_loaded['ast']) all_filenames.append(contract_loaded['sourcePath']) - contract = { - "nodeType": "SourceUnit", - "nodes" : all_contracts, - "sourcePaths": all_filenames} - - slither = Slither(contract, args.solc, args.disable_solc_warnings, args.solc_args) + slither = Slither(all_contracts, args.solc, args.disable_solc_warnings, args.solc_args) return _process(slither, detector_classes, printer_classes) diff --git a/slither/core/declarations/import_directive.py b/slither/core/declarations/import_directive.py index 069558257..8e22eb8c9 100644 --- a/slither/core/declarations/import_directive.py +++ b/slither/core/declarations/import_directive.py @@ -4,7 +4,7 @@ class Import(SourceMapping): def __init__(self, filename): super(Import, self).__init__() - self._fimename = filename + self._filename = filename @property def filename(self): diff --git a/slither/core/source_mapping/source_mapping.py b/slither/core/source_mapping/source_mapping.py index 7c67c5283..88d66db5f 100644 --- a/slither/core/source_mapping/source_mapping.py +++ b/slither/core/source_mapping/source_mapping.py @@ -51,8 +51,6 @@ class SourceMapping(Context): f = int(f) if f not in sourceUnits: - print(f) - print(sourceUnits) return {'start':s, 'length':l} filename = sourceUnits[f] @@ -72,6 +70,14 @@ class SourceMapping(Context): @property def source_mapping_str(self): + + def relative_path(path): + # Remove absolute path for printing + # Truffle returns absolutePath + if '/contracts/' in path: + return path[path.find('/contracts/'):] + return path + lines = self.source_mapping['lines'] if not lines: lines = '' @@ -79,5 +85,5 @@ class SourceMapping(Context): lines = '#{}'.format(lines[0]) else: lines = '#{}-{}'.format(lines[0], lines[-1]) - return '{}{}'.format(self.source_mapping['filename'], lines) + return '{}{}'.format(relative_path(self.source_mapping['filename']), lines) diff --git a/slither/detectors/abstract_detector.py b/slither/detectors/abstract_detector.py index 94de03228..d0015d09b 100644 --- a/slither/detectors/abstract_detector.py +++ b/slither/detectors/abstract_detector.py @@ -35,6 +35,8 @@ class AbstractDetector(metaclass=abc.ABCMeta): IMPACT = None CONFIDENCE = None + WIKI = '' + def __init__(self, slither, logger): self.slither = slither self.contracts = slither.contracts @@ -65,6 +67,8 @@ class AbstractDetector(metaclass=abc.ABCMeta): def log(self, info): if self.logger: info = "\n"+info + if self.WIKI != '': + info += 'Reference: {}'.format(self.WIKI) self.logger.info(self.color(info)) @abc.abstractmethod diff --git a/slither/detectors/attributes/constant_pragma.py b/slither/detectors/attributes/constant_pragma.py index 4fc531a0e..0d96e93e9 100644 --- a/slither/detectors/attributes/constant_pragma.py +++ b/slither/detectors/attributes/constant_pragma.py @@ -15,6 +15,8 @@ class ConstantPragma(AbstractDetector): IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH + WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#state-variables-that-could-be-declared-constant' + def detect(self): results = [] pragma = self.slither.pragma_directives diff --git a/slither/detectors/attributes/locked_ether.py b/slither/detectors/attributes/locked_ether.py index dc17ea6b9..7f3b75024 100644 --- a/slither/detectors/attributes/locked_ether.py +++ b/slither/detectors/attributes/locked_ether.py @@ -17,6 +17,8 @@ class LockedEther(AbstractDetector): IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.HIGH + WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#contracts-that-lock-ether' + @staticmethod def do_no_send_ether(contract): functions = contract.all_functions_called diff --git a/slither/detectors/attributes/old_solc.py b/slither/detectors/attributes/old_solc.py index 94b39a5f7..dc6bd51c8 100644 --- a/slither/detectors/attributes/old_solc.py +++ b/slither/detectors/attributes/old_solc.py @@ -16,6 +16,8 @@ class OldSolc(AbstractDetector): IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH + WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#old-versions-of-solidity' + @staticmethod def _convert_pragma(version): return version.replace('solidity', '').replace('^', '') diff --git a/slither/detectors/functions/arbitrary_send.py b/slither/detectors/functions/arbitrary_send.py index 0561fe61c..e509f5d16 100644 --- a/slither/detectors/functions/arbitrary_send.py +++ b/slither/detectors/functions/arbitrary_send.py @@ -31,6 +31,8 @@ class ArbitrarySend(AbstractDetector): IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.MEDIUM + WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#functions-that-send-ether-to-arbitrary-destinations' + @staticmethod def arbitrary_send(func): """ diff --git a/slither/detectors/functions/external_function.py b/slither/detectors/functions/external_function.py index 595634a88..d2f1cc322 100644 --- a/slither/detectors/functions/external_function.py +++ b/slither/detectors/functions/external_function.py @@ -16,6 +16,8 @@ class ExternalFunction(AbstractDetector): IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH + WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#public-function-that-could-be-declared-as-external' + @staticmethod def detect_functions_called(contract): """ Returns a list of InternallCall, SolidityCall @@ -45,6 +47,7 @@ class ExternalFunction(AbstractDetector): results = [] public_function_calls = [] + all_info = '' for contract in self.slither.contracts_derived: if self._contains_internal_dynamic_call(contract): @@ -60,10 +63,12 @@ class ExternalFunction(AbstractDetector): info = txt.format(func.contract.name, func.name, func.source_mapping_str) - self.log(info) + all_info += info results.append({'vuln': 'ExternalFunc', 'sourceMapping': func.source_mapping, 'filename': self.filename, 'contract': func.contract.name, 'func': func.name}) + if all_info != '': + self.log(all_info) return results diff --git a/slither/detectors/functions/suicidal.py b/slither/detectors/functions/suicidal.py index aef4a9721..8cac68977 100644 --- a/slither/detectors/functions/suicidal.py +++ b/slither/detectors/functions/suicidal.py @@ -16,6 +16,8 @@ class Suicidal(AbstractDetector): IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.HIGH + WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#suicidal' + @staticmethod def detect_suicidal_func(func): """ Detect if the function is suicidal diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index 7e64a01b2..a2f37ef84 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -17,6 +17,8 @@ class NamingConvention(AbstractDetector): IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH + WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#conformance-to-solidity-naming-conventions' + @staticmethod def is_cap_words(name): return re.search('^[A-Z]([A-Za-z0-9]+)?_?$', name) is not None @@ -42,11 +44,12 @@ class NamingConvention(AbstractDetector): def detect(self): results = [] + all_info = '' for contract in self.contracts: if not self.is_cap_words(contract.name): info = "Contract '{}' ({}) is not in CapWords\n".format(contract.name, contract.source_mapping_str) - self.log(info) + all_info += info results.append({'vuln': 'NamingConvention', 'filename': self.filename, @@ -60,7 +63,7 @@ class NamingConvention(AbstractDetector): if not self.is_cap_words(struct.name): info = "Struct '{}.{}' ({}) is not in CapWords\n" info = info.format(struct.contract.name, struct.name, struct.source_mapping_str) - self.log(info) + all_info += info results.append({'vuln': 'NamingConvention', 'filename': self.filename, @@ -75,7 +78,7 @@ class NamingConvention(AbstractDetector): if not self.is_cap_words(event.name): info = "Event '{}.{}' ({}) is not in CapWords\n" info = info.format(event.contract.name, event.name, event.source_mapping_str) - self.log(info) + all_info += info results.append({'vuln': 'NamingConvention', 'filename': self.filename, @@ -90,7 +93,7 @@ class NamingConvention(AbstractDetector): if not self.is_mixed_case(func.name): info = "Function '{}.{}' ({}) is not in mixedCase\n" info = info.format(func.contract.name, func.name, func.source_mapping_str) - self.log(info) + all_info += info results.append({'vuln': 'NamingConvention', 'filename': self.filename, @@ -109,7 +112,7 @@ class NamingConvention(AbstractDetector): argument.function.contract.name, argument.function, argument.source_mapping_str) - self.log(info) + all_info += info results.append({'vuln': 'NamingConvention', 'filename': self.filename, @@ -126,7 +129,7 @@ class NamingConvention(AbstractDetector): if not self.is_upper_case_with_underscores(var.name): info = "Variable '{}.{}' ({}) used l, O, I, which should not be used\n" info = info.format(var.contract.name, var.name, var.source_mapping_str) - self.log(info) + all_info += info results.append({'vuln': 'NamingConvention', 'filename': self.filename, @@ -142,7 +145,7 @@ class NamingConvention(AbstractDetector): if not self.is_upper_case_with_underscores(var.name): info = "Constant '{}.{}' ({}) is not in UPPER_CASE_WITH_UNDERSCORES\n" info = info.format(var.contract.name, var.name, var.source_mapping_str) - self.log(info) + all_info += info results.append({'vuln': 'NamingConvention', 'filename': self.filename, @@ -157,7 +160,8 @@ class NamingConvention(AbstractDetector): if not correct_naming: info = "Variable '{}.{}' ({}) is not in mixedCase\n" info = info.format(var.contract.name, var.name, var.source_mapping_str) - self.log(info) + all_info += info + results.append({'vuln': 'NamingConvention', 'filename': self.filename, @@ -172,7 +176,7 @@ class NamingConvention(AbstractDetector): if not self.is_cap_words(enum.name): info = "Enum '{}.{}' ({}) is not in CapWords\n" info = info.format(enum.contract.name, enum.name, enum.source_mapping_str) - self.log(info) + all_info += info results.append({'vuln': 'NamingConvention', 'filename': self.filename, @@ -187,12 +191,14 @@ class NamingConvention(AbstractDetector): if not self.is_mixed_case(modifier.name): info = "Modifier '{}.{}' ({}) is not in mixedCase\n" info = info.format(modifier.contract.name, modifier.name, modifier.source_mapping_str) - self.log(info) + all_info += info results.append({'vuln': 'NamingConvention', 'filename': self.filename, 'contract': contract.name, 'modifier': modifier.name, 'sourceMapping': modifier.source_mapping}) + if all_info != '': + self.log(all_info) return results diff --git a/slither/detectors/operations/low_level_calls.py b/slither/detectors/operations/low_level_calls.py index 2bc74099f..509536244 100644 --- a/slither/detectors/operations/low_level_calls.py +++ b/slither/detectors/operations/low_level_calls.py @@ -16,6 +16,8 @@ class LowLevelCalls(AbstractDetector): IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH + WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#low-level-calls' + @staticmethod def _contains_low_level_calls(node): """ @@ -39,12 +41,13 @@ class LowLevelCalls(AbstractDetector): """ Detect the functions that use low level calls """ results = [] + all_info = '' for c in self.contracts: values = self.detect_low_level_calls(c) for func, nodes in values: info = "Low level call in {}.{} ({})\n" info = info.format(func.contract.name, func.name, func.source_mapping_str) - self.log(info) + all_info += info sourceMapping = [n.source_mapping for n in nodes] @@ -54,4 +57,6 @@ class LowLevelCalls(AbstractDetector): 'contract': func.contract.name, 'function_name': func.name}) + if all_info != '': + self.log(all_info) return results diff --git a/slither/detectors/reentrancy/reentrancy.py b/slither/detectors/reentrancy/reentrancy.py index 2d6c63cb7..847cdc17c 100644 --- a/slither/detectors/reentrancy/reentrancy.py +++ b/slither/detectors/reentrancy/reentrancy.py @@ -21,6 +21,8 @@ class Reentrancy(AbstractDetector): IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.MEDIUM + WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#reentrancy-vulnerabilities' + key = 'REENTRANCY' @staticmethod diff --git a/slither/detectors/statements/assembly.py b/slither/detectors/statements/assembly.py index f2bd62a8b..a10697d3a 100644 --- a/slither/detectors/statements/assembly.py +++ b/slither/detectors/statements/assembly.py @@ -16,6 +16,8 @@ class Assembly(AbstractDetector): IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH + WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#assembly-usage' + @staticmethod def _contains_inline_assembly_use(node): """ @@ -39,12 +41,13 @@ class Assembly(AbstractDetector): """ Detect the functions that use inline assembly """ results = [] + all_info = '' for c in self.contracts: values = self.detect_assembly(c) for func, nodes in values: info = "{}.{} uses assembly ({})\n" info = info.format(func.contract.name, func.name, func.source_mapping_str) - self.log(info) + all_info += info sourceMapping = [n.source_mapping for n in nodes] @@ -54,4 +57,6 @@ class Assembly(AbstractDetector): 'contract': func.contract.name, 'function_name': func.name}) + if all_info != '': + self.log(all_info) return results diff --git a/slither/detectors/statements/tx_origin.py b/slither/detectors/statements/tx_origin.py index fb14bdfba..fa5ba4938 100644 --- a/slither/detectors/statements/tx_origin.py +++ b/slither/detectors/statements/tx_origin.py @@ -14,6 +14,8 @@ class TxOrigin(AbstractDetector): IMPACT = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.MEDIUM + WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#dangerous-usage-of-txorigin' + @staticmethod def _contains_incorrect_tx_origin_use(node): """ diff --git a/slither/detectors/variables/possible_const_state_variables.py b/slither/detectors/variables/possible_const_state_variables.py index 9edcc8703..61d34efff 100644 --- a/slither/detectors/variables/possible_const_state_variables.py +++ b/slither/detectors/variables/possible_const_state_variables.py @@ -23,6 +23,8 @@ class ConstCandidateStateVars(AbstractDetector): IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH + WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#state-variables-that-could-be-declared-constant' + @staticmethod def lvalues_of_operations_with_lvalue(contract): ret = [] @@ -54,6 +56,7 @@ class ConstCandidateStateVars(AbstractDetector): """ Detect state variables that could be const """ results = [] + all_info = '' for c in self.slither.contracts_derived: const_candidates = self.detect_const_candidates(c) if const_candidates: @@ -64,10 +67,8 @@ class ConstCandidateStateVars(AbstractDetector): for contract, variables in variables_by_contract.items(): variable_names = [v.name for v in variables] - info = "{} has state variables that should be constant:\n".format(contract) for v in variables: - info += "\t- {} ({})\n".format(v.name, v.source_mapping_str) - self.log(info) + all_info += "{}.{} should be constant ({})\n".format(contract, v.name, v.source_mapping_str) sourceMapping = [v.source_mapping for v in const_candidates] @@ -76,4 +77,6 @@ class ConstCandidateStateVars(AbstractDetector): 'filename': self.filename, 'contract': c.name, 'unusedVars': variable_names}) + if all_info != '': + self.log(all_info) return results diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index bc4fb3759..3046268a8 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -28,6 +28,8 @@ class UninitializedStateVarsDetection(AbstractDetector): IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.HIGH + WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#uninitialized-state-variables' + @staticmethod def written_variables(contract): ret = [] @@ -73,7 +75,6 @@ class UninitializedStateVarsDetection(AbstractDetector): ret = self.detect_uninitialized(c) for variable, functions in ret: info = "{}.{} ({}) is never initialized. It is used in:\n" - print(variable.source_mapping) info = info.format(variable.contract.name, variable.name, variable.source_mapping_str) for f in functions: info += "\t- {} ({})\n".format(f.name, f.source_mapping_str) diff --git a/slither/detectors/variables/uninitialized_storage_variables.py b/slither/detectors/variables/uninitialized_storage_variables.py index be861a1ef..ccc3fbc77 100644 --- a/slither/detectors/variables/uninitialized_storage_variables.py +++ b/slither/detectors/variables/uninitialized_storage_variables.py @@ -19,6 +19,7 @@ class UninitializedStorageVars(AbstractDetector): IMPACT = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.HIGH + WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#uninitialized-storage-variables' # node.context[self.key] contains the uninitialized storage variables key = "UNINITIALIZEDSTORAGE" diff --git a/slither/detectors/variables/unused_state_variables.py b/slither/detectors/variables/unused_state_variables.py index 621e4d8fa..36a42d267 100644 --- a/slither/detectors/variables/unused_state_variables.py +++ b/slither/detectors/variables/unused_state_variables.py @@ -14,6 +14,8 @@ class UnusedStateVars(AbstractDetector): IMPACT = DetectorClassification.INFORMATIONAL CONFIDENCE = DetectorClassification.HIGH + WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#unused-state-variables' + def detect_unused(self, contract): if contract.is_signature_only(): return None @@ -30,6 +32,7 @@ class UnusedStateVars(AbstractDetector): """ Detect unused state variables """ results = [] + all_info = '' for c in self.slither.contracts_derived: unusedVars = self.detect_unused(c) if unusedVars: @@ -38,7 +41,7 @@ class UnusedStateVars(AbstractDetector): for var in unusedVars: info += "{}.{} ({}) is never used\n".format(var.contract.name, var.name, var.source_mapping_str) - self.log(info) + all_info += info sourceMapping = [v.source_mapping for v in unusedVars] @@ -47,4 +50,6 @@ class UnusedStateVars(AbstractDetector): 'filename': self.filename, 'contract': c.name, 'unusedVars': unusedVarsName}) + if all_info != '': + self.log(all_info) return results diff --git a/slither/slither.py b/slither/slither.py index eb9cc64d4..463eebd80 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -22,9 +22,10 @@ class Slither(SlitherSolc): self._printers = [] # json text provided - if isinstance(contract, dict): + if isinstance(contract, list): super(Slither, self).__init__('') - self._parse_contracts_from_loaded_json(contract, '') + for c in contract: + self._parse_contracts_from_loaded_json(c, c['absolutePath']) # .json or .sol provided else: contracts_json = self._run_solc(contract, solc, disable_solc_warnings, solc_arguments, ast_format) diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index 247fb9d5d..ec7383fe2 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -54,7 +54,6 @@ class SlitherSolc(Slither): return False def _parse_contracts_from_loaded_json(self, data_loaded, filename): - print(filename) if 'nodeType' in data_loaded: self._is_compact_ast = True @@ -63,7 +62,6 @@ class SlitherSolc(Slither): if os.path.isfile(sourcePath): with open(sourcePath) as f: source_code = f.read() - print(sourcePath) self.source_code[sourcePath] = source_code if data_loaded[self.get_key()] == 'root': @@ -124,7 +122,7 @@ class SlitherSolc(Slither): sourceUnit = int(sourceUnit[0]) self._source_units[sourceUnit] = name - if os.path.isfile(name): + if os.path.isfile(name) and not name in self.source_code: with open(name) as f: source_code = f.read() self.source_code[name] = source_code diff --git a/slither/utils/command_line.py b/slither/utils/command_line.py index 7233b541e..57542901d 100644 --- a/slither/utils/command_line.py +++ b/slither/utils/command_line.py @@ -3,13 +3,19 @@ from prettytable import PrettyTable from slither.detectors.abstract_detector import classification_txt def output_to_markdown(detector_classes, printer_classes): + + def extract_help(detector): + if detector.WIKI == '': + return detector.HELP + return '[{}]({})'.format(detector.HELP, detector.WIKI) + detectors_list = [] for detector in detector_classes: argument = detector.ARGUMENT # dont show the backdoor example if argument == 'backdoor': continue - help_info = detector.HELP + help_info = extract_help(detector) impact = detector.IMPACT confidence = classification_txt[detector.CONFIDENCE] detectors_list.append((argument, help_info, impact, confidence)) From 56cc9f22a4aa04e9afe54ef8f81d3ba16b1d0cc5 Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Mon, 5 Nov 2018 19:15:55 +0100 Subject: [PATCH 293/308] undo sorting of detectors output --- slither/detectors/attributes/constant_pragma.py | 2 +- slither/detectors/attributes/locked_ether.py | 4 ++-- slither/detectors/attributes/old_solc.py | 2 +- slither/detectors/examples/backdoor.py | 4 ++-- slither/detectors/functions/arbitrary_send.py | 4 ++-- slither/detectors/functions/external_function.py | 4 ++-- slither/detectors/functions/suicidal.py | 4 ++-- .../naming_convention/naming_convention.py | 16 ++++++++-------- slither/detectors/operations/low_level_calls.py | 4 ++-- slither/detectors/reentrancy/reentrancy.py | 2 +- .../detectors/shadowing/shadowing_functions.py | 4 ++-- slither/detectors/statements/assembly.py | 4 ++-- slither/detectors/statements/tx_origin.py | 4 ++-- .../variables/possible_const_state_variables.py | 4 ++-- .../variables/uninitialized_state_variables.py | 4 ++-- .../variables/uninitialized_storage_variables.py | 2 +- .../variables/unused_state_variables.py | 2 +- 17 files changed, 35 insertions(+), 35 deletions(-) diff --git a/slither/detectors/attributes/constant_pragma.py b/slither/detectors/attributes/constant_pragma.py index 763f3d04a..7a1e272c0 100644 --- a/slither/detectors/attributes/constant_pragma.py +++ b/slither/detectors/attributes/constant_pragma.py @@ -19,7 +19,7 @@ class ConstantPragma(AbstractDetector): results = [] pragma = self.slither.pragma_directives versions = [p.version for p in pragma] - versions = sorted(list(set(versions))) + versions = list(set(versions)) if len(versions) > 1: info = "Different version of Solidity used in {}: {}".format(self.filename, versions) diff --git a/slither/detectors/attributes/locked_ether.py b/slither/detectors/attributes/locked_ether.py index c0ea08953..682224f02 100644 --- a/slither/detectors/attributes/locked_ether.py +++ b/slither/detectors/attributes/locked_ether.py @@ -38,10 +38,10 @@ class LockedEther(AbstractDetector): def detect(self): results = [] - for contract in sorted(self.slither.contracts_derived, key=lambda c: c.name): + for contract in self.slither.contracts_derived: if contract.is_signature_only(): continue - funcs_payable = [function for function in sorted(contract.functions, key=lambda x: x.name) if function.payable] + funcs_payable = [function for function in contract.functions if function.payable] if funcs_payable: if self.do_no_send_ether(contract): txt = "Contract locked ether in {}, Contract {}, Functions {}" diff --git a/slither/detectors/attributes/old_solc.py b/slither/detectors/attributes/old_solc.py index b733ba1a7..a14a1df50 100644 --- a/slither/detectors/attributes/old_solc.py +++ b/slither/detectors/attributes/old_solc.py @@ -21,7 +21,7 @@ class OldSolc(AbstractDetector): pragma = self.slither.pragma_directives versions = [p.version for p in pragma] versions = [p.replace('solidity', '').replace('^', '') for p in versions] - versions = sorted(list(set(versions))) + versions = list(set(versions)) old_pragma = [p for p in versions if p not in ['0.4.23', '0.4.24']] if old_pragma: diff --git a/slither/detectors/examples/backdoor.py b/slither/detectors/examples/backdoor.py index 1543e70f6..9e29724f9 100644 --- a/slither/detectors/examples/backdoor.py +++ b/slither/detectors/examples/backdoor.py @@ -14,9 +14,9 @@ class Backdoor(AbstractDetector): def detect(self): ret = [] - for contract in sorted(self.slither.contracts_derived, key=lambda c: c.name): + for contract in self.slither.contracts_derived: # Check if a function has 'backdoor' in its name - for f in sorted(contract.functions, key=lambda x: x.name): + for f in contract.functions: if 'backdoor' in f.name: # Info to be printed info = 'Backdoor function found in {}.{}'.format(contract.name, f.name) diff --git a/slither/detectors/functions/arbitrary_send.py b/slither/detectors/functions/arbitrary_send.py index 594c6ce78..88d9be7c7 100644 --- a/slither/detectors/functions/arbitrary_send.py +++ b/slither/detectors/functions/arbitrary_send.py @@ -94,9 +94,9 @@ class ArbitrarySend(AbstractDetector): taint = SolidityVariableComposed('msg.sender') run_taint_variable(self.slither, taint) - for c in sorted(self.contracts, key=lambda c: c.name): + for c in self.contracts: arbitrary_send = self.detect_arbitrary_send(c) - for (func, nodes) in sorted(arbitrary_send, key=lambda v: v[0].name): + for (func, nodes) in arbitrary_send: func_name = func.name calls_str = [str(node.expression) for node in nodes] diff --git a/slither/detectors/functions/external_function.py b/slither/detectors/functions/external_function.py index c5d860201..99ffe40fe 100644 --- a/slither/detectors/functions/external_function.py +++ b/slither/detectors/functions/external_function.py @@ -46,14 +46,14 @@ class ExternalFunction(AbstractDetector): public_function_calls = [] - for contract in sorted(self.slither.contracts_derived, key=lambda c: c.name): + for contract in self.slither.contracts_derived: if self._contains_internal_dynamic_call(contract): continue func_list = self.detect_functions_called(contract) public_function_calls.extend(func_list) - for func in [f for f in sorted(contract.functions, key=lambda x: x.name) if f.visibility == 'public' and\ + for func in [f for f in contract.functions if f.visibility == 'public' and\ not f in public_function_calls and\ not f.is_constructor]: func_name = func.name diff --git a/slither/detectors/functions/suicidal.py b/slither/detectors/functions/suicidal.py index 156aa569f..41b6f9bc5 100644 --- a/slither/detectors/functions/suicidal.py +++ b/slither/detectors/functions/suicidal.py @@ -51,9 +51,9 @@ class Suicidal(AbstractDetector): """ Detect the suicidal functions """ results = [] - for c in sorted(self.contracts, key=lambda c: c.name): + for c in self.contracts: functions = self.detect_suicidal(c) - for func in sorted(functions, key=lambda x: x.name): + for func in functions: func_name = func.name txt = "Suicidal function in {} Contract: {}, Function: {}" diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index 36d928983..f3312c1f0 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -42,7 +42,7 @@ class NamingConvention(AbstractDetector): def detect(self): results = [] - for contract in sorted(self.contracts, key=lambda c: c.name): + for contract in self.contracts: if not self.is_cap_words(contract.name): info = "Contract '{}' is not in CapWords".format(contract.name) @@ -53,7 +53,7 @@ class NamingConvention(AbstractDetector): 'contract': contract.name, 'sourceMapping': contract.source_mapping}) - for struct in sorted(contract.structures, key=lambda x: x.name): + for struct in contract.structures: if struct.contract != contract: continue @@ -67,7 +67,7 @@ class NamingConvention(AbstractDetector): 'struct': struct.name, 'sourceMapping': struct.source_mapping}) - for event in sorted(contract.events, key=lambda x: x.name): + for event in contract.events: if event.contract != contract: continue @@ -81,7 +81,7 @@ class NamingConvention(AbstractDetector): 'event': event.name, 'sourceMapping': event.source_mapping}) - for func in sorted(contract.functions, key=lambda x: x.name): + for func in contract.functions: if func.contract != contract: continue @@ -95,7 +95,7 @@ class NamingConvention(AbstractDetector): 'function': func.name, 'sourceMapping': func.source_mapping}) - for argument in sorted(func.parameters, key=lambda x: x.name): + for argument in func.parameters: if argument in func.variables_read_or_written: correct_naming = self.is_mixed_case(argument.name) else: @@ -112,7 +112,7 @@ class NamingConvention(AbstractDetector): 'argument': argument.name, 'sourceMapping': argument.source_mapping}) - for var in sorted(contract.state_variables, key=lambda x: x.name): + for var in contract.state_variables: if var.contract != contract: continue @@ -158,7 +158,7 @@ class NamingConvention(AbstractDetector): 'variable': var.name, 'sourceMapping': var.source_mapping}) - for enum in sorted(contract.enums, key=lambda x: x.name): + for enum in contract.enums: if enum.contract != contract: continue @@ -172,7 +172,7 @@ class NamingConvention(AbstractDetector): 'enum': enum.name, 'sourceMapping': enum.source_mapping}) - for modifier in sorted(contract.modifiers, key=lambda x: x.name): + for modifier in contract.modifiers: if modifier.contract != contract: continue diff --git a/slither/detectors/operations/low_level_calls.py b/slither/detectors/operations/low_level_calls.py index 11ce2c89b..ec0f546a7 100644 --- a/slither/detectors/operations/low_level_calls.py +++ b/slither/detectors/operations/low_level_calls.py @@ -39,9 +39,9 @@ class LowLevelCalls(AbstractDetector): """ Detect the functions that use low level calls """ results = [] - for c in sorted(self.contracts, key=lambda c: c.name): + for c in self.contracts: values = self.detect_low_level_calls(c) - for func, nodes in sorted(values, key=lambda v: v[0].name): + for func, nodes in values: func_name = func.name info = "Low level call in %s, Contract: %s, Function: %s" % (self.filename, c.name, diff --git a/slither/detectors/reentrancy/reentrancy.py b/slither/detectors/reentrancy/reentrancy.py index 15325feb2..e5c9966a7 100644 --- a/slither/detectors/reentrancy/reentrancy.py +++ b/slither/detectors/reentrancy/reentrancy.py @@ -176,7 +176,7 @@ class Reentrancy(AbstractDetector): results = [] - for (contract, func, calls, send_eth), varsWritten in sorted(self.result.items(), key=lambda x: (x[0][0], x[0][1], str(list(x[0][2])) )): + for (contract, func, calls, send_eth), varsWritten in self.result.items(): varsWritten_str = list(set([str(x) for x in list(varsWritten)])) calls_str = list(set([str(x.expression) for x in list(calls)])) send_eth_str = list(set([str(x.expression) for x in list(send_eth)])) diff --git a/slither/detectors/shadowing/shadowing_functions.py b/slither/detectors/shadowing/shadowing_functions.py index cecdc4b57..60d9677df 100644 --- a/slither/detectors/shadowing/shadowing_functions.py +++ b/slither/detectors/shadowing/shadowing_functions.py @@ -37,10 +37,10 @@ class ShadowingFunctionsDetection(AbstractDetector): """ results = [] - for c in sorted(self.contracts, key=lambda c: c.name): + for c in self.contracts: shadowing = self.detect_shadowing(c) if shadowing: - for contract, funcs in sorted(shadowing.items(), key=lambda x: (x[0].name, str(list(x[1])))): + for contract, funcs in shadowing.items(): results.append({'vuln': self.vuln_name, 'filename': self.filename, 'contractShadower': c.name, diff --git a/slither/detectors/statements/assembly.py b/slither/detectors/statements/assembly.py index 85ffbe934..93e9ea598 100644 --- a/slither/detectors/statements/assembly.py +++ b/slither/detectors/statements/assembly.py @@ -39,9 +39,9 @@ class Assembly(AbstractDetector): """ Detect the functions that use inline assembly """ results = [] - for c in sorted(self.contracts, key=lambda c: c.name): + for c in self.contracts: values = self.detect_assembly(c) - for func, nodes in sorted(values, key=lambda v: v[0].name): + for func, nodes in values: func_name = func.name info = "Assembly in %s, Contract: %s, Function: %s" % (self.filename, c.name, diff --git a/slither/detectors/statements/tx_origin.py b/slither/detectors/statements/tx_origin.py index fe2f5dc53..f5c474f8f 100644 --- a/slither/detectors/statements/tx_origin.py +++ b/slither/detectors/statements/tx_origin.py @@ -45,9 +45,9 @@ class TxOrigin(AbstractDetector): """ Detect the functions that use tx.origin in a conditional node """ results = [] - for c in sorted(self.contracts, key=lambda c: c.name): + for c in self.contracts: values = self.detect_tx_origin(c) - for func, nodes in sorted(values, key=lambda v: v[0].name): + for func, nodes in values: func_name = func.name info = "tx.origin in %s, Contract: %s, Function: %s" % (self.filename, c.name, diff --git a/slither/detectors/variables/possible_const_state_variables.py b/slither/detectors/variables/possible_const_state_variables.py index 4bcb0f3f4..e19a389d1 100644 --- a/slither/detectors/variables/possible_const_state_variables.py +++ b/slither/detectors/variables/possible_const_state_variables.py @@ -54,12 +54,12 @@ class ConstCandidateStateVars(AbstractDetector): """ Detect state variables that could be const """ results = [] - for c in sorted(self.slither.contracts_derived, key=lambda c: c.name): + for c in self.slither.contracts_derived: const_candidates = self.detect_const_candidates(c) if const_candidates: variables_by_contract = defaultdict(list) - for state_var in sorted(const_candidates, key=lambda x: (x.contract.name, x.name)): + for state_var in const_candidates: variables_by_contract[state_var.contract.name].append(state_var) for contract, variables in variables_by_contract.items(): diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index 17abb4a28..86f54b048 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -69,9 +69,9 @@ class UninitializedStateVarsDetection(AbstractDetector): dict: [contract name] = set(state variable uninitialized) """ results = [] - for c in sorted(self.slither.contracts_derived, key=lambda c: c.name): + for c in self.slither.contracts_derived: ret = self.detect_uninitialized(c) - for variable, functions in sorted(ret, key=lambda r: str(r[0])): + for variable, functions in ret: info = "Uninitialized state variable in %s, " % self.filename + \ "Contract: %s, Variable: %s, Used in %s" % (c.name, str(variable), diff --git a/slither/detectors/variables/uninitialized_storage_variables.py b/slither/detectors/variables/uninitialized_storage_variables.py index eef4ef4d6..37201b33c 100644 --- a/slither/detectors/variables/uninitialized_storage_variables.py +++ b/slither/detectors/variables/uninitialized_storage_variables.py @@ -79,7 +79,7 @@ class UninitializedStorageVars(AbstractDetector): function.entry_point.context[self.key] = uninitialized_storage_variables self._detect_uninitialized(function, function.entry_point, []) - for(function, uninitialized_storage_variable) in sorted(self.results, key=lambda r: (r[0].contract.name, r[0].name, r[1].name)): + for(function, uninitialized_storage_variable) in self.results: var_name = uninitialized_storage_variable.name info = "Uninitialized storage variables in %s, " % self.filename + \ diff --git a/slither/detectors/variables/unused_state_variables.py b/slither/detectors/variables/unused_state_variables.py index c9ec84c03..5be36163e 100644 --- a/slither/detectors/variables/unused_state_variables.py +++ b/slither/detectors/variables/unused_state_variables.py @@ -30,7 +30,7 @@ class UnusedStateVars(AbstractDetector): """ Detect unused state variables """ results = [] - for c in sorted(self.slither.contracts_derived, key=lambda c: c.name): + for c in self.slither.contracts_derived: unusedVars = self.detect_unused(c) if unusedVars: unusedVarsName = [v.name for v in unusedVars] From 17e6f3c24303bca668687a9ef86791bc5bd3fd90 Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Mon, 5 Nov 2018 19:16:11 +0100 Subject: [PATCH 294/308] add python script to recursively order the json output of detectors --- scripts/pretty_print_and_sort_json.py | 103 ++++++++++++++++++++++++ scripts/pretty_print_json.py | 11 --- scripts/tests_generate_expected_json.sh | 2 +- scripts/travis_test.sh | 4 +- 4 files changed, 106 insertions(+), 14 deletions(-) create mode 100644 scripts/pretty_print_and_sort_json.py delete mode 100644 scripts/pretty_print_json.py diff --git a/scripts/pretty_print_and_sort_json.py b/scripts/pretty_print_and_sort_json.py new file mode 100644 index 000000000..edbe105b8 --- /dev/null +++ b/scripts/pretty_print_and_sort_json.py @@ -0,0 +1,103 @@ +#!/usr/bin/python3 + +''' +the purpose of this file is to sort the json output from the detectors such that +the order is deterministic + +- the keys of a json object are sorted +- json objects in a list will be sorted based on the values of their keys + +''' + +import sys +import json + +raw_json_file = sys.argv[1] +pretty_json_file = sys.argv[2] + +from collections import OrderedDict + +def create_property_val_tuple(d, props_info): + p_names = props_info[0] + p_types = props_info[1] + result = [] + for p in p_names: + if not p in d: # not all objects have the same keys + if p_types[p] is 'number': + result.append(0) # to make sorting work + if p_types[p] is 'string': + result.append("") # to make sorting work + else: + result.append(d[p]) + return tuple(result) + +def get_props_info(list_of_dicts): + found_props = set() + prop_types = dict() + + # gather all prop names + for d in list_of_dicts: + for p in d: + found_props.add(p) + + # create a copy, since we are gonna possibly remove props + shared_props = set(found_props) + + # for each object, loop through list of all found property names, + # if the object contains that property, check that it's of type string or number + # if it is, save it's type (used later on for sorting with objects that don't have that property) + # if it's not of type string/number remove it from list of properties to check + # since we cannot sort on non-string/number values + for p in list(found_props): + if p in shared_props: # short circuit + for d in list_of_dicts: + if p in shared_props: # less shorter short circuit + if p in d: + # we ae only gonna sort key values if they are of type string or number + if not isinstance(d[p], basestring) and not isinstance(d[p], int): + shared_props.remove(p) + # we need to store the type of the value because not each object + # in a list of output objects for 1 detector will have the same + # keys, so if we want to sort based on the values then if a certain object + # does not have a key which another object does have we are gonna + # put in 0 for number and "" for string for that key such that sorting on values + # still works + elif isinstance(d[p], basestring): + prop_types[p] = 'string' + elif isinstance(d[p], int): + prop_types[p] = 'number' + return (list(shared_props), prop_types) + +def order_by_prop_value(list_of_dicts): + props_info = get_props_info(list_of_dicts) + return sorted(list_of_dicts, key=lambda d: create_property_val_tuple(d, props_info)) + +def order_list(list): + # TODO: sometimes slither detectors return a null value in the json output sourceMapping object array + # get rid of those values, it will break sorting (some items are an object, some are null?!) + list = filter(None, list) + if not list: + return [] + if isinstance(list[0], basestring): # it's a list of string + return sorted(list) + elif isinstance(list[0], int): # it's a list of numbers + return sorted(list) + elif isinstance(list[0], dict): # it's a list of objects + ordered_by_key = [order_dict(v) for v in list] + ordered_by_val = order_by_prop_value(ordered_by_key) + return ordered_by_val + +def order_dict(dictionary): + result = OrderedDict() # such that we keep the order + for k, v in sorted(dictionary.items()): + if isinstance(v, dict): + result[k] = order_dict(v) + elif type(v) is list: + result[k] = order_list(v) + else: # string/number + result[k] = v + return result + +with open(raw_json_file, 'r') as json_data: + with open(pretty_json_file, 'w') as out_file: + out_file.write(json.dumps(order_list(json.load(json_data)), sort_keys=False, indent=4, separators=(',',': '))) \ No newline at end of file diff --git a/scripts/pretty_print_json.py b/scripts/pretty_print_json.py deleted file mode 100644 index d86974e84..000000000 --- a/scripts/pretty_print_json.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/python3 - -import sys -import json - -raw_json_file = sys.argv[1] -pretty_json_file = sys.argv[2] - -with open(raw_json_file, 'r') as json_data: - with open(pretty_json_file, 'w') as out_file: - out_file.write(json.dumps(json.load(json_data), sort_keys=True, indent=4, separators=(',',': '))) \ No newline at end of file diff --git a/scripts/tests_generate_expected_json.sh b/scripts/tests_generate_expected_json.sh index 2e024a106..22611880d 100755 --- a/scripts/tests_generate_expected_json.sh +++ b/scripts/tests_generate_expected_json.sh @@ -13,7 +13,7 @@ generate_expected_json(){ slither "$1" --disable-solc-warnings --detectors "$2" --json "$DIR/tmp-gen.json" # convert json file to pretty print and write to destination folder - python "$DIR/pretty_print_json.py" "$DIR/tmp-gen.json" "$DIR/../tests/expected_json/$output_filename" + python "$DIR/pretty_print_and_sort_json.py" "$DIR/tmp-gen.json" "$DIR/../tests/expected_json/$output_filename" # remove the raw un-prettified json file rm "$DIR/tmp-gen.json" diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index e60fa9e71..c68477aa6 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -13,7 +13,7 @@ test_slither(){ slither "$1" --disable-solc-warnings --detectors "$2" --json "$DIR/tmp-test.json" # convert json file to pretty print and write to destination folder - python "$DIR/pretty_print_json.py" "$DIR/tmp-test.json" "$actual" + python "$DIR/pretty_print_and_sort_json.py" "$DIR/tmp-test.json" "$actual" # remove the raw un-prettified json file rm "$DIR/tmp-test.json" @@ -36,7 +36,7 @@ test_slither(){ slither "$1" --disable-solc-warnings --detectors "$2" --compact-ast --json "$DIR/tmp-test.json" # convert json file to pretty print and write to destination folder - python "$DIR/pretty_print_json.py" "$DIR/tmp-test.json" "$actual" + python "$DIR/pretty_print_and_sort_json.py" "$DIR/tmp-test.json" "$actual" # remove the raw un-prettified json file rm "$DIR/tmp-test.json" From 284fea7a4fe47eb8ed932402c1b6eeeaf8717c9a Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Mon, 5 Nov 2018 19:16:17 +0100 Subject: [PATCH 295/308] regenerate json output using new sort-json python script --- .../arbitrary_send.arbitrary-send.json | 8 +- .../inline_assembly_contract.assembly.json | 4 +- .../inline_assembly_library.assembly.json | 8 +- .../low_level_calls.low-level-calls.json | 4 +- .../naming_convention.naming-convention.json | 86 +++++++++---------- tests/expected_json/pragma.0.4.24.pragma.json | 8 +- .../expected_json/reentrancy.reentrancy.json | 4 +- tests/expected_json/tx_origin.tx-origin.json | 8 +- .../uninitialized.uninitialized-state.json | 40 ++++----- 9 files changed, 76 insertions(+), 94 deletions(-) diff --git a/tests/expected_json/arbitrary_send.arbitrary-send.json b/tests/expected_json/arbitrary_send.arbitrary-send.json index d8a46428b..8a61f737f 100644 --- a/tests/expected_json/arbitrary_send.arbitrary-send.json +++ b/tests/expected_json/arbitrary_send.arbitrary-send.json @@ -6,9 +6,7 @@ "contract": "Test", "filename": "tests/arbitrary_send.sol", "func": "direct", - "sourceMapping": [ - null - ], + "sourceMapping": [], "vuln": "SuicidalFunc" }, { @@ -18,9 +16,7 @@ "contract": "Test", "filename": "tests/arbitrary_send.sol", "func": "indirect", - "sourceMapping": [ - null - ], + "sourceMapping": [], "vuln": "SuicidalFunc" } ] \ No newline at end of file diff --git a/tests/expected_json/inline_assembly_contract.assembly.json b/tests/expected_json/inline_assembly_contract.assembly.json index 3176781ef..ff9d713d3 100644 --- a/tests/expected_json/inline_assembly_contract.assembly.json +++ b/tests/expected_json/inline_assembly_contract.assembly.json @@ -3,9 +3,7 @@ "contract": "GetCode", "filename": "tests/inline_assembly_contract.sol", "function_name": "at", - "sourceMapping": [ - null - ], + "sourceMapping": [], "vuln": "Assembly" } ] \ No newline at end of file diff --git a/tests/expected_json/inline_assembly_library.assembly.json b/tests/expected_json/inline_assembly_library.assembly.json index 7169cf959..fd6be8d62 100644 --- a/tests/expected_json/inline_assembly_library.assembly.json +++ b/tests/expected_json/inline_assembly_library.assembly.json @@ -3,18 +3,14 @@ "contract": "VectorSum", "filename": "tests/inline_assembly_library.sol", "function_name": "sumAsm", - "sourceMapping": [ - null - ], + "sourceMapping": [], "vuln": "Assembly" }, { "contract": "VectorSum", "filename": "tests/inline_assembly_library.sol", "function_name": "sumPureAsm", - "sourceMapping": [ - null - ], + "sourceMapping": [], "vuln": "Assembly" } ] \ No newline at end of file diff --git a/tests/expected_json/low_level_calls.low-level-calls.json b/tests/expected_json/low_level_calls.low-level-calls.json index 2c3b9e355..d7973de50 100644 --- a/tests/expected_json/low_level_calls.low-level-calls.json +++ b/tests/expected_json/low_level_calls.low-level-calls.json @@ -3,9 +3,7 @@ "contract": "Sender", "filename": "tests/low_level_calls.sol", "function_name": "send", - "sourceMapping": [ - null - ], + "sourceMapping": [], "vuln": "Low level call" } ] \ No newline at end of file diff --git a/tests/expected_json/naming_convention.naming-convention.json b/tests/expected_json/naming_convention.naming-convention.json index 2e9212e93..e65150f60 100644 --- a/tests/expected_json/naming_convention.naming-convention.json +++ b/tests/expected_json/naming_convention.naming-convention.json @@ -1,16 +1,4 @@ [ - { - "argument": "_used", - "contract": "T", - "filename": "tests/naming_convention.sol", - "function": "test", - "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 10, - "start": 748 - }, - "vuln": "NamingConvention" - }, { "contract": "T", "filename": "tests/naming_convention.sol", @@ -23,65 +11,64 @@ "vuln": "NamingConvention" }, { - "constant": "l", - "contract": "T", + "contract": "naming", "filename": "tests/naming_convention.sol", "sourceMapping": { "filename": "tests/naming_convention.sol", - "length": 10, - "start": 847 + "length": 598, + "start": 26 }, "vuln": "NamingConvention" }, { "contract": "naming", + "event": "event_", "filename": "tests/naming_convention.sol", - "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 598, - "start": 26 - }, + "sourceMapping": null, "vuln": "NamingConvention" }, { "contract": "naming", "filename": "tests/naming_convention.sol", + "modifier": "CantDo", "sourceMapping": { "filename": "tests/naming_convention.sol", - "length": 20, - "start": 227 + "length": 36, + "start": 545 }, - "struct": "test", "vuln": "NamingConvention" }, { "contract": "naming", - "event": "event_", "filename": "tests/naming_convention.sol", - "sourceMapping": null, + "sourceMapping": { + "filename": "tests/naming_convention.sol", + "length": 16, + "start": 183 + }, + "variable": "Var_One", "vuln": "NamingConvention" }, { "contract": "naming", + "enum": "numbers", "filename": "tests/naming_convention.sol", - "function": "GetOne", "sourceMapping": { "filename": "tests/naming_convention.sol", - "length": 71, - "start": 405 + "length": 23, + "start": 77 }, "vuln": "NamingConvention" }, { - "argument": "Number2", "contract": "naming", "filename": "tests/naming_convention.sol", - "function": "setInt", "sourceMapping": { "filename": "tests/naming_convention.sol", - "length": 12, - "start": 512 + "length": 20, + "start": 227 }, + "struct": "test", "vuln": "NamingConvention" }, { @@ -96,35 +83,48 @@ "vuln": "NamingConvention" }, { - "contract": "naming", + "constant": "l", + "contract": "T", "filename": "tests/naming_convention.sol", "sourceMapping": { "filename": "tests/naming_convention.sol", - "length": 16, - "start": 183 + "length": 10, + "start": 847 }, - "variable": "Var_One", "vuln": "NamingConvention" }, { "contract": "naming", - "enum": "numbers", "filename": "tests/naming_convention.sol", + "function": "GetOne", "sourceMapping": { "filename": "tests/naming_convention.sol", - "length": 23, - "start": 77 + "length": 71, + "start": 405 }, "vuln": "NamingConvention" }, { + "argument": "Number2", "contract": "naming", "filename": "tests/naming_convention.sol", - "modifier": "CantDo", + "function": "setInt", "sourceMapping": { "filename": "tests/naming_convention.sol", - "length": 36, - "start": 545 + "length": 12, + "start": 512 + }, + "vuln": "NamingConvention" + }, + { + "argument": "_used", + "contract": "T", + "filename": "tests/naming_convention.sol", + "function": "test", + "sourceMapping": { + "filename": "tests/naming_convention.sol", + "length": 10, + "start": 748 }, "vuln": "NamingConvention" } diff --git a/tests/expected_json/pragma.0.4.24.pragma.json b/tests/expected_json/pragma.0.4.24.pragma.json index 2fbd07f39..66f9c9761 100644 --- a/tests/expected_json/pragma.0.4.24.pragma.json +++ b/tests/expected_json/pragma.0.4.24.pragma.json @@ -2,13 +2,13 @@ { "sourceMapping": [ { - "filename": "tests/pragma.0.4.23.sol", - "length": 24, + "filename": "tests/pragma.0.4.24.sol", + "length": 23, "start": 0 }, { - "filename": "tests/pragma.0.4.24.sol", - "length": 23, + "filename": "tests/pragma.0.4.23.sol", + "length": 24, "start": 0 } ], diff --git a/tests/expected_json/reentrancy.reentrancy.json b/tests/expected_json/reentrancy.reentrancy.json index b78d91c3c..1a7f119f6 100644 --- a/tests/expected_json/reentrancy.reentrancy.json +++ b/tests/expected_json/reentrancy.reentrancy.json @@ -14,9 +14,7 @@ "filename": "tests/reentrancy.sol", "length": 37, "start": 52 - }, - null, - null + } ], "varsWritten": [ "userBalance" diff --git a/tests/expected_json/tx_origin.tx-origin.json b/tests/expected_json/tx_origin.tx-origin.json index 105614ad4..01b0f425b 100644 --- a/tests/expected_json/tx_origin.tx-origin.json +++ b/tests/expected_json/tx_origin.tx-origin.json @@ -3,18 +3,14 @@ "contract": "TxOrigin", "filename": "tests/tx_origin.sol", "function_name": "bug0", - "sourceMapping": [ - null - ], + "sourceMapping": [], "vuln": "TxOrigin" }, { "contract": "TxOrigin", "filename": "tests/tx_origin.sol", "function_name": "bug2", - "sourceMapping": [ - null - ], + "sourceMapping": [], "vuln": "TxOrigin" } ] \ No newline at end of file diff --git a/tests/expected_json/uninitialized.uninitialized-state.json b/tests/expected_json/uninitialized.uninitialized-state.json index d15bc3eb6..e243ad749 100644 --- a/tests/expected_json/uninitialized.uninitialized-state.json +++ b/tests/expected_json/uninitialized.uninitialized-state.json @@ -21,66 +21,66 @@ "vuln": "UninitializedStateVars" }, { - "contract": "Test2", + "contract": "Uninitialized", "filename": "tests/uninitialized.sol", "functions": [ - "use" + "transfer" ], "sourceMapping": [ { "filename": "tests/uninitialized.sol", - "length": 15, - "start": 695 + "length": 19, + "start": 55 }, { "filename": "tests/uninitialized.sol", - "length": 117, - "start": 875 + "length": 82, + "start": 81 } ], - "variable": "st", + "variable": "destination", "vuln": "UninitializedStateVars" }, { "contract": "Test2", "filename": "tests/uninitialized.sol", "functions": [ - "init" + "use" ], "sourceMapping": [ { "filename": "tests/uninitialized.sol", - "length": 6, - "start": 748 + "length": 15, + "start": 695 }, { "filename": "tests/uninitialized.sol", - "length": 52, - "start": 817 + "length": 117, + "start": 875 } ], - "variable": "v", + "variable": "st", "vuln": "UninitializedStateVars" }, { - "contract": "Uninitialized", + "contract": "Test2", "filename": "tests/uninitialized.sol", "functions": [ - "transfer" + "init" ], "sourceMapping": [ { "filename": "tests/uninitialized.sol", - "length": 19, - "start": 55 + "length": 6, + "start": 748 }, { "filename": "tests/uninitialized.sol", - "length": 82, - "start": 81 + "length": 52, + "start": 817 } ], - "variable": "destination", + "variable": "v", "vuln": "UninitializedStateVars" } ] \ No newline at end of file From 8bf5e9614062c0a87e83698eb6cf7349cad4acfa Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Mon, 5 Nov 2018 19:19:20 +0100 Subject: [PATCH 296/308] cast filter output to list --- scripts/pretty_print_and_sort_json.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/pretty_print_and_sort_json.py b/scripts/pretty_print_and_sort_json.py index edbe105b8..2c4718842 100644 --- a/scripts/pretty_print_and_sort_json.py +++ b/scripts/pretty_print_and_sort_json.py @@ -75,7 +75,7 @@ def order_by_prop_value(list_of_dicts): def order_list(list): # TODO: sometimes slither detectors return a null value in the json output sourceMapping object array # get rid of those values, it will break sorting (some items are an object, some are null?!) - list = filter(None, list) + list = list(filter(None, list)) if not list: return [] if isinstance(list[0], basestring): # it's a list of string From 257058e80b5c19b316c1ae16af17c9bdf2ac8ddd Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Mon, 5 Nov 2018 19:22:22 +0100 Subject: [PATCH 297/308] rename list var to l --- scripts/pretty_print_and_sort_json.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/pretty_print_and_sort_json.py b/scripts/pretty_print_and_sort_json.py index 2c4718842..2c57ebab6 100644 --- a/scripts/pretty_print_and_sort_json.py +++ b/scripts/pretty_print_and_sort_json.py @@ -72,18 +72,18 @@ def order_by_prop_value(list_of_dicts): props_info = get_props_info(list_of_dicts) return sorted(list_of_dicts, key=lambda d: create_property_val_tuple(d, props_info)) -def order_list(list): +def order_list(l): # TODO: sometimes slither detectors return a null value in the json output sourceMapping object array # get rid of those values, it will break sorting (some items are an object, some are null?!) - list = list(filter(None, list)) - if not list: + l = list(filter(None, list)) + if not l: return [] - if isinstance(list[0], basestring): # it's a list of string - return sorted(list) - elif isinstance(list[0], int): # it's a list of numbers - return sorted(list) - elif isinstance(list[0], dict): # it's a list of objects - ordered_by_key = [order_dict(v) for v in list] + if isinstance(l[0], basestring): # it's a list of string + return sorted(l) + elif isinstance(l[0], int): # it's a list of numbers + return sorted(l) + elif isinstance(l[0], dict): # it's a list of objects + ordered_by_key = [order_dict(v) for v in l] ordered_by_val = order_by_prop_value(ordered_by_key) return ordered_by_val From ded2359bdfba1f101628b1e7c777e99589260aa0 Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Mon, 5 Nov 2018 19:24:13 +0100 Subject: [PATCH 298/308] typo --- scripts/pretty_print_and_sort_json.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/pretty_print_and_sort_json.py b/scripts/pretty_print_and_sort_json.py index 2c57ebab6..e4c0e661e 100644 --- a/scripts/pretty_print_and_sort_json.py +++ b/scripts/pretty_print_and_sort_json.py @@ -75,7 +75,7 @@ def order_by_prop_value(list_of_dicts): def order_list(l): # TODO: sometimes slither detectors return a null value in the json output sourceMapping object array # get rid of those values, it will break sorting (some items are an object, some are null?!) - l = list(filter(None, list)) + l = list(filter(None, l)) if not l: return [] if isinstance(l[0], basestring): # it's a list of string From 11132a628e0ec3760fb0d30fdd2852d4200bd647 Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Mon, 5 Nov 2018 19:28:15 +0100 Subject: [PATCH 299/308] replace basestring with str --- scripts/pretty_print_and_sort_json.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/pretty_print_and_sort_json.py b/scripts/pretty_print_and_sort_json.py index e4c0e661e..e0aeef9bc 100644 --- a/scripts/pretty_print_and_sort_json.py +++ b/scripts/pretty_print_and_sort_json.py @@ -54,7 +54,7 @@ def get_props_info(list_of_dicts): if p in shared_props: # less shorter short circuit if p in d: # we ae only gonna sort key values if they are of type string or number - if not isinstance(d[p], basestring) and not isinstance(d[p], int): + if not isinstance(d[p], str) and not isinstance(d[p], int): shared_props.remove(p) # we need to store the type of the value because not each object # in a list of output objects for 1 detector will have the same @@ -62,7 +62,7 @@ def get_props_info(list_of_dicts): # does not have a key which another object does have we are gonna # put in 0 for number and "" for string for that key such that sorting on values # still works - elif isinstance(d[p], basestring): + elif isinstance(d[p], str): prop_types[p] = 'string' elif isinstance(d[p], int): prop_types[p] = 'number' @@ -78,7 +78,7 @@ def order_list(l): l = list(filter(None, l)) if not l: return [] - if isinstance(l[0], basestring): # it's a list of string + if isinstance(l[0], str): # it's a list of string return sorted(l) elif isinstance(l[0], int): # it's a list of numbers return sorted(l) From 0b7f0c47fb0139b93ac4fd4076fb778aa9da95a4 Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Mon, 5 Nov 2018 19:28:37 +0100 Subject: [PATCH 300/308] regenareted tests expected json output --- .../external_function.external-function.json | 20 +++--- .../naming_convention.naming-convention.json | 66 +++++++++---------- tests/expected_json/pragma.0.4.24.pragma.json | 8 +-- .../uninitialized.uninitialized-state.json | 40 +++++------ 4 files changed, 67 insertions(+), 67 deletions(-) diff --git a/tests/expected_json/external_function.external-function.json b/tests/expected_json/external_function.external-function.json index fdf8f8b67..50332214e 100644 --- a/tests/expected_json/external_function.external-function.json +++ b/tests/expected_json/external_function.external-function.json @@ -11,35 +11,35 @@ "vuln": "ExternalFunc" }, { - "contract": "ContractWithFunctionNotCalled", + "contract": "ContractWithFunctionNotCalled2", "filename": "tests/external_function.sol", - "func": "funcNotCalled2", + "func": "funcNotCalled", "sourceMapping": { "filename": "tests/external_function.sol", - "length": 41, - "start": 304 + "length": 304, + "start": 552 }, "vuln": "ExternalFunc" }, { "contract": "ContractWithFunctionNotCalled", "filename": "tests/external_function.sol", - "func": "funcNotCalled3", + "func": "funcNotCalled2", "sourceMapping": { "filename": "tests/external_function.sol", "length": 41, - "start": 257 + "start": 304 }, "vuln": "ExternalFunc" }, { - "contract": "ContractWithFunctionNotCalled2", + "contract": "ContractWithFunctionNotCalled", "filename": "tests/external_function.sol", - "func": "funcNotCalled", + "func": "funcNotCalled3", "sourceMapping": { "filename": "tests/external_function.sol", - "length": 304, - "start": 552 + "length": 41, + "start": 257 }, "vuln": "ExternalFunc" } diff --git a/tests/expected_json/naming_convention.naming-convention.json b/tests/expected_json/naming_convention.naming-convention.json index e65150f60..bf65c1fea 100644 --- a/tests/expected_json/naming_convention.naming-convention.json +++ b/tests/expected_json/naming_convention.naming-convention.json @@ -1,4 +1,15 @@ [ + { + "constant": "l", + "contract": "T", + "filename": "tests/naming_convention.sol", + "sourceMapping": { + "filename": "tests/naming_convention.sol", + "length": 10, + "start": 847 + }, + "vuln": "NamingConvention" + }, { "contract": "T", "filename": "tests/naming_convention.sol", @@ -20,13 +31,6 @@ }, "vuln": "NamingConvention" }, - { - "contract": "naming", - "event": "event_", - "filename": "tests/naming_convention.sol", - "sourceMapping": null, - "vuln": "NamingConvention" - }, { "contract": "naming", "filename": "tests/naming_convention.sol", @@ -39,69 +43,58 @@ "vuln": "NamingConvention" }, { + "constant": "MY_other_CONSTANT", "contract": "naming", "filename": "tests/naming_convention.sol", "sourceMapping": { "filename": "tests/naming_convention.sol", - "length": 16, - "start": 183 + "length": 35, + "start": 141 }, - "variable": "Var_One", "vuln": "NamingConvention" }, { "contract": "naming", - "enum": "numbers", "filename": "tests/naming_convention.sol", "sourceMapping": { "filename": "tests/naming_convention.sol", - "length": 23, - "start": 77 + "length": 16, + "start": 183 }, + "variable": "Var_One", "vuln": "NamingConvention" }, { "contract": "naming", "filename": "tests/naming_convention.sol", + "function": "GetOne", "sourceMapping": { "filename": "tests/naming_convention.sol", - "length": 20, - "start": 227 + "length": 71, + "start": 405 }, - "struct": "test", "vuln": "NamingConvention" }, { - "constant": "MY_other_CONSTANT", "contract": "naming", + "enum": "numbers", "filename": "tests/naming_convention.sol", "sourceMapping": { "filename": "tests/naming_convention.sol", - "length": 35, - "start": 141 - }, - "vuln": "NamingConvention" - }, - { - "constant": "l", - "contract": "T", - "filename": "tests/naming_convention.sol", - "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 10, - "start": 847 + "length": 23, + "start": 77 }, "vuln": "NamingConvention" }, { "contract": "naming", "filename": "tests/naming_convention.sol", - "function": "GetOne", "sourceMapping": { "filename": "tests/naming_convention.sol", - "length": 71, - "start": 405 + "length": 20, + "start": 227 }, + "struct": "test", "vuln": "NamingConvention" }, { @@ -127,5 +120,12 @@ "start": 748 }, "vuln": "NamingConvention" + }, + { + "contract": "naming", + "event": "event_", + "filename": "tests/naming_convention.sol", + "sourceMapping": null, + "vuln": "NamingConvention" } ] \ No newline at end of file diff --git a/tests/expected_json/pragma.0.4.24.pragma.json b/tests/expected_json/pragma.0.4.24.pragma.json index 66f9c9761..2fbd07f39 100644 --- a/tests/expected_json/pragma.0.4.24.pragma.json +++ b/tests/expected_json/pragma.0.4.24.pragma.json @@ -2,13 +2,13 @@ { "sourceMapping": [ { - "filename": "tests/pragma.0.4.24.sol", - "length": 23, + "filename": "tests/pragma.0.4.23.sol", + "length": 24, "start": 0 }, { - "filename": "tests/pragma.0.4.23.sol", - "length": 24, + "filename": "tests/pragma.0.4.24.sol", + "length": 23, "start": 0 } ], diff --git a/tests/expected_json/uninitialized.uninitialized-state.json b/tests/expected_json/uninitialized.uninitialized-state.json index e243ad749..d15bc3eb6 100644 --- a/tests/expected_json/uninitialized.uninitialized-state.json +++ b/tests/expected_json/uninitialized.uninitialized-state.json @@ -21,66 +21,66 @@ "vuln": "UninitializedStateVars" }, { - "contract": "Uninitialized", + "contract": "Test2", "filename": "tests/uninitialized.sol", "functions": [ - "transfer" + "use" ], "sourceMapping": [ { "filename": "tests/uninitialized.sol", - "length": 19, - "start": 55 + "length": 15, + "start": 695 }, { "filename": "tests/uninitialized.sol", - "length": 82, - "start": 81 + "length": 117, + "start": 875 } ], - "variable": "destination", + "variable": "st", "vuln": "UninitializedStateVars" }, { "contract": "Test2", "filename": "tests/uninitialized.sol", "functions": [ - "use" + "init" ], "sourceMapping": [ { "filename": "tests/uninitialized.sol", - "length": 15, - "start": 695 + "length": 6, + "start": 748 }, { "filename": "tests/uninitialized.sol", - "length": 117, - "start": 875 + "length": 52, + "start": 817 } ], - "variable": "st", + "variable": "v", "vuln": "UninitializedStateVars" }, { - "contract": "Test2", + "contract": "Uninitialized", "filename": "tests/uninitialized.sol", "functions": [ - "init" + "transfer" ], "sourceMapping": [ { "filename": "tests/uninitialized.sol", - "length": 6, - "start": 748 + "length": 19, + "start": 55 }, { "filename": "tests/uninitialized.sol", - "length": 52, - "start": 817 + "length": 82, + "start": 81 } ], - "variable": "v", + "variable": "destination", "vuln": "UninitializedStateVars" } ] \ No newline at end of file From de05b1fdad7659b2038119da2639744a77cc15fe Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Mon, 5 Nov 2018 19:37:13 +0100 Subject: [PATCH 301/308] sort shared_props list of strings, which is a set (and thus does not keep order between runs) --- scripts/pretty_print_and_sort_json.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/pretty_print_and_sort_json.py b/scripts/pretty_print_and_sort_json.py index e0aeef9bc..e1d9ba5a9 100644 --- a/scripts/pretty_print_and_sort_json.py +++ b/scripts/pretty_print_and_sort_json.py @@ -66,7 +66,7 @@ def get_props_info(list_of_dicts): prop_types[p] = 'string' elif isinstance(d[p], int): prop_types[p] = 'number' - return (list(shared_props), prop_types) + return (sorted(list(shared_props)), prop_types) def order_by_prop_value(list_of_dicts): props_info = get_props_info(list_of_dicts) From ab5fb8371e67a25f17458a52580d048aaa901f68 Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Mon, 5 Nov 2018 19:37:20 +0100 Subject: [PATCH 302/308] regenareted tests expected json output --- ...onst_state_variables.constable-states.json | 20 +++---- .../external_function.external-function.json | 20 +++---- .../naming_convention.naming-convention.json | 60 +++++++++---------- ...storage_pointer.uninitialized-storage.json | 8 +-- 4 files changed, 54 insertions(+), 54 deletions(-) diff --git a/tests/expected_json/const_state_variables.constable-states.json b/tests/expected_json/const_state_variables.constable-states.json index 947425c07..e35d9514b 100644 --- a/tests/expected_json/const_state_variables.constable-states.json +++ b/tests/expected_json/const_state_variables.constable-states.json @@ -3,11 +3,6 @@ "contract": "B", "filename": "tests/const_state_variables.sol", "sourceMapping": [ - { - "filename": "tests/const_state_variables.sol", - "length": 76, - "start": 130 - }, { "filename": "tests/const_state_variables.sol", "length": 20, @@ -18,6 +13,11 @@ "length": 20, "start": 331 }, + { + "filename": "tests/const_state_variables.sol", + "length": 76, + "start": 130 + }, { "filename": "tests/const_state_variables.sol", "length": 76, @@ -35,11 +35,6 @@ "contract": "B", "filename": "tests/const_state_variables.sol", "sourceMapping": [ - { - "filename": "tests/const_state_variables.sol", - "length": 76, - "start": 130 - }, { "filename": "tests/const_state_variables.sol", "length": 20, @@ -50,6 +45,11 @@ "length": 20, "start": 331 }, + { + "filename": "tests/const_state_variables.sol", + "length": 76, + "start": 130 + }, { "filename": "tests/const_state_variables.sol", "length": 76, diff --git a/tests/expected_json/external_function.external-function.json b/tests/expected_json/external_function.external-function.json index 50332214e..fdf8f8b67 100644 --- a/tests/expected_json/external_function.external-function.json +++ b/tests/expected_json/external_function.external-function.json @@ -11,35 +11,35 @@ "vuln": "ExternalFunc" }, { - "contract": "ContractWithFunctionNotCalled2", + "contract": "ContractWithFunctionNotCalled", "filename": "tests/external_function.sol", - "func": "funcNotCalled", + "func": "funcNotCalled2", "sourceMapping": { "filename": "tests/external_function.sol", - "length": 304, - "start": 552 + "length": 41, + "start": 304 }, "vuln": "ExternalFunc" }, { "contract": "ContractWithFunctionNotCalled", "filename": "tests/external_function.sol", - "func": "funcNotCalled2", + "func": "funcNotCalled3", "sourceMapping": { "filename": "tests/external_function.sol", "length": 41, - "start": 304 + "start": 257 }, "vuln": "ExternalFunc" }, { - "contract": "ContractWithFunctionNotCalled", + "contract": "ContractWithFunctionNotCalled2", "filename": "tests/external_function.sol", - "func": "funcNotCalled3", + "func": "funcNotCalled", "sourceMapping": { "filename": "tests/external_function.sol", - "length": 41, - "start": 257 + "length": 304, + "start": 552 }, "vuln": "ExternalFunc" } diff --git a/tests/expected_json/naming_convention.naming-convention.json b/tests/expected_json/naming_convention.naming-convention.json index bf65c1fea..9635efcbc 100644 --- a/tests/expected_json/naming_convention.naming-convention.json +++ b/tests/expected_json/naming_convention.naming-convention.json @@ -1,15 +1,4 @@ [ - { - "constant": "l", - "contract": "T", - "filename": "tests/naming_convention.sol", - "sourceMapping": { - "filename": "tests/naming_convention.sol", - "length": 10, - "start": 847 - }, - "vuln": "NamingConvention" - }, { "contract": "T", "filename": "tests/naming_convention.sol", @@ -34,34 +23,34 @@ { "contract": "naming", "filename": "tests/naming_convention.sol", - "modifier": "CantDo", "sourceMapping": { "filename": "tests/naming_convention.sol", - "length": 36, - "start": 545 + "length": 16, + "start": 183 }, + "variable": "Var_One", "vuln": "NamingConvention" }, { - "constant": "MY_other_CONSTANT", "contract": "naming", "filename": "tests/naming_convention.sol", "sourceMapping": { "filename": "tests/naming_convention.sol", - "length": 35, - "start": 141 + "length": 20, + "start": 227 }, + "struct": "test", "vuln": "NamingConvention" }, { "contract": "naming", "filename": "tests/naming_convention.sol", + "modifier": "CantDo", "sourceMapping": { "filename": "tests/naming_convention.sol", - "length": 16, - "start": 183 + "length": 36, + "start": 545 }, - "variable": "Var_One", "vuln": "NamingConvention" }, { @@ -75,6 +64,13 @@ }, "vuln": "NamingConvention" }, + { + "contract": "naming", + "event": "event_", + "filename": "tests/naming_convention.sol", + "sourceMapping": null, + "vuln": "NamingConvention" + }, { "contract": "naming", "enum": "numbers", @@ -87,14 +83,25 @@ "vuln": "NamingConvention" }, { + "constant": "MY_other_CONSTANT", "contract": "naming", "filename": "tests/naming_convention.sol", "sourceMapping": { "filename": "tests/naming_convention.sol", - "length": 20, - "start": 227 + "length": 35, + "start": 141 + }, + "vuln": "NamingConvention" + }, + { + "constant": "l", + "contract": "T", + "filename": "tests/naming_convention.sol", + "sourceMapping": { + "filename": "tests/naming_convention.sol", + "length": 10, + "start": 847 }, - "struct": "test", "vuln": "NamingConvention" }, { @@ -120,12 +127,5 @@ "start": 748 }, "vuln": "NamingConvention" - }, - { - "contract": "naming", - "event": "event_", - "filename": "tests/naming_convention.sol", - "sourceMapping": null, - "vuln": "NamingConvention" } ] \ No newline at end of file diff --git a/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json b/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json index ce783152c..7e9329e29 100644 --- a/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json +++ b/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json @@ -6,13 +6,13 @@ "sourceMapping": [ { "filename": "tests/uninitialized_storage_pointer.sol", - "length": 138, - "start": 67 + "length": 9, + "start": 171 }, { "filename": "tests/uninitialized_storage_pointer.sol", - "length": 9, - "start": 171 + "length": 138, + "start": 67 } ], "variable": "st_bug", From 1994e35fadebf12f19461bf24b3d841d427cca8c Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Mon, 5 Nov 2018 20:12:52 +0100 Subject: [PATCH 303/308] improve comment --- scripts/pretty_print_and_sort_json.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/pretty_print_and_sort_json.py b/scripts/pretty_print_and_sort_json.py index e1d9ba5a9..f21581d7d 100644 --- a/scripts/pretty_print_and_sort_json.py +++ b/scripts/pretty_print_and_sort_json.py @@ -6,6 +6,7 @@ the order is deterministic - the keys of a json object are sorted - json objects in a list will be sorted based on the values of their keys +- lists of strings/numbers are sorted ''' From 693d4d12095ac5a8db0e40594afa840ba6501366 Mon Sep 17 00:00:00 2001 From: Alexander Remie Date: Mon, 5 Nov 2018 20:17:08 +0100 Subject: [PATCH 304/308] improve python script --- scripts/pretty_print_and_sort_json.py | 32 +++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/scripts/pretty_print_and_sort_json.py b/scripts/pretty_print_and_sort_json.py index f21581d7d..9331caabf 100644 --- a/scripts/pretty_print_and_sort_json.py +++ b/scripts/pretty_print_and_sort_json.py @@ -42,7 +42,7 @@ def get_props_info(list_of_dicts): found_props.add(p) # create a copy, since we are gonna possibly remove props - shared_props = set(found_props) + props_whose_value_we_can_sort_on = set(found_props) # for each object, loop through list of all found property names, # if the object contains that property, check that it's of type string or number @@ -50,13 +50,13 @@ def get_props_info(list_of_dicts): # if it's not of type string/number remove it from list of properties to check # since we cannot sort on non-string/number values for p in list(found_props): - if p in shared_props: # short circuit + if p in props_whose_value_we_can_sort_on: # short circuit for d in list_of_dicts: - if p in shared_props: # less shorter short circuit + if p in props_whose_value_we_can_sort_on: # less shorter short circuit if p in d: # we ae only gonna sort key values if they are of type string or number if not isinstance(d[p], str) and not isinstance(d[p], int): - shared_props.remove(p) + props_whose_value_we_can_sort_on.remove(p) # we need to store the type of the value because not each object # in a list of output objects for 1 detector will have the same # keys, so if we want to sort based on the values then if a certain object @@ -67,12 +67,23 @@ def get_props_info(list_of_dicts): prop_types[p] = 'string' elif isinstance(d[p], int): prop_types[p] = 'number' - return (sorted(list(shared_props)), prop_types) + return (sorted(list(props_whose_value_we_can_sort_on)), prop_types) def order_by_prop_value(list_of_dicts): props_info = get_props_info(list_of_dicts) return sorted(list_of_dicts, key=lambda d: create_property_val_tuple(d, props_info)) +def order_dict(d): + result = OrderedDict() # such that we keep the order + for k, v in sorted(d.items()): + if isinstance(v, dict): + result[k] = order_dict(v) + elif type(v) is list: + result[k] = order_list(v) + else: # string/number + result[k] = v + return result + def order_list(l): # TODO: sometimes slither detectors return a null value in the json output sourceMapping object array # get rid of those values, it will break sorting (some items are an object, some are null?!) @@ -88,17 +99,6 @@ def order_list(l): ordered_by_val = order_by_prop_value(ordered_by_key) return ordered_by_val -def order_dict(dictionary): - result = OrderedDict() # such that we keep the order - for k, v in sorted(dictionary.items()): - if isinstance(v, dict): - result[k] = order_dict(v) - elif type(v) is list: - result[k] = order_list(v) - else: # string/number - result[k] = v - return result - with open(raw_json_file, 'r') as json_data: with open(pretty_json_file, 'w') as out_file: out_file.write(json.dumps(order_list(json.load(json_data)), sort_keys=False, indent=4, separators=(',',': '))) \ No newline at end of file From 5c238c9c939a9a89186714463b119c757643d8bf Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 7 Nov 2018 11:30:17 +0100 Subject: [PATCH 305/308] Change --printers to --print and --detectors to --detect (close #71) --- README.md | 2 +- scripts/travis_test.sh | 4 ++-- slither/__main__.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1c654acd5..b3eaf0d4b 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Num | Detector | What it Detects | Impact | Confidence ### Printers -To run a printer, use `--printers` and a comma-separated list of printers. +To run a printer, use `--print` and a comma-separated list of printers. Num | Printer | Description --- | --- | --- diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index a6b4dcbc9..ced7850c0 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -4,12 +4,12 @@ # test_slither file.sol detectors number_results test_slither(){ - slither "$1" --disable-solc-warnings --detectors "$2" + slither "$1" --disable-solc-warnings --detect "$2" if [ $? -ne "$3" ]; then exit 1 fi - slither "$1" --disable-solc-warnings --detectors "$2" --compact-ast + slither "$1" --disable-solc-warnings --detect "$2" --compact-ast if [ $? -ne "$3" ]; then exit 1 fi diff --git a/slither/__main__.py b/slither/__main__.py index 017bebaa6..77d6dbcba 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -257,7 +257,7 @@ def parse_args(detector_classes, printer_classes): group_solc = parser.add_argument_group('Solc options') group_misc = parser.add_argument_group('Additional option') - group_detector.add_argument('--detectors', + group_detector.add_argument('--detect', help='Comma-separated list of detectors, defaults to all, ' 'available detectors: {}'.format( ', '.join(d.ARGUMENT for d in detector_classes)), @@ -265,7 +265,7 @@ def parse_args(detector_classes, printer_classes): dest='detectors_to_run', default='all') - group_printer.add_argument('--printers', + group_printer.add_argument('--print', help='Comma-separated list fo contract information printers, ' 'available printers: {}'.format( ', '.join(d.ARGUMENT for d in printer_classes)), From 12a52f0ffa8aa48a53ba8586d894bd386479d710 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 7 Nov 2018 12:17:21 +0100 Subject: [PATCH 306/308] Fix incorrect pragma json output pretty_print_and_sort: Remove filter on None Update expected json (add line numbers) --- scripts/pretty_print_and_sort_json.py | 5 +- slither/detectors/attributes/old_solc.py | 2 +- .../arbitrary_send.arbitrary-send.json | 26 ++++- tests/expected_json/backdoor.backdoor.json | 5 + tests/expected_json/backdoor.suicidal.json | 5 + ...onst_state_variables.constable-states.json | 24 +++++ .../external_function.external-function.json | 25 +++++ .../inline_assembly_contract.assembly.json | 24 ++++- .../inline_assembly_library.assembly.json | 46 ++++++++- .../locked_ether.locked-ether.json | 5 + .../low_level_calls.low-level-calls.json | 11 ++- .../naming_convention.naming-convention.json | 94 ++++++++++++++++++- .../old_solc.sol.json.solc-version.json | 1 + tests/expected_json/pragma.0.4.24.pragma.json | 6 ++ .../expected_json/reentrancy.reentrancy.json | 25 ++++- tests/expected_json/tx_origin.tx-origin.json | 24 ++++- .../uninitialized.uninitialized-state.json | 34 +++++++ ...storage_pointer.uninitialized-storage.json | 11 +++ .../unused_state.unused-state.json | 3 + 19 files changed, 359 insertions(+), 17 deletions(-) diff --git a/scripts/pretty_print_and_sort_json.py b/scripts/pretty_print_and_sort_json.py index 9331caabf..08c0002b2 100644 --- a/scripts/pretty_print_and_sort_json.py +++ b/scripts/pretty_print_and_sort_json.py @@ -85,9 +85,6 @@ def order_dict(d): return result def order_list(l): - # TODO: sometimes slither detectors return a null value in the json output sourceMapping object array - # get rid of those values, it will break sorting (some items are an object, some are null?!) - l = list(filter(None, l)) if not l: return [] if isinstance(l[0], str): # it's a list of string @@ -101,4 +98,4 @@ def order_list(l): with open(raw_json_file, 'r') as json_data: with open(pretty_json_file, 'w') as out_file: - out_file.write(json.dumps(order_list(json.load(json_data)), sort_keys=False, indent=4, separators=(',',': '))) \ No newline at end of file + out_file.write(json.dumps(order_list(json.load(json_data)), sort_keys=False, indent=4, separators=(',',': '))) diff --git a/slither/detectors/attributes/old_solc.py b/slither/detectors/attributes/old_solc.py index dc6bd51c8..4bd21c3df 100644 --- a/slither/detectors/attributes/old_solc.py +++ b/slither/detectors/attributes/old_solc.py @@ -35,7 +35,7 @@ class OldSolc(AbstractDetector): source = [p.source_mapping for p in pragma] results.append({'vuln': 'OldPragma', - 'pragma': old_pragma, + 'pragma': [p.version for p in old_pragma], 'sourceMapping': source}) return results diff --git a/tests/expected_json/arbitrary_send.arbitrary-send.json b/tests/expected_json/arbitrary_send.arbitrary-send.json index 8a61f737f..f8aa83cc1 100644 --- a/tests/expected_json/arbitrary_send.arbitrary-send.json +++ b/tests/expected_json/arbitrary_send.arbitrary-send.json @@ -6,8 +6,17 @@ "contract": "Test", "filename": "tests/arbitrary_send.sol", "func": "direct", - "sourceMapping": [], - "vuln": "SuicidalFunc" + "sourceMapping": [ + { + "filename": "tests/arbitrary_send.sol", + "length": 29, + "lines": [ + 12 + ], + "start": 174 + } + ], + "vuln": "ArbitrarySend" }, { "calls": [ @@ -16,7 +25,16 @@ "contract": "Test", "filename": "tests/arbitrary_send.sol", "func": "indirect", - "sourceMapping": [], - "vuln": "SuicidalFunc" + "sourceMapping": [ + { + "filename": "tests/arbitrary_send.sol", + "length": 30, + "lines": [ + 20 + ], + "start": 307 + } + ], + "vuln": "ArbitrarySend" } ] \ No newline at end of file diff --git a/tests/expected_json/backdoor.backdoor.json b/tests/expected_json/backdoor.backdoor.json index 9b51501b2..9ebfb2d2b 100644 --- a/tests/expected_json/backdoor.backdoor.json +++ b/tests/expected_json/backdoor.backdoor.json @@ -4,6 +4,11 @@ "sourceMapping": { "filename": "tests/backdoor.sol", "length": 74, + "lines": [ + 5, + 6, + 7 + ], "start": 42 }, "vuln": "backdoor" diff --git a/tests/expected_json/backdoor.suicidal.json b/tests/expected_json/backdoor.suicidal.json index 28ec9d278..663b33ef4 100644 --- a/tests/expected_json/backdoor.suicidal.json +++ b/tests/expected_json/backdoor.suicidal.json @@ -6,6 +6,11 @@ "sourceMapping": { "filename": "tests/backdoor.sol", "length": 74, + "lines": [ + 5, + 6, + 7 + ], "start": 42 }, "vuln": "SuicidalFunc" diff --git a/tests/expected_json/const_state_variables.constable-states.json b/tests/expected_json/const_state_variables.constable-states.json index e35d9514b..eb3a44afa 100644 --- a/tests/expected_json/const_state_variables.constable-states.json +++ b/tests/expected_json/const_state_variables.constable-states.json @@ -6,21 +6,33 @@ { "filename": "tests/const_state_variables.sol", "length": 20, + "lines": [ + 10 + ], "start": 235 }, { "filename": "tests/const_state_variables.sol", "length": 20, + "lines": [ + 14 + ], "start": 331 }, { "filename": "tests/const_state_variables.sol", "length": 76, + "lines": [ + 7 + ], "start": 130 }, { "filename": "tests/const_state_variables.sol", "length": 76, + "lines": [ + 26 + ], "start": 494 } ], @@ -38,21 +50,33 @@ { "filename": "tests/const_state_variables.sol", "length": 20, + "lines": [ + 10 + ], "start": 235 }, { "filename": "tests/const_state_variables.sol", "length": 20, + "lines": [ + 14 + ], "start": 331 }, { "filename": "tests/const_state_variables.sol", "length": 76, + "lines": [ + 7 + ], "start": 130 }, { "filename": "tests/const_state_variables.sol", "length": 76, + "lines": [ + 26 + ], "start": 494 } ], diff --git a/tests/expected_json/external_function.external-function.json b/tests/expected_json/external_function.external-function.json index fdf8f8b67..e17984067 100644 --- a/tests/expected_json/external_function.external-function.json +++ b/tests/expected_json/external_function.external-function.json @@ -6,6 +6,11 @@ "sourceMapping": { "filename": "tests/external_function.sol", "length": 40, + "lines": [ + 21, + 22, + 23 + ], "start": 351 }, "vuln": "ExternalFunc" @@ -17,6 +22,11 @@ "sourceMapping": { "filename": "tests/external_function.sol", "length": 41, + "lines": [ + 17, + 18, + 19 + ], "start": 304 }, "vuln": "ExternalFunc" @@ -28,6 +38,11 @@ "sourceMapping": { "filename": "tests/external_function.sol", "length": 41, + "lines": [ + 13, + 14, + 15 + ], "start": 257 }, "vuln": "ExternalFunc" @@ -39,6 +54,16 @@ "sourceMapping": { "filename": "tests/external_function.sol", "length": 304, + "lines": [ + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39 + ], "start": 552 }, "vuln": "ExternalFunc" diff --git a/tests/expected_json/inline_assembly_contract.assembly.json b/tests/expected_json/inline_assembly_contract.assembly.json index ff9d713d3..376d3a49d 100644 --- a/tests/expected_json/inline_assembly_contract.assembly.json +++ b/tests/expected_json/inline_assembly_contract.assembly.json @@ -3,7 +3,29 @@ "contract": "GetCode", "filename": "tests/inline_assembly_contract.sol", "function_name": "at", - "sourceMapping": [], + "sourceMapping": [ + { + "filename": "tests/inline_assembly_contract.sol", + "length": 628, + "lines": [ + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20 + ], + "start": 191 + } + ], "vuln": "Assembly" } ] \ No newline at end of file diff --git a/tests/expected_json/inline_assembly_library.assembly.json b/tests/expected_json/inline_assembly_library.assembly.json index fd6be8d62..5183fc8cd 100644 --- a/tests/expected_json/inline_assembly_library.assembly.json +++ b/tests/expected_json/inline_assembly_library.assembly.json @@ -3,14 +3,56 @@ "contract": "VectorSum", "filename": "tests/inline_assembly_library.sol", "function_name": "sumAsm", - "sourceMapping": [], + "sourceMapping": [ + { + "filename": "tests/inline_assembly_library.sol", + "length": 114, + "lines": [ + 18, + 19, + 20, + 21 + ], + "start": 720 + } + ], "vuln": "Assembly" }, { "contract": "VectorSum", "filename": "tests/inline_assembly_library.sol", "function_name": "sumPureAsm", - "sourceMapping": [], + "sourceMapping": [ + { + "filename": "tests/inline_assembly_library.sol", + "length": 677, + "lines": [ + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47 + ], + "start": 1000 + } + ], "vuln": "Assembly" } ] \ No newline at end of file diff --git a/tests/expected_json/locked_ether.locked-ether.json b/tests/expected_json/locked_ether.locked-ether.json index eef591ddc..10aa6213a 100644 --- a/tests/expected_json/locked_ether.locked-ether.json +++ b/tests/expected_json/locked_ether.locked-ether.json @@ -8,6 +8,11 @@ { "filename": "tests/locked_ether.sol", "length": 72, + "lines": [ + 4, + 5, + 6 + ], "start": 46 } ], diff --git a/tests/expected_json/low_level_calls.low-level-calls.json b/tests/expected_json/low_level_calls.low-level-calls.json index d7973de50..eb0e7529f 100644 --- a/tests/expected_json/low_level_calls.low-level-calls.json +++ b/tests/expected_json/low_level_calls.low-level-calls.json @@ -3,7 +3,16 @@ "contract": "Sender", "filename": "tests/low_level_calls.sol", "function_name": "send", - "sourceMapping": [], + "sourceMapping": [ + { + "filename": "tests/low_level_calls.sol", + "length": 43, + "lines": [ + 6 + ], + "start": 100 + } + ], "vuln": "Low level call" } ] \ No newline at end of file diff --git a/tests/expected_json/naming_convention.naming-convention.json b/tests/expected_json/naming_convention.naming-convention.json index 9635efcbc..51086bc23 100644 --- a/tests/expected_json/naming_convention.naming-convention.json +++ b/tests/expected_json/naming_convention.naming-convention.json @@ -5,6 +5,9 @@ "sourceMapping": { "filename": "tests/naming_convention.sol", "length": 17, + "lines": [ + 56 + ], "start": 695 }, "variable": "_myPublicVar", @@ -16,6 +19,54 @@ "sourceMapping": { "filename": "tests/naming_convention.sol", "length": 598, + "lines": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48 + ], "start": 26 }, "vuln": "NamingConvention" @@ -26,6 +77,9 @@ "sourceMapping": { "filename": "tests/naming_convention.sol", "length": 16, + "lines": [ + 11 + ], "start": 183 }, "variable": "Var_One", @@ -37,6 +91,11 @@ "sourceMapping": { "filename": "tests/naming_convention.sol", "length": 20, + "lines": [ + 14, + 15, + 16 + ], "start": 227 }, "struct": "test", @@ -49,6 +108,11 @@ "sourceMapping": { "filename": "tests/naming_convention.sol", "length": 36, + "lines": [ + 41, + 42, + 43 + ], "start": 545 }, "vuln": "NamingConvention" @@ -60,6 +124,12 @@ "sourceMapping": { "filename": "tests/naming_convention.sol", "length": 71, + "lines": [ + 30, + 31, + 32, + 33 + ], "start": 405 }, "vuln": "NamingConvention" @@ -68,7 +138,14 @@ "contract": "naming", "event": "event_", "filename": "tests/naming_convention.sol", - "sourceMapping": null, + "sourceMapping": { + "filename": "tests/naming_convention.sol", + "length": 19, + "lines": [ + 23 + ], + "start": 303 + }, "vuln": "NamingConvention" }, { @@ -78,6 +155,9 @@ "sourceMapping": { "filename": "tests/naming_convention.sol", "length": 23, + "lines": [ + 6 + ], "start": 77 }, "vuln": "NamingConvention" @@ -89,6 +169,9 @@ "sourceMapping": { "filename": "tests/naming_convention.sol", "length": 35, + "lines": [ + 9 + ], "start": 141 }, "vuln": "NamingConvention" @@ -100,6 +183,9 @@ "sourceMapping": { "filename": "tests/naming_convention.sol", "length": 10, + "lines": [ + 67 + ], "start": 847 }, "vuln": "NamingConvention" @@ -112,6 +198,9 @@ "sourceMapping": { "filename": "tests/naming_convention.sol", "length": 12, + "lines": [ + 35 + ], "start": 512 }, "vuln": "NamingConvention" @@ -124,6 +213,9 @@ "sourceMapping": { "filename": "tests/naming_convention.sol", "length": 10, + "lines": [ + 59 + ], "start": 748 }, "vuln": "NamingConvention" diff --git a/tests/expected_json/old_solc.sol.json.solc-version.json b/tests/expected_json/old_solc.sol.json.solc-version.json index 842ca0f8a..6b5dd3c6f 100644 --- a/tests/expected_json/old_solc.sol.json.solc-version.json +++ b/tests/expected_json/old_solc.sol.json.solc-version.json @@ -7,6 +7,7 @@ { "filename": "old_solc.sol", "length": 23, + "lines": [], "start": 0 } ], diff --git a/tests/expected_json/pragma.0.4.24.pragma.json b/tests/expected_json/pragma.0.4.24.pragma.json index 2fbd07f39..0601053f2 100644 --- a/tests/expected_json/pragma.0.4.24.pragma.json +++ b/tests/expected_json/pragma.0.4.24.pragma.json @@ -4,11 +4,17 @@ { "filename": "tests/pragma.0.4.23.sol", "length": 24, + "lines": [ + 1 + ], "start": 0 }, { "filename": "tests/pragma.0.4.24.sol", "length": 23, + "lines": [ + 1 + ], "start": 0 } ], diff --git a/tests/expected_json/reentrancy.reentrancy.json b/tests/expected_json/reentrancy.reentrancy.json index 1a7f119f6..26532ccf4 100644 --- a/tests/expected_json/reentrancy.reentrancy.json +++ b/tests/expected_json/reentrancy.reentrancy.json @@ -5,7 +5,7 @@ ], "contract": "Reentrancy", "filename": "tests/reentrancy.sol", - "function_name": "withdrawBalance()", + "function_name": "withdrawBalance", "send_eth": [ "! (msg.sender.call.value(userBalance[msg.sender])())" ], @@ -13,7 +13,30 @@ { "filename": "tests/reentrancy.sol", "length": 37, + "lines": [ + 4 + ], "start": 52 + }, + { + "filename": "tests/reentrancy.sol", + "length": 92, + "lines": [ + 17, + 18, + 19 + ], + "start": 478 + }, + { + "filename": "tests/reentrancy.sol", + "length": 92, + "lines": [ + 17, + 18, + 19 + ], + "start": 478 } ], "varsWritten": [ diff --git a/tests/expected_json/tx_origin.tx-origin.json b/tests/expected_json/tx_origin.tx-origin.json index 01b0f425b..f88642adf 100644 --- a/tests/expected_json/tx_origin.tx-origin.json +++ b/tests/expected_json/tx_origin.tx-origin.json @@ -3,14 +3,34 @@ "contract": "TxOrigin", "filename": "tests/tx_origin.sol", "function_name": "bug0", - "sourceMapping": [], + "sourceMapping": [ + { + "filename": "tests/tx_origin.sol", + "length": 27, + "lines": [ + 10 + ], + "start": 140 + } + ], "vuln": "TxOrigin" }, { "contract": "TxOrigin", "filename": "tests/tx_origin.sol", "function_name": "bug2", - "sourceMapping": [], + "sourceMapping": [ + { + "filename": "tests/tx_origin.sol", + "length": 57, + "lines": [ + 14, + 15, + 16 + ], + "start": 206 + } + ], "vuln": "TxOrigin" } ] \ No newline at end of file diff --git a/tests/expected_json/uninitialized.uninitialized-state.json b/tests/expected_json/uninitialized.uninitialized-state.json index d15bc3eb6..e57b7fe44 100644 --- a/tests/expected_json/uninitialized.uninitialized-state.json +++ b/tests/expected_json/uninitialized.uninitialized-state.json @@ -9,11 +9,20 @@ { "filename": "tests/uninitialized.sol", "length": 34, + "lines": [ + 15 + ], "start": 189 }, { "filename": "tests/uninitialized.sol", "length": 143, + "lines": [ + 23, + 24, + 25, + 26 + ], "start": 356 } ], @@ -30,11 +39,20 @@ { "filename": "tests/uninitialized.sol", "length": 15, + "lines": [ + 45 + ], "start": 695 }, { "filename": "tests/uninitialized.sol", "length": 117, + "lines": [ + 53, + 54, + 55, + 56 + ], "start": 875 } ], @@ -51,11 +69,19 @@ { "filename": "tests/uninitialized.sol", "length": 6, + "lines": [ + 47 + ], "start": 748 }, { "filename": "tests/uninitialized.sol", "length": 52, + "lines": [ + 49, + 50, + 51 + ], "start": 817 } ], @@ -72,11 +98,19 @@ { "filename": "tests/uninitialized.sol", "length": 19, + "lines": [ + 5 + ], "start": 55 }, { "filename": "tests/uninitialized.sol", "length": 82, + "lines": [ + 7, + 8, + 9 + ], "start": 81 } ], diff --git a/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json b/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json index 7e9329e29..eefb6befb 100644 --- a/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json +++ b/tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json @@ -7,11 +7,22 @@ { "filename": "tests/uninitialized_storage_pointer.sol", "length": 9, + "lines": [ + 10 + ], "start": 171 }, { "filename": "tests/uninitialized_storage_pointer.sol", "length": 138, + "lines": [ + 7, + 8, + 9, + 10, + 11, + 12 + ], "start": 67 } ], diff --git a/tests/expected_json/unused_state.unused-state.json b/tests/expected_json/unused_state.unused-state.json index f65be6eba..b3941f53c 100644 --- a/tests/expected_json/unused_state.unused-state.json +++ b/tests/expected_json/unused_state.unused-state.json @@ -6,6 +6,9 @@ { "filename": "tests/unused_state.sol", "length": 14, + "lines": [ + 4 + ], "start": 41 } ], From 375e56e28219e42ca3900d3f72df4dd294581edb Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 7 Nov 2018 14:35:26 +0100 Subject: [PATCH 307/308] Add Uninitialized local variable detector --- README.md | 29 ++--- scripts/tests_generate_expected_json.sh | 1 + slither/__main__.py | 6 +- .../uninitialized_local_variables.py | 100 ++++++++++++++++++ ...ed_local_variable.uninitialized-local.json | 31 ++++++ 5 files changed, 151 insertions(+), 16 deletions(-) create mode 100644 slither/detectors/variables/uninitialized_local_variables.py create mode 100644 tests/expected_json/uninitialized_local_variable.uninitialized-local.json diff --git a/README.md b/README.md index 0b8df7dba..74baa949e 100644 --- a/README.md +++ b/README.md @@ -48,20 +48,21 @@ By default, all the detectors are run. Num | Detector | What it Detects | Impact | Confidence --- | --- | --- | --- | --- 1 | `suicidal` | [Functions allowing anyone to destruct the contract](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#suicidal) | High | High -2 | `uninitialized-state` | [Uninitialized state variables](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#uninitialized-state-variables) | High | High -3 | `uninitialized-storage` | [Uninitialized storage variables](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#uninitialized-storage-variables) | High | High -4 | `arbitrary-send` | [Functions that send ether to arbitrary destinations](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#functions-that-send-ether-to-arbitrary-destinations) | High | Medium -5 | `reentrancy` | [Reentrancy vulnerabilities](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#reentrancy-vulnerabilities) | High | Medium -6 | `locked-ether` | [Contracts that lock ether](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#contracts-that-lock-ether) | Medium | High -7 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#dangerous-usage-of-txorigin) | Medium | Medium -8 | `assembly` | [Assembly usage](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#assembly-usage) | Informational | High -9 | `constable-states` | [State variables that could be declared constant](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#state-variables-that-could-be-declared-constant) | Informational | High -10 | `external-function` | [Public function that could be declared as external](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#public-function-that-could-be-declared-as-external) | Informational | High -11 | `low-level-calls` | [Low level calls](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#low-level-calls) | Informational | High -12 | `naming-convention` | [Conformance to Solidity naming conventions](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#conformance-to-solidity-naming-conventions) | Informational | High -13 | `pragma` | [If different pragma directives are used](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#state-variables-that-could-be-declared-constant) | Informational | High -14 | `solc-version` | [Old versions of Solidity (< 0.4.23)](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#old-versions-of-solidity) | Informational | High -15 | `unused-state` | [Unused state variables](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#unused-state-variables) | Informational | High +2 | `uninitialized-local` | [Uninitialized local variables](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#uninitialized-local-variables) | High | High +3 | `uninitialized-state` | [Uninitialized state variables](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#uninitialized-state-variables) | High | High +4 | `uninitialized-storage` | [Uninitialized storage variables](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#uninitialized-storage-variables) | High | High +5 | `arbitrary-send` | [Functions that send ether to arbitrary destinations](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#functions-that-send-ether-to-arbitrary-destinations) | High | Medium +6 | `reentrancy` | [Reentrancy vulnerabilities](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#reentrancy-vulnerabilities) | High | Medium +7 | `locked-ether` | [Contracts that lock ether](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#contracts-that-lock-ether) | Medium | High +8 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#dangerous-usage-of-txorigin) | Medium | Medium +9 | `assembly` | [Assembly usage](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#assembly-usage) | Informational | High +10 | `constable-states` | [State variables that could be declared constant](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#state-variables-that-could-be-declared-constant) | Informational | High +11 | `external-function` | [Public function that could be declared as external](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#public-function-that-could-be-declared-as-external) | Informational | High +12 | `low-level-calls` | [Low level calls](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#low-level-calls) | Informational | High +13 | `naming-convention` | [Conformance to Solidity naming conventions](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#conformance-to-solidity-naming-conventions) | Informational | High +14 | `pragma` | [If different pragma directives are used](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#state-variables-that-could-be-declared-constant) | Informational | High +15 | `solc-version` | [Old versions of Solidity (< 0.4.23)](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#old-versions-of-solidity) | Informational | High +16 | `unused-state` | [Unused state variables](https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#unused-state-variables) | Informational | High [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. diff --git a/scripts/tests_generate_expected_json.sh b/scripts/tests_generate_expected_json.sh index e3c2f39d4..492eb5ba4 100755 --- a/scripts/tests_generate_expected_json.sh +++ b/scripts/tests_generate_expected_json.sh @@ -36,3 +36,4 @@ generate_expected_json tests/low_level_calls.sol "low-level-calls" generate_expected_json tests/const_state_variables.sol "constable-states" generate_expected_json tests/external_function.sol "external-function" generate_expected_json tests/naming_convention.sol "naming-convention" +generate_expected_json tests/uninitialized_local_variable.sol "uninitialized-local" diff --git a/slither/__main__.py b/slither/__main__.py index 651a84021..8c4c51eca 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -94,6 +94,8 @@ def get_detectors_and_printers(): """ from slither.detectors.examples.backdoor import Backdoor from slither.detectors.variables.uninitialized_state_variables import UninitializedStateVarsDetection + from slither.detectors.variables.uninitialized_storage_variables import UninitializedStorageVars + from slither.detectors.variables.uninitialized_local_variables import UninitializedLocalVars from slither.detectors.attributes.constant_pragma import ConstantPragma from slither.detectors.attributes.old_solc import OldSolc from slither.detectors.attributes.locked_ether import LockedEther @@ -101,7 +103,6 @@ def get_detectors_and_printers(): from slither.detectors.functions.suicidal import Suicidal from slither.detectors.functions.complex_function import ComplexFunction from slither.detectors.reentrancy.reentrancy import Reentrancy - from slither.detectors.variables.uninitialized_storage_variables import UninitializedStorageVars from slither.detectors.variables.unused_state_variables import UnusedStateVars from slither.detectors.variables.possible_const_state_variables import ConstCandidateStateVars from slither.detectors.statements.tx_origin import TxOrigin @@ -112,10 +113,11 @@ def get_detectors_and_printers(): detectors = [Backdoor, UninitializedStateVarsDetection, + UninitializedStorageVars, + UninitializedLocalVars, ConstantPragma, OldSolc, Reentrancy, - UninitializedStorageVars, LockedEther, ArbitrarySend, Suicidal, diff --git a/slither/detectors/variables/uninitialized_local_variables.py b/slither/detectors/variables/uninitialized_local_variables.py new file mode 100644 index 000000000..111b0cf0e --- /dev/null +++ b/slither/detectors/variables/uninitialized_local_variables.py @@ -0,0 +1,100 @@ +""" + Module detecting state uninitialized local variables + + Recursively explore the CFG to only report uninitialized local variables that are + written before being read +""" + +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification + +from slither.visitors.expression.find_push import FindPush + + +class UninitializedLocalVars(AbstractDetector): + """ + """ + + ARGUMENT = 'uninitialized-local' + HELP = 'Uninitialized local variables' + IMPACT = DetectorClassification.HIGH + CONFIDENCE = DetectorClassification.HIGH + + WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#uninitialized-local-variables' + + key = "UNINITIALIZEDLOCAL" + + def _detect_uninitialized(self, function, node, visited): + if node in visited: + return + + visited = visited + [node] + + fathers_context = [] + + for father in node.fathers: + if self.key in father.context: + fathers_context += father.context[self.key] + + # Exclude path that dont bring further information + if node in self.visited_all_paths: + if all(f_c in self.visited_all_paths[node] for f_c in fathers_context): + return + else: + self.visited_all_paths[node] = [] + + self.visited_all_paths[node] = list(set(self.visited_all_paths[node] + fathers_context)) + + if self.key in node.context: + fathers_context += node.context[self.key] + + variables_read = node.variables_read + for uninitialized_local_variable in fathers_context: + if uninitialized_local_variable in variables_read: + self.results.append((function, uninitialized_local_variable)) + + # Only save the local variables that are not yet written + uninitialized_local_variables = list(set(fathers_context) - set(node.variables_written)) + node.context[self.key] = uninitialized_local_variables + + for son in node.sons: + self._detect_uninitialized(function, son, visited) + + + def detect(self): + """ Detect uninitialized state variables + + Recursively visit the calls + Returns: + dict: [contract name] = set(state variable uninitialized) + """ + results = [] + + self.results = [] + self.visited_all_paths = {} + + for contract in self.slither.contracts: + for function in contract.functions: + if function.is_implemented: + # dont consider storage variable, as they are detected by another detector + uninitialized_local_variables = [v for v in function.local_variables if not v.is_storage and v.uninitialized] + function.entry_point.context[self.key] = uninitialized_local_variables + self._detect_uninitialized(function, function.entry_point, []) + + for(function, uninitialized_local_variable) in self.results: + var_name = uninitialized_local_variable.name + + info = "{} in {}.{} ({}) is a local variable never initialiazed\n" + info = info.format(var_name, function.contract.name, function.name, uninitialized_local_variable.source_mapping_str) + + self.log(info) + + source = [function.source_mapping, uninitialized_local_variable.source_mapping] + + results.append({'vuln': 'UninitializedLocalVars', + 'sourceMapping': source, + 'filename': self.filename, + 'contract': function.contract.name, + 'function': function.name, + 'variable': var_name}) + + return results diff --git a/tests/expected_json/uninitialized_local_variable.uninitialized-local.json b/tests/expected_json/uninitialized_local_variable.uninitialized-local.json new file mode 100644 index 000000000..b6d922b15 --- /dev/null +++ b/tests/expected_json/uninitialized_local_variable.uninitialized-local.json @@ -0,0 +1,31 @@ +[ + { + "contract": "Uninitialized", + "filename": "tests/uninitialized_local_variable.sol", + "function": "func", + "sourceMapping": [ + { + "filename": "tests/uninitialized_local_variable.sol", + "length": 18, + "lines": [ + 4 + ], + "start": 77 + }, + { + "filename": "tests/uninitialized_local_variable.sol", + "length": 143, + "lines": [ + 3, + 4, + 5, + 6, + 7 + ], + "start": 29 + } + ], + "variable": "uint_not_init", + "vuln": "UninitializedLocalVars" + } +] \ No newline at end of file From e4599ed9408c6ace65f5be283da2bb604e614e78 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 7 Nov 2018 15:39:36 +0100 Subject: [PATCH 308/308] Fix incorrect read information for assignement --- slither/slithir/operations/assignment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/slithir/operations/assignment.py b/slither/slithir/operations/assignment.py index 0440a1697..3ea100f74 100644 --- a/slither/slithir/operations/assignment.py +++ b/slither/slithir/operations/assignment.py @@ -25,7 +25,7 @@ class Assignment(OperationWithLValue): @property def read(self): - return list(self.variables) + return [self.rvalue] @property def variable_return_type(self):