Merge branch 'dev' into 0xalpharush-patch-1

pull/1083/head
Josselin 3 years ago
commit 4b188a0e17
  1. 3
      .github/workflows/black.yml
  2. 3
      .github/workflows/linter.yml
  3. 3
      .github/workflows/pylint.yml
  4. 2
      README.md
  5. 5
      slither/__main__.py
  6. 80
      slither/analyses/data_dependency/data_dependency.py
  7. 1
      slither/core/declarations/solidity_variables.py
  8. 16
      slither/core/expressions/binary_operation.py
  9. 2
      slither/core/expressions/expression.py
  10. 2
      slither/core/expressions/expression_typed.py
  11. 5
      slither/core/scope/scope.py
  12. 4
      slither/core/variables/top_level_variable.py
  13. 2
      slither/detectors/abstract_detector.py
  14. 3
      slither/detectors/functions/dead_code.py
  15. 2
      slither/detectors/operations/missing_zero_address_validation.py
  16. 11
      slither/detectors/reentrancy/reentrancy.py
  17. 70
      slither/detectors/statements/unprotected_upgradeable.py
  18. 22
      slither/printers/abstract_printer.py
  19. 1
      slither/printers/guidance/echidna.py
  20. 2
      slither/printers/summary/modifier_calls.py
  21. 12
      slither/slithir/convert.py
  22. 2
      slither/slithir/operations/member.py
  23. 2
      slither/slithir/utils/ssa.py
  24. 2
      slither/slithir/utils/utils.py
  25. 18
      slither/solc_parsing/expressions/find_variable.py
  26. 10
      slither/solc_parsing/slither_compilation_unit_solc.py
  27. 11
      slither/solc_parsing/solidity_types/type_parsing.py
  28. 32
      slither/solc_parsing/variables/top_level_variable.py
  29. 50
      slither/solc_parsing/yul/parse_yul.py
  30. 3
      slither/tools/properties/properties/erc20.py
  31. 12
      slither/utils/command_line.py
  32. BIN
      tests/ast-parsing/compile/assembly-0.8.12-compact.zip
  33. BIN
      tests/ast-parsing/compile/assignment-0.8.12-compact.zip
  34. BIN
      tests/ast-parsing/compile/binaryoperation-0.8.12-compact.zip
  35. BIN
      tests/ast-parsing/compile/break-0.8.12-compact.zip
  36. BIN
      tests/ast-parsing/compile/call_to_variable-0.8.12-compact.zip
  37. BIN
      tests/ast-parsing/compile/comment-0.8.12-compact.zip
  38. BIN
      tests/ast-parsing/compile/conditional-0.8.12-compact.zip
  39. BIN
      tests/ast-parsing/compile/continue-0.8.12-compact.zip
  40. BIN
      tests/ast-parsing/compile/contract-0.8.12-compact.zip
  41. BIN
      tests/ast-parsing/compile/custom_error-0.8.12-compact.zip
  42. BIN
      tests/ast-parsing/compile/dowhile-0.8.12-compact.zip
  43. BIN
      tests/ast-parsing/compile/emit-0.8.12-compact.zip
  44. BIN
      tests/ast-parsing/compile/enum-0.8.12-compact.zip
  45. BIN
      tests/ast-parsing/compile/event-0.8.12-compact.zip
  46. BIN
      tests/ast-parsing/compile/for-0.8.12-compact.zip
  47. BIN
      tests/ast-parsing/compile/function-0.8.12-compact.zip
  48. BIN
      tests/ast-parsing/compile/functioncall-0.8.12-compact.zip
  49. BIN
      tests/ast-parsing/compile/if-0.8.12-compact.zip
  50. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.0-legacy.zip
  51. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.1-legacy.zip
  52. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.10-legacy.zip
  53. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.11-legacy.zip
  54. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.12-compact.zip
  55. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.12-legacy.zip
  56. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.13-compact.zip
  57. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.13-legacy.zip
  58. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.14-compact.zip
  59. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.14-legacy.zip
  60. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.15-compact.zip
  61. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.15-legacy.zip
  62. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.16-compact.zip
  63. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.16-legacy.zip
  64. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.17-compact.zip
  65. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.17-legacy.zip
  66. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.18-compact.zip
  67. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.18-legacy.zip
  68. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.19-compact.zip
  69. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.19-legacy.zip
  70. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.2-legacy.zip
  71. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.20-compact.zip
  72. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.20-legacy.zip
  73. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.21-compact.zip
  74. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.21-legacy.zip
  75. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.22-compact.zip
  76. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.22-legacy.zip
  77. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.23-compact.zip
  78. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.23-legacy.zip
  79. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.24-compact.zip
  80. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.24-legacy.zip
  81. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.25-compact.zip
  82. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.25-legacy.zip
  83. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.26-compact.zip
  84. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.26-legacy.zip
  85. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.3-legacy.zip
  86. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.4-legacy.zip
  87. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.5-legacy.zip
  88. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.6-legacy.zip
  89. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.7-legacy.zip
  90. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.8-legacy.zip
  91. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.4.9-legacy.zip
  92. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.5.0-compact.zip
  93. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.5.0-legacy.zip
  94. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.5.1-compact.zip
  95. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.5.1-legacy.zip
  96. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.5.10-compact.zip
  97. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.5.10-legacy.zip
  98. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.5.11-compact.zip
  99. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.5.11-legacy.zip
  100. BIN
      tests/ast-parsing/compile/import_interface_with_struct_from_top_level-0.5.12-compact.zip
  101. Some files were not shown because too many files have changed in this diff Show More

