Merge branch 'dev' into dev-embark-support

pull/196/head
Feist Josselin 6 years ago committed by GitHub
commit e579fce61e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .travis.yml
  2. 66
      README.md
  3. 54
      examples/scripts/call_graph.py
  4. 13
      examples/scripts/data_dependency.sol
  5. BIN
      logo.png
  6. 16
      scripts/travis_test_find_paths.sh
  7. 12
      scripts/travis_test_upgradability.sh
  8. 5
      setup.py
  9. 15
      slither/__main__.py
  10. 14
      slither/analyses/data_dependency/data_dependency.py
  11. 40
      slither/core/declarations/function.py
  12. 1
      slither/core/expressions/__init__.py
  13. 4
      slither/core/source_mapping/source_mapping.py
  14. 6
      slither/detectors/abstract_detector.py
  15. 6
      slither/detectors/attributes/const_functions.py
  16. 2
      slither/detectors/attributes/constant_pragma.py
  17. 4
      slither/detectors/attributes/incorrect_solc.py
  18. 6
      slither/detectors/attributes/locked_ether.py
  19. 5
      slither/detectors/erc20/incorrect_interface.py
  20. 4
      slither/detectors/erc20/unindexed_event_parameters.py
  21. 4
      slither/detectors/functions/arbitrary_send.py
  22. 2
      slither/detectors/functions/external_function.py
  23. 6
      slither/detectors/functions/suicidal.py
  24. 2
      slither/detectors/naming_convention/naming_convention.py
  25. 11
      slither/detectors/operations/block_timestamp.py
  26. 2
      slither/detectors/operations/low_level_calls.py
  27. 8
      slither/detectors/operations/unused_return_values.py
  28. 2
      slither/detectors/reentrancy/reentrancy_benign.py
  29. 2
      slither/detectors/reentrancy/reentrancy_eth.py
  30. 2
      slither/detectors/reentrancy/reentrancy_read_before_write.py
  31. 2
      slither/detectors/shadowing/abstract.py
  32. 2
      slither/detectors/shadowing/builtin_symbols.py
  33. 2
      slither/detectors/shadowing/local.py
  34. 2
      slither/detectors/shadowing/state.py
  35. 2
      slither/detectors/statements/assembly.py
  36. 4
      slither/detectors/statements/calls_in_loop.py
  37. 6
      slither/detectors/statements/controlled_delegatecall.py
  38. 6
      slither/detectors/statements/deprecated_calls.py
  39. 4
      slither/detectors/statements/incorrect_strict_equality.py
  40. 8
      slither/detectors/statements/tx_origin.py
  41. 2
      slither/detectors/variables/possible_const_state_variables.py
  42. 10
      slither/detectors/variables/uninitialized_local_variables.py
  43. 2
      slither/detectors/variables/uninitialized_state_variables.py
  44. 10
      slither/detectors/variables/uninitialized_storage_variables.py
  45. 2
      slither/detectors/variables/unused_state_variables.py
  46. 116
      slither/printers/call/call_graph.py
  47. 9
      slither/slither.py
  48. 30
      slither/slithir/convert.py
  49. 12
      slither/solc_parsing/declarations/function.py
  50. 33
      slither/solc_parsing/expressions/expression_parsing.py
  51. 6
      slither/solc_parsing/slitherSolc.py
  52. 2
      slither/visitors/slithir/expression_to_slithir.py
  53. 49
      tests/check-upgradability/contract_initialization.sol
  54. 1
      tests/check-upgradability/test_1.txt
  55. 2
      tests/check-upgradability/test_2.txt
  56. 2
      tests/check-upgradability/test_3.txt
  57. 2
      tests/check-upgradability/test_4.txt
  58. 13
      tests/check-upgradability/test_5.txt
  59. 2
      tests/expected_json/arbitrary_send-0.5.1.arbitrary-send.json
  60. 2
      tests/expected_json/arbitrary_send.arbitrary-send.json
  61. 17
      tests/possible_paths/paths.sol
  62. 14
      tests/possible_paths/paths.txt
  63. 0
      utils/possible_paths/__init__.py
  64. 72
      utils/possible_paths/__main__.py
  65. 127
      utils/possible_paths/possible_paths.py
  66. 40
      utils/upgradability/__main__.py
  67. 78
      utils/upgradability/check_initialization.py
  68. 24
      utils/upgradability/compare_function_ids.py
  69. 2
      utils/upgradability/compare_variables_order.py

@ -18,5 +18,6 @@ script:
- scripts/travis_test_5.sh
- scripts/travis_test_upgradability.sh
- scripts/travis_test_data_dependency.sh
- scripts/travis_test_find_paths.sh

