Use new *Calls API in detectors

pull/2555/head
Simone 3 months ago
parent 13b25e8756
commit 439010aefa
  1. 11
      slither/detectors/assembly/incorrect_return.py
  2. 10
      slither/detectors/assembly/return_instead_of_leave.py
  3. 19
      slither/detectors/compiler_bugs/array_by_reference.py
  4. 39
      slither/detectors/erc/erc20/arbitrary_send_erc20.py
  5. 11
      slither/detectors/functions/external_function.py
  6. 19
      slither/detectors/operations/encode_packed.py
  7. 7
      slither/detectors/operations/low_level_calls.py
  8. 10
      slither/detectors/statements/assert_state_change.py
  9. 8
      slither/detectors/statements/controlled_delegatecall.py
  10. 13
      slither/detectors/statements/return_bomb.py
  11. 23
      slither/detectors/statements/unprotected_upgradeable.py
  12. 7
      slither/detectors/variables/var_read_using_this.py

@ -21,10 +21,8 @@ def _assembly_node(function: Function) -> Optional[SolidityCall]:
""" """
for ir in function.all_slithir_operations(): for ir in function.all_solidity_calls():
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction( if ir.function == SolidityFunction("return(uint256,uint256)"):
"return(uint256,uint256)"
):
return ir return ir
return None return None
@ -71,9 +69,8 @@ The function will return 6 bytes starting from offset 5, instead of returning a
for c in self.contracts: for c in self.contracts:
for f in c.functions_and_modifiers_declared: for f in c.functions_and_modifiers_declared:
for node in f.nodes: for ir in f.internal_calls:
if node.sons: if ir.node.sons:
for ir in node.internal_calls:
function_called = ir.function function_called = ir.function
if isinstance(function_called, Function): if isinstance(function_called, Function):
found = _assembly_node(function_called) found = _assembly_node(function_called)

@ -6,7 +6,6 @@ from slither.detectors.abstract_detector import (
DetectorClassification, DetectorClassification,
DETECTOR_INFO, DETECTOR_INFO,
) )
from slither.slithir.operations import SolidityCall
from slither.utils.output import Output from slither.utils.output import Output
@ -42,12 +41,9 @@ The function will halt the execution, instead of returning a two uint."""
def _check_function(self, f: Function) -> List[Output]: def _check_function(self, f: Function) -> List[Output]:
results: List[Output] = [] results: List[Output] = []
for node in f.nodes: for ir in f.solidity_calls:
for ir in node.irs: if ir.function == SolidityFunction("return(uint256,uint256)"):
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction( info: DETECTOR_INFO = [f, " contains an incorrect call to return: ", ir.node, "\n"]
"return(uint256,uint256)"
):
info: DETECTOR_INFO = [f, " contains an incorrect call to return: ", node, "\n"]
json = self.generate_result(info) json = self.generate_result(info)
results.append(json) results.append(json)

@ -13,8 +13,6 @@ from slither.detectors.abstract_detector import (
from slither.core.solidity_types.array_type import ArrayType from slither.core.solidity_types.array_type import ArrayType
from slither.core.variables.state_variable import StateVariable from slither.core.variables.state_variable import StateVariable
from slither.core.variables.local_variable import LocalVariable from slither.core.variables.local_variable import LocalVariable
from slither.slithir.operations.high_level_call import HighLevelCall
from slither.slithir.operations.internal_call import InternalCall
from slither.core.cfg.node import Node from slither.core.cfg.node import Node
from slither.core.declarations.contract import Contract from slither.core.declarations.contract import Contract
from slither.core.declarations.function_contract import FunctionContract from slither.core.declarations.function_contract import FunctionContract
@ -117,16 +115,7 @@ As a result, Bob's usage of the contract is incorrect."""
# pylint: disable=too-many-nested-blocks # pylint: disable=too-many-nested-blocks
for contract in contracts: for contract in contracts:
for function in contract.functions_and_modifiers_declared: for function in contract.functions_and_modifiers_declared:
for node in function.nodes: for ir in [ir for _, ir in function.high_level_calls] + function.internal_calls:
# If this node has no expression, skip it.
if not node.expression:
continue
for ir in node.irs:
# Verify this is a high level call.
if not isinstance(ir, (HighLevelCall, InternalCall)):
continue
# Verify this references a function in our array modifying functions collection. # Verify this references a function in our array modifying functions collection.
if ir.function not in array_modifying_funcs: if ir.function not in array_modifying_funcs:
@ -144,10 +133,8 @@ As a result, Bob's usage of the contract is incorrect."""
if ( if (
isinstance(arg, StateVariable) isinstance(arg, StateVariable)
or (isinstance(arg, LocalVariable) and arg.location == "storage") or (isinstance(arg, LocalVariable) and arg.location == "storage")
) and ( ) and (isinstance(param.type, ArrayType) and param.location != "storage"):
isinstance(param.type, ArrayType) and param.location != "storage" results.append((ir.node, arg, ir.function))
):
results.append((node, arg, ir.function))
return results return results
def _detect(self) -> List[Output]: def _detect(self) -> List[Output]:

