Beispiel #1
0
 def _arbitrary_from(nodes: List[Node], results: List[Node]):
     """Finds instances of (safe)transferFrom that do not use msg.sender or address(this) as from parameter."""
     for node in nodes:
         for ir in node.irs:
             if (isinstance(ir, HighLevelCall)
                     and isinstance(ir.function, Function)
                     and ir.function.solidity_signature
                     == "transferFrom(address,address,uint256)"
                     and not (is_dependent(
                         ir.arguments[0],
                         SolidityVariableComposed("msg.sender"),
                         node.function.contract,
                     ) or is_dependent(
                         ir.arguments[0],
                         SolidityVariable("this"),
                         node.function.contract,
                     ))):
                 results.append(ir.node)
             elif (isinstance(ir, LibraryCall)
                   and ir.function.solidity_signature
                   == "safeTransferFrom(address,address,address,uint256)"
                   and not (is_dependent(
                       ir.arguments[1],
                       SolidityVariableComposed("msg.sender"),
                       node.function.contract,
                   ) or is_dependent(
                       ir.arguments[1],
                       SolidityVariable("this"),
                       node.function.contract,
                   ))):
                 results.append(ir.node)
Beispiel #2
0
    def can_reenter(self, callstack=None):
        """
        Must be called after slithIR analysis pass
        For Solidity > 0.5, filter access to public variables and constant/pure/view
        For call to this. check if the destination can re-enter
        :param callstack: check for recursion
        :return: bool
        """
        if self.is_static_call():
            return False
        # If there is a call to itself
        # We can check that the function called is
        # reentrancy-safe
        if self.destination == SolidityVariable("this"):
            if isinstance(self.function, Variable):
                return False
            # In case of recursion, return False
            callstack = [] if callstack is None else callstack
            if self.function in callstack:
                return False
            callstack = callstack + [self.function]
            if self.function.can_reenter(callstack):
                return True
        if isinstance(self.destination, Variable):
            if not self.destination.is_reentrant:
                return False

        return True
Beispiel #3
0
 def can_reenter(self, callstack=None):
     '''
     Must be called after slithIR analysis pass
     For Solidity > 0.5, filter access to public variables and constant/pure/view
     For call to this. check if the destination can re-enter
     :param callstack: check for recursion
     :return: bool
     '''
     # If solidity >0.5, STATICCALL is used
     if self.slither.solc_version and self.slither.solc_version >= '0.5.0':
         if isinstance(self.function, Function) and (self.function.view
                                                     or self.function.pure):
             return False
         if isinstance(self.function, Variable):
             return False
     # If there is a call to itself
     # We can check that the function called is
     # reentrancy-safe
     if self.destination == SolidityVariable('this'):
         if isinstance(self.function, Variable):
             return False
         # In case of recursion, return False
         callstack = [] if callstack is None else callstack
         if self.function in callstack:
             return False
         callstack = callstack + [self.function]
         if self.function.can_reenter(callstack):
             return True
     return True
Beispiel #4
0
def contains_bad_PRNG_sources(
        func: Function, blockhash_ret_values: List[Variable]) -> List[Node]:
    """
         Check if any node in function has a modulus operator and the first operand is dependent on block.timestamp, now or blockhash()
    Returns:
        (nodes)
    """
    ret = set()
    # pylint: disable=too-many-nested-blocks
    for node in func.nodes:
        for ir in node.irs_ssa:
            if isinstance(ir, Binary) and ir.type == BinaryType.MODULO:
                if is_dependent_ssa(
                        ir.variable_left,
                        SolidityVariableComposed("block.timestamp"),
                        func.contract) or is_dependent_ssa(
                            ir.variable_left, SolidityVariable("now"),
                            func.contract):
                    ret.add(node)
                    break

                for ret_val in blockhash_ret_values:
                    if is_dependent_ssa(ir.variable_left, ret_val,
                                        func.contract):
                        ret.add(node)
                        break
    return list(ret)
Beispiel #5
0
def _timestamp(func: Function) -> List[Node]:
    ret = set()
    for node in func.nodes:
        if node.contains_require_or_assert():
            for var in node.variables_read:
                if is_dependent(var, SolidityVariableComposed("block.timestamp"), func.contract):
                    ret.add(node)
                if is_dependent(var, SolidityVariable("now"), func.contract):
                    ret.add(node)
        for ir in node.irs:
            if isinstance(ir, Binary) and BinaryType.return_bool(ir.type):
                for var in ir.read:
                    if is_dependent(
                        var, SolidityVariableComposed("block.timestamp"), func.contract
                    ):
                        ret.add(node)
                    if is_dependent(var, SolidityVariable("now"), func.contract):
                        ret.add(node)
    return sorted(list(ret), key=lambda x: x.node_id)
