Use new *Calls API in detectors

pull/2555/head
Simone 3 months ago
parent 13b25e8756
commit 439010aefa
  1. 43
      slither/detectors/assembly/incorrect_return.py
  2. 14
      slither/detectors/assembly/return_instead_of_leave.py
  3. 41
      slither/detectors/compiler_bugs/array_by_reference.py
  4. 79
      slither/detectors/erc/erc20/arbitrary_send_erc20.py
  5. 13
      slither/detectors/functions/external_function.py
  6. 37
      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. 16
      slither/detectors/statements/controlled_delegatecall.py
  10. 55
      slither/detectors/statements/return_bomb.py
  11. 23
      slither/detectors/statements/unprotected_upgradeable.py
  12. 17
      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,24 +69,23 @@ 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) if found:
if found:
info: DETECTOR_INFO = [
info: DETECTOR_INFO = [ f,
f, " calls ",
" calls ", function_called,
function_called, " which halt the execution ",
" which halt the execution ", found.node,
found.node, "\n",
"\n", ]
] json = self.generate_result(info)
json = self.generate_result(info)
results.append(json)
results.append(json)
return results return results

@ -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,15 +41,12 @@ 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)" json = self.generate_result(info)
):
info: DETECTOR_INFO = [f, " contains an incorrect call to return: ", node, "\n"]
json = self.generate_result(info)
results.append(json) results.append(json)
return results return results
def _detect(self) -> List[Output]: def _detect(self) -> List[Output]:

@ -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,37 +115,26 @@ 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. # Verify this references a function in our array modifying functions collection.
if not node.expression: if ir.function not in array_modifying_funcs:
continue continue
for ir in node.irs: # Verify one of these parameters is an array in storage.
# Verify this is a high level call. for (param, arg) in zip(ir.function.parameters, ir.arguments):
if not isinstance(ir, (HighLevelCall, InternalCall)): # Verify this argument is a variable that is an array type.
if not isinstance(arg, (StateVariable, LocalVariable)):
continue continue
if not isinstance(arg.type, ArrayType):
# Verify this references a function in our array modifying functions collection.
if ir.function not in array_modifying_funcs:
continue continue
# Verify one of these parameters is an array in storage. # If it is a state variable OR a local variable referencing storage, we add it to the list.
for (param, arg) in zip(ir.function.parameters, ir.arguments): if (
# Verify this argument is a variable that is an array type. isinstance(arg, StateVariable)
if not isinstance(arg, (StateVariable, LocalVariable)): or (isinstance(arg, LocalVariable) and arg.location == "storage")
continue ) and (isinstance(param.type, ArrayType) and param.location != "storage"):
if not isinstance(arg.type, ArrayType): results.append((ir.node, arg, ir.function))
continue
# If it is a state variable OR a local variable referencing storage, we add it to the list.
if (
isinstance(arg, StateVariable)
or (isinstance(arg, LocalVariable) and arg.location == "storage")
) and (
isinstance(param.type, ArrayType) and param.location != "storage"
):
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,51 +44,50 @@ 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, 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"), ir.node,
node,
)
or is_dependent(
ir.arguments[0],
SolidityVariable("this"),
node,
)
) )
): or is_dependent(
results.append(ir.node) ir.arguments[1],
elif ( SolidityVariable("this"),
isinstance(ir, LibraryCall) ir.node,
and ir.function.solidity_signature
== "safeTransferFrom(address,address,address,uint256)"
and not (
is_dependent(
ir.arguments[1],
SolidityVariableComposed("msg.sender"),
node,
)
or is_dependent(
ir.arguments[1],
SolidityVariable("this"),
node,
)
) )
): )
results.append(ir.node) ):
results.append(ir.node)
elif (
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"),
ir.node,
)
or is_dependent(
ir.arguments[0],
SolidityVariable("this"),
ir.node,
)
)
):
results.append(ir.node)
def detect(self) -> None: def detect(self) -> None:
"""Detect transfers that use arbitrary `from` parameter.""" """Detect transfers that use arbitrary `from` parameter."""

@ -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( dynamic_type_count = 0
"abi.encodePacked()" for arg in ir.arguments:
): if is_tainted(arg, contract) and _is_dynamic_type(arg):
dynamic_type_count = 0 dynamic_type_count += 1
for arg in ir.arguments: elif dynamic_type_count > 1:
if is_tainted(arg, contract) and _is_dynamic_type(arg): ret.append((f, ir.node))
dynamic_type_count += 1 dynamic_type_count = 0
elif dynamic_type_count > 1: else:
ret.append((f, n)) dynamic_type_count = 0
dynamic_type_count = 0 if dynamic_type_count > 1:
else: ret.append((f, ir.node))
dynamic_type_count = 0
if dynamic_type_count > 1:
ret.append((f, n))
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(ir.node)
ret.append(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,34 +71,31 @@ 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
if isinstance(ir, HighLevelCall) and isinstance(ir.function, Function):
if isinstance(ir, HighLevelCall) and isinstance(ir.function, Function): # in normal highlevel calls return bombs are _possible_
# in normal highlevel calls return bombs are _possible_ # 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(self.is_dynamic_type(ty) for ty in ir.function.return_type)
has_dyn = any(
self.is_dynamic_type(ty) for ty in ir.function.return_type if not has_dyn:
) continue
if not has_dyn: # If a gas budget was specified then the
continue # user may not know about the return bomb
if ir.call_gas is None:
# If a gas budget was specified then the # if a gas budget was NOT specified then the caller
# user may not know about the return bomb # may already suspect the call may spend all gas?
if ir.call_gas is None: continue
# if a gas budget was NOT specified then the caller
# may already suspect the call may spend all gas? nodes.append(ir.node)
continue # TODO: check that there is some state change after the call
nodes.append(node)
# 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 (
if isinstance(ir, HighLevelCall): ir.destination == SolidityVariable("this")
if ( and ir.is_static_call()
ir.destination == SolidityVariable("this") and ir.function.visibility == "public"
and ir.is_static_call() ):
and ir.function.visibility == "public" results.append(ir.node)
):
results.append(node)
return sorted(results, key=lambda x: x.node_id) return sorted(results, key=lambda x: x.node_id)

Loading…
Cancel
Save