@ -36,7 +36,7 @@ jobs:
cp pyproject.toml .github/linters
- name: Black
uses: docker://github/super-linter:v4
uses: github/super-linter/slim@v4.8.7
if: always()
env:
# run linter on everything to catch preexisting problems
@ -46,3 +46,4 @@ jobs:
# Run only black
VALIDATE_PYTHON_BLACK: true
PYTHON_BLACK_CONFIG_FILE: pyproject.toml
FILTER_REGEX_EXCLUDE: .*tests/.*.(json|zip|sol)

@ -36,7 +36,7 @@ jobs:
cp pyproject.toml .github/linters
- name: Lint everything else
uses: docker://github/super-linter:v4
uses: github/super-linter/slim@v4
if: always()
env:
# run linter on everything to catch preexisting problems
@ -58,3 +58,4 @@ jobs:
VALIDATE_JSCPD: false
VALIDATE_PYTHON_MYPY: false
SHELLCHECK_OPTS: "-e SC1090"
FILTER_REGEX_EXCLUDE: .*tests/.*.(json|zip|sol)

@ -36,7 +36,7 @@ jobs:
cp pyproject.toml .github/linters
- name: Pylint
uses: docker://github/super-linter:v4
uses: github/super-linter/slim@v4
if: always()
env:
# run linter on everything to catch preexisting problems
@ -47,3 +47,4 @@ jobs:
VALIDATE_PYTHON: true
VALIDATE_PYTHON_PYLINT: true
PYTHON_PYLINT_CONFIG_FILE: pyproject.toml
FILTER_REGEX_EXCLUDE: .*tests/.*.(json|zip|sol)