Beispiel #6
0
class IncorrectStrictEquality(AbstractDetector):
    ARGUMENT = 'incorrect-equality'
    HELP = 'Dangerous strict equalities'
    IMPACT = DetectorClassification.MEDIUM
    CONFIDENCE = DetectorClassification.HIGH

    WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#dangerous-strict-equalities'

    WIKI_TITLE = 'Dangerous strict equalities'
    WIKI_DESCRIPTION = 'Use of strick equalities that can be easily manipulated by an attacker.'
    WIKI_EXPLOIT_SCENARIO = '''
```solidity
contract Crowdsale{
    function fund_reached() public returns(bool){
        return this.balance == 100 ether;
    }
```
`Crowdsale` relies on `fund_reached` to know when to stop the sale of tokens. `Crowdsale` reaches 100 ether. Bob sends 0.1 ether. As a result, `fund_reached` is always false and the crowdsale never ends.'''

    WIKI_RECOMMENDATION = '''Don't use strict equality to determine if an account has enough ethers or tokens.'''

    sources_taint = [
        SolidityVariable('now'),
        SolidityVariableComposed('block.number'),
        SolidityVariableComposed('block.timestamp')
    ]

    @staticmethod
    def is_direct_comparison(ir):
        return isinstance(ir, Binary) and ir.type == BinaryType.EQUAL

    @staticmethod
    def is_any_tainted(variables, taints, function):
        return any([
            is_dependent_ssa(var, taint, function.contract)
            for var in variables for taint in taints
        ])

    def taint_balance_equalities(self, functions):
        taints = []
        for func in functions:
            for node in func.nodes:
                for ir in node.irs_ssa:
                    if isinstance(ir, Balance):
                        taints.append(ir.lvalue)
                    if isinstance(ir, HighLevelCall):
                        #print(ir.function.full_name)
                        if isinstance(ir.function, Function) and\
                            ir.function.full_name == 'balanceOf(address)':
                            taints.append(ir.lvalue)
                        if isinstance(ir.function, StateVariable) and\
                            isinstance(ir.function.type, MappingType) and\
                            ir.function.name == 'balanceOf' and\
                            ir.function.type.type_from == ElementaryType('address') and\
                            ir.function.type.type_to == ElementaryType('uint256'):
                            taints.append(ir.lvalue)
                    if isinstance(ir, Assignment):
                        if ir.rvalue in self.sources_taint:
                            taints.append(ir.lvalue)

        return taints

    # Retrieve all tainted (node, function) pairs
    def tainted_equality_nodes(self, funcs, taints):
        results = dict()
        taints += self.sources_taint

        for func in funcs:
            for node in func.nodes:
                for ir in node.irs_ssa:

                    # Filter to only tainted equality (==) comparisons
                    if self.is_direct_comparison(ir) and self.is_any_tainted(
                            ir.used, taints, func):
                        if func not in results:
                            results[func] = []
                        results[func].append(node)

        return results

    def detect_strict_equality(self, contract):
        funcs = contract.all_functions_called + contract.modifiers

        # Taint all BALANCE accesses
        taints = self.taint_balance_equalities(funcs)

        # Accumulate tainted (node,function) pairs involved in strict equality (==) comparisons
        results = self.tainted_equality_nodes(funcs, taints)

        return results

    def detect(self):
        results = []

        for c in self.slither.contracts_derived:
            ret = self.detect_strict_equality(c)

            info = ''
            # sort ret to get deterministic results
            ret = sorted(list(ret.items()), key=lambda x: x[0].name)
            for f, nodes in ret:
                info += "{}.{} ({}) uses a dangerous strict equality:\n".format(
                    f.contract.name, f.name, f.source_mapping_str)

                # sort the nodes to get deterministic results
                nodes.sort(key=lambda x: x.node_id)
                for node in nodes:
                    info += "\t- {}\n".format(str(node.expression))

                json = self.generate_json_result(info)
                self.add_function_to_json(f, json)
                self.add_nodes_to_json(nodes, json)
                results.append(json)

            if info:
                self.log(info)

        return results
