-
Notifications
You must be signed in to change notification settings - Fork 59
/
ecrecover.py
100 lines (90 loc) · 4.14 KB
/
ecrecover.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
import re
from typing import List, Tuple
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import (
SolidityCall,
Condition,
)
from slither.core.declarations import (
FunctionContract,
)
from slither.core.cfg.node import Node, NodeType
from slither.slithir.operations import LowLevelCall
from slither.analyses.data_dependency.data_dependency import is_dependent
class Ecrecover(AbstractDetector):
"""
Detects not checked results of ecrecover
"""
ARGUMENT = "pess-ecrecover" # slither will launch the detector with slither.py --detect mydetector
HELP = "signer = ecrecover(hash, v, r, s)"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/pessimistic-io/slitherin/blob/master/docs/ecrecover.md"
WIKI_TITLE = "Ecrecover"
WIKI_DESCRIPTION = "Check docs"
WIKI_EXPLOIT_SCENARIO = "Attacker can validate signatures from 0x0 address"
WIKI_RECOMMENDATION = "Check the result of ecrecover"
def analyze_function(
self, function: FunctionContract
) -> List[Tuple[FunctionContract, Node, LowLevelCall, bool, bool]]:
unchecked_results = set()
var_to_node = {}
for node in function.nodes:
for ir in node.irs:
try:
node_contains_0 = re.search(
r"address\((0|0x0*)\)", str(node)
) # check if the node contains address(0|0x0..)
if isinstance(ir, SolidityCall):
if (
ir.function.name
== "ecrecover(bytes32,uint8,bytes32,bytes32)"
):
unchecked_results.add(ir.lvalue)
# print(ir.lvalue, node)
var_to_node[ir.lvalue] = node
elif (
ir.function.name == "require(bool,string)"
or ir.function.name == "assert(bool)"
):
if not node_contains_0: # does not contain 0 check
continue
checking_var = ir.arguments[0]
for ur in unchecked_results:
if is_dependent(checking_var, ur, node):
unchecked_results.remove(ur)
break
elif isinstance(ir, Condition):
# this is copypaste, for now, couldn't figure out how to make this better without overcomplicating
if not node_contains_0: # does not contain 0 check
continue
for ur in unchecked_results:
if is_dependent(ir.value, ur, node):
unchecked_results.remove(ur)
break
except Exception as e:
print("failed", e)
for node in function.nodes:
if node._node_type == NodeType.RETURN:
node_contains_0 = re.search(
r"address\((0|0x0*)\)", str(node)
) # check if the node contains address(0|0x0..)
if not node_contains_0:
continue
temp_unchecked = set(unchecked_results)
for ur in temp_unchecked:
if is_dependent(ur, ur, node):
unchecked_results.remove(ur)
return [var_to_node[ur] for ur in unchecked_results]
def _detect(self):
results = []
for contract in self.compilation_unit.contracts_derived:
for f in contract.functions:
res = self.analyze_function(f)
if res:
for r in res:
info = ["Unchecked result of ecrecover for 0:\n\t", r, "\n"]
tres = self.generate_result(info)
tres.add(r)
results.append(tres)
return results