@ -3,7 +3,7 @@ from typing import List
from slither.analyses.data_dependency.data_dependency import is_dependent from slither.analyses.data_dependency.data_dependency import is_dependent
from slither.core.cfg.node import Node from slither.core.cfg.node import Node
from slither.core.compilation_unit import SlitherCompilationUnit from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.declarations import Contract, Function, SolidityVariableComposed from slither.core.declarations import Contract, Function, SolidityVariableComposed, FunctionContract
from slither.core.declarations.solidity_variables import SolidityVariable from slither.core.declarations.solidity_variables import SolidityVariable
from slither.slithir.operations import HighLevelCall, LibraryCall from slither.slithir.operations import HighLevelCall, LibraryCall
@ -44,47 +44,46 @@ class ArbitrarySendErc20:
"permit(address,address,uint256,uint256,uint8,bytes32,bytes32)" "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)"
in all_high_level_calls in all_high_level_calls
): ):
ArbitrarySendErc20._arbitrary_from(f.nodes, self._permit_results) ArbitrarySendErc20._arbitrary_from(f, self._permit_results)
else: else:
ArbitrarySendErc20._arbitrary_from(f.nodes, self._no_permit_results) ArbitrarySendErc20._arbitrary_from(f, self._no_permit_results)
@staticmethod @staticmethod
def _arbitrary_from(nodes: List[Node], results: List[Node]) -> None: def _arbitrary_from(function: FunctionContract, results: List[Node]) -> None:
"""Finds instances of (safe)transferFrom that do not use msg.sender or address(this) as from parameter.""" """Finds instances of (safe)transferFrom that do not use msg.sender or address(this) as from parameter."""
for node in nodes: for _, ir in function.high_level_calls:
for ir in node.irs:
if ( if (
isinstance(ir, HighLevelCall) isinstance(ir, LibraryCall)
and isinstance(ir.function, Function) and ir.function.solidity_signature
and ir.function.solidity_signature == "transferFrom(address,address,uint256)" == "safeTransferFrom(address,address,address,uint256)"
and not ( and not (
is_dependent( is_dependent(
ir.arguments[0], ir.arguments[1],
SolidityVariableComposed("msg.sender"), SolidityVariableComposed("msg.sender"),
node, ir.node,
) )
or is_dependent( or is_dependent(
ir.arguments[0], ir.arguments[1],
SolidityVariable("this"), SolidityVariable("this"),
node, ir.node,
) )
) )
): ):
results.append(ir.node) results.append(ir.node)
elif ( elif (
isinstance(ir, LibraryCall) isinstance(ir, HighLevelCall)
and ir.function.solidity_signature and isinstance(ir.function, Function)
== "safeTransferFrom(address,address,address,uint256)" and ir.function.solidity_signature == "transferFrom(address,address,uint256)"
and not ( and not (
is_dependent( is_dependent(
ir.arguments[1], ir.arguments[0],
SolidityVariableComposed("msg.sender"), SolidityVariableComposed("msg.sender"),
node, ir.node,
) )
or is_dependent( or is_dependent(
ir.arguments[1], ir.arguments[0],
SolidityVariable("this"), SolidityVariable("this"),
node, ir.node,
) )
) )
): ):

@ -13,8 +13,7 @@ from slither.detectors.abstract_detector import (
make_solc_versions, make_solc_versions,
) )
from slither.formatters.functions.external_function import custom_format from slither.formatters.functions.external_function import custom_format
from slither.slithir.operations import InternalCall, InternalDynamicCall from slither.slithir.operations import InternalDynamicCall
from slither.slithir.operations import SolidityCall
from slither.utils.output import Output from slither.utils.output import Output
@ -55,11 +54,11 @@ class ExternalFunction(AbstractDetector):
for func in contract.all_functions_called: for func in contract.all_functions_called:
if not isinstance(func, Function): if not isinstance(func, Function):
continue continue
# Loop through all nodes in the function, add all calls to a list.
for node in func.nodes: # Loop through all internal and solidity calls in the function, add them to a list.
for ir in node.irs: for ir in func.internal_calls + func.solidity_calls:
if isinstance(ir, (InternalCall, SolidityCall)):
result.append(ir.function) result.append(ir.function)
return result return result
@staticmethod @staticmethod