@ -1,5 +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)
<img src="./logo.png" alt="Logo" width="500"/>
[![Build Status](https://travis-ci.com/crytic/slither.svg?token=JEF97dFy1QsDCfQ2Wusd&branch=master)](https://travis-ci.com/crytic/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)
@ -37,37 +39,37 @@ By default, all the detectors are run.
Num | Detector | What it Detects | Impact | Confidence
--- | --- | --- | --- | ---
1 | `shadowing-state` | [State variables shadowing](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#state-variable-shadowing) | High | High
2 | `suicidal` | [Functions allowing anyone to destruct the contract](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#suicidal) | High | High
3 | `uninitialized-state` | [Uninitialized state variables](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#uninitialized-state-variables) | High | High
4 | `uninitialized-storage` | [Uninitialized storage variables](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#uninitialized-storage-variables) | High | High
5 | `arbitrary-send` | [Functions that send ether to arbitrary destinations](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#functions-that-send-ether-to-arbitrary-destinations) | High | Medium
6 | `controlled-delegatecall` | [Controlled delegatecall destination](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#controlled-delegatecall) | High | Medium
7 | `reentrancy-eth` | [Reentrancy vulnerabilities (theft of ethers)](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#reentrancy-vulnerabilities) | High | Medium
8 | `erc20-interface` | [Incorrect ERC20 interfaces](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#incorrect-erc20-interface) | Medium | High
9 | `incorrect-equality` | [Dangerous strict equalities](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#dangerous-strict-equalities) | Medium | High
10 | `locked-ether` | [Contracts that lock ether](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#contracts-that-lock-ether) | Medium | High
11 | `shadowing-abstract` | [State variables shadowing from abstract contracts](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#state-variable-shadowing-from-abstract-contracts) | Medium | High
12 | `constant-function` | [Constant functions changing the state](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#constant-functions-changing-the-state) | Medium | Medium
13 | `reentrancy-no-eth` | [Reentrancy vulnerabilities (no theft of ethers)](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#reentrancy-vulnerabilities-1) | Medium | Medium
14 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#dangerous-usage-of-txorigin) | Medium | Medium
15 | `uninitialized-local` | [Uninitialized local variables](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#uninitialized-local-variables) | Medium | Medium
16 | `unused-return` | [Unused return values](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#unused-return) | Medium | Medium
17 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#builtin-symbol-shadowing) | Low | High
18 | `shadowing-local` | [Local variables shadowing](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#local-variable-shadowing) | Low | High
19 | `calls-loop` | [Multiple calls in a loop](https://github.com/trailofbits/slither/wiki/Detectors-Documentation/_edit#calls-inside-a-loop) | Low | Medium
20 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#reentrancy-vulnerabilities-2) | Low | Medium
21 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#block-timestamp) | Low | Medium
22 | `assembly` | [Assembly usage](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#assembly-usage) | Informational | High
23 | `constable-states` | [State variables that could be declared constant](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#state-variables-that-could-be-declared-constant) | Informational | High
24 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#deprecated-standards) | Informational | High
25 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#unindexed-erc20-event-parameters) | Informational | High
26 | `external-function` | [Public function that could be declared as external](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#public-function-that-could-be-declared-as-external) | Informational | High
27 | `low-level-calls` | [Low level calls](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#low-level-calls) | Informational | High
28 | `naming-convention` | [Conformance to Solidity naming conventions](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#conformance-to-solidity-naming-conventions) | Informational | High
29 | `pragma` | [If different pragma directives are used](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#different-pragma-directives-are-used) | Informational | High
30 | `solc-version` | [Incorrect Solidity version (< 0.4.24 or complex pragma)](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#incorrect-version-of-solidity) | Informational | High
31 | `unused-state` | [Unused state variables](https://github.com/trailofbits/slither/wiki/Detectors-Documentation#unused-state-variables) | Informational | High
1 | `shadowing-state` | [State variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing) | High | High
2 | `suicidal` | [Functions allowing anyone to destruct the contract](https://github.com/crytic/slither/wiki/Detector-Documentation#suicidal) | High | High
3 | `uninitialized-state` | [Uninitialized state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-state-variables) | High | High
4 | `uninitialized-storage` | [Uninitialized storage variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-storage-variables) | High | High
5 | `arbitrary-send` | [Functions that send ether to arbitrary destinations](https://github.com/crytic/slither/wiki/Detector-Documentation#functions-that-send-ether-to-arbitrary-destinations) | High | Medium
6 | `controlled-delegatecall` | [Controlled delegatecall destination](https://github.com/crytic/slither/wiki/Detector-Documentation#controlled-delegatecall) | High | Medium
7 | `reentrancy-eth` | [Reentrancy vulnerabilities (theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities) | High | Medium
8 | `erc20-interface` | [Incorrect ERC20 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface) | Medium | High
9 | `incorrect-equality` | [Dangerous strict equalities](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities) | Medium | High
10 | `locked-ether` | [Contracts that lock ether](https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether) | Medium | High
11 | `shadowing-abstract` | [State variables shadowing from abstract contracts](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing-from-abstract-contracts) | Medium | High
12 | `constant-function` | [Constant functions changing the state](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state) | Medium | Medium
13 | `reentrancy-no-eth` | [Reentrancy vulnerabilities (no theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1) | Medium | Medium
14 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin) | Medium | Medium
15 | `uninitialized-local` | [Uninitialized local variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables) | Medium | Medium
16 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium
17 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | Low | High
18 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High
19 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/_edit#calls-inside-a-loop) | Low | Medium
20 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium
21 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium
22 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High
23 | `constable-states` | [State variables that could be declared constant](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant) | Informational | High
24 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High
25 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High
26 | `external-function` | [Public function that could be declared as external](https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-as-external) | Informational | High
27 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High
28 | `naming-convention` | [Conformance to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High
29 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High
30 | `solc-version` | [Incorrect Solidity version (< 0.4.24 or complex pragma)](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-version-of-solidity) | Informational | High
31 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variables) | Informational | High
[Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors.

@ -0,0 +1,54 @@
import os
import logging
import argparse
from slither import Slither
from slither.printers.all_printers import PrinterCallGraph
from slither.core.declarations.function import Function
logging.basicConfig()
logging.getLogger("Slither").setLevel(logging.INFO)
logging.getLogger("Printers").setLevel(logging.INFO)
class PrinterCallGraphStateChange(PrinterCallGraph):
def _process_function(self, contract, function, contract_functions, contract_calls, solidity_functions, solidity_calls, external_calls, all_contracts):
if function.view or function.pure:
return
super()._process_function(contract, function, contract_functions, contract_calls, solidity_functions, solidity_calls, external_calls, all_contracts)
def _process_internal_call(self, contract, function, internal_call, contract_calls, solidity_functions, solidity_calls):
if isinstance(internal_call, Function):
if internal_call.view or internal_call.pure:
return
super()._process_internal_call(contract, function, internal_call, contract_calls, solidity_functions, solidity_calls)
def _process_external_call(self, contract, function, external_call, contract_functions, external_calls, all_contracts):
if isinstance(external_call[1], Function):
if external_call[1].view or external_call[1].pure:
return
super()._process_external_call(contract, function, external_call, contract_functions, external_calls, all_contracts)
def parse_args():
"""
"""
parser = argparse.ArgumentParser(description='Call graph printer. Similar to --print call-graph, but without printing the view/pure functions',
usage='call_graph.py filename')
parser.add_argument('filename',
help='The filename of the contract or truffle directory to analyze.')
parser.add_argument('--solc', help='solc path', default='solc')
return parser.parse_args()
def main():
args = parse_args()
slither = Slither(args.filename, is_truffle=os.path.isdir(args.filename), solc=args.solc)
slither.register_printer(PrinterCallGraphStateChange)
slither.run_printers()
if __name__ == '__main__':
main()

@ -102,3 +102,16 @@ contract PropagateThroughArguments {
var_not_tainted = y;
}
}
contract PropagateThroughReturnValue {
uint var_dependant;
uint var_state;
function foo() public {
var_dependant = bar();
}
function bar() internal returns (uint) {
return (var_state);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@ -0,0 +1,16 @@
#!/usr/bin/env bash
### Test slither-check-upgradability
DIR_TESTS="tests/possible_paths"
slither-find-paths "$DIR_TESTS/paths.sol" A.destination --solc solc-0.5.0 > test_possible_paths.txt 2>&1
DIFF=$(diff test_possible_paths.txt "$DIR_TESTS/paths.txt")
if [ "$DIFF" != "" ]
then
echo "slither-find-paths failed"
cat test_possible_paths.txt
cat "$DIR_TESTS/paths.txt"
exit -1
fi
rm test_possible_paths.txt

@ -44,7 +44,19 @@ then
exit -1
fi
slither-check-upgradability "$DIR_TESTS/proxy.sol" Proxy "$DIR_TESTS/contract_initialization.sol" Contract_no_bug --solc solc-0.5.0 > test_5.txt 2>&1
DIFF=$(diff test_5.txt "$DIR_TESTS/test_5.txt")
if [ "$DIFF" != "" ]
then
echo "slither-check-upgradability failed"
cat test_5.txt
cat "$DIR_TESTS/test_5.txt"
exit -1
fi
rm test_1.txt
rm test_2.txt
rm test_3.txt
rm test_4.txt
rm test_5.txt

@ -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.6.0',
version='0.6.1',
packages=find_packages(),
python_requires='>=3.6',
install_requires=['prettytable>=0.7.2', 'pysha3>=1.0.2'],
@ -14,7 +14,8 @@ setup(
entry_points={
'console_scripts': [
'slither = slither.__main__:main',
'slither-check-upgradability = utils.upgradability.__main__:main'
'slither-check-upgradability = utils.upgradability.__main__:main',
'slither-find-paths = utils.possible_paths.__main__:main'
]
}
)

@ -112,6 +112,7 @@ def process_truffle(dirname, args, detector_classes, printer_classes):
disable_solc_warnings=args.disable_solc_warnings,
solc_arguments=args.solc_args,
is_truffle=True,
truffle_build_directory=args.truffle_build_directory,
filter_paths=parse_filter_paths(args),
triage_mode=args.triage_mode)
@ -299,6 +300,7 @@ defaults_flag_in_config = {
'disable_color': False,
'filter_paths': None,
'ignore_truffle_compile': False,
'truffle_build_directory': 'build/contracts',
'legacy_ast': False
}
@ -419,6 +421,12 @@ def parse_args(detector_classes, printer_classes):
dest='ignore_truffle_compile',
default=defaults_flag_in_config['ignore_truffle_compile'])
group_misc.add_argument('--truffle-build-directory',
help='Do not run truffle compile',
action='store',
dest='truffle_build_directory',
default=defaults_flag_in_config['truffle_build_directory'])
group_misc.add_argument('--triage-mode',
help='Run triage mode (save results in slither.db.json)',
action='store_true',
@ -469,6 +477,11 @@ def parse_args(detector_classes, printer_classes):
action='store_true',
default=defaults_flag_in_config['legacy_ast'])
parser.add_argument('--ignore-return-value',
help=argparse.SUPPRESS,
action='store_true',
default=False)
# if the json is splitted in different files
parser.add_argument('--splitted',
help=argparse.SUPPRESS,
@ -613,6 +626,8 @@ def main_impl(all_detector_classes, all_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))
if args.ignore_return_value:
return
exit(results)
except Exception:

@ -4,7 +4,7 @@
from slither.core.declarations import (Contract, Enum, Function,
SolidityFunction, SolidityVariable,
SolidityVariableComposed, Structure)
from slither.slithir.operations import Index, OperationWithLValue
from slither.slithir.operations import Index, OperationWithLValue, InternalCall
from slither.slithir.variables import (Constant, LocalIRVariable,
ReferenceVariable, ReferenceVariableSSA,
StateIRVariable, TemporaryVariable,
@ -63,7 +63,7 @@ GENERIC_TAINT = {SolidityVariableComposed('msg.sender'),
SolidityVariableComposed('msg.data'),
SolidityVariableComposed('tx.origin')}
def is_tainted(variable, context, only_unprotected=False):
def is_tainted(variable, context, only_unprotected=False, ignore_generic_taint=False):
'''
Args:
variable
@ -78,10 +78,11 @@ def is_tainted(variable, context, only_unprotected=False):
return False
slither = context.slither
taints = slither.context[KEY_INPUT]
taints |= GENERIC_TAINT
if not ignore_generic_taint:
taints |= GENERIC_TAINT
return variable in taints or any(is_dependent(variable, t, context, only_unprotected) for t in taints)
def is_tainted_ssa(variable, context, only_unprotected=False):
def is_tainted_ssa(variable, context, only_unprotected=False, ignore_generic_taint=False):
'''
Args:
variable
@ -96,7 +97,8 @@ def is_tainted_ssa(variable, context, only_unprotected=False):
return False
slither = context.slither
taints = slither.context[KEY_INPUT_SSA]
taints |= GENERIC_TAINT
if not ignore_generic_taint:
taints |= GENERIC_TAINT
return variable in taints or any(is_dependent_ssa(variable, t, context, only_unprotected) for t in taints)
@ -230,6 +232,8 @@ def add_dependency(lvalue, function, ir, is_protected):
function.context[KEY_SSA_UNPROTECTED][lvalue] = set()
if isinstance(ir, Index):
read = [ir.variable_left]
elif isinstance(ir, InternalCall):
read = ir.function.return_values_ssa
else:
read = ir.read
[function.context[KEY_SSA][lvalue].add(v) for v in read if not isinstance(v, Constant)]

@ -9,10 +9,8 @@ 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.index_access import IndexAccess
from slither.core.expressions.member_access import MemberAccess
from slither.core.expressions.unary_operation import UnaryOperation
from slither.core.expressions import (Identifier, IndexAccess, MemberAccess,
UnaryOperation)
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.state_variable import StateVariable
@ -43,6 +41,8 @@ class Function(ChildContract, SourceMapping):
self._parameters_ssa = []
self._returns = []
self._returns_ssa = []
self._return_values = None
self._return_values_ssa = None
self._vars_read = []
self._vars_written = []
self._state_vars_read = []
@ -468,6 +468,38 @@ class Function(ChildContract, SourceMapping):
self._expressions = expressions
return self._expressions
@property
def return_values(self):
"""
list(Return Values): List of the return values
"""
from slither.core.cfg.node import NodeType
from slither.slithir.operations import Return
from slither.slithir.variables import Constant
if self._return_values is None:
return_values = list()
returns = [n for n in self.nodes if n.type == NodeType.RETURN]
[return_values.extend(ir.values) for node in returns for ir in node.irs if isinstance(ir, Return)]
self._return_values = list(set([x for x in return_values if not isinstance(x, Constant)]))
return self._return_values
@property
def return_values_ssa(self):
"""
list(Return Values in SSA form): List of the return values in ssa form
"""
from slither.core.cfg.node import NodeType
from slither.slithir.operations import Return
from slither.slithir.variables import Constant
if self._return_values_ssa is None:
return_values_ssa = list()
returns = [n for n in self.nodes if n.type == NodeType.RETURN]
[return_values_ssa.extend(ir.values) for node in returns for ir in node.irs_ssa if isinstance(ir, Return)]
self._return_values_ssa = list(set([x for x in return_values_ssa if not isinstance(x, Constant)]))
return self._return_values_ssa
# endregion
###################################################################################
###################################################################################

@ -6,6 +6,7 @@ from .elementary_type_name_expression import ElementaryTypeNameExpression
from .identifier import Identifier
from .index_access import IndexAccess
from .literal import Literal
from .member_access import MemberAccess
from .new_array import NewArray
from .new_contract import NewContract
from .new_elementary_type import NewElementaryType

@ -19,12 +19,12 @@ class SourceMapping(Context):
Not done in an efficient way
"""
total_length = len(source_code)
source_code = source_code.split('\n')
source_code = source_code.splitlines(True)
counter = 0
i = 0
lines = []
while counter < total_length:
counter += len(source_code[i]) +1
counter += len(source_code[i])
i = i+1
if counter > start:
lines.append(i)

@ -102,8 +102,10 @@ class AbstractDetector(metaclass=abc.ABCMeta):
return
def detect(self):
results = self._detect()
results = [r for r in results if self.slither.valid_result(r)]
all_results = self._detect()
results = []
# only keep valid result, and remove dupplicate
[results.append(r) for r in all_results if self.slither.valid_result(r) and r not in results]
if results:
if self.logger:
info = '\n'

@ -15,7 +15,7 @@ class ConstantFunctions(AbstractDetector):
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#constant-functions-changing-the-state'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state'
WIKI_TITLE = 'Constant functions changing the state'
WIKI_DESCRIPTION = '''
@ -37,9 +37,9 @@ contract Constant{
}
```
`Constant` was deployed with Solidity 0.4.25. Bob writes a smart contract interacting with `Constant` in Solidity 0.5.0.
All the calls to `get` reverts, breaking Bob's smart contract execution.'''
All the calls to `get` revert, breaking Bob's smart contract execution.'''
WIKI_RECOMMENDATION = 'Ensure that the attributes of contracts compiled prior Solidity 0.5.0 are correct.'
WIKI_RECOMMENDATION = 'Ensure that the attributes of contracts compiled prior to Solidity 0.5.0 are correct.'
def _detect(self):
""" Detect the constant function changing the state

@ -15,7 +15,7 @@ class ConstantPragma(AbstractDetector):
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#different-pragma-directives-are-used'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used'
WIKI_TITLE = 'Different pragma directives are used'

@ -23,11 +23,11 @@ class IncorrectSolc(AbstractDetector):
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#incorrect-version-of-solidity'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-version-of-solidity'
WIKI_TITLE = 'Incorrect versions of Solidity'
WIKI_DESCRIPTION = '''
Solc frequently releases new compiler versions. Using an old version prevent access to new Solidity security checks.
Solc frequently releases new compiler versions. Using an old version prevents access to new Solidity security checks.
We recommend avoiding complex pragma statement.'''
WIKI_RECOMMENDATION = 'Use Solidity 0.4.25 or 0.5.2.'

@ -1,5 +1,5 @@
"""
Check if ether are locked in the contract
Check if ethers are locked in the contract
"""
from slither.detectors.abstract_detector import (AbstractDetector,
@ -17,7 +17,7 @@ class LockedEther(AbstractDetector):
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#contracts-that-lock-ether'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether'
WIKI_TITLE = 'Contracts that lock ether'
@ -30,7 +30,7 @@ contract Locked{
}
}
```
Every ethers send to `Locked` will be lost.'''
Every ether sent to `Locked` will be lost.'''
WIKI_RECOMMENDATION = 'Remove the payable attribute or add a withdraw function.'

@ -1,7 +1,6 @@
"""
Detect incorrect erc20 interface.
Some contracts do not return a bool on transfer/transferFrom/approve, which may lead to prevent
the contract to be used with contracts compiled with recent solc (>0.4.22)
Some contracts do not return a bool on transfer/transferFrom/approve, which may lead to preventing the contract to be used with contracts compiled with recent solc (>0.4.22)
"""
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
@ -16,7 +15,7 @@ class IncorrectERC20InterfaceDetection(AbstractDetector):
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#incorrect-erc20-interface'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface'
WIKI_TITLE = 'Incorrect erc20 interface'
WIKI_DESCRIPTION = 'Lack of return value for the ERC20 `approve`/`transfer`/`transferFrom` functions. A contract compiled with solidity > 0.4.22 interacting with these functions will fail to execute them, as the return value is missing.'

@ -14,10 +14,10 @@ class UnindexedERC20EventParameters(AbstractDetector):
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#unindexed-erc20-event-parameters'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters'
WIKI_TITLE = 'Unindexed ERC20 Event Parameters'
WIKI_DESCRIPTION = 'Detects that events defined by the ERC20 specification which are meant to have some parameters as `indexed`, are not missing the `indexed` keyword.'
WIKI_DESCRIPTION = 'Detects that events defined by the ERC20 specification which are meant to have some parameters as `indexed`, are missing the `indexed` keyword.'
WIKI_EXPLOIT_SCENARIO = '''
```solidity
contract ERC20Bad {

@ -28,7 +28,7 @@ class ArbitrarySend(AbstractDetector):
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#functions-that-send-ether-to-arbitrary-destinations'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#functions-that-send-ether-to-arbitrary-destinations'
WIKI_TITLE = 'Functions that send ether to arbitrary destinations'
WIKI_DESCRIPTION = 'Unprotected call to a function executing sending ethers to an arbitrary address.'
@ -109,7 +109,7 @@ Bob calls `setDestination` and `withdraw`. As a result he withdraws the contract
arbitrary_send = self.detect_arbitrary_send(c)
for (func, nodes) in arbitrary_send:
info = "{}.{} ({}) sends eth to arbirary user\n"
info = "{}.{} ({}) sends eth to arbitrary user\n"
info = info.format(func.contract.name,
func.name,
func.source_mapping_str)

@ -16,7 +16,7 @@ class ExternalFunction(AbstractDetector):
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#public-function-that-could-be-declared-as-external'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-as-external'
WIKI_TITLE = 'Public function that could be declared as external'

@ -16,7 +16,7 @@ class Suicidal(AbstractDetector):
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#suicidal'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#suicidal'
WIKI_TITLE = 'Suicidal'
@ -25,11 +25,11 @@ class Suicidal(AbstractDetector):
```solidity
contract Suicidal{
function kill() public{
selfdestruct(msg.value);
selfdestruct(msg.sender);
}
}
```
Bob calls `kill` and destruct the contract.'''
Bob calls `kill` and destructs the contract.'''
WIKI_RECOMMENDATION = 'Protect access to all sensitive functions.'

@ -17,7 +17,7 @@ class NamingConvention(AbstractDetector):
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#conformance-to-solidity-naming-conventions'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions'
WIKI_TITLE = 'Conformance to Solidity naming conventions'
WIKI_DESCRIPTION = '''

@ -1,13 +1,6 @@
"""
Module detecting send to arbitrary address
Module detecting dangerous use of block.timestamp
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)
- If there is a call to transferFrom
TODO: dont report if the value is tainted by msg.value
"""
from slither.core.declarations import Function
from slither.analyses.data_dependency.data_dependency import is_tainted, is_dependent
@ -27,7 +20,7 @@ class Timestamp(AbstractDetector):
IMPACT = DetectorClassification.LOW
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#block-timestamp'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp'
WIKI_TITLE = 'Block timestamp'

@ -16,7 +16,7 @@ class LowLevelCalls(AbstractDetector):
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#low-level-calls'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls'
WIKI_TITLE = 'Low level calls'
WIKI_DESCRIPTION = 'The use of low-level calls is error-prone. Low-level calls do not check for [code existence](https://solidity.readthedocs.io/en/v0.4.25/control-structures.html#error-handling-assert-require-revert-and-exceptions) or call success.'

@ -17,7 +17,7 @@ class UnusedReturnValues(AbstractDetector):
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#unused-return'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return'
WIKI_TITLE = 'Unused return'
@ -31,9 +31,9 @@ contract MyConc{
}
}
```
`MyConc` call `add` of safemath, but does not store the result in `a`. As a result, the computation has no effect.'''
`MyConc` calls `add` of SafeMath, but does not store the result in `a`. As a result, the computation has no effect.'''
WIKI_RECOMMENDATION = 'Ensure that all the return value of the function call are stored in a local or state variable.'
WIKI_RECOMMENDATION = 'Ensure that all the return values of the function calls are stored in a local or state variable.'
def detect_unused_return_values(self, f):
"""
@ -59,7 +59,7 @@ contract MyConc{
return [nodes_origin[value].node for value in values_returned]
def _detect(self):
""" Detect unused high level calls that return a value but are never used
""" Detect high level calls which return a value that are never used
"""
results = []
for c in self.slither.contracts:

@ -22,7 +22,7 @@ class ReentrancyBenign(Reentrancy):
IMPACT = DetectorClassification.LOW
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#reentrancy-vulnerabilities-2'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2'
WIKI_TITLE = 'Reentrancy vulnerabilities'
WIKI_DESCRIPTION = '''

@ -20,7 +20,7 @@ class ReentrancyEth(Reentrancy):
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#reentrancy-vulnerabilities'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities'
WIKI_TITLE = 'Reentrancy vulnerabilities'
WIKI_DESCRIPTION = '''

@ -23,7 +23,7 @@ class ReentrancyReadBeforeWritten(Reentrancy):
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#reentrancy-vulnerabilities-1'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1'
WIKI_TITLE = 'Reentrancy vulnerabilities'
WIKI_DESCRIPTION = '''

@ -16,7 +16,7 @@ class ShadowingAbstractDetection(AbstractDetector):
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#state-variable-shadowing-from-abstract-contracts'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing-from-abstract-contracts'
WIKI_TITLE = 'State variable shadowing from abstract contracts'

@ -15,7 +15,7 @@ class BuiltinSymbolShadowing(AbstractDetector):
IMPACT = DetectorClassification.LOW
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#builtin-symbol-shadowing'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing'
WIKI_TITLE = 'Builtin Symbol Shadowing'

@ -15,7 +15,7 @@ class LocalShadowing(AbstractDetector):
IMPACT = DetectorClassification.LOW
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#local-variable-shadowing'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing'
WIKI_TITLE = 'Local Variable Shadowing'
WIKI_DESCRIPTION = 'Detection of shadowing using local variables.'

@ -15,7 +15,7 @@ class StateShadowing(AbstractDetector):
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#state-variable-shadowing'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing'
WIKI_TITLE = 'State variable shadowing'
WIKI_DESCRIPTION = 'Detection of state variables shadowed.'

@ -16,7 +16,7 @@ class Assembly(AbstractDetector):
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#assembly-usage'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage'
WIKI_TITLE = 'Assembly usage'

@ -16,7 +16,7 @@ class MultipleCallsInLoop(AbstractDetector):
IMPACT = DetectorClassification.LOW
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation/_edit#calls-inside-a-loop'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation/_edit#calls-inside-a-loop'
WIKI_TITLE = 'Calls inside a loop'
@ -82,7 +82,7 @@ If one of the destinations has a fallback function which reverts, `bad` will alw
"""
"""
results = []
for c in self.contracts:
for c in self.slither.contracts_derived:
values = self.detect_call_in_loop(c)
for node in values:
func = node.function

@ -11,7 +11,7 @@ class ControlledDelegateCall(AbstractDetector):
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#controlled-delegatecall'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#controlled-delegatecall'
WIKI_TITLE = 'Controlled Delegatecall'
@ -24,7 +24,7 @@ contract Delegatecall{
}
}
```
Bob calls `delegate` and delegate the execution to its malicious contract. As a result, Bob withdraws the funds of the contract and destruct it.'''
Bob calls `delegate` and delegates the execution to its malicious contract. As a result, Bob withdraws the funds of the contract and destructs it.'''
WIKI_RECOMMENDATION = 'Avoid using `delegatecall`. Use only trusted destinations.'
@ -32,7 +32,7 @@ Bob calls `delegate` and delegate the execution to its malicious contract. As a
ret = []
for node in function.nodes:
for ir in node.irs:
if isinstance(ir, LowLevelCall) and ir.function_name in ['delegatecall', 'codecall']:
if isinstance(ir, LowLevelCall) and ir.function_name in ['delegatecall', 'callcode']:
if is_tainted(ir.destination, function.contract):
ret.append(node)
return ret

@ -20,7 +20,7 @@ class DeprecatedStandards(AbstractDetector):
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#deprecated-standards'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards'
WIKI_TITLE = 'Deprecated Standards'
WIKI_DESCRIPTION = 'Detect the usage of deprecated standards (as defined by SWC-111), excluding only `constant` keyword detection on functions.'
@ -144,7 +144,7 @@ contract ContractWithDeprecatedReferences {
return results
def _detect(self):
""" Detect shadowing local variables
""" Detects if an expression makes use of any deprecated standards.
Recursively visit the calls
Returns:
@ -165,7 +165,7 @@ contract ContractWithDeprecatedReferences {
recommended_disc)
# Generate relevant JSON data for this shadowing definition.
# Generate relevant JSON data for this deprecated standard.
json = self.generate_json_result(info)
if isinstance(source_object, StateVariableSolc) or isinstance(source_object, StateVariable):
self.add_variable_to_json(source_object, json)

@ -23,10 +23,10 @@ class IncorrectStrictEquality(AbstractDetector):
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#dangerous-strict-equalities'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities'
WIKI_TITLE = 'Dangerous strict equalities'
WIKI_DESCRIPTION = 'Use of strick equalities that can be easily manipulated by an attacker.'
WIKI_DESCRIPTION = 'Use of strict equalities that can be easily manipulated by an attacker.'
WIKI_EXPLOIT_SCENARIO = '''
```solidity
contract Crowdsale{

@ -14,7 +14,7 @@ class TxOrigin(AbstractDetector):
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#dangerous-usage-of-txorigin'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin'
WIKI_TITLE = 'Dangerous usage of `tx.origin`'
WIKI_DESCRIPTION = '`tx.origin`-based protection can be abused by malicious contract if a legitimate user interacts with the malicious contract.'
@ -27,14 +27,14 @@ contract TxOrigin {
require(tx.origin == owner);
}
```
Bob is the owner of `TxOrigin`. Bob calls Eve's contract. Eve's contact calls `TxOrigin` and bypass the `tx.origin` protection.'''
Bob is the owner of `TxOrigin`. Bob calls Eve's contract. Eve's contract calls `TxOrigin` and bypasses the `tx.origin` protection.'''
WIKI_RECOMMENDATION = 'Do not use `tx.origin` for authentification.'
WIKI_RECOMMENDATION = 'Do not use `tx.origin` for authorization.'
@staticmethod
def _contains_incorrect_tx_origin_use(node):
"""
Check if the node read tx.origin and dont read msg.sender
Check if the node reads tx.origin and doesn't read msg.sender
Avoid the FP due to (msg.sender == tx.origin)
Returns:
(bool)

@ -21,7 +21,7 @@ class ConstCandidateStateVars(AbstractDetector):
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#state-variables-that-could-be-declared-constant'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant'
WIKI_TITLE = 'State variables that could be declared constant'

@ -1,8 +1,8 @@
"""
Module detecting state uninitialized local variables
Module detecting uninitialized local variables
Recursively explore the CFG to only report uninitialized local variables that are
written before being read
read before being written
"""
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
@ -19,7 +19,7 @@ class UninitializedLocalVars(AbstractDetector):
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#uninitialized-local-variables'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables'
WIKI_TITLE = 'Uninitialized local variables'
@ -77,11 +77,11 @@ Bob calls `transfer`. As a result, the ethers are sent to the address 0x0 and ar
def _detect(self):
""" Detect uninitialized state variables
""" Detect uninitialized local variables
Recursively visit the calls
Returns:
dict: [contract name] = set(state variable uninitialized)
dict: [contract name] = set(local variable uninitialized)
"""
results = []

@ -28,7 +28,7 @@ class UninitializedStateVarsDetection(AbstractDetector):
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#uninitialized-state-variables'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-state-variables'
WIKI_TITLE = 'Uninitialized state variables'
WIKI_DESCRIPTION = 'Uninitialized state variables.'

@ -1,5 +1,5 @@
"""
Module detecting state uninitialized storage variables
Module detecting uninitialized storage variables
Recursively explore the CFG to only report uninitialized storage variables that are
written before being read
@ -19,7 +19,7 @@ class UninitializedStorageVars(AbstractDetector):
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#uninitialized-storage-variables'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-storage-variables'
WIKI_TITLE = 'Uninitialized storage variables'
WIKI_DESCRIPTION = 'An uinitialized storage variable will act as a reference to the first state variable, and can override a critical variable.'
@ -58,7 +58,7 @@ Bob calls `func`. As a result, `owner` is override to 0.
if self.key in father.context:
fathers_context += father.context[self.key]
# Exclude path that dont bring further information
# Exclude paths 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
@ -84,11 +84,11 @@ Bob calls `func`. As a result, `owner` is override to 0.
def _detect(self):
""" Detect uninitialized state variables
""" Detect uninitialized storage variables
Recursively visit the calls
Returns:
dict: [contract name] = set(state variable uninitialized)
dict: [contract name] = set(storage variable uninitialized)
"""
results = []

@ -17,7 +17,7 @@ class UnusedStateVars(AbstractDetector):
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/trailofbits/slither/wiki/Detectors-Documentation#unused-state-variables'
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variables'
WIKI_TITLE = 'Unused state variables'

@ -5,7 +5,7 @@
what are the contracts/functions called.
The output is a dot file named filename.dot
"""
from collections import defaultdict
from slither.printers.abstract_printer import AbstractPrinter
from slither.core.declarations.solidity_variables import SolidityFunction
from slither.core.declarations.function import Function
@ -44,97 +44,113 @@ class PrinterCallGraph(AbstractPrinter):
WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#call-graph'
def __init__(self, slither, logger):
super(PrinterCallGraph, self).__init__(slither, logger)
def _process_functions(self, functions):
contract_functions = defaultdict(set) # contract -> contract functions nodes
contract_calls = defaultdict(set) # contract -> contract calls edges
solidity_functions = set() # solidity function nodes
solidity_calls = set() # solidity calls edges
external_calls = set() # external calls edges
self.contract_functions = {} # contract -> contract functions nodes
self.contract_calls = {} # contract -> contract calls edges
all_contracts = set()
for contract in slither.contracts:
self.contract_functions[contract] = set()
self.contract_calls[contract] = set()
for function in functions:
all_contracts.add(function.contract)
for function in functions:
self._process_function(function.contract,
function,
contract_functions,
contract_calls,
solidity_functions,
solidity_calls,
external_calls,
all_contracts)
self.solidity_functions = set() # solidity function nodes
self.solidity_calls = set() # solidity calls edges
render_internal_calls = ''
for contract in all_contracts:
render_internal_calls += self._render_internal_calls(contract, contract_functions, contract_calls)
self.external_calls = set() # external calls edges
render_solidity_calls = '' #self._render_solidity_calls(solidity_functions, solidity_calls)
self._process_contracts(slither.contracts)
render_external_calls = self._render_external_calls(external_calls)
def _process_contracts(self, contracts):
for contract in contracts:
for function in contract.functions:
self._process_function(contract, function)
return render_internal_calls + render_solidity_calls + render_external_calls
def _process_function(self, contract, function):
self.contract_functions[contract].add(
def _process_function(self, contract, function, contract_functions, contract_calls, solidity_functions, solidity_calls, external_calls, all_contracts):
contract_functions[contract].add(
_node(_function_node(contract, function), function.name),
)
for internal_call in function.internal_calls:
self._process_internal_call(contract, function, internal_call)
self._process_internal_call(contract, function, internal_call, contract_calls, solidity_functions, solidity_calls)
for external_call in function.high_level_calls:
self._process_external_call(contract, function, external_call)
self._process_external_call(contract, function, external_call, contract_functions, external_calls, all_contracts)
def _process_internal_call(self, contract, function, internal_call):
def _process_internal_call(self, contract, function, internal_call, contract_calls, solidity_functions, solidity_calls):
if isinstance(internal_call, (Function)):
self.contract_calls[contract].add(_edge(
contract_calls[contract].add(_edge(
_function_node(contract, function),
_function_node(contract, internal_call),
))
elif isinstance(internal_call, (SolidityFunction)):
self.solidity_functions.add(
solidity_functions.add(
_node(_solidity_function_node(internal_call)),
)
self.solidity_calls.add(_edge(
solidity_calls.add(_edge(
_function_node(contract, function),
_solidity_function_node(internal_call),
))
def _process_external_call(self, contract, function, external_call):
def _process_external_call(self, contract, function, external_call, contract_functions, external_calls, all_contracts):
external_contract, external_function = external_call
if not external_contract in all_contracts:
return
# add variable as node to respective contract
if isinstance(external_function, (Variable)):
self.contract_functions[external_contract].add(_node(
return
contract_functions[external_contract].add(_node(
_function_node(external_contract, external_function),
external_function.name
))
self.external_calls.add(_edge(
external_calls.add(_edge(
_function_node(contract, function),
_function_node(external_contract, external_function),
))
def _render_internal_calls(self):
def _render_internal_calls(self, contract, contract_functions, contract_calls):
lines = []
for contract in self.contract_functions:
lines.append(f'subgraph {_contract_subgraph(contract)} {{')
lines.append(f'label = "{contract.name}"')
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.extend(contract_functions[contract])
lines.extend(contract_calls[contract])
lines.append('}')
lines.append('}')
return '\n'.join(lines)
def _render_solidity_calls(self):
def _render_solidity_calls(self, solidity_functions, solidity_calls):
lines = []
lines.append('subgraph cluster_solidity {')
lines.append('label = "[Solidity]"')
lines.extend(self.solidity_functions)
lines.extend(self.solidity_calls)
lines.extend(solidity_functions)
lines.extend(solidity_calls)
lines.append('}')
return '\n'.join(lines)
def _render_external_calls(self):
return '\n'.join(self.external_calls)
def _render_external_calls(self, external_calls):
return '\n'.join(external_calls)
def output(self, filename):
"""
@ -142,19 +158,19 @@ class PrinterCallGraph(AbstractPrinter):
Args:
filename(string)
"""
if not filename:
filename = "contracts.dot"
if not filename.endswith('.dot'):
filename += '.dot'
self.info(f'Call Graph: {filename}')
if filename == ".dot":
filename = "all_contracts.dot"
with open(filename, 'w', encoding='utf8') as f:
f.write('\n'.join([
'strict digraph {',
self._render_internal_calls(),
self._render_solidity_calls(),
self._render_external_calls(),
'}',
]))
self.info(f'Call Graph: {filename}')
f.write('\n'.join(['strict digraph {'] + [self._process_functions(self.slither.functions)] + ['}']))
for derived_contract in self.slither.contracts_derived:
with open(f'{derived_contract.name}.dot', 'w', encoding='utf8') as f:
self.info(f'Call Graph: {derived_contract.name}.dot')
f.write('\n'.join(['strict digraph {'] + [self._process_functions(derived_contract.functions)] + ['}']))

@ -29,6 +29,7 @@ class Slither(SlitherSolc):
solc_argeuments (str): solc arguments (default '')
ast_format (str): ast format (default '--ast-compact-json')
is_truffle (bool): is a truffle directory (default false)
truffle_build_directory (str): build truffle directory (default 'build/contracts')
is_embark (bool): is an embark directory (default false)
force_embark_plugin (bool): force use of embark plugin for this embark directory (default false)
filter_paths (list(str)): list of path to filter (default [])
@ -43,7 +44,6 @@ class Slither(SlitherSolc):
# truffle directory
if is_truffle:
self._init_from_truffle(contract)
# embark repo
elif is_embark:
self._init_from_embark(contract, force_embark_plugin)
# list of files provided (see --splitted option)
@ -105,13 +105,12 @@ class Slither(SlitherSolc):
logger.error(stderr)
sys.exit(-1)
def _init_from_truffle(self, contract):
if not os.path.isdir(os.path.join(contract, 'build'))\
or not os.path.isdir(os.path.join(contract, 'build', 'contracts')):
def _init_from_truffle(self, contract, build_directory):
if not os.path.isdir(os.path.join(contract, build_directory)):
logger.info(red('No truffle build directory found, did you run `truffle compile`?'))
sys.exit(-1)
super(Slither, self).__init__('')
filenames = glob.glob(os.path.join(contract, 'build', 'contracts', '*.json'))
filenames = glob.glob(os.path.join(contract, build_directory, '*.json'))
for filename in filenames:
with open(filename, encoding='utf8') as f:
contract_loaded = json.load(f)

@ -82,7 +82,7 @@ def is_gas(ins):
return True
return False
def get_sig(ir):
def get_sig(ir, name):
'''
Return a list of potential signature
It is a list, as Constant variables can be converted to int256
@ -92,11 +92,14 @@ def get_sig(ir):
list(str)
'''
sig = '{}({})'
name = ir.function_name
# list of list of arguments
argss = convert_arguments(ir.arguments)
return [sig.format(name, ','.join(args)) for args in argss]
def convert_arguments(arguments):
argss = [[]]
for arg in ir.arguments:
for arg in arguments:
if isinstance(arg, (list,)):
type_arg = '{}[{}]'.format(get_type(arg[0].type), len(arg))
elif isinstance(arg, Function):
@ -119,7 +122,7 @@ def get_sig(ir):
else:
for args in argss:
args.append(type_arg)
return [sig.format(name, ','.join(args)) for args in argss]
return argss
def is_temporary(ins):
return isinstance(ins, (Argument,
@ -445,11 +448,16 @@ def extract_tmp_call(ins, contract):
call.call_id = ins.call_id
return call
if isinstance(ins.ori, Member):
# If there is a call on an inherited contract, it is an internal call
# If there is a call on an inherited contract, it is an internal call or an event
if ins.ori.variable_left in contract.inheritance + [contract]:
internalcall = InternalCall(ins.ori.variable_right, ins.ori.variable_left, ins.nbr_arguments, ins.lvalue, ins.type_call)
internalcall.call_id = ins.call_id
return internalcall
if str(ins.ori.variable_right) in [f.name for f in contract.functions]:
internalcall = InternalCall(ins.ori.variable_right, ins.ori.variable_left, ins.nbr_arguments, ins.lvalue, ins.type_call)
internalcall.call_id = ins.call_id
return internalcall
if str(ins.ori.variable_right) in [f.name for f in contract.events]:
eventcall = EventCall(ins.ori.variable_right)
eventcall.call_id = ins.call_id
return eventcall
if isinstance(ins.ori.variable_left, Contract):
st = ins.ori.variable_left.get_structure_from_name(ins.ori.variable_right)
if st:
@ -645,7 +653,7 @@ def get_type(t):
return str(t)
def convert_type_library_call(ir, lib_contract):
sigs = get_sig(ir)
sigs = get_sig(ir, ir.function_name)
func = None
for sig in sigs:
func = lib_contract.get_function_from_signature(sig)
@ -680,7 +688,7 @@ def convert_type_library_call(ir, lib_contract):
def convert_type_of_high_and_internal_level_call(ir, contract):
func = None
sigs = get_sig(ir)
sigs = get_sig(ir, ir.function_name)
for sig in sigs:
func = contract.get_function_from_signature(sig)
if not func:
@ -697,7 +705,7 @@ def convert_type_of_high_and_internal_level_call(ir, contract):
# lowlelvel lookup needs to be done at last step
if not func and ir.function_name in ['call',
'delegatecall',
'codecall',
'callcode',
'transfer',
'send']:
return convert_to_low_level(ir)

@ -37,8 +37,13 @@ class FunctionSolc(Function):
def __init__(self, function, contract):
super(FunctionSolc, self).__init__()
self._contract = contract
# Only present if compact AST
self._referenced_declaration = None
if self.is_compact_ast:
self._name = function['name']
if 'id' in function:
self._referenced_declaration = function['id']
else:
self._name = function['attributes'][self.get_key()]
self._functionNotParsed = function
@ -73,6 +78,13 @@ class FunctionSolc(Function):
def is_compact_ast(self):
return self.slither.is_compact_ast
@property
def referenced_declaration(self):
'''
Return the compact AST referenced declaration id (None for legacy AST)
'''
return self._referenced_declaration
# endregion
###################################################################################
###################################################################################

@ -70,6 +70,7 @@ def get_pointer_name(variable):
def find_variable(var_name, caller_context, referenced_declaration=None):
if isinstance(caller_context, Contract):
function = None
contract = caller_context
@ -153,6 +154,9 @@ def find_variable(var_name, caller_context, referenced_declaration=None):
for contract in contract.slither.contracts:
if contract.id == referenced_declaration:
return contract
for function in contract.slither.functions:
if function.referenced_declaration == referenced_declaration:
return function
raise VariableNotFound('Variable not found: {}'.format(var_name))
@ -315,6 +319,18 @@ def parse_super_name(expression, is_compact_ast):
return base_name+arguments
def _parse_elementary_type_name_expression(expression, is_compact_ast, caller_context):
# nop exression
# uint;
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)
def parse_expression(expression, caller_context):
"""
@ -516,6 +532,12 @@ def parse_expression(expression, caller_context):
assert len(children) == 2
left = children[0]
right = children[1]
# IndexAccess is used to describe ElementaryTypeNameExpression
# if abi.decode is used
# For example, abi.decode(data, ...(uint[]) )
if right is None:
return _parse_elementary_type_name_expression(left, is_compact_ast, caller_context)
left_expression = parse_expression(left, caller_context)
right_expression = parse_expression(right, caller_context)
index = IndexAccess(left_expression, right_expression, index_type)
@ -555,16 +577,7 @@ def parse_expression(expression, caller_context):
return member_access
elif name == 'ElementaryTypeNameExpression':
# nop exression
# uint;
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)
return _parse_elementary_type_name_expression(expression, is_compact_ast, caller_context)
# NewExpression is not a root expression, it's always the child of another expression

@ -89,7 +89,7 @@ class SlitherSolc(Slither):
if 'sourcePaths' in data_loaded:
for sourcePath in data_loaded['sourcePaths']:
if os.path.isfile(sourcePath):
with open(sourcePath, encoding='utf8') as f:
with open(sourcePath, encoding='utf8', newline='') as f:
source_code = f.read()
self.source_code[sourcePath] = source_code
@ -152,13 +152,13 @@ class SlitherSolc(Slither):
self._source_units[sourceUnit] = name
if os.path.isfile(name) and not name in self.source_code:
with open(name, encoding='utf8') as f:
with open(name, encoding='utf8', newline='') as f:
source_code = f.read()
self.source_code[name] = source_code
else:
lib_name = os.path.join('node_modules', name)
if os.path.isfile(lib_name) and not name in self.source_code:
with open(lib_name, encoding='utf8') as f:
with open(lib_name, encoding='utf8', newline='') as f:
source_code = f.read()
self.source_code[name] = source_code

@ -5,7 +5,7 @@ from slither.core.expressions import (AssignmentOperationType,
UnaryOperationType)
from slither.slithir.operations import (Assignment, Binary, BinaryType, Delete,
Index, InitArray, InternalCall, Member,
NewArray, NewContract, NewStructure,
NewArray, NewContract,
TypeConversion, Unary, Unpack, Return)
from slither.slithir.tmp_operations.argument import Argument
from slither.slithir.tmp_operations.tmp_call import TmpCall

@ -0,0 +1,49 @@
contract Initializable{
address destination;
modifier initializer(){
_;
}
}
contract Contract_no_bug is Initializable{
function initialize() public initializer{
}
}
contract Contract_lack_to_call_modifier is Initializable{
function initialize() public {
}
}
contract Contract_not_called_super_init is Contract_no_bug{
function initialize() public initializer{
}
}
contract Contract_no_bug_inherits is Contract_no_bug{
function initialize() public initializer{
Contract_no_bug.initialize();
}
}
contract Contract_double_call is Contract_no_bug, Contract_no_bug_inherits{
function initialize() public initializer{
Contract_no_bug_inherits.initialize();
Contract_no_bug.initialize();
}
}

@ -1,3 +1,4 @@
INFO:CheckInitialization:Initializable contract not found, the contract does not follow a standard initalization schema.
INFO:CompareFunctions:No function id collision found
INFO:VariablesOrder:Variable in the proxy: destination address
INFO:VariablesOrder:No error found (variables ordering proxy <-> implementation)

@ -1,3 +1,5 @@
INFO:CheckInitialization:Initializable contract not found, the contract does not follow a standard initalization schema.
INFO:CheckInitialization:Initializable contract not found, the contract does not follow a standard initalization schema.
INFO:CompareFunctions:No function id collision found
INFO:VariablesOrder:Variable in the proxy: destination address
INFO:VariablesOrder:No error found (variables ordering proxy <-> implementation)

@ -1,3 +1,5 @@
INFO:CheckInitialization:Initializable contract not found, the contract does not follow a standard initalization schema.
INFO:CheckInitialization:Initializable contract not found, the contract does not follow a standard initalization schema.
INFO:CompareFunctions:Function id collision found myFunc() myFunc()
INFO:VariablesOrder:Different variables between proxy and implem: destination address -> destination uint256
INFO:VariablesOrder:Different variables between v1 and v2: destination address -> destination uint256

@ -1,3 +1,5 @@
INFO:CheckInitialization:Initializable contract not found, the contract does not follow a standard initalization schema.
INFO:CheckInitialization:Initializable contract not found, the contract does not follow a standard initalization schema.
INFO:CompareFunctions:No function id collision found
INFO:VariablesOrder:Different variables between proxy and implem: destination address -> val uint256
INFO:VariablesOrder:Different variables between v1 and v2: destination address -> val uint256

@ -0,0 +1,13 @@
INFO:CheckInitialization:Contract_lack_to_call_modifier.initialize does not call initializer
INFO:CheckInitialization:Missing call to Contract_no_bug.initialize in Contract_not_called_super_init
INFO:CheckInitialization:Contract_no_bug.initialize() is called multiple time in Contract_double_call
INFO:CheckInitialization:Check the deployement script to ensure that these functions are called:
Contract_no_bug needs to be initialized by initialize()
Contract_lack_to_call_modifier needs to be initialized by initialize()
Contract_not_called_super_init needs to be initialized by initialize()
Contract_no_bug_inherits needs to be initialized by initialize()
Contract_double_call needs to be initialized by initialize()

INFO:CompareFunctions:No function id collision found
INFO:VariablesOrder:Variable in the proxy: destination address
INFO:VariablesOrder:No error found (variables ordering proxy <-> implementation)

@ -1 +1 @@
[{"check": "arbitrary-send", "impact": "High", "confidence": "Medium", "description": "Test.direct (tests/arbitrary_send-0.5.1.sol#11-13) sends eth to arbirary user\n\tDangerous calls:\n\t- msg.sender.send(address(this).balance) (tests/arbitrary_send-0.5.1.sol#12)\n", "elements": [{"type": "function", "name": "direct", "source_mapping": {"start": 162, "length": 79, "filename": "tests/arbitrary_send-0.5.1.sol", "lines": [11, 12, 13]}, "contract": {"type": "contract", "name": "Test", "source_mapping": {"start": 0, "length": 884, "filename": "tests/arbitrary_send-0.5.1.sol", "lines": [1, 2, 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]}}}, {"type": "expression", "expression": "msg.sender.send(address(this).balance)", "source_mapping": {"start": 196, "length": 38, "filename": "tests/arbitrary_send-0.5.1.sol", "lines": [12]}}]}, {"check": "arbitrary-send", "impact": "High", "confidence": "Medium", "description": "Test.indirect (tests/arbitrary_send-0.5.1.sol#19-21) sends eth to arbirary user\n\tDangerous calls:\n\t- destination.send(address(this).balance) (tests/arbitrary_send-0.5.1.sol#20)\n", "elements": [{"type": "function", "name": "indirect", "source_mapping": {"start": 316, "length": 82, "filename": "tests/arbitrary_send-0.5.1.sol", "lines": [19, 20, 21]}, "contract": {"type": "contract", "name": "Test", "source_mapping": {"start": 0, "length": 884, "filename": "tests/arbitrary_send-0.5.1.sol", "lines": [1, 2, 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]}}}, {"type": "expression", "expression": "destination.send(address(this).balance)", "source_mapping": {"start": 352, "length": 39, "filename": "tests/arbitrary_send-0.5.1.sol", "lines": [20]}}]}]
[{"check": "arbitrary-send", "impact": "High", "confidence": "Medium", "description": "Test.direct (tests/arbitrary_send-0.5.1.sol#11-13) sends eth to arbitrary user\n\tDangerous calls:\n\t- msg.sender.send(address(this).balance) (tests/arbitrary_send-0.5.1.sol#12)\n", "elements": [{"type": "function", "name": "direct", "source_mapping": {"start": 162, "length": 79, "filename": "tests/arbitrary_send-0.5.1.sol", "lines": [11, 12, 13]}, "contract": {"type": "contract", "name": "Test", "source_mapping": {"start": 0, "length": 884, "filename": "tests/arbitrary_send-0.5.1.sol", "lines": [1, 2, 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]}}}, {"type": "expression", "expression": "msg.sender.send(address(this).balance)", "source_mapping": {"start": 196, "length": 38, "filename": "tests/arbitrary_send-0.5.1.sol", "lines": [12]}}]}, {"check": "arbitrary-send", "impact": "High", "confidence": "Medium", "description": "Test.indirect (tests/arbitrary_send-0.5.1.sol#19-21) sends eth to arbitrary user\n\tDangerous calls:\n\t- destination.send(address(this).balance) (tests/arbitrary_send-0.5.1.sol#20)\n", "elements": [{"type": "function", "name": "indirect", "source_mapping": {"start": 316, "length": 82, "filename": "tests/arbitrary_send-0.5.1.sol", "lines": [19, 20, 21]}, "contract": {"type": "contract", "name": "Test", "source_mapping": {"start": 0, "length": 884, "filename": "tests/arbitrary_send-0.5.1.sol", "lines": [1, 2, 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]}}}, {"type": "expression", "expression": "destination.send(address(this).balance)", "source_mapping": {"start": 352, "length": 39, "filename": "tests/arbitrary_send-0.5.1.sol", "lines": [20]}}]}]

@ -1 +1 @@
[{"check": "arbitrary-send", "impact": "High", "confidence": "Medium", "description": "Test.direct (tests/arbitrary_send.sol#11-13) sends eth to arbirary user\n\tDangerous calls:\n\t- msg.sender.send(address(this).balance) (tests/arbitrary_send.sol#12)\n", "elements": [{"type": "function", "name": "direct", "source_mapping": {"start": 147, "length": 79, "filename": "tests/arbitrary_send.sol", "lines": [11, 12, 13]}, "contract": {"type": "contract", "name": "Test", "source_mapping": {"start": 0, "length": 869, "filename": "tests/arbitrary_send.sol", "lines": [1, 2, 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]}}}, {"type": "expression", "expression": "msg.sender.send(address(this).balance)", "source_mapping": {"start": 181, "length": 38, "filename": "tests/arbitrary_send.sol", "lines": [12]}}]}, {"check": "arbitrary-send", "impact": "High", "confidence": "Medium", "description": "Test.indirect (tests/arbitrary_send.sol#19-21) sends eth to arbirary user\n\tDangerous calls:\n\t- destination.send(address(this).balance) (tests/arbitrary_send.sol#20)\n", "elements": [{"type": "function", "name": "indirect", "source_mapping": {"start": 301, "length": 82, "filename": "tests/arbitrary_send.sol", "lines": [19, 20, 21]}, "contract": {"type": "contract", "name": "Test", "source_mapping": {"start": 0, "length": 869, "filename": "tests/arbitrary_send.sol", "lines": [1, 2, 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]}}}, {"type": "expression", "expression": "destination.send(address(this).balance)", "source_mapping": {"start": 337, "length": 39, "filename": "tests/arbitrary_send.sol", "lines": [20]}}]}]
[{"check": "arbitrary-send", "impact": "High", "confidence": "Medium", "description": "Test.direct (tests/arbitrary_send.sol#11-13) sends eth to arbitrary user\n\tDangerous calls:\n\t- msg.sender.send(address(this).balance) (tests/arbitrary_send.sol#12)\n", "elements": [{"type": "function", "name": "direct", "source_mapping": {"start": 147, "length": 79, "filename": "tests/arbitrary_send.sol", "lines": [11, 12, 13]}, "contract": {"type": "contract", "name": "Test", "source_mapping": {"start": 0, "length": 869, "filename": "tests/arbitrary_send.sol", "lines": [1, 2, 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]}}}, {"type": "expression", "expression": "msg.sender.send(address(this).balance)", "source_mapping": {"start": 181, "length": 38, "filename": "tests/arbitrary_send.sol", "lines": [12]}}]}, {"check": "arbitrary-send", "impact": "High", "confidence": "Medium", "description": "Test.indirect (tests/arbitrary_send.sol#19-21) sends eth to arbitrary user\n\tDangerous calls:\n\t- destination.send(address(this).balance) (tests/arbitrary_send.sol#20)\n", "elements": [{"type": "function", "name": "indirect", "source_mapping": {"start": 301, "length": 82, "filename": "tests/arbitrary_send.sol", "lines": [19, 20, 21]}, "contract": {"type": "contract", "name": "Test", "source_mapping": {"start": 0, "length": 869, "filename": "tests/arbitrary_send.sol", "lines": [1, 2, 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]}}}, {"type": "expression", "expression": "destination.send(address(this).balance)", "source_mapping": {"start": 337, "length": 39, "filename": "tests/arbitrary_send.sol", "lines": [20]}}]}]

@ -0,0 +1,17 @@
contract A{
function destination() private{
}
function call() public{
destination();
}
}
contract B{
function call2(A a) public{
a.call();
}
}

@ -0,0 +1,14 @@
Target functions:
- A.destination()
The following functions reach the specified targets:
- A.call()
- B.call2(A)
The following paths reach the specified targets:
A.call() -> A.destination()
B.call2(A) -> A.call() -> A.destination()

@ -0,0 +1,72 @@
import os
import argparse
from slither import Slither
from slither.utils.colors import red
import logging
from .possible_paths import find_target_paths, resolve_functions, ResolveFunctionException
logging.basicConfig()
logging.getLogger("Slither").setLevel(logging.INFO)
def parse_args():
"""
Parse the underlying arguments for the program.
:return: Returns the arguments for the program.
"""
parser = argparse.ArgumentParser(description='PossiblePaths',
usage='possible_paths.py filename [contract.function targets]')
parser.add_argument('filename',
help='The filename of the contract or truffle directory to analyze.')
parser.add_argument('targets', nargs='+')
parser.add_argument('--solc', help='solc path', default='solc')
return parser.parse_args()
def main():
# ------------------------------
# PossiblePaths.py
# Usage: python3 possible_paths.py filename targets
# Example: python3 possible_paths.py contract.sol contract1.function1 contract2.function2 contract3.function3
# ------------------------------
# Parse all arguments
args = parse_args()
# Perform slither analysis on the given filename
slither = Slither(args.filename, is_truffle=os.path.isdir(args.filename), solc=args.solc, disable_solc_warnings=True)
try:
targets = resolve_functions(slither, args.targets)
except ResolveFunctionException as r:
print(red(r))
exit(-1)
# Print out all target functions.
print(f"Target functions:")
for target in targets:
print(f"- {target.contract.name}.{target.full_name}")
print("\n")
# Obtain all paths which reach the target functions.
reaching_paths = find_target_paths(slither, targets)
reaching_functions = set([y for x in reaching_paths for y in x if y not in targets])
# Print out all function names which can reach the targets.
print(f"The following functions reach the specified targets:")
for function_desc in sorted([f"{f.contract.name}.{f.full_name}" for f in reaching_functions]):
print(f"- {function_desc}")
print("\n")
# Format all function paths.
reaching_paths_str = [' -> '.join([f"{f.contract.name}.{f.full_name}" for f in reaching_path]) for reaching_path in reaching_paths]
# Print a sorted list of all function paths which can reach the targets.
print(f"The following paths reach the specified targets:")
for reaching_path in sorted(reaching_paths_str):
print(f"{reaching_path}\n")
if __name__ == '__main__':
main()

@ -0,0 +1,127 @@
class ResolveFunctionException(Exception): pass
def resolve_function(slither, contract_name, function_name):
"""
Resolves a function instance, given a contract name and function.
:param contract_name: The name of the contract the function is declared in.
:param function_name: The name of the function to resolve.
:return: Returns the resolved function, raises an exception otherwise.
"""
# Obtain the target contract
contract = slither.get_contract_from_name(contract_name)
# Verify the contract was resolved successfully
if contract is None:
raise ResolveFunctionException(f"Could not resolve target contract: {contract_name}")
# Obtain the target function
target_function = next((function for function in contract.functions if function.name == function_name), None)
# Verify we have resolved the function specified.
if target_function is None:
raise ResolveFunctionException(f"Could not resolve target function: {contract_name}.{function_name}")
# Add the resolved function to the new list.
return target_function
def resolve_functions(slither, functions):
"""
Resolves the provided function descriptors.
:param functions: A list of tuples (contract_name, function_name) or str (of form "ContractName.FunctionName")
to resolve into function objects.
:return: Returns a list of resolved functions.
"""
# Create the resolved list.
resolved = []
# Verify that the provided argument is a list.
if not isinstance(functions, list):
raise ResolveFunctionException("Provided functions to resolve must be a list type.")
# Loop for each item in the list.
for item in functions:
if isinstance(item, str):
# If the item is a single string, we assume it is of form 'ContractName.FunctionName'.
parts = item.split('.')
if len(parts) < 2:
raise ResolveFunctionException("Provided string descriptor must be of form 'ContractName.FunctionName'")
resolved.append(resolve_function(slither, parts[0], parts[1]))
elif isinstance(item, tuple):
# If the item is a tuple, it should be a 2-tuple providing contract and function names.
if len(item) != 2:
raise ResolveFunctionException("Provided tuple descriptor must provide a contract and function name.")
resolved.append(resolve_function(slither, item[0], item[1]))
else:
raise ResolveFunctionException(f"Unexpected function descriptor type to resolve in list: {type(item)}")
# Return the resolved list.
return resolved
def all_function_definitions(function):
"""
Obtains a list of representing this function and any base definitions
:param function: The function to obtain all definitions at and beneath.
:return: Returns a list composed of the provided function definition and any base definitions.
"""
return [function] + [f for c in function.contract.inheritance
for f in c.functions_and_modifiers_not_inherited
if f.full_name == function.full_name]
def __find_target_paths(slither, target_function, current_path=[]):
# Create our results list
results = set()
# Add our current function to the path.
current_path = [target_function] + current_path
# Obtain this target function and any base definitions.
all_target_functions = set(all_function_definitions(target_function))
# Look through all functions
for contract in slither.contracts:
for function in contract.functions_and_modifiers_not_inherited:
# If the function is already in our path, skip it.
if function in current_path:
continue
# Find all function calls in this function (except for low level)
called_functions = [f for (_, f) in function.high_level_calls + function.library_calls]
called_functions += function.internal_calls
called_functions = set(called_functions)
# If any of our target functions are reachable from this function, it's a result.
if all_target_functions.intersection(called_functions):
path_results = __find_target_paths(slither, function, current_path.copy())
if path_results:
results = results.union(path_results)
# If this path is external accessible from this point, we add the current path to the list.
if target_function.visibility in ['public', 'external'] and len(current_path) > 1:
results.add(tuple(current_path))
return results
def find_target_paths(slither, target_functions):
"""
Obtains all functions which can lead to any of the target functions being called.
:param target_functions: The functions we are interested in reaching.
:return: Returns a list of all functions which can reach any of the target_functions.
"""
# Create our results list
results = set()
# Loop for each target function
for target_function in target_functions:
results = results.union(__find_target_paths(slither, target_function))
return results

@ -1,3 +1,4 @@
import os
import logging
import argparse
import sys
@ -6,9 +7,12 @@ from slither import Slither
from .compare_variables_order import compare_variables_order_implementation, compare_variables_order_proxy
from .compare_function_ids import compare_function_ids
from .check_initialization import check_initialization
logging.basicConfig()
logger = logging.getLogger("Slither-check-upgradability")
logging.getLogger("Slither-check-upgradability").setLevel(logging.INFO)
logging.getLogger("Slither").setLevel(logging.INFO)
def parse_args():
@ -35,23 +39,23 @@ def parse_args():
def main():
args = parse_args()
proxy = Slither(vars(args)['proxy.sol'], solc=args.solc)
proxy_filename = vars(args)['proxy.sol']
proxy = Slither(proxy_filename, is_truffle=os.path.isdir(proxy_filename), solc=args.solc, disable_solc_warnings=True)
proxy_name = args.ProxyName
v1 = Slither(vars(args)['implem.sol'], solc=args.solc)
v1_name = args.ContractName
last_contract = v1
last_name = v1_name
if args.new_version:
v2 = Slither(args.new_version, solc=args.solc)
last_contract = v2
if args.new_contract_name:
last_name = args.new_contract_name
compare_function_ids(last_contract, proxy)
compare_variables_order_proxy(last_contract, last_name, proxy, proxy_name)
v1_filename = vars(args)['implem.sol']
v1 = Slither(v1_filename, is_truffle=os.path.isdir(v1_filename), solc=args.solc, disable_solc_warnings=True)
v1_name = args.ContractName
if args.new_version:
compare_variables_order_implementation(v1, v1_name, v2, last_name)
check_initialization(v1)
if not args.new_version:
compare_function_ids(v1, v1_name, proxy, proxy_name)
compare_variables_order_proxy(v1, v1_name, proxy, proxy_name)
else:
v2 = Slither(args.new_version, is_truffle=os.path.isdir(args.new_version), solc=args.solc, disable_solc_warnings=True)
v2_name = v1_name if not args.new_contract_name else args.new_contract_name
check_initialization(v2)
compare_function_ids(v2, v2_name, proxy, proxy_name)
compare_variables_order_proxy(v2, v2_name, proxy, proxy_name)
compare_variables_order_implementation(v1, v1_name, v2, v2_name)

@ -0,0 +1,78 @@
import logging
from slither import Slither
from slither.slithir.operations import InternalCall
from slither.utils.colors import green,red
from slither.utils.colors import red, yellow, green
logger = logging.getLogger("CheckInitialization")
logger.setLevel(logging.INFO)
class MultipleInitTarget(Exception):
pass
def _get_initialize_functions(contract):
return [f for father in contract.inheritance + [contract] for f in father.functions_not_inherited if f.name == 'initialize']
def _get_all_internal_calls(function):
all_ir = function.all_slithir_operations()
return [i.function for i in all_ir if isinstance(i, InternalCall) and i.function_name == "initialize"]
def _get_most_derived_init(contract):
for c in [contract] + contract.inheritance:
init_functions = [f for f in c.functions_not_inherited if f.name == 'initialize']
if len(init_functions) > 1:
raise MultipleInitTarget
if init_functions:
return init_functions[0]
return None
def check_initialization(s):
initializable = s.get_contract_from_name('Initializable')
if initializable is None:
logger.info(yellow('Initializable contract not found, the contract does not follow a standard initalization schema.'))
return
initializer = initializable.get_modifier_from_signature('initializer()')
init_info = ''
double_calls_found = False
missing_call = False
initializer_modifier_missing = False
for contract in s.contracts:
if initializable in contract.inheritance:
all_init_functions = _get_initialize_functions(contract)
for f in all_init_functions:
if not initializer in f.modifiers:
initializer_modifier_missing = True
logger.info(red(f'{f.contract.name}.{f.name} does not call initializer'))
most_derived_init = _get_most_derived_init(contract)
if most_derived_init is None:
init_info += f'{contract.name} has no initialize function\n'
continue
else:
init_info += f'{contract.name} needs to be initialized by {most_derived_init.full_name}\n'
all_init_functions_called = _get_all_internal_calls(most_derived_init) + [most_derived_init]
missing_calls = [f for f in all_init_functions if not f in all_init_functions_called]
for f in missing_calls:
logger.info(red(f'Missing call to {f.contract.name}.{f.name} in {contract.name}'))
missing_call = True
double_calls = list(set([f for f in all_init_functions_called if all_init_functions_called.count(f) > 1]))
for f in double_calls:
logger.info(red(f'{f.contract.name + "." + f.full_name} is called multiple time in {contract.name}'))
double_calls_found = True
if not initializer_modifier_missing:
logger.info(green('All the init functions have the initiliazer modifier'))
if not double_calls_found:
logger.info(green('No double call to init functions found'))
if not missing_call:
logger.info(green('No missing call to an init function found'))
logger.info(green('Check the deployement script to ensure that these functions are called:\n'+ init_info))

@ -11,21 +11,27 @@ from slither.utils.colors import red, green
logger = logging.getLogger("CompareFunctions")
logger.setLevel(logging.INFO)
def get_signatures(s):
functions = [contract.functions for contract in s.contracts_derived]
functions = [item for sublist in functions for item in sublist]
functions = [f.full_name for f in functions if f.visibility in ['public', 'external']]
def get_signatures(c):
functions = c.functions
functions = [f.full_name for f in functions if f.visibility in ['public', 'external'] and not f.is_constructor]
variables = [contract.state_variables for contract in s.contracts_derived]
variables = [item for sublist in variables for item in sublist]
variables = c.state_variables
variables = [variable.name+ '()' for variable in variables if variable.visibility in ['public']]
return list(set(functions+variables))
def compare_function_ids(implem, proxy):
def compare_function_ids(implem, implem_name, proxy, proxy_name):
implem_contract = implem.get_contract_from_name(implem_name)
if implem_contract is None:
logger.info(red(f'{implem_name} not found in {implem.filename}'))
return
proxy_contract = proxy.get_contract_from_name(proxy_name)
if proxy_contract is None:
logger.info(red(f'{proxy_name} not found in {proxy.filename}'))
return
signatures_implem = get_signatures(implem)
signatures_proxy = get_signatures(proxy)
signatures_implem = get_signatures(implem_contract)
signatures_proxy = get_signatures(proxy_contract)
signatures_ids_implem = {get_function_id(s): s for s in signatures_implem}
signatures_ids_proxy = {get_function_id(s): s for s in signatures_proxy}

@ -69,7 +69,7 @@ def compare_variables_order_proxy(implem, implem_name, proxy, proxy_name):
found = False
for idx in range(0, len(order_proxy)):
(proxy_name, proxy_type) = order_proxy[idx]
if len(order_proxy) < idx:
if len(order_implem) <= idx:
logger.info(red('Extra variable in the proxy: {} {}'.format(proxy_name, proxy_type)))
continue
(implem_name, implem_type) = order_implem[idx]

Loading…
Cancel
Save