mirror of https://github.com/crytic/slither
Merge pull request #117 from trailofbits/dev-reentrancy
Refactor reentrancy detector into three variantspull/125/head
commit
c9d18ab4c8
@ -0,0 +1,226 @@ |
|||||||
|
"""" |
||||||
|
Re-entrancy detection |
||||||
|
|
||||||
|
Based on heuristics, it may lead to FP and FN |
||||||
|
Iterate over all the nodes of the graph until reaching a fixpoint |
||||||
|
""" |
||||||
|
|
||||||
|
from slither.core.cfg.node import NodeType |
||||||
|
from slither.core.declarations import Function, SolidityFunction |
||||||
|
from slither.core.expressions import UnaryOperation, UnaryOperationType |
||||||
|
from slither.detectors.abstract_detector import (AbstractDetector, |
||||||
|
DetectorClassification) |
||||||
|
from slither.visitors.expression.export_values import ExportValues |
||||||
|
from slither.slithir.operations import (HighLevelCall, LowLevelCall, |
||||||
|
LibraryCall, |
||||||
|
Send, Transfer) |
||||||
|
|
||||||
|
class ReentrancyBenign(AbstractDetector): |
||||||
|
ARGUMENT = 'reentrancy-benign' |
||||||
|
HELP = 'Benign reentrancy vulnerabilities' |
||||||
|
IMPACT = DetectorClassification.LOW |
||||||
|
CONFIDENCE = DetectorClassification.MEDIUM |
||||||
|
|
||||||
|
WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#reentrancy-vulnerabilities' |
||||||
|
|
||||||
|
key = 'REENTRANCY-BENIGN' |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def _can_callback(node): |
||||||
|
""" |
||||||
|
Detect if the node contains a call that can |
||||||
|
be used to re-entrance |
||||||
|
|
||||||
|
Consider as valid target: |
||||||
|
- low level call |
||||||
|
- high level call |
||||||
|
|
||||||
|
Do not consider Send/Transfer as there is not enough gas |
||||||
|
""" |
||||||
|
for ir in node.irs: |
||||||
|
if isinstance(ir, LowLevelCall): |
||||||
|
return True |
||||||
|
if isinstance(ir, HighLevelCall) and not isinstance(ir, LibraryCall): |
||||||
|
return True |
||||||
|
return False |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def _can_send_eth(node): |
||||||
|
""" |
||||||
|
Detect if the node can send eth |
||||||
|
""" |
||||||
|
for ir in node.irs: |
||||||
|
if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)): |
||||||
|
if ir.call_value: |
||||||
|
return True |
||||||
|
return False |
||||||
|
|
||||||
|
def _filter_if(self, node): |
||||||
|
""" |
||||||
|
Check if the node is a condtional node where |
||||||
|
there is an external call checked |
||||||
|
Heuristic: |
||||||
|
- The call is a IF node |
||||||
|
- It contains a, external call |
||||||
|
- The condition is the negation (!) |
||||||
|
|
||||||
|
This will work only on naive implementation |
||||||
|
""" |
||||||
|
return isinstance(node.expression, UnaryOperation)\ |
||||||
|
and node.expression.type == UnaryOperationType.BANG |
||||||
|
|
||||||
|
def _explore(self, node, visited, skip_father=None): |
||||||
|
""" |
||||||
|
Explore the CFG and look for re-entrancy |
||||||
|
Heuristic: There is a re-entrancy if a state variable is written |
||||||
|
after an external call |
||||||
|
|
||||||
|
node.context will contains the external calls executed |
||||||
|
It contains the calls executed in father nodes |
||||||
|
|
||||||
|
if node.context is not empty, and variables are written, a re-entrancy is possible |
||||||
|
""" |
||||||
|
if node in visited: |
||||||
|
return |
||||||
|
|
||||||
|
visited = visited + [node] |
||||||
|
|
||||||
|
# First we add the external calls executed in previous nodes |
||||||
|
# send_eth returns the list of calls sending value |
||||||
|
# calls returns the list of calls that can callback |
||||||
|
# read returns the variable read |
||||||
|
fathers_context = {'send_eth':[], 'calls':[], 'read':[]} |
||||||
|
|
||||||
|
for father in node.fathers: |
||||||
|
if self.key in father.context: |
||||||
|
fathers_context['send_eth'] += [s for s in father.context[self.key]['send_eth'] if s!=skip_father] |
||||||
|
fathers_context['calls'] += [c for c in father.context[self.key]['calls'] if c!=skip_father] |
||||||
|
fathers_context['read'] += father.context[self.key]['read'] |
||||||
|
|
||||||
|
# Exclude path that dont bring further information |
||||||
|
if node in self.visited_all_paths: |
||||||
|
if all(call in self.visited_all_paths[node]['calls'] for call in fathers_context['calls']): |
||||||
|
if all(send in self.visited_all_paths[node]['send_eth'] for send in fathers_context['send_eth']): |
||||||
|
if all(read in self.visited_all_paths[node]['read'] for read in fathers_context['read']): |
||||||
|
return |
||||||
|
else: |
||||||
|
self.visited_all_paths[node] = {'send_eth':[], 'calls':[], 'read':[]} |
||||||
|
|
||||||
|
self.visited_all_paths[node]['send_eth'] = list(set(self.visited_all_paths[node]['send_eth'] + fathers_context['send_eth'])) |
||||||
|
self.visited_all_paths[node]['calls'] = list(set(self.visited_all_paths[node]['calls'] + fathers_context['calls'])) |
||||||
|
self.visited_all_paths[node]['read'] = list(set(self.visited_all_paths[node]['read'] + fathers_context['read'])) |
||||||
|
|
||||||
|
node.context[self.key] = fathers_context |
||||||
|
|
||||||
|
contains_call = False |
||||||
|
if self._can_callback(node): |
||||||
|
node.context[self.key]['calls'] = list(set(node.context[self.key]['calls'] + [node])) |
||||||
|
contains_call = True |
||||||
|
if self._can_send_eth(node): |
||||||
|
node.context[self.key]['send_eth'] = list(set(node.context[self.key]['send_eth'] + [node])) |
||||||
|
|
||||||
|
|
||||||
|
# All the state variables written |
||||||
|
state_vars_written = node.state_variables_written |
||||||
|
# Add the state variables written in internal calls |
||||||
|
for internal_call in node.internal_calls: |
||||||
|
# Filter to Function, as internal_call can be a solidity call |
||||||
|
if isinstance(internal_call, Function): |
||||||
|
state_vars_written += internal_call.all_state_variables_written() |
||||||
|
|
||||||
|
not_read_then_written = [(v, node) for v in state_vars_written if v not in node.context[self.key]['read']] |
||||||
|
|
||||||
|
node.context[self.key]['read'] = list(set(node.context[self.key]['read'] + node.state_variables_read)) |
||||||
|
# If a state variables was read and is then written, there is a dangerous call and |
||||||
|
# ether were sent |
||||||
|
# We found a potential re-entrancy bug |
||||||
|
if (not_read_then_written and |
||||||
|
node.context[self.key]['calls']): |
||||||
|
# calls are ordered |
||||||
|
finding_key = (node.function, |
||||||
|
tuple(set(node.context[self.key]['calls'])), |
||||||
|
tuple(set(node.context[self.key]['send_eth']))) |
||||||
|
finding_vars = not_read_then_written |
||||||
|
if finding_key not in self.result: |
||||||
|
self.result[finding_key] = [] |
||||||
|
self.result[finding_key] = list(set(self.result[finding_key] + finding_vars)) |
||||||
|
|
||||||
|
sons = node.sons |
||||||
|
if contains_call and node.type in [NodeType.IF, NodeType.IFLOOP]: |
||||||
|
if self._filter_if(node): |
||||||
|
son = sons[0] |
||||||
|
self._explore(son, visited, node) |
||||||
|
sons = sons[1:] |
||||||
|
else: |
||||||
|
son = sons[1] |
||||||
|
self._explore(son, visited, node) |
||||||
|
sons = [sons[0]] |
||||||
|
|
||||||
|
|
||||||
|
for son in sons: |
||||||
|
self._explore(son, visited) |
||||||
|
|
||||||
|
def detect_reentrancy(self, contract): |
||||||
|
""" |
||||||
|
""" |
||||||
|
for function in contract.functions: |
||||||
|
if function.is_implemented: |
||||||
|
self._explore(function.entry_point, []) |
||||||
|
|
||||||
|
def detect(self): |
||||||
|
""" |
||||||
|
""" |
||||||
|
self.result = {} |
||||||
|
|
||||||
|
# if a node was already visited by another path |
||||||
|
# we will only explore it if the traversal brings |
||||||
|
# new variables written |
||||||
|
# This speedup the exploration through a light fixpoint |
||||||
|
# Its particular useful on 'complex' functions with several loops and conditions |
||||||
|
self.visited_all_paths = {} |
||||||
|
|
||||||
|
for c in self.contracts: |
||||||
|
self.detect_reentrancy(c) |
||||||
|
|
||||||
|
results = [] |
||||||
|
|
||||||
|
result_sorted = sorted(list(self.result.items()), key=lambda x:x[0][0].name) |
||||||
|
for (func, calls, send_eth), varsWritten in result_sorted: |
||||||
|
calls = list(set(calls)) |
||||||
|
send_eth = list(set(send_eth)) |
||||||
|
info = 'Reentrancy in {}.{} ({}):\n' |
||||||
|
info = info.format(func.contract.name, func.name, func.source_mapping_str) |
||||||
|
info += '\tExternal calls:\n' |
||||||
|
for call_info in calls: |
||||||
|
info += '\t- {} ({})\n'.format(call_info.expression, call_info.source_mapping_str) |
||||||
|
if calls != send_eth: |
||||||
|
info += '\tExternal calls sending eth:\n' |
||||||
|
for call_info in send_eth: |
||||||
|
info += '\t- {} ({})\n'.format(call_info.expression, call_info.source_mapping_str) |
||||||
|
info += '\tState variables written after the call(s):\n' |
||||||
|
for (v, node) in varsWritten: |
||||||
|
info += '\t- {} ({})\n'.format(v, node.source_mapping_str) |
||||||
|
self.log(info) |
||||||
|
|
||||||
|
sending_eth_json = [] |
||||||
|
if calls != send_eth: |
||||||
|
sending_eth_json = [{'type' : 'external_calls_sending_eth', |
||||||
|
'expression': str(call_info.expression), |
||||||
|
'source_mapping': call_info.source_mapping} |
||||||
|
for call_info in send_eth] |
||||||
|
|
||||||
|
json = self.generate_json_result(info) |
||||||
|
self.add_function_to_json(func, json) |
||||||
|
json['elements'] += [{'type': 'external_calls', |
||||||
|
'expression': str(call_info.expression), |
||||||
|
'source_mapping': call_info.source_mapping} |
||||||
|
for call_info in calls] |
||||||
|
json['elements'] += sending_eth_json |
||||||
|
json['elements'] += [{'type':'variables_written', |
||||||
|
'name': v.name, |
||||||
|
'expression': str(node.expression), |
||||||
|
'source_mapping': node.source_mapping} |
||||||
|
for (v, node) in varsWritten] |
||||||
|
results.append(json) |
||||||
|
|
||||||
|
return results |
@ -0,0 +1,216 @@ |
|||||||
|
"""" |
||||||
|
Re-entrancy detection |
||||||
|
|
||||||
|
Based on heuristics, it may lead to FP and FN |
||||||
|
Iterate over all the nodes of the graph until reaching a fixpoint |
||||||
|
""" |
||||||
|
|
||||||
|
from slither.core.cfg.node import NodeType |
||||||
|
from slither.core.declarations import Function, SolidityFunction |
||||||
|
from slither.core.expressions import UnaryOperation, UnaryOperationType |
||||||
|
from slither.detectors.abstract_detector import (AbstractDetector, |
||||||
|
DetectorClassification) |
||||||
|
from slither.visitors.expression.export_values import ExportValues |
||||||
|
from slither.slithir.operations import (HighLevelCall, LowLevelCall, |
||||||
|
LibraryCall, |
||||||
|
Send, Transfer) |
||||||
|
|
||||||
|
class ReentrancyReadBeforeWritten(AbstractDetector): |
||||||
|
ARGUMENT = 'reentrancy-no-eth' |
||||||
|
HELP = 'Reentrancy vulnerabilities (no theft of ethers)' |
||||||
|
IMPACT = DetectorClassification.MEDIUM |
||||||
|
CONFIDENCE = DetectorClassification.MEDIUM |
||||||
|
|
||||||
|
WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#reentrancy-vulnerabilities' |
||||||
|
|
||||||
|
key = 'REENTRANCY-NO-ETHER' |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def _can_callback(node): |
||||||
|
""" |
||||||
|
Detect if the node contains a call that can |
||||||
|
be used to re-entrance |
||||||
|
|
||||||
|
Consider as valid target: |
||||||
|
- low level call |
||||||
|
- high level call |
||||||
|
|
||||||
|
Do not consider Send/Transfer as there is not enough gas |
||||||
|
""" |
||||||
|
for ir in node.irs: |
||||||
|
if isinstance(ir, LowLevelCall): |
||||||
|
return True |
||||||
|
if isinstance(ir, HighLevelCall) and not isinstance(ir, LibraryCall): |
||||||
|
return True |
||||||
|
return False |
||||||
|
|
||||||
|
@staticmethod |
||||||
|
def _can_send_eth(node): |
||||||
|
""" |
||||||
|
Detect if the node can send eth |
||||||
|
""" |
||||||
|
for ir in node.irs: |
||||||
|
if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)): |
||||||
|
if ir.call_value: |
||||||
|
return True |
||||||
|
return False |
||||||
|
|
||||||
|
def _filter_if(self, node): |
||||||
|
""" |
||||||
|
Check if the node is a condtional node where |
||||||
|
there is an external call checked |
||||||
|
Heuristic: |
||||||
|
- The call is a IF node |
||||||
|
- It contains a, external call |
||||||
|
- The condition is the negation (!) |
||||||
|
|
||||||
|
This will work only on naive implementation |
||||||
|
""" |
||||||
|
return isinstance(node.expression, UnaryOperation)\ |
||||||
|
and node.expression.type == UnaryOperationType.BANG |
||||||
|
|
||||||
|
def _explore(self, node, visited, skip_father=None): |
||||||
|
""" |
||||||
|
Explore the CFG and look for re-entrancy |
||||||
|
Heuristic: There is a re-entrancy if a state variable is written |
||||||
|
after an external call |
||||||
|
|
||||||
|
node.context will contains the external calls executed |
||||||
|
It contains the calls executed in father nodes |
||||||
|
|
||||||
|
if node.context is not empty, and variables are written, a re-entrancy is possible |
||||||
|
""" |
||||||
|
if node in visited: |
||||||
|
return |
||||||
|
|
||||||
|
visited = visited + [node] |
||||||
|
|
||||||
|
# First we add the external calls executed in previous nodes |
||||||
|
# send_eth returns the list of calls sending value |
||||||
|
# calls returns the list of calls that can callback |
||||||
|
# read returns the variable read |
||||||
|
fathers_context = {'send_eth':[], 'calls':[], 'read':[]} |
||||||
|
|
||||||
|
for father in node.fathers: |
||||||
|
if self.key in father.context: |
||||||
|
fathers_context['send_eth'] += [s for s in father.context[self.key]['send_eth'] if s!=skip_father] |
||||||
|
fathers_context['calls'] += [c for c in father.context[self.key]['calls'] if c!=skip_father] |
||||||
|
fathers_context['read'] += father.context[self.key]['read'] |
||||||
|
|
||||||
|
# Exclude path that dont bring further information |
||||||
|
if node in self.visited_all_paths: |
||||||
|
if all(call in self.visited_all_paths[node]['calls'] for call in fathers_context['calls']): |
||||||
|
if all(send in self.visited_all_paths[node]['send_eth'] for send in fathers_context['send_eth']): |
||||||
|
if all(read in self.visited_all_paths[node]['read'] for read in fathers_context['read']): |
||||||
|
return |
||||||
|
else: |
||||||
|
self.visited_all_paths[node] = {'send_eth':[], 'calls':[], 'read':[]} |
||||||
|
|
||||||
|
self.visited_all_paths[node]['send_eth'] = list(set(self.visited_all_paths[node]['send_eth'] + fathers_context['send_eth'])) |
||||||
|
self.visited_all_paths[node]['calls'] = list(set(self.visited_all_paths[node]['calls'] + fathers_context['calls'])) |
||||||
|
self.visited_all_paths[node]['read'] = list(set(self.visited_all_paths[node]['read'] + fathers_context['read'])) |
||||||
|
|
||||||
|
node.context[self.key] = fathers_context |
||||||
|
|
||||||
|
contains_call = False |
||||||
|
if self._can_callback(node): |
||||||
|
node.context[self.key]['calls'] = list(set(node.context[self.key]['calls'] + [node])) |
||||||
|
contains_call = True |
||||||
|
if self._can_send_eth(node): |
||||||
|
node.context[self.key]['send_eth'] = list(set(node.context[self.key]['send_eth'] + [node])) |
||||||
|
|
||||||
|
|
||||||
|
# All the state variables written |
||||||
|
state_vars_written = node.state_variables_written |
||||||
|
# Add the state variables written in internal calls |
||||||
|
for internal_call in node.internal_calls: |
||||||
|
# Filter to Function, as internal_call can be a solidity call |
||||||
|
if isinstance(internal_call, Function): |
||||||
|
state_vars_written += internal_call.all_state_variables_written() |
||||||
|
|
||||||
|
read_then_written = [(v, node) for v in state_vars_written if v in node.context[self.key]['read']] |
||||||
|
|
||||||
|
node.context[self.key]['read'] = list(set(node.context[self.key]['read'] + node.state_variables_read)) |
||||||
|
# If a state variables was read and is then written, there is a dangerous call and |
||||||
|
# ether were sent |
||||||
|
# We found a potential re-entrancy bug |
||||||
|
if (read_then_written and |
||||||
|
node.context[self.key]['calls'] and |
||||||
|
not node.context[self.key]['send_eth']): |
||||||
|
# calls are ordered |
||||||
|
finding_key = (node.function, |
||||||
|
tuple(set(node.context[self.key]['calls']))) |
||||||
|
finding_vars = read_then_written |
||||||
|
if finding_key not in self.result: |
||||||
|
self.result[finding_key] = [] |
||||||
|
self.result[finding_key] = list(set(self.result[finding_key] + finding_vars)) |
||||||
|
|
||||||
|
sons = node.sons |
||||||
|
if contains_call and node.type in [NodeType.IF, NodeType.IFLOOP]: |
||||||
|
if self._filter_if(node): |
||||||
|
son = sons[0] |
||||||
|
self._explore(son, visited, node) |
||||||
|
sons = sons[1:] |
||||||
|
else: |
||||||
|
son = sons[1] |
||||||
|
self._explore(son, visited, node) |
||||||
|
sons = [sons[0]] |
||||||
|
|
||||||
|
|
||||||
|
for son in sons: |
||||||
|
self._explore(son, visited) |
||||||
|
|
||||||
|
def detect_reentrancy(self, contract): |
||||||
|
""" |
||||||
|
""" |
||||||
|
for function in contract.functions: |
||||||
|
if function.is_implemented: |
||||||
|
self._explore(function.entry_point, []) |
||||||
|
|
||||||
|
def detect(self): |
||||||
|
""" |
||||||
|
""" |
||||||
|
self.result = {} |
||||||
|
|
||||||
|
# if a node was already visited by another path |
||||||
|
# we will only explore it if the traversal brings |
||||||
|
# new variables written |
||||||
|
# This speedup the exploration through a light fixpoint |
||||||
|
# Its particular useful on 'complex' functions with several loops and conditions |
||||||
|
self.visited_all_paths = {} |
||||||
|
|
||||||
|
for c in self.contracts: |
||||||
|
self.detect_reentrancy(c) |
||||||
|
|
||||||
|
results = [] |
||||||
|
|
||||||
|
result_sorted = sorted(list(self.result.items()), key=lambda x:x[0][0].name) |
||||||
|
for (func, calls), varsWritten in result_sorted: |
||||||
|
calls = list(set(calls)) |
||||||
|
info = 'Reentrancy in {}.{} ({}):\n' |
||||||
|
info = info.format(func.contract.name, func.name, func.source_mapping_str) |
||||||
|
info += '\tExternal calls:\n' |
||||||
|
for call_info in calls: |
||||||
|
info += '\t- {} ({})\n'.format(call_info.expression, call_info.source_mapping_str) |
||||||
|
info += '\tState variables written after the call(s):\n' |
||||||
|
for (v, node) in varsWritten: |
||||||
|
info += '\t- {} ({})\n'.format(v, node.source_mapping_str) |
||||||
|
self.log(info) |
||||||
|
|
||||||
|
sending_eth_json = [] |
||||||
|
|
||||||
|
json = self.generate_json_result(info) |
||||||
|
self.add_function_to_json(func, json) |
||||||
|
json['elements'] += [{'type': 'external_calls', |
||||||
|
'expression': str(call_info.expression), |
||||||
|
'source_mapping': call_info.source_mapping} |
||||||
|
for call_info in calls] |
||||||
|
json['elements'] += sending_eth_json |
||||||
|
json['elements'] += [{'type':'variables_written', |
||||||
|
'name': v.name, |
||||||
|
'expression': str(node.expression), |
||||||
|
'source_mapping': node.source_mapping} |
||||||
|
for (v, node) in varsWritten] |
||||||
|
results.append(json) |
||||||
|
|
||||||
|
return results |
@ -0,0 +1 @@ |
|||||||
|
[{"check": "reentrancy-eth", "impact": "High", "confidence": "Medium", "description": "Reentrancy in Reentrancy.withdrawBalance (tests/reentrancy-0.5.1.sol#14-22):\n\tExternal calls:\n\t- (ret,mem) = msg.sender.call.value(userBalance[msg.sender])() (tests/reentrancy-0.5.1.sol#17)\n\tState variables written after the call(s):\n\t- userBalance (tests/reentrancy-0.5.1.sol#21)\n", "elements": [{"type": "function", "name": "withdrawBalance", "source_mapping": {"start": 298, "length": 357, "filename": "tests/reentrancy-0.5.1.sol", "lines": [14, 15, 16, 17, 18, 19, 20, 21, 22]}, "contract": {"type": "contract", "name": "Reentrancy", "source_mapping": {"start": 25, "length": 1807, "filename": "tests/reentrancy-0.5.1.sol", "lines": [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]}}}, {"type": "external_calls", "expression": "(ret,mem) = msg.sender.call.value(userBalance[msg.sender])()", "source_mapping": {"start": 477, "length": 81, "filename": "tests/reentrancy-0.5.1.sol", "lines": [17]}}, {"type": "variables_written", "name": "userBalance", "expression": "userBalance[msg.sender] = 0", "source_mapping": {"start": 621, "length": 27, "filename": "tests/reentrancy-0.5.1.sol", "lines": [21]}}]}, {"check": "reentrancy-eth", "impact": "High", "confidence": "Medium", "description": "Reentrancy in Reentrancy.withdrawBalance_fixed_3 (tests/reentrancy-0.5.1.sol#44-53):\n\tExternal calls:\n\t- (ret,mem) = msg.sender.call.value(amount)() (tests/reentrancy-0.5.1.sol#49)\n\tState variables written after the call(s):\n\t- userBalance (tests/reentrancy-0.5.1.sol#51)\n", "elements": [{"type": "function", "name": "withdrawBalance_fixed_3", "source_mapping": {"start": 1434, "length": 393, "filename": "tests/reentrancy-0.5.1.sol", "lines": [44, 45, 46, 47, 48, 49, 50, 51, 52, 53]}, "contract": {"type": "contract", "name": "Reentrancy", "source_mapping": {"start": 25, "length": 1807, "filename": "tests/reentrancy-0.5.1.sol", "lines": [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]}}}, {"type": "external_calls", "expression": "(ret,mem) = msg.sender.call.value(amount)()", "source_mapping": {"start": 1679, "length": 64, "filename": "tests/reentrancy-0.5.1.sol", "lines": [49]}}, {"type": "variables_written", "name": "userBalance", "expression": "userBalance[msg.sender] = amount", "source_mapping": {"start": 1778, "length": 32, "filename": "tests/reentrancy-0.5.1.sol", "lines": [51]}}]}] |
@ -1 +0,0 @@ |
|||||||
[{"check": "reentrancy", "impact": "High", "confidence": "Medium", "description": "Reentrancy in Reentrancy.withdrawBalance (tests/reentrancy-0.5.1.sol#14-22):\n\tExternal calls:\n\t- (ret,mem) = msg.sender.call.value(userBalance[msg.sender])() (tests/reentrancy-0.5.1.sol#17)\n\tState variables written after the call(s):\n\t- userBalance (tests/reentrancy-0.5.1.sol#21)\n", "elements": [{"type": "function", "name": "withdrawBalance", "source_mapping": {"start": 298, "length": 357, "filename": "tests/reentrancy-0.5.1.sol", "lines": [14, 15, 16, 17, 18, 19, 20, 21, 22]}, "contract": {"type": "contract", "name": "Reentrancy", "source_mapping": {"start": 25, "length": 1807, "filename": "tests/reentrancy-0.5.1.sol", "lines": [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]}}}, {"type": "external_calls", "expression": "(ret,mem) = msg.sender.call.value(userBalance[msg.sender])()", "source_mapping": {"start": 477, "length": 81, "filename": "tests/reentrancy-0.5.1.sol", "lines": [17]}}, {"type": "variables_written", "name": "userBalance", "expression": "userBalance[msg.sender] = 0", "source_mapping": {"start": 621, "length": 27, "filename": "tests/reentrancy-0.5.1.sol", "lines": [21]}}]}, {"check": "reentrancy", "impact": "High", "confidence": "Medium", "description": "Reentrancy in Reentrancy.withdrawBalance_fixed_3 (tests/reentrancy-0.5.1.sol#44-53):\n\tExternal calls:\n\t- (ret,mem) = msg.sender.call.value(amount)() (tests/reentrancy-0.5.1.sol#49)\n\tState variables written after the call(s):\n\t- userBalance (tests/reentrancy-0.5.1.sol#51)\n", "elements": [{"type": "function", "name": "withdrawBalance_fixed_3", "source_mapping": {"start": 1434, "length": 393, "filename": "tests/reentrancy-0.5.1.sol", "lines": [44, 45, 46, 47, 48, 49, 50, 51, 52, 53]}, "contract": {"type": "contract", "name": "Reentrancy", "source_mapping": {"start": 25, "length": 1807, "filename": "tests/reentrancy-0.5.1.sol", "lines": [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]}}}, {"type": "external_calls", "expression": "(ret,mem) = msg.sender.call.value(amount)()", "source_mapping": {"start": 1679, "length": 64, "filename": "tests/reentrancy-0.5.1.sol", "lines": [49]}}, {"type": "variables_written", "name": "userBalance", "expression": "userBalance[msg.sender] = amount", "source_mapping": {"start": 1778, "length": 32, "filename": "tests/reentrancy-0.5.1.sol", "lines": [51]}}]}] |
|
@ -0,0 +1 @@ |
|||||||
|
[{"check": "reentrancy-eth", "impact": "High", "confidence": "Medium", "description": "Reentrancy in Reentrancy.withdrawBalance (tests/reentrancy.sol#14-21):\n\tExternal calls:\n\t- ! (msg.sender.call.value(userBalance[msg.sender])()) (tests/reentrancy.sol#17-19)\n\tState variables written after the call(s):\n\t- userBalance (tests/reentrancy.sol#20)\n", "elements": [{"type": "function", "name": "withdrawBalance", "source_mapping": {"start": 299, "length": 314, "filename": "tests/reentrancy.sol", "lines": [14, 15, 16, 17, 18, 19, 20, 21]}, "contract": {"type": "contract", "name": "Reentrancy", "source_mapping": {"start": 26, "length": 2334, "filename": "tests/reentrancy.sol", "lines": [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]}}}, {"type": "external_calls", "expression": "! (msg.sender.call.value(userBalance[msg.sender])())", "source_mapping": {"start": 478, "length": 92, "filename": "tests/reentrancy.sol", "lines": [17, 18, 19]}}, {"type": "variables_written", "name": "userBalance", "expression": "userBalance[msg.sender] = 0", "source_mapping": {"start": 579, "length": 27, "filename": "tests/reentrancy.sol", "lines": [20]}}]}, {"check": "reentrancy-eth", "impact": "High", "confidence": "Medium", "description": "Reentrancy in Reentrancy.withdrawBalance_nested (tests/reentrancy.sol#64-70):\n\tExternal calls:\n\t- msg.sender.call.value(amount / 2)() (tests/reentrancy.sol#67)\n\tState variables written after the call(s):\n\t- userBalance (tests/reentrancy.sol#68)\n", "elements": [{"type": "function", "name": "withdrawBalance_nested", "source_mapping": {"start": 2108, "length": 246, "filename": "tests/reentrancy.sol", "lines": [64, 65, 66, 67, 68, 69, 70]}, "contract": {"type": "contract", "name": "Reentrancy", "source_mapping": {"start": 26, "length": 2334, "filename": "tests/reentrancy.sol", "lines": [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]}}}, {"type": "external_calls", "expression": "msg.sender.call.value(amount / 2)()", "source_mapping": {"start": 2263, "length": 33, "filename": "tests/reentrancy.sol", "lines": [67]}}, {"type": "variables_written", "name": "userBalance", "expression": "userBalance[msg.sender] = 0", "source_mapping": {"start": 2310, "length": 27, "filename": "tests/reentrancy.sol", "lines": [68]}}]}] |
@ -1 +0,0 @@ |
|||||||
[{"check": "reentrancy", "impact": "High", "confidence": "Medium", "description": "Reentrancy in Reentrancy.withdrawBalance (tests/reentrancy.sol#14-21):\n\tExternal calls:\n\t- ! (msg.sender.call.value(userBalance[msg.sender])()) (tests/reentrancy.sol#17-19)\n\tState variables written after the call(s):\n\t- userBalance (tests/reentrancy.sol#20)\n", "elements": [{"type": "function", "name": "withdrawBalance", "source_mapping": {"start": 299, "length": 314, "filename": "tests/reentrancy.sol", "lines": [14, 15, 16, 17, 18, 19, 20, 21]}, "contract": {"type": "contract", "name": "Reentrancy", "source_mapping": {"start": 26, "length": 1678, "filename": "tests/reentrancy.sol", "lines": [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]}}}, {"type": "external_calls", "expression": "! (msg.sender.call.value(userBalance[msg.sender])())", "source_mapping": {"start": 478, "length": 92, "filename": "tests/reentrancy.sol", "lines": [17, 18, 19]}}, {"type": "variables_written", "name": "userBalance", "expression": "userBalance[msg.sender] = 0", "source_mapping": {"start": 579, "length": 27, "filename": "tests/reentrancy.sol", "lines": [20]}}]}] |
|
Loading…
Reference in new issue