|
|
@ -1,15 +1,24 @@ |
|
|
|
""" |
|
|
|
""" |
|
|
|
Module detecting unused return values from low level |
|
|
|
Module detecting unused return values from low level |
|
|
|
""" |
|
|
|
""" |
|
|
|
from slither.detectors.abstract_detector import DetectorClassification |
|
|
|
from typing import List |
|
|
|
from slither.detectors.operations.unused_return_values import UnusedReturnValues |
|
|
|
|
|
|
|
|
|
|
|
from slither.core.cfg.node import Node |
|
|
|
from slither.slithir.operations import LowLevelCall |
|
|
|
from slither.slithir.operations import LowLevelCall |
|
|
|
from slither.slithir.operations.operation import Operation |
|
|
|
|
|
|
|
|
|
|
|
from slither.core.declarations.function_contract import FunctionContract |
|
|
|
|
|
|
|
from slither.core.variables.state_variable import StateVariable |
|
|
|
|
|
|
|
from slither.detectors.abstract_detector import ( |
|
|
|
|
|
|
|
AbstractDetector, |
|
|
|
|
|
|
|
DetectorClassification, |
|
|
|
|
|
|
|
DETECTOR_INFO, |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
from slither.utils.output import Output |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class UncheckedLowLevel(UnusedReturnValues): |
|
|
|
class UncheckedLowLevel(AbstractDetector): |
|
|
|
""" |
|
|
|
""" |
|
|
|
If the return value of a send is not checked, it might lead to losing ether |
|
|
|
If the return value of a low-level call is not checked, it might lead to losing ether |
|
|
|
""" |
|
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
ARGUMENT = "unchecked-lowlevel" |
|
|
|
ARGUMENT = "unchecked-lowlevel" |
|
|
@ -38,5 +47,44 @@ If the low level is used to prevent blocking operations, consider logging failed |
|
|
|
|
|
|
|
|
|
|
|
WIKI_RECOMMENDATION = "Ensure that the return value of a low-level call is checked or logged." |
|
|
|
WIKI_RECOMMENDATION = "Ensure that the return value of a low-level call is checked or logged." |
|
|
|
|
|
|
|
|
|
|
|
def _is_instance(self, ir: Operation) -> bool: # pylint: disable=no-self-use |
|
|
|
@staticmethod |
|
|
|
return isinstance(ir, LowLevelCall) |
|
|
|
def detect_unused_return_values(f: FunctionContract) -> List[Node]: |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
Return the nodes where the return value of a call is unused |
|
|
|
|
|
|
|
Args: |
|
|
|
|
|
|
|
f (Function) |
|
|
|
|
|
|
|
Returns: |
|
|
|
|
|
|
|
list(Node) |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
values_returned = [] |
|
|
|
|
|
|
|
nodes_origin = {} |
|
|
|
|
|
|
|
for n in f.nodes: |
|
|
|
|
|
|
|
for ir in n.irs: |
|
|
|
|
|
|
|
if isinstance(ir, LowLevelCall): |
|
|
|
|
|
|
|
# if a return value is stored in a state variable, it's ok |
|
|
|
|
|
|
|
if ir.lvalue and not isinstance(ir.lvalue, StateVariable): |
|
|
|
|
|
|
|
values_returned.append(ir.lvalue) |
|
|
|
|
|
|
|
nodes_origin[ir.lvalue] = ir |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for read in ir.read: |
|
|
|
|
|
|
|
if read in values_returned: |
|
|
|
|
|
|
|
values_returned.remove(read) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return [nodes_origin[value].node for value in values_returned] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _detect(self) -> List[Output]: |
|
|
|
|
|
|
|
"""Detect low level calls where the success value is not checked""" |
|
|
|
|
|
|
|
results = [] |
|
|
|
|
|
|
|
for c in self.compilation_unit.contracts_derived: |
|
|
|
|
|
|
|
for f in c.functions_and_modifiers: |
|
|
|
|
|
|
|
unused_return = UncheckedLowLevel.detect_unused_return_values(f) |
|
|
|
|
|
|
|
if unused_return: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for node in unused_return: |
|
|
|
|
|
|
|
info: DETECTOR_INFO = [f, " ignores return value by ", node, "\n"] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
res = self.generate_result(info) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
results.append(res) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return results |
|
|
|