mirror of https://github.com/crytic/slither
commit
722b9a6867
@ -0,0 +1,68 @@ |
||||
""" |
||||
Module detecting unused return values from external calls |
||||
""" |
||||
|
||||
from collections import defaultdict |
||||
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification |
||||
from slither.slithir.operations.high_level_call import HighLevelCall |
||||
from slither.core.variables.state_variable import StateVariable |
||||
|
||||
class UnusedReturnValues(AbstractDetector): |
||||
""" |
||||
If the return value of a function is never used, it's likely to be bug |
||||
""" |
||||
|
||||
ARGUMENT = 'unused-return' |
||||
HELP = 'Unused return values' |
||||
IMPACT = DetectorClassification.LOW |
||||
CONFIDENCE = DetectorClassification.MEDIUM |
||||
|
||||
WIKI = 'https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#unused-return' |
||||
|
||||
def detect_unused_return_values(self, f): |
||||
""" |
||||
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, HighLevelCall): |
||||
# 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): |
||||
""" Detect unused high level calls that return a value but are never used |
||||
""" |
||||
results = [] |
||||
for c in self.slither.contracts: |
||||
for f in c.functions + c.modifiers: |
||||
unused_return = self.detect_unused_return_values(f) |
||||
if unused_return: |
||||
info = "{}.{} ({}) does not use the value returned by external calls:\n" |
||||
info = info.format(f.contract.name, |
||||
f.name, |
||||
f.source_mapping_str) |
||||
for node in unused_return: |
||||
info += "\t-{} ({})\n".format(node.expression, node.source_mapping_str) |
||||
self.log(info) |
||||
|
||||
sourceMapping = [v.source_mapping for v in unused_return] |
||||
|
||||
results.append({'vuln': 'UnusedReturn', |
||||
'sourceMapping': sourceMapping, |
||||
'filename': self.filename, |
||||
'contract': c.name, |
||||
'expressions':[str(n.expression) for n in unused_return]}) |
||||
return results |
@ -0,0 +1,29 @@ |
||||
[ |
||||
{ |
||||
"contract": "User", |
||||
"expressions": [ |
||||
"a.add(0)", |
||||
"t.f()" |
||||
], |
||||
"filename": "tests/unused_return.sol", |
||||
"sourceMapping": [ |
||||
{ |
||||
"filename": "tests/unused_return.sol", |
||||
"length": 5, |
||||
"lines": [ |
||||
18 |
||||
], |
||||
"start": 263 |
||||
}, |
||||
{ |
||||
"filename": "tests/unused_return.sol", |
||||
"length": 8, |
||||
"lines": [ |
||||
22 |
||||
], |
||||
"start": 337 |
||||
} |
||||
], |
||||
"vuln": "UnusedReturn" |
||||
} |
||||
] |
@ -0,0 +1,30 @@ |
||||
pragma solidity ^0.4.24; |
||||
|
||||
library SafeMath{ |
||||
function add(uint a, uint b) public returns(uint){ |
||||
return a+b; |
||||
} |
||||
} |
||||
|
||||
contract Target{ |
||||
function f() returns(uint); |
||||
} |
||||
|
||||
contract User{ |
||||
|
||||
using SafeMath for uint; |
||||
|
||||
function test(Target t){ |
||||
t.f(); |
||||
|
||||
// example with library usage |
||||
uint a; |
||||
a.add(0); |
||||
|
||||
// The value is not used |
||||
// But the detector should not detect it |
||||
// As the value returned by the call is stored |
||||
// (unused local variable should be another issue) |
||||
uint b = a.add(1); |
||||
} |
||||
} |
Loading…
Reference in new issue