-
Notifications
You must be signed in to change notification settings - Fork 61
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ec0830f
commit f92059a
Showing
7 changed files
with
905 additions
and
1 deletion.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
from slither_pess.detectors.arbitrary_call import ArbitraryCall | ||
from slither_pess.detectors.double_entry_token_possibility import ( | ||
DoubleEntryTokenPossiblity, | ||
) | ||
from slither_pess.detectors.dubious_typecast import DubiousTypecast | ||
from slither_pess.detectors.falsy_only_eoa_modifier import OnlyEOACheck | ||
from slither_pess.detectors.magic_number import MagicNumber | ||
from slither_pess.detectors.strange_setter import StrangeSetter | ||
from slither_pess.detectors.unprotected_setter import UnprotectedSetter | ||
from slither_pess.detectors.nft_approve_warning import NftApproveWarning | ||
from slither_pess.detectors.inconsistent_nonreentrant import InconsistentNonreentrant | ||
from slither_pess.detectors.call_forward_to_protected import CallForwardToProtected | ||
from slither_pess.detectors.multiple_storage_read import MultipleStorageRead | ||
from slither_pess.detectors.timelock_controller import TimelockController | ||
from slither_pess.detectors.tx_gasprice_warning import TxGaspriceWarning | ||
from slither_pess.detectors.unprotected_initialize import UnprotectedInitialize | ||
from slither_pess.detectors.readonly_reentrancy.read_only_reentrancy import ( | ||
ReadOnlyReentrancy, | ||
) | ||
from slither_pess.detectors.event_setter import EventSetter | ||
from slither_pess.detectors.before_token_transfer import BeforeTokenTransfer | ||
from slither_pess.detectors.uni_v2 import UniswapV2 | ||
from slither_pess.detectors.token_fallback import TokenFallback | ||
from slither_pess.detectors.for_continue_increment import ForContinueIncrement | ||
from slither_pess.detectors.ecrecover import Ecrecover | ||
from slither_pess.detectors.public_vs_external import PublicVsExternal | ||
from slither_pess.detectors.readonly_reentrancy.balancer_readonly_reentrancy import ( | ||
BalancerReadonlyReentrancy, | ||
) | ||
|
||
|
||
def make_plugin(): | ||
plugin_detectors = [ | ||
DoubleEntryTokenPossiblity, | ||
UnprotectedSetter, | ||
NftApproveWarning, | ||
InconsistentNonreentrant, | ||
StrangeSetter, | ||
OnlyEOACheck, | ||
MagicNumber, | ||
DubiousTypecast, | ||
CallForwardToProtected, | ||
MultipleStorageRead, | ||
TimelockController, | ||
TxGaspriceWarning, | ||
UnprotectedInitialize, | ||
ReadOnlyReentrancy, | ||
EventSetter, | ||
BeforeTokenTransfer, | ||
UniswapV2, | ||
TokenFallback, | ||
ForContinueIncrement, | ||
ArbitraryCall, | ||
Ecrecover, | ||
PublicVsExternal, | ||
BalancerReadonlyReentrancy, | ||
] | ||
plugin_printers = [] | ||
|
||
return plugin_detectors, plugin_printers |
Empty file.
96 changes: 96 additions & 0 deletions
96
slither_pess/detectors/readonly_reentrancy/balancer_readonly_reentrancy.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
from typing import List | ||
from slither.utils.output import Output | ||
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification | ||
from slither.core.declarations import Function, Contract | ||
from slither.core.cfg.node import Node | ||
|
||
|
||
class BalancerReadonlyReentrancy(AbstractDetector): | ||
""" | ||
Sees if a contract has a beforeTokenTransfer function. | ||
""" | ||
|
||
ARGUMENT = "pess-balancer-readonly-reentrancy" # slither will launch the detector with slither.py --detect mydetector | ||
HELP = "beforeTokenTransfer function does not follow OZ documentation" | ||
IMPACT = DetectorClassification.LOW | ||
CONFIDENCE = DetectorClassification.HIGH | ||
|
||
WIKI = ( | ||
"https://docs.openzeppelin.com/contracts/4.x/extending-contracts#rules_of_hooks" | ||
) | ||
WIKI_TITLE = "Before Token Transfer" | ||
WIKI_DESCRIPTION = "Follow OZ documentation using their contracts" | ||
WIKI_EXPLOIT_SCENARIO = "-" | ||
WIKI_RECOMMENDATION = ( | ||
"Make sure that beforeTokenTransfer function is used in the correct way." | ||
) | ||
|
||
VULNERABLE_FUNCTION_CALLS = ["getRate", "getPoolTokens"] | ||
visited = [] | ||
contains_reentrancy_check = {} | ||
|
||
def is_balancer_integration(self, c: Contract) -> bool: | ||
""" | ||
Iterates over all external function calls, and checks the interface/contract name | ||
for a specific keywords to decide if the contract integrates with balancer | ||
""" | ||
for ( | ||
fcontract, | ||
_, | ||
) in c.all_high_level_calls: | ||
contract_name = fcontract.name.lower() | ||
if any(map(lambda x: x in contract_name, ["balancer", "ivault", "pool"])): | ||
return True | ||
|
||
def _has_reentrancy_check(self, node: Node) -> bool: | ||
if node in self.visited: | ||
return self.contains_reentrancy_check[node] | ||
|
||
self.visited.append(node) | ||
self.contains_reentrancy_check[node] = False | ||
|
||
for c, n in node.high_level_calls: | ||
if isinstance(n, Function): | ||
if ( | ||
n.name == "ensureNotInVaultContext" | ||
and c.name == "VaultReentrancyLib" | ||
) or ( | ||
n.name == "manageUserBalance" | ||
): # TODO check if errors out | ||
self.contains_reentrancy_check[node] = True | ||
return True | ||
|
||
has_check = False | ||
for internal_call in node.internal_calls: | ||
if isinstance(internal_call, Function): | ||
has_check |= self._has_reentrancy_check(internal_call) | ||
# self.contains_reentrancy_check[internal_call] |= has_check | ||
|
||
self.contains_reentrancy_check[node] = has_check | ||
return has_check | ||
|
||
def _check_function(self, function: Function) -> list: | ||
has_dangerous_call = False | ||
dangerous_call = None | ||
for n in function.nodes: | ||
for c, fc in n.high_level_calls: | ||
if isinstance(fc, Function): | ||
if fc.name in self.VULNERABLE_FUNCTION_CALLS: | ||
dangerous_call = fc | ||
has_dangerous_call = True | ||
break | ||
|
||
if has_dangerous_call and not any( | ||
[self._has_reentrancy_check(node) for node in function.nodes] | ||
): | ||
print("READONLY_REENTRANCY!!!") | ||
|
||
def _detect(self) -> List[Output]: | ||
"""Main function""" | ||
res = [] | ||
for contract in self.compilation_unit.contracts_derived: | ||
if not self.is_balancer_integration(contract): | ||
continue | ||
for f in contract.functions_and_modifiers_declared: | ||
self._check_function(f) | ||
return res |
Oops, something went wrong.