-
Notifications
You must be signed in to change notification settings - Fork 1k
/
Copy pathcalls_in_loop.py
119 lines (94 loc) · 3.43 KB
/
calls_in_loop.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
from typing import List, Optional, Tuple
from slither.core.cfg.node import NodeType, Node
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.core.declarations import Contract
from slither.utils.output import Output
from slither.slithir.operations import (
HighLevelCall,
LibraryCall,
LowLevelCall,
Send,
Transfer,
InternalCall,
)
Result = List[Tuple[Node, List[str]]]
def detect_call_in_loop(contract: Contract) -> Result:
ret: Result = []
for f in contract.functions_entry_points:
if f.is_implemented:
call_in_loop(f.entry_point, 0, [], [], ret)
return ret
def call_in_loop(
node: Optional[Node],
in_loop_counter: int,
visited: List[Node],
calls_stack: List[str],
ret: Result,
) -> None:
if node is None:
return
if node in visited:
return
# shared visited
visited.append(node)
if node.type == NodeType.STARTLOOP:
in_loop_counter += 1
elif node.type == NodeType.ENDLOOP:
in_loop_counter -= 1
for ir in node.irs:
if isinstance(ir, (LowLevelCall, HighLevelCall, Send, Transfer)) and in_loop_counter > 0:
if isinstance(ir, LibraryCall):
continue
ret.append((ir.node, calls_stack.copy()))
if isinstance(ir, (InternalCall)):
assert ir.function
calls_stack.append(node.function.canonical_name)
call_in_loop(ir.function.entry_point, in_loop_counter, visited, calls_stack, ret)
calls_stack.pop()
for son in node.sons:
call_in_loop(son, in_loop_counter, visited, calls_stack, ret)
class MultipleCallsInLoop(AbstractDetector):
ARGUMENT = "calls-loop"
HELP = "Multiple calls in a loop"
IMPACT = DetectorClassification.LOW
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop"
WIKI_TITLE = "Calls inside a loop"
WIKI_DESCRIPTION = "Calls inside a loop might lead to a denial-of-service attack."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract CallsInLoop{
address[] destinations;
constructor(address[] newDestinations) public{
destinations = newDestinations;
}
function bad() external{
for (uint i=0; i < destinations.length; i++){
destinations[i].transfer(i);
}
}
}
```
If one of the destinations has a fallback function that reverts, `bad` will always revert."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Favor [pull over push](https://github.com/ethereum/wiki/wiki/Safety#favor-pull-over-push-for-external-calls) strategy for external calls."
def _detect(self) -> List[Output]:
""""""
results: List[Output] = []
for c in self.compilation_unit.contracts_derived:
values = detect_call_in_loop(c)
for node, calls_stack in values:
func = node.function
info: DETECTOR_INFO = [func, " has external calls inside a loop: ", node, "\n"]
if len(calls_stack) > 0:
info.append("\tCalls stack containing the loop:\n")
for call in calls_stack:
info.extend(["\t\t", call, "\n"])
res = self.generate_result(info)
results.append(res)
return results