mirror of https://github.com/crytic/slither
Add support for enhanced analyses through code comments (#1089)
* Add support for enhanced analyses through code comments - Parse /// ... on variable declaration - Disable reentrancy for variables marked as 'sec:non-reentrant' - Create a new detector looking for wrong access control ('@custom:security write-protection="some_func")pull/746/merge
parent
81daa56f66
commit
d41861e6cf
@ -0,0 +1,81 @@ |
|||||||
|
""" |
||||||
|
Module detecting suicidal contract |
||||||
|
|
||||||
|
A suicidal contract is an unprotected function that calls selfdestruct |
||||||
|
""" |
||||||
|
from typing import List |
||||||
|
|
||||||
|
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification |
||||||
|
from slither.core.declarations import Function, Contract |
||||||
|
from slither.utils.output import Output |
||||||
|
|
||||||
|
|
||||||
|
class ProtectedVariables(AbstractDetector): |
||||||
|
|
||||||
|
ARGUMENT = "protected-vars" |
||||||
|
HELP = "Detected unprotected variables" |
||||||
|
IMPACT = DetectorClassification.HIGH |
||||||
|
CONFIDENCE = DetectorClassification.HIGH |
||||||
|
|
||||||
|
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#protected-variables" |
||||||
|
|
||||||
|
WIKI_TITLE = "Protected Variables" |
||||||
|
WIKI_DESCRIPTION = "Detect unprotected variable that are marked protected" |
||||||
|
|
||||||
|
# region wiki_exploit_scenario |
||||||
|
WIKI_EXPLOIT_SCENARIO = """ |
||||||
|
```solidity |
||||||
|
contract Buggy{ |
||||||
|
|
||||||
|
/// @custom:security write-protection="onlyOwner()" |
||||||
|
address owner; |
||||||
|
|
||||||
|
function set_protected() public onlyOwner(){ |
||||||
|
owner = msg.sender; |
||||||
|
} |
||||||
|
|
||||||
|
function set_not_protected() public{ |
||||||
|
owner = msg.sender; |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
`owner` must be always written by function using `onlyOwner` (`write-protection="onlyOwner()"`), however anyone can call `set_not_protected`. |
||||||
|
""" |
||||||
|
# endregion wiki_exploit_scenario |
||||||
|
|
||||||
|
WIKI_RECOMMENDATION = "Add access controls to the vulnerable function" |
||||||
|
|
||||||
|
def _analyze_function(self, function: Function, contract: Contract) -> List[Output]: |
||||||
|
results = [] |
||||||
|
|
||||||
|
for state_variable_written in function.state_variables_written: |
||||||
|
if state_variable_written.write_protection: |
||||||
|
for function_sig in state_variable_written.write_protection: |
||||||
|
function_protection = contract.get_function_from_signature(function_sig) |
||||||
|
if not function_protection: |
||||||
|
function_protection = contract.get_modifier_from_signature(function_sig) |
||||||
|
if not function_protection: |
||||||
|
self.logger.error(f"{function_sig} not found") |
||||||
|
continue |
||||||
|
if function_protection not in function.all_internal_calls(): |
||||||
|
info = [ |
||||||
|
function, |
||||||
|
" should have ", |
||||||
|
function_protection, |
||||||
|
" to protect ", |
||||||
|
state_variable_written, |
||||||
|
"\n", |
||||||
|
] |
||||||
|
|
||||||
|
res = self.generate_result(info) |
||||||
|
results.append(res) |
||||||
|
return results |
||||||
|
|
||||||
|
def _detect(self): |
||||||
|
"""Detect the suicidal functions""" |
||||||
|
results = [] |
||||||
|
for contract in self.compilation_unit.contracts_derived: |
||||||
|
for function in contract.functions_entry_points: |
||||||
|
results += self._analyze_function(function, contract) |
||||||
|
|
||||||
|
return results |
@ -0,0 +1,35 @@ |
|||||||
|
interface I{ |
||||||
|
function external_call() external; |
||||||
|
} |
||||||
|
|
||||||
|
contract ReentrancyAndWrite{ |
||||||
|
|
||||||
|
/// @custom:security non-reentrant |
||||||
|
/// @custom:security write-protection="onlyOwner()" |
||||||
|
I external_contract; |
||||||
|
|
||||||
|
modifier onlyOwner(){ |
||||||
|
// lets assume there is an access control |
||||||
|
_; |
||||||
|
} |
||||||
|
|
||||||
|
mapping(address => uint) balances; |
||||||
|
|
||||||
|
function withdraw() public{ |
||||||
|
uint balance = balances[msg.sender]; |
||||||
|
|
||||||
|
external_contract.external_call(); |
||||||
|
|
||||||
|
balances[msg.sender] = 0; |
||||||
|
payable(msg.sender).transfer(balance); |
||||||
|
} |
||||||
|
|
||||||
|
function set_protected() public onlyOwner(){ |
||||||
|
external_contract = I(msg.sender); |
||||||
|
} |
||||||
|
|
||||||
|
function set_not_protected() public{ |
||||||
|
external_contract = I(msg.sender); |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,223 @@ |
|||||||
|
[ |
||||||
|
[ |
||||||
|
{ |
||||||
|
"elements": [ |
||||||
|
{ |
||||||
|
"type": "function", |
||||||
|
"name": "set_not_protected", |
||||||
|
"source_mapping": { |
||||||
|
"start": 653, |
||||||
|
"length": 85, |
||||||
|
"filename_used": "/GENERIC_PATH", |
||||||
|
"filename_relative": "tests/detectors/protected-vars/0.8.2/comment.sol", |
||||||
|
"filename_absolute": "/GENERIC_PATH", |
||||||
|
"filename_short": "tests/detectors/protected-vars/0.8.2/comment.sol", |
||||||
|
"is_dependency": false, |
||||||
|
"lines": [ |
||||||
|
31, |
||||||
|
32, |
||||||
|
33 |
||||||
|
], |
||||||
|
"starting_column": 5, |
||||||
|
"ending_column": 6 |
||||||
|
}, |
||||||
|
"type_specific_fields": { |
||||||
|
"parent": { |
||||||
|
"type": "contract", |
||||||
|
"name": "ReentrancyAndWrite", |
||||||
|
"source_mapping": { |
||||||
|
"start": 55, |
||||||
|
"length": 685, |
||||||
|
"filename_used": "/GENERIC_PATH", |
||||||
|
"filename_relative": "tests/detectors/protected-vars/0.8.2/comment.sol", |
||||||
|
"filename_absolute": "/GENERIC_PATH", |
||||||
|
"filename_short": "tests/detectors/protected-vars/0.8.2/comment.sol", |
||||||
|
"is_dependency": false, |
||||||
|
"lines": [ |
||||||
|
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 |
||||||
|
], |
||||||
|
"starting_column": 1, |
||||||
|
"ending_column": 2 |
||||||
|
} |
||||||
|
}, |
||||||
|
"signature": "set_not_protected()" |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
"type": "function", |
||||||
|
"name": "onlyOwner", |
||||||
|
"source_mapping": { |
||||||
|
"start": 210, |
||||||
|
"length": 88, |
||||||
|
"filename_used": "/GENERIC_PATH", |
||||||
|
"filename_relative": "tests/detectors/protected-vars/0.8.2/comment.sol", |
||||||
|
"filename_absolute": "/GENERIC_PATH", |
||||||
|
"filename_short": "tests/detectors/protected-vars/0.8.2/comment.sol", |
||||||
|
"is_dependency": false, |
||||||
|
"lines": [ |
||||||
|
11, |
||||||
|
12, |
||||||
|
13, |
||||||
|
14 |
||||||
|
], |
||||||
|
"starting_column": 5, |
||||||
|
"ending_column": 6 |
||||||
|
}, |
||||||
|
"type_specific_fields": { |
||||||
|
"parent": { |
||||||
|
"type": "contract", |
||||||
|
"name": "ReentrancyAndWrite", |
||||||
|
"source_mapping": { |
||||||
|
"start": 55, |
||||||
|
"length": 685, |
||||||
|
"filename_used": "/GENERIC_PATH", |
||||||
|
"filename_relative": "tests/detectors/protected-vars/0.8.2/comment.sol", |
||||||
|
"filename_absolute": "/GENERIC_PATH", |
||||||
|
"filename_short": "tests/detectors/protected-vars/0.8.2/comment.sol", |
||||||
|
"is_dependency": false, |
||||||
|
"lines": [ |
||||||
|
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 |
||||||
|
], |
||||||
|
"starting_column": 1, |
||||||
|
"ending_column": 2 |
||||||
|
} |
||||||
|
}, |
||||||
|
"signature": "onlyOwner()" |
||||||
|
} |
||||||
|
}, |
||||||
|
{ |
||||||
|
"type": "variable", |
||||||
|
"name": "external_contract", |
||||||
|
"source_mapping": { |
||||||
|
"start": 184, |
||||||
|
"length": 19, |
||||||
|
"filename_used": "/GENERIC_PATH", |
||||||
|
"filename_relative": "tests/detectors/protected-vars/0.8.2/comment.sol", |
||||||
|
"filename_absolute": "/GENERIC_PATH", |
||||||
|
"filename_short": "tests/detectors/protected-vars/0.8.2/comment.sol", |
||||||
|
"is_dependency": false, |
||||||
|
"lines": [ |
||||||
|
9 |
||||||
|
], |
||||||
|
"starting_column": 5, |
||||||
|
"ending_column": 24 |
||||||
|
}, |
||||||
|
"type_specific_fields": { |
||||||
|
"parent": { |
||||||
|
"type": "contract", |
||||||
|
"name": "ReentrancyAndWrite", |
||||||
|
"source_mapping": { |
||||||
|
"start": 55, |
||||||
|
"length": 685, |
||||||
|
"filename_used": "/GENERIC_PATH", |
||||||
|
"filename_relative": "tests/detectors/protected-vars/0.8.2/comment.sol", |
||||||
|
"filename_absolute": "/GENERIC_PATH", |
||||||
|
"filename_short": "tests/detectors/protected-vars/0.8.2/comment.sol", |
||||||
|
"is_dependency": false, |
||||||
|
"lines": [ |
||||||
|
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 |
||||||
|
], |
||||||
|
"starting_column": 1, |
||||||
|
"ending_column": 2 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
], |
||||||
|
"description": "ReentrancyAndWrite.set_not_protected() (tests/detectors/protected-vars/0.8.2/comment.sol#31-33) should have ReentrancyAndWrite.onlyOwner() (tests/detectors/protected-vars/0.8.2/comment.sol#11-14) to protect ReentrancyAndWrite.external_contract (tests/detectors/protected-vars/0.8.2/comment.sol#9)\n", |
||||||
|
"markdown": "[ReentrancyAndWrite.set_not_protected()](tests/detectors/protected-vars/0.8.2/comment.sol#L31-L33) should have [ReentrancyAndWrite.onlyOwner()](tests/detectors/protected-vars/0.8.2/comment.sol#L11-L14) to protect [ReentrancyAndWrite.external_contract](tests/detectors/protected-vars/0.8.2/comment.sol#L9)\n", |
||||||
|
"first_markdown_element": "tests/detectors/protected-vars/0.8.2/comment.sol#L31-L33", |
||||||
|
"id": "3f3bc8c8a9b3e23482f47f1133aceaed81c2c781c6aaf25656a8e578c9f6cb0e", |
||||||
|
"check": "protected-vars", |
||||||
|
"impact": "High", |
||||||
|
"confidence": "High" |
||||||
|
} |
||||||
|
] |
||||||
|
] |
@ -0,0 +1,35 @@ |
|||||||
|
interface I{ |
||||||
|
function external_call() external; |
||||||
|
} |
||||||
|
|
||||||
|
contract ReentrancyAndWrite{ |
||||||
|
|
||||||
|
/// @custom:security non-reentrant |
||||||
|
/// @custom:security write-protection="onlyOwner()" |
||||||
|
I external_contract; |
||||||
|
|
||||||
|
modifier onlyOwner(){ |
||||||
|
// lets assume there is an access control |
||||||
|
_; |
||||||
|
} |
||||||
|
|
||||||
|
mapping(address => uint) balances; |
||||||
|
|
||||||
|
function withdraw() public{ |
||||||
|
uint balance = balances[msg.sender]; |
||||||
|
|
||||||
|
external_contract.external_call(); |
||||||
|
|
||||||
|
balances[msg.sender] = 0; |
||||||
|
payable(msg.sender).transfer(balance); |
||||||
|
} |
||||||
|
|
||||||
|
function set_protected() public onlyOwner(){ |
||||||
|
external_contract = I(msg.sender); |
||||||
|
} |
||||||
|
|
||||||
|
function set_not_protected() public{ |
||||||
|
external_contract = I(msg.sender); |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,3 @@ |
|||||||
|
[ |
||||||
|
[] |
||||||
|
] |
Loading…
Reference in new issue