class IncorrectStrictEquality(AbstractDetector):
    ARGUMENT = "incorrect-equality"
    HELP = "Dangerous strict equalities"
    IMPACT = DetectorClassification.MEDIUM
    CONFIDENCE = DetectorClassification.HIGH

    WIKI = (
        "https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities"
    )

    WIKI_TITLE = "Dangerous strict equalities"
    WIKI_DESCRIPTION = "Use of strict equalities that can be easily manipulated by an attacker."
    WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Crowdsale{
    function fund_reached() public returns(bool){
        return this.balance == 100 ether;
    }
```
`Crowdsale` relies on `fund_reached` to know when to stop the sale of tokens.
`Crowdsale` reaches 100 Ether. Bob sends 0.1 Ether. As a result, `fund_reached` is always false and the `crowdsale` never ends."""

    WIKI_RECOMMENDATION = (
        """Don't use strict equality to determine if an account has enough Ether or tokens."""
    )

    sources_taint = [
        SolidityVariable("now"),
        SolidityVariableComposed("block.number"),
        SolidityVariableComposed("block.timestamp"),
    ]

    @staticmethod
    def is_direct_comparison(ir):
        return isinstance(ir, Binary) and ir.type == BinaryType.EQUAL

    @staticmethod
    def is_any_tainted(variables, taints, function) -> bool:
        return any((is_dependent_ssa(var, taint, function.contract)
                    for var in variables for taint in taints))

    def taint_balance_equalities(self, functions):
        taints = []
        for func in functions:
            for node in func.nodes:
                for ir in node.irs_ssa:
                    if isinstance(ir, Balance):
                        taints.append(ir.lvalue)
                    if isinstance(ir, HighLevelCall):
                        # print(ir.function.full_name)
                        if (isinstance(ir.function, Function) and
                                ir.function.full_name == "balanceOf(address)"):
                            taints.append(ir.lvalue)
                        if (isinstance(ir.function, StateVariable)
                                and isinstance(ir.function.type, MappingType)
                                and ir.function.name == "balanceOf"
                                and ir.function.type.type_from
                                == ElementaryType("address")
                                and ir.function.type.type_to
                                == ElementaryType("uint256")):
                            taints.append(ir.lvalue)
                    if isinstance(ir, Assignment):
                        if ir.rvalue in self.sources_taint:
                            taints.append(ir.lvalue)

        return taints

    # Retrieve all tainted (node, function) pairs
    def tainted_equality_nodes(self, funcs, taints):
        results = dict()
        taints += self.sources_taint

        for func in funcs:
            for node in func.nodes:
                for ir in node.irs_ssa:

                    # Filter to only tainted equality (==) comparisons
                    if self.is_direct_comparison(ir) and self.is_any_tainted(
                            ir.used, taints, func):
                        if func not in results:
                            results[func] = []
                        results[func].append(node)

        return results

    def detect_strict_equality(self, contract):
        funcs = contract.all_functions_called + contract.modifiers

        # Taint all BALANCE accesses
        taints = self.taint_balance_equalities(funcs)

        # Accumulate tainted (node,function) pairs involved in strict equality (==) comparisons
        results = self.tainted_equality_nodes(funcs, taints)

        return results

    def _detect(self):
        results = []

        for c in self.compilation_unit.contracts_derived:
            ret = self.detect_strict_equality(c)

            # sort ret to get deterministic results
            ret = sorted(list(ret.items()), key=lambda x: x[0].name)
            for f, nodes in ret:

                func_info = [f, " uses a dangerous strict equality:\n"]

                # sort the nodes to get deterministic results
                nodes.sort(key=lambda x: x.node_id)

                # Output each node with the function info header as a separate result.
                for node in nodes:
                    node_info = func_info + ["\t- ", node, "\n"]

                    res = self.generate_result(node_info)
                    results.append(res)

        return results
Beispiel #8
0
def find_variable(var_name, caller_context):

    if isinstance(caller_context, Contract):
        function = None
        contract = caller_context
    elif isinstance(caller_context, Function):
        function = caller_context
        contract = function.contract
    else:
        logger.error('Incorrect caller context')
        exit(-1)

    if function:
        func_variables = function.variables_as_dict()
        if var_name in func_variables:
            return func_variables[var_name]
        # A local variable can be a pointer
        # for example
        # function test(function(uint) internal returns(bool) t) interna{
        # Will have a local variable t which will match the signature
        # t(uint256)
        func_variables_ptr = {
            get_pointer_name(f): f
            for f in function.variables
        }
        if var_name and var_name in func_variables_ptr:
            return func_variables_ptr[var_name]

    contract_variables = contract.variables_as_dict()
    if var_name in contract_variables:
        return contract_variables[var_name]

    # A state variable can be a pointer
    conc_variables_ptr = {get_pointer_name(f): f for f in contract.variables}
    if var_name and var_name in conc_variables_ptr:
        return conc_variables_ptr[var_name]

    functions = contract.functions_as_dict()
    if var_name in functions:
        return functions[var_name]

    modifiers = contract.modifiers_as_dict()
    if var_name in modifiers:
        return modifiers[var_name]

    structures = contract.structures_as_dict()
    if var_name in structures:
        return structures[var_name]

    events = contract.events_as_dict()
    if var_name in events:
        return events[var_name]

    enums = contract.enums_as_dict()
    if var_name in enums:
        return enums[var_name]

    # If the enum is refered as its name rather than its canonicalName
    enums = {e.name: e for e in contract.enums}
    if var_name in enums:
        return enums[var_name]

    # Could refer to any enum
    all_enums = [c.enums_as_dict() for c in contract.slither.contracts]
    all_enums = {k: v for d in all_enums for k, v in d.items()}
    if var_name in all_enums:
        return all_enums[var_name]

    if var_name in SOLIDITY_VARIABLES:
        return SolidityVariable(var_name)

    if var_name in SOLIDITY_FUNCTIONS:
        return SolidityFunction(var_name)

    contracts = contract.slither.contracts_as_dict()
    if var_name in contracts:
        return contracts[var_name]

    raise VariableNotFound('Variable not found: {}'.format(var_name))
    def _find_read_write_call(self):  # pylint: disable=too-many-statements

        for ir in self.irs:

            self._slithir_vars |= {
                v
                for v in ir.read if self._is_valid_slithir_var(v)
            }

            if isinstance(ir, OperationWithLValue):
                var = ir.lvalue
                if var and self._is_valid_slithir_var(var):
                    self._slithir_vars.add(var)

            if not isinstance(ir, (Phi, Index, Member)):
                self._vars_read += [
                    v for v in ir.read if self._is_non_slithir_var(v)
                ]
                for var in ir.read:
                    if isinstance(var, ReferenceVariable):
                        self._vars_read.append(var.points_to_origin)
            elif isinstance(ir, (Member, Index)):
                var = ir.variable_left if isinstance(
                    ir, Member) else ir.variable_right
                if self._is_non_slithir_var(var):
                    self._vars_read.append(var)
                if isinstance(var, ReferenceVariable):
                    origin = var.points_to_origin
                    if self._is_non_slithir_var(origin):
                        self._vars_read.append(origin)

            if isinstance(ir, OperationWithLValue):
                if isinstance(ir, (Index, Member, Length)):
                    continue  # Don't consider Member and Index operations -> ReferenceVariable
                var = ir.lvalue
                if isinstance(var, ReferenceVariable):
                    var = var.points_to_origin
                if var and self._is_non_slithir_var(var):
                    self._vars_written.append(var)

            if isinstance(ir, InternalCall):
                self._internal_calls.append(ir.function)
            if isinstance(ir, SolidityCall):
                # TODO: consider removing dependancy of solidity_call to internal_call
                self._solidity_calls.append(ir.function)
                self._internal_calls.append(ir.function)
            if isinstance(ir, LowLevelCall):
                assert isinstance(ir.destination, (Variable, SolidityVariable))
                self._low_level_calls.append(
                    (ir.destination, ir.function_name.value))
            elif isinstance(ir,
                            HighLevelCall) and not isinstance(ir, LibraryCall):
                if isinstance(ir.destination.type, Contract):
                    self._high_level_calls.append(
                        (ir.destination.type, ir.function))
                elif ir.destination == SolidityVariable("this"):
                    self._high_level_calls.append(
                        (self.function.contract, ir.function))
                else:
                    try:
                        self._high_level_calls.append(
                            (ir.destination.type.type, ir.function))
                    except AttributeError as error:
                        #  pylint: disable=raise-missing-from
                        raise SlitherException(
                            f"Function not found on IR: {ir}.\nNode: {self} ({self.source_mapping_str})\nFunction: {self.function}\nPlease try compiling with a recent Solidity version. {error}"
                        )
            elif isinstance(ir, LibraryCall):
                assert isinstance(ir.destination, Contract)
                self._high_level_calls.append((ir.destination, ir.function))
                self._library_calls.append((ir.destination, ir.function))

        self._vars_read = list(set(self._vars_read))
        self._state_vars_read = [
            v for v in self._vars_read if isinstance(v, StateVariable)
        ]
        self._local_vars_read = [
            v for v in self._vars_read if isinstance(v, LocalVariable)
        ]
        self._solidity_vars_read = [
            v for v in self._vars_read if isinstance(v, SolidityVariable)
        ]
        self._vars_written = list(set(self._vars_written))
        self._state_vars_written = [
            v for v in self._vars_written if isinstance(v, StateVariable)
        ]
        self._local_vars_written = [
            v for v in self._vars_written if isinstance(v, LocalVariable)
        ]
        self._internal_calls = list(set(self._internal_calls))
        self._solidity_calls = list(set(self._solidity_calls))
        self._high_level_calls = list(set(self._high_level_calls))
        self._library_calls = list(set(self._library_calls))
        self._low_level_calls = list(set(self._low_level_calls))
Beispiel #10
0
def find_variable(var_name,
                  caller_context,
                  referenced_declaration=None,
                  is_super=False):
    # variable are looked from the contract declarer
    # functions can be shadowed, but are looked from the contract instance, rather than the contract declarer
    # the difference between function and variable come from the fact that an internal call, or an variable access
    # in a function does not behave similariy, for example in:
    # contract C{
    #   function f(){
    #     state_var = 1
    #     f2()
    #  }
    # state_var will refer to C.state_var, no mater if C is inherited
    # while f2() will refer to the function definition of the inherited contract (C.f2() in the context of C, or
    # the contract inheriting from C)
    # for events it's unclear what should be the behavior, as they can be shadowed, but there is not impact
    # structure/enums cannot be shadowed

    if isinstance(caller_context, Contract):
        function = None
        contract = caller_context
        contract_declarer = caller_context
    elif isinstance(caller_context, Function):
        function = caller_context
        contract = function.contract
        contract_declarer = function.contract_declarer
    else:
        raise ParsingError('Incorrect caller context')

    if function:
        # We look for variable declared with the referencedDeclaration attr
        func_variables = function.variables_renamed
        if referenced_declaration and referenced_declaration in func_variables:
            return func_variables[referenced_declaration]
        # If not found, check for name
        func_variables = function.variables_as_dict()
        if var_name in func_variables:
            return func_variables[var_name]
        # A local variable can be a pointer
        # for example
        # function test(function(uint) internal returns(bool) t) interna{
        # Will have a local variable t which will match the signature
        # t(uint256)
        func_variables_ptr = {
            get_pointer_name(f): f
            for f in function.variables
        }
        if var_name and var_name in func_variables_ptr:
            return func_variables_ptr[var_name]

    # variable are looked from the contract declarer
    contract_variables = contract_declarer.variables_as_dict()
    if var_name in contract_variables:
        return contract_variables[var_name]

    # A state variable can be a pointer
    conc_variables_ptr = {
        get_pointer_name(f): f
        for f in contract_declarer.variables
    }
    if var_name and var_name in conc_variables_ptr:
        return conc_variables_ptr[var_name]

    if is_super:
        getter_available = lambda f: f.functions_declared
        d = {f.canonical_name: f for f in contract.functions}
        functions = {
            f.full_name: f
            for f in contract_declarer.available_elements_from_inheritances(
                d, getter_available).values()
        }
    else:
        functions = contract.available_functions_as_dict()
    if var_name in functions:
        return functions[var_name]

    if is_super:
        getter_available = lambda m: m.modifiers_declared
        d = {m.canonical_name: m for m in contract.modifiers}
        modifiers = {
            m.full_name: m
            for m in contract_declarer.available_elements_from_inheritances(
                d, getter_available).values()
        }
    else:
        modifiers = contract.available_modifiers_as_dict()
    if var_name in modifiers:
        return modifiers[var_name]

    # structures are looked on the contract declarer
    structures = contract.structures_as_dict()
    if var_name in structures:
        return structures[var_name]

    events = contract.events_as_dict()
    if var_name in events:
        return events[var_name]

    enums = contract.enums_as_dict()
    if var_name in enums:
        return enums[var_name]

    # If the enum is refered as its name rather than its canonicalName
    enums = {e.name: e for e in contract.enums}
    if var_name in enums:
        return enums[var_name]

    # Could refer to any enum
    all_enums = [c.enums_as_dict() for c in contract.slither.contracts]
    all_enums = {k: v for d in all_enums for k, v in d.items()}
    if var_name in all_enums:
        return all_enums[var_name]

    if var_name in SOLIDITY_VARIABLES:
        return SolidityVariable(var_name)

    if var_name in SOLIDITY_FUNCTIONS:
        return SolidityFunction(var_name)

    contracts = contract.slither.contracts_as_dict()
    if var_name in contracts:
        return contracts[var_name]

    if referenced_declaration:
        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: {} (context {})'.format(
        var_name, caller_context))
Beispiel #11
0
    def _find_read_write_call(self):

        for ir in self.irs:

            self._slithir_vars |= set([v for v in ir.read if self._is_valid_slithir_var(v)])
            if isinstance(ir, OperationWithLValue):
                var = ir.lvalue
                if var and self._is_valid_slithir_var(var):
                    self._slithir_vars.add(var)

            if not isinstance(ir, (Phi, Index, Member)):
                self._vars_read += [v for v in ir.read if self._is_non_slithir_var(v)]
                for var in ir.read:
                    if isinstance(var, (ReferenceVariable)):
                        self._vars_read.append(var.points_to_origin)
            elif isinstance(ir, (Member, Index)):
                var = ir.variable_left if isinstance(ir, Member) else ir.variable_right
                if self._is_non_slithir_var(var):
                    self._vars_read.append(var)
                if isinstance(var, (ReferenceVariable)):
                    origin = var.points_to_origin
                    if self._is_non_slithir_var(origin):
                        self._vars_read.append(origin)

            if isinstance(ir, OperationWithLValue):
                if isinstance(ir, (Index, Member, Length, Balance)):
                    continue  # Don't consider Member and Index operations -> ReferenceVariable
                var = ir.lvalue
                if isinstance(var, (ReferenceVariable)):
                    var = var.points_to_origin
                if var and self._is_non_slithir_var(var):
                        self._vars_written.append(var)

            if isinstance(ir, InternalCall):
                self._internal_calls.append(ir.function)
            if isinstance(ir, SolidityCall):
                # TODO: consider removing dependancy of solidity_call to internal_call
                self._solidity_calls.append(ir.function)
                self._internal_calls.append(ir.function)
            if isinstance(ir, LowLevelCall):
                assert isinstance(ir.destination, (Variable, SolidityVariable))
                self._low_level_calls.append((ir.destination, ir.function_name.value))
            elif isinstance(ir, (HighLevelCall)) and not isinstance(ir, LibraryCall):
                if isinstance(ir.destination.type, Contract):
                    self._high_level_calls.append((ir.destination.type, ir.function))
                elif ir.destination == SolidityVariable('this'):
                    self._high_level_calls.append((self.function.contract, ir.function))
                else:
                    try:
                        self._high_level_calls.append((ir.destination.type.type, ir.function))
                    except AttributeError:
                        raise SlitherException(f'Function not found on {ir}. Please try compiling with a recent Solidity version.')
            elif isinstance(ir, LibraryCall):
                assert isinstance(ir.destination, Contract)
                self._high_level_calls.append((ir.destination, ir.function))
                self._library_calls.append((ir.destination, ir.function))

        self._vars_read = list(set(self._vars_read))
        self._state_vars_read = [v for v in self._vars_read if isinstance(v, StateVariable)]
        self._local_vars_read = [v for v in self._vars_read if isinstance(v, LocalVariable)]
        self._solidity_vars_read = [v for v in self._vars_read if isinstance(v, SolidityVariable)]
        self._vars_written = list(set(self._vars_written))
        self._state_vars_written = [v for v in self._vars_written if isinstance(v, StateVariable)]
        self._local_vars_written = [v for v in self._vars_written if isinstance(v, LocalVariable)]
        self._internal_calls = list(set(self._internal_calls))
        self._solidity_calls = list(set(self._solidity_calls))
        self._high_level_calls = list(set(self._high_level_calls))
        self._library_calls = list(set(self._library_calls))
        self._low_level_calls = list(set(self._low_level_calls))
def find_variable(
    var_name: str,
    caller_context: CallerContext,
    referenced_declaration: Optional[int] = None,
    is_super=False,
) -> Union[
    Variable,
    Function,
    Contract,
    SolidityVariable,
    SolidityFunction,
    Event,
    Enum,
    Structure,
]:
    from slither.solc_parsing.declarations.function import FunctionSolc
    from slither.solc_parsing.declarations.contract import ContractSolc

    # variable are looked from the contract declarer
    # functions can be shadowed, but are looked from the contract instance, rather than the contract declarer
    # the difference between function and variable come from the fact that an internal call, or an variable access
    # in a function does not behave similariy, for example in:
    # contract C{
    #   function f(){
    #     state_var = 1
    #     f2()
    #  }
    # state_var will refer to C.state_var, no mater if C is inherited
    # while f2() will refer to the function definition of the inherited contract (C.f2() in the context of C, or
    # the contract inheriting from C)
    # for events it's unclear what should be the behavior, as they can be shadowed, but there is not impact
    # structure/enums cannot be shadowed

    direct_contracts, direct_functions_parser, sl, sl_parser = _find_variable_init(caller_context)

    all_contracts = sl.contracts
    all_functions_parser = sl_parser.all_functions_and_modifiers_parser

    # Look for all references delcaration
    # First look only in the context of function/contract
    # Then look everywhere
    # Because functions are copied between contracts, two functions can have the same ref
    # So we need to first look with respect to the direct context

    ret = _find_variable_from_ref_declaration(
        referenced_declaration, direct_contracts, direct_functions_parser
    )
    if ret:
        return ret

    ret = _find_variable_from_ref_declaration(
        referenced_declaration, all_contracts, all_functions_parser
    )
    if ret:
        return ret

    function_parser: Optional[FunctionSolc] = (
        caller_context if isinstance(caller_context, FunctionSolc) else None
    )
    ret = _find_variable_in_function_parser(var_name, function_parser, referenced_declaration)
    if ret:
        return ret

    contract: Optional[Contract] = None
    contract_declarer: Optional[Contract] = None
    if isinstance(caller_context, ContractSolc):
        contract = caller_context.underlying_contract
        contract_declarer = caller_context.underlying_contract
    elif isinstance(caller_context, FunctionSolc):
        underlying_func = caller_context.underlying_function
        # If contract_parser is set to None, then underlying_function is a functionContract
        assert isinstance(underlying_func, FunctionContract)
        contract = underlying_func.contract
        contract_declarer = underlying_func.contract_declarer

    ret = _find_in_contract(var_name, contract, contract_declarer, is_super)
    if ret:
        return ret

    # Could refer to any enum
    all_enumss = [c.enums_as_dict for c in sl.contracts]
    all_enums = {k: v for d in all_enumss for k, v in d.items()}
    if var_name in all_enums:
        return all_enums[var_name]

    contracts = sl.contracts_as_dict
    if var_name in contracts:
        return contracts[var_name]

    if var_name in SOLIDITY_VARIABLES:
        return SolidityVariable(var_name)

    if var_name in SOLIDITY_FUNCTIONS:
        return SolidityFunction(var_name)

    # Top level must be at the end, if nothing else was found
    ret = _find_top_level(var_name, sl)
    if ret:
        return ret

    raise VariableNotFound("Variable not found: {} (context {})".format(var_name, caller_context))
Beispiel #13
0
def find_variable(
    var_name: str,
    caller_context: CallerContext,
    referenced_declaration: Optional[int] = None,
    is_super=False,
) -> Union[Variable, Function, Contract, SolidityVariable, SolidityFunction,
           Event, Enum, Structure]:
    from slither.solc_parsing.declarations.contract import ContractSolc
    from slither.solc_parsing.declarations.function import FunctionSolc

    # variable are looked from the contract declarer
    # functions can be shadowed, but are looked from the contract instance, rather than the contract declarer
    # the difference between function and variable come from the fact that an internal call, or an variable access
    # in a function does not behave similariy, for example in:
    # contract C{
    #   function f(){
    #     state_var = 1
    #     f2()
    #  }
    # state_var will refer to C.state_var, no mater if C is inherited
    # while f2() will refer to the function definition of the inherited contract (C.f2() in the context of C, or
    # the contract inheriting from C)
    # for events it's unclear what should be the behavior, as they can be shadowed, but there is not impact
    # structure/enums cannot be shadowed

    if isinstance(caller_context, ContractSolc):
        function: Optional[FunctionSolc] = None
        contract = caller_context.underlying_contract
        contract_declarer = caller_context.underlying_contract
    elif isinstance(caller_context, FunctionSolc):
        function = caller_context
        contract = function.underlying_function.contract
        contract_declarer = function.underlying_function.contract_declarer
    else:
        raise ParsingError("Incorrect caller context")

    if function:
        # We look for variable declared with the referencedDeclaration attr
        func_variables = function.variables_renamed
        if referenced_declaration and referenced_declaration in func_variables:
            return func_variables[referenced_declaration].underlying_variable
        # If not found, check for name
        func_variables = function.underlying_function.variables_as_dict
        if var_name in func_variables:
            return func_variables[var_name]
        # A local variable can be a pointer
        # for example
        # function test(function(uint) internal returns(bool) t) interna{
        # Will have a local variable t which will match the signature
        # t(uint256)
        func_variables_ptr = {
            get_pointer_name(f): f
            for f in function.underlying_function.variables
        }
        if var_name and var_name in func_variables_ptr:
            return func_variables_ptr[var_name]

    # variable are looked from the contract declarer
    contract_variables = contract_declarer.variables_as_dict
    if var_name in contract_variables:
        return contract_variables[var_name]

    # A state variable can be a pointer
    conc_variables_ptr = {
        get_pointer_name(f): f
        for f in contract_declarer.variables
    }
    if var_name and var_name in conc_variables_ptr:
        return conc_variables_ptr[var_name]

    if is_super:
        getter_available = lambda f: f.functions_declared
        d = {f.canonical_name: f for f in contract.functions}
        functions = {
            f.full_name: f
            for f in contract_declarer.available_elements_from_inheritances(
                d, getter_available).values()
        }
    else:
        functions = contract.available_functions_as_dict()
    if var_name in functions:
        return functions[var_name]

    if is_super:
        getter_available = lambda m: m.modifiers_declared
        d = {m.canonical_name: m for m in contract.modifiers}
        modifiers = {
            m.full_name: m
            for m in contract_declarer.available_elements_from_inheritances(
                d, getter_available).values()
        }
    else:
        modifiers = contract.available_modifiers_as_dict()
    if var_name in modifiers:
        return modifiers[var_name]

    # structures are looked on the contract declarer
    structures = contract.structures_as_dict
    if var_name in structures:
        return structures[var_name]

    structures_top_level = contract.slither.top_level_structures
    for st in structures_top_level:
        if st.name == var_name:
            return st

    events = contract.events_as_dict
    if var_name in events:
        return events[var_name]

    enums = contract.enums_as_dict
    if var_name in enums:
        return enums[var_name]

    enums_top_level = contract.slither.top_level_enums
    for enum in enums_top_level:
        if enum.name == var_name:
            return enum

    # If the enum is refered as its name rather than its canonicalName
    enums = {e.name: e for e in contract.enums}
    if var_name in enums:
        return enums[var_name]

    # Could refer to any enum
    all_enums = [c.enums_as_dict for c in contract.slither.contracts]
    all_enums = {k: v for d in all_enums for k, v in d.items()}
    if var_name in all_enums:
        return all_enums[var_name]

    if var_name in SOLIDITY_VARIABLES:
        return SolidityVariable(var_name)

    if var_name in SOLIDITY_FUNCTIONS:
        return SolidityFunction(var_name)

    contracts = contract.slither.contracts_as_dict
    if var_name in contracts:
        return contracts[var_name]

    if referenced_declaration:
        # id of the contracts is the referenced declaration
        # This is not true for the functions, as we dont always have the referenced_declaration
        # But maybe we could? (TODO)
        for contract_candidate in contract.slither.contracts:
            if contract_candidate.id == referenced_declaration:
                return contract_candidate
        for function_candidate in caller_context.slither_parser.all_functions_parser:
            if function_candidate.referenced_declaration == referenced_declaration:
                return function_candidate.underlying_function

    raise VariableNotFound("Variable not found: {} (context {})".format(
        var_name, caller_context))
Beispiel #14
0
def find_variable(
    var_name: str,
    caller_context: CallerContextExpression,
    referenced_declaration: Optional[int] = None,
    is_super: bool = False,
) -> Tuple[
    Union[
        Variable,
        Function,
        Contract,
        SolidityVariable,
        SolidityFunction,
        Event,
        Enum,
        Structure,
        CustomError,
        TypeAlias,
    ],
    bool,
]:
    """
    Return the variable found and a boolean indicating if the variable was created
    If the variable was created, it has no source mapping, and it the caller must add it

    :param var_name:
    :type var_name:
    :param caller_context:
    :type caller_context:
    :param referenced_declaration:
    :type referenced_declaration:
    :param is_super:
    :type is_super:
    :return:
    :rtype:
    """
    from slither.solc_parsing.declarations.function import FunctionSolc
    from slither.solc_parsing.declarations.contract import ContractSolc

    # variable are looked from the contract declarer
    # functions can be shadowed, but are looked from the contract instance, rather than the contract declarer
    # the difference between function and variable come from the fact that an internal call, or an variable access
    # in a function does not behave similariy, for example in:
    # contract C{
    #   function f(){
    #     state_var = 1
    #     f2()
    #  }
    # state_var will refer to C.state_var, no mater if C is inherited
    # while f2() will refer to the function definition of the inherited contract (C.f2() in the context of C, or
    # the contract inheriting from C)
    # for events it's unclear what should be the behavior, as they can be shadowed, but there is not impact
    # structure/enums cannot be shadowed

    direct_contracts, direct_functions, current_scope = _find_variable_init(caller_context)
    # Only look for reference declaration in the direct contract, see comment at the end
    # Reference looked are split between direct and all
    # Because functions are copied between contracts, two functions can have the same ref
    # So we need to first look with respect to the direct context

    if var_name in current_scope.renaming:
        var_name = current_scope.renaming[var_name]

    if var_name in current_scope.user_defined_types:
        return current_scope.user_defined_types[var_name], False

    # Use ret0/ret1 to help mypy
    ret0 = _find_variable_from_ref_declaration(
        referenced_declaration, direct_contracts, direct_functions
    )
    if ret0:
        return ret0, False

    function_parser: Optional[FunctionSolc] = (
        caller_context if isinstance(caller_context, FunctionSolc) else None
    )
    ret1 = _find_variable_in_function_parser(var_name, function_parser, referenced_declaration)
    if ret1:
        return ret1, False

    contract: Optional[Contract] = None
    contract_declarer: Optional[Contract] = None
    if isinstance(caller_context, ContractSolc):
        contract = caller_context.underlying_contract
        contract_declarer = caller_context.underlying_contract
    elif isinstance(caller_context, FunctionSolc):
        underlying_func = caller_context.underlying_function
        if isinstance(underlying_func, FunctionContract):
            contract = underlying_func.contract
            contract_declarer = underlying_func.contract_declarer
        else:
            assert isinstance(underlying_func, FunctionTopLevel)

    ret = _find_in_contract(var_name, contract, contract_declarer, is_super)
    if ret:
        return ret, False

    # Could refer to any enum
    all_enumss = [c.enums_as_dict for c in current_scope.contracts.values()]
    all_enums = {k: v for d in all_enumss for k, v in d.items()}
    if var_name in all_enums:
        return all_enums[var_name], False

    contracts = current_scope.contracts
    if var_name in contracts:
        return contracts[var_name], False

    if var_name in SOLIDITY_VARIABLES:
        return SolidityVariable(var_name), False

    if var_name in SOLIDITY_FUNCTIONS:
        return SolidityFunction(var_name), False

    # Top level must be at the end, if nothing else was found
    ret, var_was_created = _find_top_level(var_name, current_scope)
    if ret:
        return ret, var_was_created

    # Look from reference declaration in all the contracts at the end
    # Because they are many instances where this can't be trusted
    # For example in
    # contract A{
    #     function _f() internal view returns(uint){
    #         return 1;
    #     }
    #
    #     function get() public view returns(uint){
    #         return _f();
    #     }
    # }
    #
    # contract B is A{
    #     function _f() internal view returns(uint){
    #         return 2;
    #     }
    #
    # }
    # get's AST will say that the ref declaration for _f() is A._f(), but in the context of B, its not

    ret = _find_variable_from_ref_declaration(
        referenced_declaration,
        list(current_scope.contracts.values()),
        list(current_scope.functions),
    )
    if ret:
        return ret, False

    raise VariableNotFound(f"Variable not found: {var_name} (context {contract})")
Beispiel #15
0
class IncorrectStrictEquality(AbstractDetector):
    ARGUMENT = 'incorrect-equality'
    HELP = 'Dangerous strict equalities'
    IMPACT = DetectorClassification.MEDIUM
    CONFIDENCE = DetectorClassification.HIGH

    WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#dangerous-strict-equalities'

    sources_taint = [SolidityVariable('now'),
                     SolidityVariableComposed('block.number'),
                     SolidityVariableComposed('block.timestamp')]

    @staticmethod
    def is_direct_comparison(ir):
        return isinstance(ir, Binary) and ir.type == BinaryType.EQUAL

    @staticmethod
    def is_any_tainted(variables, taints, function):
        return any([is_dependent_ssa(var, taint, function.contract) for var in variables for taint in taints])

    def taint_balance_equalities(self, functions):
        taints = []
        for func in functions:
            for node in func.nodes:
                for ir in node.irs_ssa:
                    if isinstance(ir, Balance):
                        taints.append(ir.lvalue)
                    if isinstance(ir, HighLevelCall):
                        #print(ir.function.full_name)
                        if isinstance(ir.function, Function) and\
                            ir.function.full_name == 'balanceOf(address)':
                                taints.append(ir.lvalue)
                        if isinstance(ir.function, StateVariable) and\
                            isinstance(ir.function.type, MappingType) and\
                            ir.function.name == 'balanceOf' and\
                            ir.function.type.type_from == ElementaryType('address') and\
                            ir.function.type.type_to == ElementaryType('uint256'):
                                taints.append(ir.lvalue)
                    if isinstance(ir, Assignment):
                        if ir.rvalue in self.sources_taint:
                            taints.append(ir.lvalue)

        return taints

    # Retrieve all tainted (node, function) pairs
    def tainted_equality_nodes(self, funcs, taints):
        results = dict()
        taints += self.sources_taint

        for func in funcs:
            for node in func.nodes:
                for ir in node.irs_ssa:

                    # Filter to only tainted equality (==) comparisons
                    if self.is_direct_comparison(ir) and self.is_any_tainted(ir.used, taints, func):
                        if func not in results:
                            results[func] = []
                        results[func].append(node)

        return results

    def detect_strict_equality(self, contract):
        funcs = contract.all_functions_called + contract.modifiers

        # Taint all BALANCE accesses
        taints = self.taint_balance_equalities(funcs)

        # Accumulate tainted (node,function) pairs involved in strict equality (==) comparisons
        results = self.tainted_equality_nodes(funcs, taints)

        return results

    def detect(self):
        results = []

        for c in self.slither.contracts_derived:
            ret = self.detect_strict_equality(c)

            info = ''
            # sort ret to get deterministic results
            ret = sorted(list(ret.items()), key=lambda x:x[0].name)
            for f, nodes in ret:
                info += "{}.{} ({}) uses a dangerous strict equality:\n".format(f.contract.name,
                                                                                f.name,
                                                                                f.source_mapping_str)

                # sort the nodes to get deterministic results
                nodes.sort(key=lambda x: x.node_id)
                for node in nodes:
                    info += "\t- {}\n".format(str(node.expression))

                json = self.generate_json_result(info)
                self.add_function_to_json(f, json)
                self.add_nodes_to_json(nodes, json)
                results.append(json)

            if info:
                self.log(info)

        return results