@ -3,14 +3,14 @@ Module detecting usage of more than one dynamic type in abi.encodePacked() argum
""" """
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.declarations.solidity_variables import SolidityFunction from slither.core.declarations import Contract, SolidityFunction
from slither.slithir.operations import SolidityCall from slither.core.variables import Variable
from slither.analyses.data_dependency.data_dependency import is_tainted from slither.analyses.data_dependency.data_dependency import is_tainted
from slither.core.solidity_types import ElementaryType from slither.core.solidity_types import ElementaryType
from slither.core.solidity_types import ArrayType from slither.core.solidity_types import ArrayType
def _is_dynamic_type(arg): def _is_dynamic_type(arg: Variable):
""" """
Args: Args:
arg (function argument) arg (function argument)
@ -25,7 +25,7 @@ def _is_dynamic_type(arg):
return False return False
def _detect_abi_encodePacked_collision(contract): def _detect_abi_encodePacked_collision(contract: Contract):
""" """
Args: Args:
contract (Contract) contract (Contract)
@ -35,22 +35,19 @@ def _detect_abi_encodePacked_collision(contract):
ret = [] ret = []
# pylint: disable=too-many-nested-blocks # pylint: disable=too-many-nested-blocks
for f in contract.functions_and_modifiers_declared: for f in contract.functions_and_modifiers_declared:
for n in f.nodes: for ir in f.solidity_calls:
for ir in n.irs: if ir.function == SolidityFunction("abi.encodePacked()"):
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction(
"abi.encodePacked()"
):
dynamic_type_count = 0 dynamic_type_count = 0
for arg in ir.arguments: for arg in ir.arguments:
if is_tainted(arg, contract) and _is_dynamic_type(arg): if is_tainted(arg, contract) and _is_dynamic_type(arg):
dynamic_type_count += 1 dynamic_type_count += 1
elif dynamic_type_count > 1: elif dynamic_type_count > 1:
ret.append((f, n)) ret.append((f, ir.node))
dynamic_type_count = 0 dynamic_type_count = 0
else: else:
dynamic_type_count = 0 dynamic_type_count = 0
if dynamic_type_count > 1: if dynamic_type_count > 1:
ret.append((f, n)) ret.append((f, ir.node))
return ret return ret

@ -44,10 +44,9 @@ class LowLevelCalls(AbstractDetector):
) -> List[Tuple[FunctionContract, List[Node]]]: ) -> List[Tuple[FunctionContract, List[Node]]]:
ret = [] ret = []
for f in [f for f in contract.functions if contract == f.contract_declarer]: for f in [f for f in contract.functions if contract == f.contract_declarer]:
nodes = f.nodes low_level_nodes = [ir.node for ir in f.low_level_calls]
assembly_nodes = [n for n in nodes if self._contains_low_level_calls(n)] if low_level_nodes:
if assembly_nodes: ret.append((f, low_level_nodes))
ret.append((f, assembly_nodes))
return ret return ret
def _detect(self) -> List[Output]: def _detect(self) -> List[Output]:

@ -30,22 +30,22 @@ def detect_assert_state_change(
# Loop for each function and modifier. # Loop for each function and modifier.
for function in contract.functions_declared + list(contract.modifiers_declared): for function in contract.functions_declared + list(contract.modifiers_declared):
for node in function.nodes: for ir_call in function.internal_calls:
# Detect assert() calls # Detect assert() calls
if any(ir.function.name == "assert(bool)" for ir in node.internal_calls) and ( if ir_call.function.name == "assert(bool)" and (
# Detect direct changes to state # Detect direct changes to state
node.state_variables_written ir_call.node.state_variables_written
or or
# Detect changes to state via function calls # Detect changes to state via function calls
any( any(
ir ir
for ir in node.irs for ir in ir_call.node.irs
if isinstance(ir, InternalCall) if isinstance(ir, InternalCall)
and ir.function and ir.function
and ir.function.state_variables_written and ir.function.state_variables_written
) )
): ):
results.append((function, node)) results.append((function, ir_call.node))
# Return the resulting set of nodes # Return the resulting set of nodes
return results return results

@ -8,20 +8,18 @@ from slither.detectors.abstract_detector import (
DetectorClassification, DetectorClassification,
DETECTOR_INFO, DETECTOR_INFO,
) )
from slither.slithir.operations import LowLevelCall
from slither.utils.output import Output from slither.utils.output import Output
def controlled_delegatecall(function: FunctionContract) -> List[Node]: def controlled_delegatecall(function: FunctionContract) -> List[Node]:
ret = [] ret = []
for node in function.nodes: for ir in function.low_level_calls:
for ir in node.irs: if ir.function_name in [
if isinstance(ir, LowLevelCall) and ir.function_name in [
"delegatecall", "delegatecall",
"callcode", "callcode",
]: ]:
if is_tainted(ir.destination, function.contract): if is_tainted(ir.destination, function.contract):
ret.append(node) ret.append(ir.node)
return ret return ret

