/
uninitialized_state_variables.py
155 lines (130 loc) · 5.81 KB
/
uninitialized_state_variables.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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
"""
Module detecting state uninitialized variables
Recursively check the called functions
The heuristic checks:
- state variables including mappings/refs
- LibraryCalls, InternalCalls, InternalDynamicCalls with storage variables
Only analyze "leaf" contracts (contracts that are not inherited by another contract)
"""
from typing import List, Tuple
from slither.core.declarations import Function
from slither.core.declarations.contract import Contract
from slither.core.variables import Variable
from slither.core.variables.state_variable import StateVariable
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import InternalCall, LibraryCall
from slither.slithir.variables import ReferenceVariable
from slither.utils.output import Output
class UninitializedStateVarsDetection(AbstractDetector):
"""
Constant function detector
"""
ARGUMENT = "uninitialized-state"
HELP = "Uninitialized state variables"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-state-variables"
WIKI_TITLE = "Uninitialized state variables"
WIKI_DESCRIPTION = "Uninitialized state variables."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Uninitialized{
address destination;
function transfer() payable public{
destination.transfer(msg.value);
}
}
```
Bob calls `transfer`. As a result, the Ether are sent to the address `0x0` and are lost.
"""
# endregion wiki_exploit_scenario
# region wiki_recommendation
WIKI_RECOMMENDATION = """
Initialize all the variables. If a variable is meant to be initialized to zero, explicitly set it to zero to improve code readability.
"""
# endregion wiki_recommendation
@staticmethod
def _written_variables(contract: Contract) -> List[StateVariable]:
ret = []
# pylint: disable=too-many-nested-blocks
for f in contract.all_functions_called + contract.modifiers:
for n in f.nodes:
ret += n.state_variables_written
for ir in n.irs:
if isinstance(ir, (LibraryCall, InternalCall)):
idx = 0
if ir.function:
for param in ir.function.parameters:
if param.location == "storage":
# If its a storage variable, add either the variable
# Or the variable it points to if its a reference
if isinstance(ir.arguments[idx], ReferenceVariable):
ret.append(ir.arguments[idx].points_to_origin)
else:
ret.append(ir.arguments[idx])
idx = idx + 1
return ret
def _variable_written_in_proxy(self):
# Hack to memoize without having it define in the init
if hasattr(self, "__variables_written_in_proxy"):
# pylint: disable=access-member-before-definition
return self.__variables_written_in_proxy
variables_written_in_proxy = []
for c in self.compilation_unit.contracts:
if c.is_upgradeable_proxy:
variables_written_in_proxy += self._written_variables(c)
# pylint: disable=attribute-defined-outside-init
self.__variables_written_in_proxy = list({v.name for v in variables_written_in_proxy})
return self.__variables_written_in_proxy
def _written_variables_in_proxy(self, contract: Contract) -> List[StateVariable]:
variables: List[StateVariable] = []
if contract.is_upgradeable:
variables_name_written_in_proxy = self._variable_written_in_proxy()
if variables_name_written_in_proxy:
variables_in_contract = [
contract.get_state_variable_from_name(v)
for v in variables_name_written_in_proxy
]
variables += [v for v in variables_in_contract if v]
return list(set(variables))
@staticmethod
def _read_variables(contract: Contract) -> List[StateVariable]:
ret = []
for f in contract.all_functions_called:
if isinstance(f, Function):
ret += f.state_variables_read
for m in contract.modifiers:
ret += m.state_variables_read
return ret
def _detect_uninitialized(self, contract: Contract) -> List[Tuple[Variable, List[Function]]]:
written_variables = self._written_variables(contract)
written_variables += self._written_variables_in_proxy(contract)
read_variables = self._read_variables(contract)
return [
(variable, contract.get_functions_reading_from_variable(variable))
for variable in contract.state_variables
if variable not in written_variables
and not variable.expression
and variable in read_variables
]
def _detect(self) -> List[Output]:
"""Detect uninitialized state variables
Recursively visit the calls
Returns:
dict: [contract name] = set(state variable uninitialized)
"""
results = []
for c in self.compilation_unit.contracts_derived:
ret = self._detect_uninitialized(c)
for variable, functions in ret:
info: DETECTOR_INFO = [variable, " is never initialized. It is used in:\n"]
for f in functions:
info += ["\t- ", f, "\n"]
json = self.generate_result(info)
results.append(json)
return results