@ -40,7 +40,7 @@ Run Slither on a single file:
slither tests/uninitialized.sol
```
For additional configuration, see the [usage](https://github.com/trailofbits/slither/wiki/Usage) documentation.
For GitHub action integration, see [slither-action](https://github.com/marketplace/actions/slither-action). For additional configuration, see the [usage](https://github.com/trailofbits/slither/wiki/Usage) documentation.
Use [solc-select](https://github.com/crytic/solc-select) if your contracts require older versions of solc.

@ -64,6 +64,9 @@ def process_single(target, args, detector_classes, printer_classes):
ast = "--ast-compact-json"
if args.legacy_ast:
ast = "--ast-json"
if args.checklist:
args.show_ignored_findings = True
slither = Slither(target, ast_format=ast, **vars(args))
return _process(slither, detector_classes, printer_classes)
@ -462,7 +465,7 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
help="Provide a config file (default: slither.config.json)",
action="store",
dest="config_file",
default="slither.config.json",
default=None,
)
group_misc.add_argument(

@ -14,8 +14,9 @@ from slither.core.declarations import (
Structure,
)
from slither.core.declarations.solidity_import_placeholder import SolidityImportPlaceHolder
from slither.core.variables.top_level_variable import TopLevelVariable
from slither.core.variables.variable import Variable
from slither.slithir.operations import Index, OperationWithLValue, InternalCall
from slither.slithir.operations import Index, OperationWithLValue, InternalCall, Operation
from slither.slithir.variables import (
Constant,
LocalIRVariable,
@ -38,10 +39,14 @@ if TYPE_CHECKING:
###################################################################################
Variable_types = Union[Variable, SolidityVariable]
Context_types = Union[Contract, Function]
def is_dependent(
variable: Variable,
source: Variable,
context: Union[Contract, Function],
variable: Variable_types,
source: Variable_types,
context: Context_types,
only_unprotected: bool = False,
) -> bool:
"""
@ -69,9 +74,9 @@ def is_dependent(
def is_dependent_ssa(
variable: Variable,
source: Variable,
context: Union[Contract, Function],
variable: Variable_types,
source: Variable_types,
context: Context_types,
only_unprotected: bool = False,
) -> bool:
"""
@ -105,7 +110,12 @@ GENERIC_TAINT = {
}
def is_tainted(variable, context, only_unprotected=False, ignore_generic_taint=False):
def is_tainted(
variable: Variable_types,
context: Context_types,
only_unprotected: bool = False,
ignore_generic_taint: bool = False,
) -> bool:
"""
Args:
variable
@ -127,7 +137,12 @@ def is_tainted(variable, context, only_unprotected=False, ignore_generic_taint=F
)
def is_tainted_ssa(variable, context, only_unprotected=False, ignore_generic_taint=False):
def is_tainted_ssa(
variable: Variable_types,
context: Context_types,
only_unprotected: bool = False,
ignore_generic_taint: bool = False,
):
"""
Args:
variable
@ -150,8 +165,8 @@ def is_tainted_ssa(variable, context, only_unprotected=False, ignore_generic_tai
def get_dependencies(
variable: Variable,
context: Union[Contract, Function],
variable: Variable_types,
context: Context_types,
only_unprotected: bool = False,
) -> Set[Variable]:
"""
@ -170,7 +185,7 @@ def get_dependencies(
def get_all_dependencies(
context: Union[Contract, Function], only_unprotected: bool = False
context: Context_types, only_unprotected: bool = False
) -> Dict[Variable, Set[Variable]]:
"""
Return the dictionary of dependencies.
@ -187,8 +202,8 @@ def get_all_dependencies(
def get_dependencies_ssa(
variable: Variable,
context: Union[Contract, Function],
variable: Variable_types,
context: Context_types,
only_unprotected: bool = False,
) -> Set[Variable]:
"""
@ -207,7 +222,7 @@ def get_dependencies_ssa(
def get_all_dependencies_ssa(
context: Union[Contract, Function], only_unprotected: bool = False
context: Context_types, only_unprotected: bool = False
) -> Dict[Variable, Set[Variable]]:
"""
Return the dictionary of dependencies.
@ -249,9 +264,9 @@ KEY_INPUT_SSA = "DATA_DEPENDENCY_INPUT_SSA"
###################################################################################
def pprint_dependency(context):
def pprint_dependency(caller_context: Context_types) -> None:
print("#### SSA ####")
context = context.context
context = caller_context.context
for k, values in context[KEY_SSA].items():
print("{} ({}):".format(k, id(k)))
for v in values:
@ -272,7 +287,7 @@ def pprint_dependency(context):
###################################################################################
def compute_dependency(compilation_unit: "SlitherCompilationUnit"):
def compute_dependency(compilation_unit: "SlitherCompilationUnit") -> None:
compilation_unit.context[KEY_INPUT] = set()
compilation_unit.context[KEY_INPUT_SSA] = set()
@ -280,14 +295,16 @@ def compute_dependency(compilation_unit: "SlitherCompilationUnit"):
compute_dependency_contract(contract, compilation_unit)
def compute_dependency_contract(contract, compilation_unit: "SlitherCompilationUnit"):
def compute_dependency_contract(
contract: Contract, compilation_unit: "SlitherCompilationUnit"
) -> None:
if KEY_SSA in contract.context:
return
contract.context[KEY_SSA] = {}
contract.context[KEY_SSA_UNPROTECTED] = {}
for function in contract.functions + contract.modifiers:
for function in contract.functions + list(contract.modifiers):
compute_dependency_function(function)
propagate_function(contract, function, KEY_SSA, KEY_NON_SSA)
@ -302,7 +319,9 @@ def compute_dependency_contract(contract, compilation_unit: "SlitherCompilationU
propagate_contract(contract, KEY_SSA_UNPROTECTED, KEY_NON_SSA_UNPROTECTED)
def propagate_function(contract, function, context_key, context_key_non_ssa):
def propagate_function(
contract: Contract, function: Function, context_key: str, context_key_non_ssa: str
) -> None:
transitive_close_dependencies(function, context_key, context_key_non_ssa)
# Propage data dependency
data_depencencies = function.context[context_key]
@ -313,7 +332,9 @@ def propagate_function(contract, function, context_key, context_key_non_ssa):
contract.context[context_key][key].union(values)
def transitive_close_dependencies(context, context_key, context_key_non_ssa):
def transitive_close_dependencies(
context: Context_types, context_key: str, context_key_non_ssa: str
) -> None:
# transitive closure
changed = True
keys = context.context[context_key].keys()
@ -336,11 +357,11 @@ def transitive_close_dependencies(context, context_key, context_key_non_ssa):
context.context[context_key_non_ssa] = convert_to_non_ssa(context.context[context_key])
def propagate_contract(contract, context_key, context_key_non_ssa):
def propagate_contract(contract: Contract, context_key: str, context_key_non_ssa: str) -> None:
transitive_close_dependencies(contract, context_key, context_key_non_ssa)
def add_dependency(lvalue, function, ir, is_protected):
def add_dependency(lvalue: Variable, function: Function, ir: Operation, is_protected: bool) -> None:
if not lvalue in function.context[KEY_SSA]:
function.context[KEY_SSA][lvalue] = set()
if not is_protected:
@ -361,7 +382,7 @@ def add_dependency(lvalue, function, ir, is_protected):
]
def compute_dependency_function(function):
def compute_dependency_function(function: Function) -> None:
if KEY_SSA in function.context:
return
@ -386,7 +407,7 @@ def compute_dependency_function(function):
)
def convert_variable_to_non_ssa(v):
def convert_variable_to_non_ssa(v: Variable_types) -> Variable_types:
if isinstance(
v,
(
@ -410,14 +431,17 @@ def convert_variable_to_non_ssa(v):
Function,
Type,
SolidityImportPlaceHolder,
TopLevelVariable,
),
)
return v
def convert_to_non_ssa(data_depencies):
def convert_to_non_ssa(
data_depencies: Dict[Variable_types, Set[Variable_types]]
) -> Dict[Variable_types, Set[Variable_types]]:
# Need to create new set() as its changed during iteration
ret = {}
ret: Dict[Variable_types, Set[Variable_types]] = {}
for (k, values) in data_depencies.items():
var = convert_variable_to_non_ssa(k)
if not var in ret:

@ -71,6 +71,7 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"abi.encodeWithSelector()": ["bytes"],
"abi.encodeWithSignature()": ["bytes"],
"bytes.concat()": ["bytes"],
"string.concat()": ["string"],
# abi.decode returns an a list arbitrary types
"abi.decode()": [],
"type(address)": [],

@ -40,8 +40,11 @@ class BinaryOperationType(Enum):
GREATER_SIGNED = 22
RIGHT_SHIFT_ARITHMETIC = 23
# pylint: disable=too-many-branches
@staticmethod
def get_type(operation_type: "BinaryOperation"): # pylint: disable=too-many-branches
def get_type(
operation_type: "BinaryOperation",
) -> "BinaryOperationType":
if operation_type == "**":
return BinaryOperationType.POWER
if operation_type == "*":
@ -93,7 +96,7 @@ class BinaryOperationType(Enum):
raise SlitherCoreError("get_type: Unknown operation type {})".format(operation_type))
def __str__(self): # pylint: disable=too-many-branches
def __str__(self) -> str: # pylint: disable=too-many-branches
if self == BinaryOperationType.POWER:
return "**"
if self == BinaryOperationType.MULTIPLICATION:
@ -146,7 +149,12 @@ class BinaryOperationType(Enum):
class BinaryOperation(ExpressionTyped):
def __init__(self, left_expression, right_expression, expression_type):
def __init__(
self,
left_expression: Expression,
right_expression: Expression,
expression_type: BinaryOperationType,
) -> None:
assert isinstance(left_expression, Expression)
assert isinstance(right_expression, Expression)
super().__init__()
@ -169,5 +177,5 @@ class BinaryOperation(ExpressionTyped):
def type(self) -> BinaryOperationType:
return self._type
def __str__(self):
def __str__(self) -> str:
return str(self.expression_left) + " " + str(self.type) + " " + str(self.expression_right)

@ -2,7 +2,7 @@ from slither.core.source_mapping.source_mapping import SourceMapping
class Expression(SourceMapping):
def __init__(self):
def __init__(self) -> None:
super().__init__()
self._is_lvalue = False

@ -7,7 +7,7 @@ if TYPE_CHECKING:
class ExpressionTyped(Expression): # pylint: disable=too-few-public-methods
def __init__(self):
def __init__(self) -> None:
super().__init__()
self._type: Optional["Type"] = None

@ -6,6 +6,7 @@ from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.structure_top_level import StructureTopLevel
from slither.core.variables.top_level_variable import TopLevelVariable
from slither.slithir.variables import Constant
@ -36,6 +37,7 @@ class FileScope:
self.imports: Set[Import] = set()
self.pragmas: Set[Pragma] = set()
self.structures: Dict[str, StructureTopLevel] = {}
self.variables: Dict[str, TopLevelVariable] = {}
def add_accesible_scopes(self) -> bool:
"""
@ -69,6 +71,9 @@ class FileScope:
if not _dict_contain(new_scope.structures, self.structures):
self.structures.update(new_scope.structures)
learn_something = True
if not _dict_contain(new_scope.variables, self.variables):
self.variables.update(new_scope.variables)
learn_something = True
return learn_something

@ -5,12 +5,14 @@ from slither.core.variables.variable import Variable
if TYPE_CHECKING:
from slither.core.cfg.node import Node
from slither.core.scope.scope import FileScope
class TopLevelVariable(TopLevel, Variable):
def __init__(self):
def __init__(self, scope: "FileScope"):
super().__init__()
self._node_initialization: Optional["Node"] = None
self.file_scope = scope
# endregion
###################################################################################

@ -208,6 +208,8 @@ class AbstractDetector(metaclass=abc.ABCMeta):
return [r for (idx, r) in enumerate(results) if idx not in indexes_converted]
except ValueError:
self.logger.error(yellow("Malformed input. Example of valid input: 0,1,2,3"))
results = sorted(results, key=lambda x: x["id"])
return results
@property

@ -68,6 +68,9 @@ contract Contract{
function.contract_declarer.is_from_dependency()
):
continue
# Continue if the functon is not implemented because it means the contract is abstract
if not function.is_implemented:
continue
info = [function, " is never used and should be removed\n"]
res = self.generate_result(info)
results.append(res)

@ -40,7 +40,7 @@ contract C {
}
}
```
Bob calls `updateOwner` without specifying the `newOwner`, soBob loses ownership of the contract.
Bob calls `updateOwner` without specifying the `newOwner`, so Bob loses ownership of the contract.
"""
# endregion wiki_exploit_scenario

@ -283,11 +283,12 @@ class Reentrancy(AbstractDetector):
def detect_reentrancy(self, contract):
for function in contract.functions_and_modifiers_declared:
if function.is_implemented:
if self.KEY in function.context:
continue
self._explore(function.entry_point, [])
function.context[self.KEY] = True
if not function.is_constructor:
if function.is_implemented:
if self.KEY in function.context:
continue
self._explore(function.entry_point, [])
function.context[self.KEY] = True
def _detect(self):
""""""

@ -21,6 +21,14 @@ def _can_be_destroyed(contract) -> List[Function]:
return targets
def _has_initializer_modifier(functions: List[Function]) -> bool:
for f in functions:
for m in f.modifiers:
if m.name == "initializer":
return True
return False
class UnprotectedUpgradeable(AbstractDetector):
ARGUMENT = "unprotected-upgrade"
@ -61,34 +69,38 @@ class UnprotectedUpgradeable(AbstractDetector):
for contract in self.compilation_unit.contracts_derived:
if contract.is_upgradeable:
functions_that_can_destroy = _can_be_destroyed(contract)
if functions_that_can_destroy:
initiliaze_functions = [f for f in contract.functions if f.name == "initialize"]
vars_init_ = [
init.all_state_variables_written() for init in initiliaze_functions
]
vars_init = [item for sublist in vars_init_ for item in sublist]
vars_init_in_constructors_ = [
f.all_state_variables_written() for f in contract.constructors
]
vars_init_in_constructors = [
item for sublist in vars_init_in_constructors_ for item in sublist
]
if vars_init and (set(vars_init) - set(vars_init_in_constructors)):
info = (
[
contract,
" is an upgradeable contract that does not protect its initiliaze functions: ",
]
+ initiliaze_functions
+ [
". Anyone can delete the contract with: ",
]
+ functions_that_can_destroy
)
res = self.generate_result(info)
results.append(res)
if not _has_initializer_modifier(contract.constructors):
functions_that_can_destroy = _can_be_destroyed(contract)
if functions_that_can_destroy:
initiliaze_functions = [
f for f in contract.functions if f.name == "initialize"
]
vars_init_ = [
init.all_state_variables_written() for init in initiliaze_functions
]
vars_init = [item for sublist in vars_init_ for item in sublist]
vars_init_in_constructors_ = [
f.all_state_variables_written() for f in contract.constructors
]
vars_init_in_constructors = [
item for sublist in vars_init_in_constructors_ for item in sublist
]
if vars_init and (set(vars_init) - set(vars_init_in_constructors)):
info = (
[
contract,
" is an upgradeable contract that does not protect its initiliaze functions: ",
]
+ initiliaze_functions
+ [
". Anyone can delete the contract with: ",
]
+ functions_that_can_destroy
)
res = self.generate_result(info)
results.append(res)
return results

@ -1,6 +1,13 @@
import abc
from logging import Logger
from typing import TYPE_CHECKING, Union, List, Optional, Dict
from slither.utils import output
from slither.utils.output import SupportedOutput
if TYPE_CHECKING:
from slither import Slither
class IncorrectPrinterInitialization(Exception):
@ -13,7 +20,7 @@ class AbstractPrinter(metaclass=abc.ABCMeta):
WIKI = ""
def __init__(self, slither, logger):
def __init__(self, slither: "Slither", logger: Logger) -> None:
self.slither = slither
self.contracts = slither.contracts
self.filename = slither.filename
@ -34,11 +41,15 @@ class AbstractPrinter(metaclass=abc.ABCMeta):
f"WIKI is not initialized {self.__class__.__name__}"
)
def info(self, info):
def info(self, info: str) -> None:
if self.logger:
self.logger.info(info)
def generate_output(self, info, additional_fields=None):
def generate_output(
self,
info: Union[str, List[Union[str, SupportedOutput]]],
additional_fields: Optional[Dict] = None,
) -> output.Output:
if additional_fields is None:
additional_fields = {}
printer_output = output.Output(info, additional_fields)
@ -47,6 +58,5 @@ class AbstractPrinter(metaclass=abc.ABCMeta):
return printer_output
@abc.abstractmethod
def output(self, filename):
"""TODO Documentation"""
return
def output(self, filename: str) -> output.Output:
pass

@ -391,6 +391,7 @@ class Echidna(AbstractPrinter):
"have_external_calls": external_calls,
"call_a_parameter": call_parameters,
"use_balance": use_balance,
"solc_versions": [unit.solc_version for unit in self.slither.compilation_units],
}
self.info(json.dumps(d, indent=4))

@ -38,6 +38,8 @@ class Modifiers(AbstractPrinter):
table.add_row([function.name, [m.name for m in set(modifiers)]])
txt += "\n" + str(table)
self.info(txt)
all_txt += txt
all_tables.append((contract.name, table))
res = self.generate_output(all_txt)
for name, table in all_tables:

@ -944,6 +944,18 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]): # pylint: dis
s.set_expression(ins.expression)
return s
if ins.ori.variable_left == ElementaryType("string") and ins.ori.variable_right == Constant(
"concat"
):
s = SolidityCall(
SolidityFunction("string.concat()"),
ins.nbr_arguments,
ins.lvalue,
ins.type_call,
)
s.set_expression(ins.expression)
return s
msgcall = HighLevelCall(
ins.ori.variable_left,
ins.ori.variable_right,

@ -22,7 +22,7 @@ class Member(OperationWithLValue):
# f.h(1);
# }
# }
# Can be an ElementaryType because of bytes.concat
# Can be an ElementaryType because of bytes.concat, string.concat
assert is_valid_rvalue(variable_left) or isinstance(
variable_left,
(Contract, Enum, Function, CustomError, SolidityImportPlaceHolder, ElementaryType),

@ -13,6 +13,7 @@ from slither.core.declarations.solidity_import_placeholder import SolidityImport
from slither.core.solidity_types.type import Type
from slither.core.variables.local_variable import LocalVariable
from slither.core.variables.state_variable import StateVariable
from slither.core.variables.top_level_variable import TopLevelVariable
from slither.slithir.operations import (
Assignment,
Binary,
@ -617,6 +618,7 @@ def get(
Function,
Type,
SolidityImportPlaceHolder,
TopLevelVariable,
),
) # type for abi.decode(.., t)
return variable

@ -2,6 +2,7 @@ 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.core.variables.top_level_variable import TopLevelVariable
from slither.slithir.variables.temporary import TemporaryVariable
from slither.slithir.variables.constant import Constant
@ -15,6 +16,7 @@ def is_valid_rvalue(v):
(
StateVariable,
LocalVariable,
TopLevelVariable,
TemporaryVariable,
Constant,
SolidityVariable,

@ -19,6 +19,7 @@ from slither.core.solidity_types import (
FunctionType,
MappingType,
)
from slither.core.variables.top_level_variable import TopLevelVariable
from slither.core.variables.variable import Variable
from slither.exceptions import SlitherError
from slither.solc_parsing.declarations.caller_context import CallerContextExpression
@ -98,7 +99,9 @@ def _find_variable_in_function_parser(
def _find_top_level(
var_name: str, scope: "FileScope"
) -> Tuple[Optional[Union[Enum, Structure, SolidityImportPlaceHolder, CustomError]], bool]:
) -> Tuple[
Optional[Union[Enum, Structure, SolidityImportPlaceHolder, CustomError, TopLevelVariable]], bool
]:
"""
Return the top level variable use, and a boolean indicating if the variable returning was cretead
If the variable was created, it has no source_mapping
@ -126,6 +129,9 @@ def _find_top_level(
if custom_error.solidity_signature == var_name:
return custom_error, False
if var_name in scope.variables:
return scope.variables[var_name], False
return None, False
@ -210,6 +216,8 @@ def _find_variable_init(
) -> Tuple[List[Contract], List["Function"], FileScope,]:
from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc
from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc
direct_contracts: List[Contract]
direct_functions_parser: List[Function]
@ -244,6 +252,14 @@ def _find_variable_init(
else:
assert isinstance(underlying_function, FunctionContract)
scope = underlying_function.contract.file_scope
elif isinstance(caller_context, StructureTopLevelSolc):
direct_contracts = []
direct_functions_parser = []
scope = caller_context.underlying_structure.file_scope
elif isinstance(caller_context, TopLevelVariableSolc):
direct_contracts = []
direct_functions_parser = []
scope = caller_context.underlying_variable.file_scope
else:
raise SlitherError(
f"{type(caller_context)} ({caller_context} is not valid for find_variable"

@ -239,12 +239,13 @@ class SlitherCompilationUnitSolc:
self._parse_enum(top_level_data, filename)
elif top_level_data[self.get_key()] == "VariableDeclaration":
var = TopLevelVariable()
var_parser = TopLevelVariableSolc(var, top_level_data)
var = TopLevelVariable(scope)
var_parser = TopLevelVariableSolc(var, top_level_data, self)
var.set_offset(top_level_data["src"], self._compilation_unit)
self._compilation_unit.variables_top_level.append(var)
self._variables_top_level_parser.append(var_parser)
scope.variables[var.name] = var
elif top_level_data[self.get_key()] == "FunctionDefinition":
scope = self.compilation_unit.get_scope(filename)
func = FunctionTopLevel(self._compilation_unit, scope)
@ -495,6 +496,7 @@ Please rename it, this name is reserved for Slither's internals"""
for lib in libraries:
self._analyze_struct_events(lib)
self._analyze_top_level_variables()
self._analyze_top_level_structures()
# Start with the contracts without inheritance
@ -580,9 +582,9 @@ Please rename it, this name is reserved for Slither's internals"""
def _analyze_top_level_variables(self):
try:
for var in self._variables_top_level_parser:
var.analyze(self)
var.analyze(var)
except (VariableNotFound, KeyError) as e:
raise SlitherException(f"Missing struct {e} during top level structure analyze") from e
raise SlitherException(f"Missing {e} during variable analyze") from e
def _analyze_params_top_level_function(self):
for func_parser in self._functions_top_level_parser:

@ -220,6 +220,7 @@ def parse_type(
from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc
sl: "SlitherCompilationUnit"
# Note: for convenicence top level functions use the same parser than function in contract
@ -245,9 +246,11 @@ def parse_type(
all_enums += enums_direct_access
contracts = sl.contracts
functions = []
elif isinstance(caller_context, (StructureTopLevelSolc, CustomErrorSolc)):
elif isinstance(caller_context, (StructureTopLevelSolc, CustomErrorSolc, TopLevelVariableSolc)):
if isinstance(caller_context, StructureTopLevelSolc):
scope = caller_context.underlying_structure.file_scope
elif isinstance(caller_context, TopLevelVariableSolc):
scope = caller_context.underlying_variable.file_scope
else:
assert isinstance(caller_context, CustomErrorSolc)
custom_error = caller_context.underlying_custom_error
@ -258,8 +261,10 @@ def parse_type(
scope = custom_error.contract.file_scope
next_context = caller_context.slither_parser
structures_direct_access = []
all_structures = scope.structures.values()
structures_direct_access = list(scope.structures.values())
all_structuress = [c.structures for c in scope.contracts.values()]
all_structures = [item for sublist in all_structuress for item in sublist]
all_structures += structures_direct_access
enums_direct_access = []
all_enums = scope.enums.values()
contracts = scope.contracts.values()

@ -1,12 +1,38 @@
from typing import Dict
from typing import Dict, TYPE_CHECKING
from slither.core.variables.top_level_variable import TopLevelVariable
from slither.solc_parsing.variables.variable_declaration import VariableDeclarationSolc
from slither.solc_parsing.declarations.caller_context import CallerContextExpression
if TYPE_CHECKING:
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
from slither.core.compilation_unit import SlitherCompilationUnit
class TopLevelVariableSolc(VariableDeclarationSolc):
def __init__(self, variable: TopLevelVariable, variable_data: Dict):
class TopLevelVariableSolc(VariableDeclarationSolc, CallerContextExpression):
def __init__(
self,
variable: TopLevelVariable,
variable_data: Dict,
slither_parser: "SlitherCompilationUnitSolc",
):
super().__init__(variable, variable_data)
self._slither_parser = slither_parser
@property
def is_compact_ast(self) -> bool:
return self._slither_parser.is_compact_ast
@property
def compilation_unit(self) -> "SlitherCompilationUnit":
return self._slither_parser.compilation_unit
def get_key(self) -> str:
return self._slither_parser.get_key()
@property
def slither_parser(self) -> "SlitherCompilationUnitSolc":
return self._slither_parser
@property
def underlying_variable(self) -> TopLevelVariable:

@ -730,6 +730,39 @@ def _check_for_state_variable_name(root: YulScope, potential_name: str) -> Optio
return None
def _parse_yul_magic_suffixes(name: str, root: YulScope) -> Optional[Expression]:
# check for magic suffixes
# TODO: the following leads to wrong IR
# Currently SlithIR doesnt support raw access to memory
# So things like .offset/.slot will return the variable
# Instaed of the actual offset/slot
if name.endswith("_slot") or name.endswith(".slot"):
potential_name = name[:-5]
variable_found = _check_for_state_variable_name(root, potential_name)
if variable_found:
return variable_found
var = root.function.get_local_variable_from_name(potential_name)
if var and var.is_storage:
return Identifier(var)
if name.endswith("_offset") or name.endswith(".offset"):
potential_name = name[:-7]
variable_found = _check_for_state_variable_name(root, potential_name)
if variable_found:
return variable_found
var = root.function.get_local_variable_from_name(potential_name)
if var and var.location == "calldata":
return Identifier(var)
if name.endswith(".length"):
# TODO: length should create a new IP operation LENGTH var
# The code below is an hotfix to allow slither to process length in yul
# Until we have a better support
potential_name = name[:-7]
var = root.function.get_local_variable_from_name(potential_name)
if var and var.location == "calldata":
return Identifier(var)
return None
def parse_yul_identifier(root: YulScope, _node: YulNode, ast: Dict) -> Optional[Expression]:
name = ast["name"]
@ -759,20 +792,9 @@ def parse_yul_identifier(root: YulScope, _node: YulNode, ast: Dict) -> Optional[
if func:
return Identifier(func.underlying)
# check for magic suffixes
if name.endswith("_slot") or name.endswith(".slot"):
potential_name = name[:-5]
variable_found = _check_for_state_variable_name(root, potential_name)
if variable_found:
return variable_found
var = root.function.get_local_variable_from_name(potential_name)
if var and var.is_storage:
return Identifier(var)
if name.endswith("_offset") or name.endswith(".offset"):
potential_name = name[:-7]
variable_found = _check_for_state_variable_name(root, potential_name)
if variable_found:
return variable_found
magic_suffix = _parse_yul_magic_suffixes(name, root)
if magic_suffix:
return magic_suffix
raise SlitherException(f"unresolved reference to identifier {name}")

@ -78,6 +78,7 @@ def generate_erc20(
if contract.compilation_unit.core.crytic_compile.type not in [
PlatformType.TRUFFLE,
PlatformType.SOLC,
PlatformType.BUILDER,
]:
logging.error(
f"{contract.compilation_unit.core.crytic_compile.type} not yet supported by slither-prop"
@ -162,7 +163,7 @@ def _initialization_recommendation(type_property: str) -> str:
# TODO: move this to crytic-compile
def _platform_to_output_dir(platform: AbstractPlatform) -> Path:
if platform.TYPE == PlatformType.TRUFFLE:
if platform.TYPE in [PlatformType.TRUFFLE, platform.TYPE == PlatformType.BUILDER]:
return Path(platform.target, "contracts", "crytic")
if platform.TYPE == PlatformType.SOLC:
return Path(platform.target).parent

@ -52,6 +52,15 @@ defaults_flag_in_config = {
def read_config_file(args):
# No config file was provided as an argument
if args.config_file is None:
# Check wether the default config file is present
if os.path.exists("slither.config.json"):
# The default file exists, use it
args.config_file = "slither.config.json"
else:
return
if os.path.isfile(args.config_file):
try:
with open(args.config_file, encoding="utf8") as f:
@ -70,6 +79,9 @@ def read_config_file(args):
logger.error(
red("Impossible to read {}, please check the file {}".format(args.config_file, e))
)
else:
logger.error(red("File {} is not a file or does not exist".format(args.config_file)))
logger.error(yellow("Falling back to the default settings..."))
def output_to_markdown(detector_classes, printer_classes, filter_wiki):

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save