@ -9,7 +9,7 @@ from slither.detectors.abstract_detector import (
DetectorClassification, DetectorClassification,
DETECTOR_INFO, DETECTOR_INFO,
) )
from slither.slithir.operations import LowLevelCall, HighLevelCall from slither.slithir.operations import HighLevelCall
from slither.analyses.data_dependency.data_dependency import is_tainted from slither.analyses.data_dependency.data_dependency import is_tainted
from slither.utils.output import Output from slither.utils.output import Output
@ -71,9 +71,8 @@ Callee unexpectedly makes the caller OOG.
def get_nodes_for_function(self, function: Function, contract: Contract) -> List[Node]: def get_nodes_for_function(self, function: Function, contract: Contract) -> List[Node]:
nodes = [] nodes = []
for node in function.nodes:
for ir in node.irs: for ir in [ir for _, ir in function.high_level_calls] + function.low_level_calls:
if isinstance(ir, (HighLevelCall, LowLevelCall)):
if not is_tainted(ir.destination, contract): # type:ignore if not is_tainted(ir.destination, contract): # type:ignore
# Only interested if the target address is controlled/tainted # Only interested if the target address is controlled/tainted
continue continue
@ -83,9 +82,7 @@ Callee unexpectedly makes the caller OOG.
# if the return type is dynamic and the caller tries to copy and decode large data # if the return type is dynamic and the caller tries to copy and decode large data
has_dyn = False has_dyn = False
if ir.function.return_type: if ir.function.return_type:
has_dyn = any( has_dyn = any(self.is_dynamic_type(ty) for ty in ir.function.return_type)
self.is_dynamic_type(ty) for ty in ir.function.return_type
)
if not has_dyn: if not has_dyn:
continue continue
@ -97,7 +94,7 @@ Callee unexpectedly makes the caller OOG.
# may already suspect the call may spend all gas? # may already suspect the call may spend all gas?
continue continue
nodes.append(node) nodes.append(ir.node)
# TODO: check that there is some state change after the call # TODO: check that there is some state change after the call
return nodes return nodes

@ -7,23 +7,28 @@ from slither.detectors.abstract_detector import (
DetectorClassification, DetectorClassification,
DETECTOR_INFO, DETECTOR_INFO,
) )
from slither.slithir.operations import LowLevelCall, SolidityCall
from slither.utils.output import Output from slither.utils.output import Output
def _can_be_destroyed(contract: Contract) -> List[Function]: def _can_be_destroyed(contract: Contract) -> List[Function]:
targets = [] targets = []
for f in contract.functions_entry_points: for f in contract.functions_entry_points:
for ir in f.all_slithir_operations(): found = False
if ( for ir in f.all_low_level_calls():
isinstance(ir, LowLevelCall) and ir.function_name in ["delegatecall", "codecall"] if ir.function_name in ["delegatecall", "codecall"]:
) or (
isinstance(ir, SolidityCall)
and ir.function
in [SolidityFunction("suicide(address)"), SolidityFunction("selfdestruct(address)")]
):
targets.append(f) targets.append(f)
found = True
break break
if not found:
for ir in f.all_solidity_calls():
if ir.function in [
SolidityFunction("suicide(address)"),
SolidityFunction("selfdestruct(address)"),
]:
targets.append(f)
break
return targets return targets

@ -7,7 +7,6 @@ from slither.detectors.abstract_detector import (
DetectorClassification, DetectorClassification,
DETECTOR_INFO, DETECTOR_INFO,
) )
from slither.slithir.operations.high_level_call import HighLevelCall
from slither.utils.output import Output from slither.utils.output import Output
@ -54,13 +53,11 @@ contract C {
@staticmethod @staticmethod
def _detect_var_read_using_this(func: Function) -> List[Node]: def _detect_var_read_using_this(func: Function) -> List[Node]:
results: List[Node] = [] results: List[Node] = []
for node in func.nodes: for _, ir in func.high_level_calls:
for ir in node.irs:
if isinstance(ir, HighLevelCall):
if ( if (
ir.destination == SolidityVariable("this") ir.destination == SolidityVariable("this")
and ir.is_static_call() and ir.is_static_call()
and ir.function.visibility == "public" and ir.function.visibility == "public"
): ):
results.append(node) results.append(ir.node)
return sorted(results, key=lambda x: x.node_id) return sorted(results, key=lambda x: x.node_id)

Loading…
Cancel
Save