mirror of https://github.com/crytic/slither
commit
4214b932ad
@ -0,0 +1,48 @@ |
||||
--- |
||||
body: |
||||
- |
||||
attributes: |
||||
value: | |
||||
Please check the issues tab to avoid duplicates. |
||||
Thanks for taking the time to fill out this bug report! |
||||
type: markdown |
||||
- |
||||
attributes: |
||||
label: "Describe the issue:" |
||||
id: what-happened |
||||
type: textarea |
||||
validations: |
||||
required: true |
||||
- |
||||
attributes: |
||||
description: "It can be a github repo, etherscan link, or code snippet." |
||||
label: "Code example to reproduce the issue:" |
||||
placeholder: "`contract A {}`\n" |
||||
id: reproduce |
||||
type: textarea |
||||
validations: |
||||
required: true |
||||
- |
||||
attributes: |
||||
description: | |
||||
What version of slither are you running? |
||||
Run `slither --version` |
||||
label: "Version:" |
||||
id: version |
||||
type: textarea |
||||
validations: |
||||
required: true |
||||
- |
||||
attributes: |
||||
description: | |
||||
Please copy and paste any relevant log output. This |
||||
will be automatically formatted into code, so no need for backticks. |
||||
render: shell |
||||
label: "Relevant log output:" |
||||
id: logs |
||||
type: textarea |
||||
description: "File a bug report" |
||||
labels: |
||||
- bug-candidate |
||||
name: "Bug Report" |
||||
title: "[Bug-Candidate]: " |
@ -0,0 +1,17 @@ |
||||
--- |
||||
name: Feature request |
||||
description: Suggest a feature |
||||
labels: ["enhancement"] |
||||
body: |
||||
- type: markdown |
||||
attributes: |
||||
value: | |
||||
Please check the issues tab to avoid duplicates. |
||||
|
||||
Thanks for providing feedback on Slither! |
||||
- type: textarea |
||||
attributes: |
||||
label: Describe the desired feature |
||||
description: Explain what the feature solves/ improves. |
||||
validations: |
||||
required: true |
@ -0,0 +1,42 @@ |
||||
--- |
||||
name: IR tests |
||||
|
||||
defaults: |
||||
run: |
||||
# To load bashrc |
||||
shell: bash -ieo pipefail {0} |
||||
|
||||
on: |
||||
pull_request: |
||||
branches: [master, dev] |
||||
schedule: |
||||
# run CI every day even if no PRs/merges occur |
||||
- cron: '0 12 * * *' |
||||
|
||||
jobs: |
||||
build: |
||||
name: IR tests |
||||
runs-on: ${{ matrix.os }} |
||||
strategy: |
||||
fail-fast: false |
||||
matrix: |
||||
os: [ubuntu-latest, windows-2022] |
||||
|
||||
steps: |
||||
- name: Checkout Code |
||||
uses: actions/checkout@v2 |
||||
|
||||
- name: Set up Python 3.8 |
||||
uses: actions/setup-python@v3 |
||||
with: |
||||
python-version: 3.8 |
||||
|
||||
- name: Install dependencies |
||||
run: | |
||||
pip install ".[dev]" |
||||
solc-select install all |
||||
solc-select use 0.8.11 |
||||
|
||||
- name: Test with pytest |
||||
run: | |
||||
pytest tests/test_ssa_generation.py |
@ -0,0 +1,50 @@ |
||||
--- |
||||
name: Test slither-read-storage |
||||
|
||||
defaults: |
||||
run: |
||||
# To load bashrc |
||||
shell: bash -ieo pipefail {0} |
||||
|
||||
on: |
||||
pull_request: |
||||
branches: [master, dev] |
||||
schedule: |
||||
# run CI every day even if no PRs/merges occur |
||||
- cron: '0 12 * * *' |
||||
|
||||
jobs: |
||||
build: |
||||
name: Test slither-read-storage |
||||
runs-on: ubuntu-latest |
||||
|
||||
steps: |
||||
- uses: actions/checkout@v2 |
||||
- name: Setup node |
||||
uses: actions/setup-node@v2 |
||||
with: |
||||
node-version: '14' |
||||
|
||||
- name: Install ganache |
||||
run: npm install --global ganache |
||||
|
||||
- name: Set up Python 3.8 |
||||
uses: actions/setup-python@v2 |
||||
with: |
||||
python-version: 3.8 |
||||
|
||||
- name: Install python dependencies |
||||
run: | |
||||
pip install ".[dev]" |
||||
pip install web3 |
||||
solc-select install 0.8.1 |
||||
solc-select install 0.8.10 |
||||
solc-select use 0.8.1 |
||||
|
||||
- name: Run slither-read-storage |
||||
run: | |
||||
pytest tests/test_read_storage.py |
||||
|
||||
- name: Run storage layout tests |
||||
run: | |
||||
pytest tests/test_storage_layout.py |
@ -0,0 +1,41 @@ |
||||
from typing import TYPE_CHECKING, Tuple |
||||
|
||||
from slither.core.children.child_contract import ChildContract |
||||
from slither.core.declarations.top_level import TopLevel |
||||
from slither.core.solidity_types import Type |
||||
|
||||
if TYPE_CHECKING: |
||||
from slither.core.declarations import Contract |
||||
from slither.core.scope.scope import FileScope |
||||
|
||||
|
||||
class TypeAlias(Type): |
||||
def __init__(self, underlying_type: Type, name: str): |
||||
super().__init__() |
||||
self.name = name |
||||
self.underlying_type = underlying_type |
||||
|
||||
@property |
||||
def storage_size(self) -> Tuple[int, bool]: |
||||
return self.underlying_type.storage_size |
||||
|
||||
def __hash__(self): |
||||
return hash(str(self)) |
||||
|
||||
|
||||
class TypeAliasTopLevel(TypeAlias, TopLevel): |
||||
def __init__(self, underlying_type: Type, name: str, scope: "FileScope"): |
||||
super().__init__(underlying_type, name) |
||||
self.file_scope: "FileScope" = scope |
||||
|
||||
def __str__(self): |
||||
return self.name |
||||
|
||||
|
||||
class TypeAliasContract(TypeAlias, ChildContract): |
||||
def __init__(self, underlying_type: Type, name: str, contract: "Contract"): |
||||
super().__init__(underlying_type, name) |
||||
self._contract: "Contract" = contract |
||||
|
||||
def __str__(self): |
||||
return self.contract.name + "." + self.name |
@ -0,0 +1,95 @@ |
||||
from typing import List |
||||
from slither.core.cfg.node import Node |
||||
from slither.core.declarations.solidity_variables import SolidityVariable |
||||
from slither.slithir.operations import HighLevelCall, LibraryCall |
||||
from slither.core.declarations import Contract, Function, SolidityVariableComposed |
||||
from slither.analyses.data_dependency.data_dependency import is_dependent |
||||
from slither.core.compilation_unit import SlitherCompilationUnit |
||||
|
||||
|
||||
class ArbitrarySendErc20: |
||||
"""Detects instances where ERC20 can be sent from an arbitrary from address.""" |
||||
|
||||
def __init__(self, compilation_unit: SlitherCompilationUnit): |
||||
self._compilation_unit = compilation_unit |
||||
self._no_permit_results: List[Node] = [] |
||||
self._permit_results: List[Node] = [] |
||||
|
||||
@property |
||||
def compilation_unit(self) -> SlitherCompilationUnit: |
||||
return self._compilation_unit |
||||
|
||||
@property |
||||
def no_permit_results(self) -> List[Node]: |
||||
return self._no_permit_results |
||||
|
||||
@property |
||||
def permit_results(self) -> List[Node]: |
||||
return self._permit_results |
||||
|
||||
def _detect_arbitrary_from(self, contract: Contract): |
||||
for f in contract.functions: |
||||
all_high_level_calls = [ |
||||
f_called[1].solidity_signature |
||||
for f_called in f.high_level_calls |
||||
if isinstance(f_called[1], Function) |
||||
] |
||||
all_library_calls = [f_called[1].solidity_signature for f_called in f.library_calls] |
||||
if ( |
||||
"transferFrom(address,address,uint256)" in all_high_level_calls |
||||
or "safeTransferFrom(address,address,address,uint256)" in all_library_calls |
||||
): |
||||
if ( |
||||
"permit(address,address,uint256,uint256,uint8,bytes32,bytes32)" |
||||
in all_high_level_calls |
||||
): |
||||
ArbitrarySendErc20._arbitrary_from(f.nodes, self._permit_results) |
||||
else: |
||||
ArbitrarySendErc20._arbitrary_from(f.nodes, self._no_permit_results) |
||||
|
||||
@staticmethod |
||||
def _arbitrary_from(nodes: List[Node], results: List[Node]): |
||||
"""Finds instances of (safe)transferFrom that do not use msg.sender or address(this) as from parameter.""" |
||||
for node in nodes: |
||||
for ir in node.irs: |
||||
if ( |
||||
isinstance(ir, HighLevelCall) |
||||
and isinstance(ir.function, Function) |
||||
and ir.function.solidity_signature == "transferFrom(address,address,uint256)" |
||||
and not ( |
||||
is_dependent( |
||||
ir.arguments[0], |
||||
SolidityVariableComposed("msg.sender"), |
||||
node.function.contract, |
||||
) |
||||
or is_dependent( |
||||
ir.arguments[0], |
||||
SolidityVariable("this"), |
||||
node.function.contract, |
||||
) |
||||
) |
||||
): |
||||
results.append(ir.node) |
||||
elif ( |
||||
isinstance(ir, LibraryCall) |
||||
and ir.function.solidity_signature |
||||
== "safeTransferFrom(address,address,address,uint256)" |
||||
and not ( |
||||
is_dependent( |
||||
ir.arguments[1], |
||||
SolidityVariableComposed("msg.sender"), |
||||
node.function.contract, |
||||
) |
||||
or is_dependent( |
||||
ir.arguments[1], |
||||
SolidityVariable("this"), |
||||
node.function.contract, |
||||
) |
||||
) |
||||
): |
||||
results.append(ir.node) |
||||
|
||||
def detect(self): |
||||
"""Detect transfers that use arbitrary `from` parameter.""" |
||||
for c in self.compilation_unit.contracts_derived: |
||||
self._detect_arbitrary_from(c) |
@ -0,0 +1,45 @@ |
||||
from typing import List |
||||
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification |
||||
from slither.utils.output import Output |
||||
from .arbitrary_send_erc20 import ArbitrarySendErc20 |
||||
|
||||
|
||||
class ArbitrarySendErc20NoPermit(AbstractDetector): |
||||
""" |
||||
Detect when `msg.sender` is not used as `from` in transferFrom |
||||
""" |
||||
|
||||
ARGUMENT = "arbitrary-send-erc20" |
||||
HELP = "transferFrom uses arbitrary `from`" |
||||
IMPACT = DetectorClassification.HIGH |
||||
CONFIDENCE = DetectorClassification.HIGH |
||||
|
||||
WIKI = "https://github.com/trailofbits/slither/wiki/Detector-Documentation#arbitrary-send-erc20" |
||||
|
||||
WIKI_TITLE = "Arbitrary `from` in transferFrom" |
||||
WIKI_DESCRIPTION = "Detect when `msg.sender` is not used as `from` in transferFrom." |
||||
WIKI_EXPLOIT_SCENARIO = """ |
||||
```solidity |
||||
function a(address from, address to, uint256 amount) public { |
||||
erc20.transferFrom(from, to, am); |
||||
} |
||||
``` |
||||
Alice approves this contract to spend her ERC20 tokens. Bob can call `a` and specify Alice's address as the `from` parameter in `transferFrom`, allowing him to transfer Alice's tokens to himself.""" |
||||
|
||||
WIKI_RECOMMENDATION = """ |
||||
Use `msg.sender` as `from` in transferFrom. |
||||
""" |
||||
|
||||
def _detect(self) -> List[Output]: |
||||
"""""" |
||||
results: List[Output] = [] |
||||
|
||||
arbitrary_sends = ArbitrarySendErc20(self.compilation_unit) |
||||
arbitrary_sends.detect() |
||||
for node in arbitrary_sends.no_permit_results: |
||||
func = node.function |
||||
info = [func, " uses arbitrary from in transferFrom: ", node, "\n"] |
||||
res = self.generate_result(info) |
||||
results.append(res) |
||||
|
||||
return results |
@ -0,0 +1,53 @@ |
||||
from typing import List |
||||
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification |
||||
from slither.utils.output import Output |
||||
from .arbitrary_send_erc20 import ArbitrarySendErc20 |
||||
|
||||
|
||||
class ArbitrarySendErc20Permit(AbstractDetector): |
||||
""" |
||||
Detect when `msg.sender` is not used as `from` in transferFrom along with the use of permit. |
||||
""" |
||||
|
||||
ARGUMENT = "arbitrary-send-erc20-permit" |
||||
HELP = "transferFrom uses arbitrary from with permit" |
||||
IMPACT = DetectorClassification.HIGH |
||||
CONFIDENCE = DetectorClassification.MEDIUM |
||||
|
||||
WIKI = "https://github.com/trailofbits/slither/wiki/Detector-Documentation#arbitrary-send-erc20-permit" |
||||
|
||||
WIKI_TITLE = "Arbitrary `from` in transferFrom used with permit" |
||||
WIKI_DESCRIPTION = ( |
||||
"Detect when `msg.sender` is not used as `from` in transferFrom and permit is used." |
||||
) |
||||
WIKI_EXPLOIT_SCENARIO = """ |
||||
```solidity |
||||
function bad(address from, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s, address to) public { |
||||
erc20.permit(from, address(this), value, deadline, v, r, s); |
||||
erc20.transferFrom(from, to, value); |
||||
} |
||||
``` |
||||
If an ERC20 token does not implement permit and has a fallback function e.g. WETH, transferFrom allows an attacker to transfer all tokens approved for this contract.""" |
||||
|
||||
WIKI_RECOMMENDATION = """ |
||||
Ensure that the underlying ERC20 token correctly implements a permit function. |
||||
""" |
||||
|
||||
def _detect(self) -> List[Output]: |
||||
"""""" |
||||
results: List[Output] = [] |
||||
|
||||
arbitrary_sends = ArbitrarySendErc20(self.compilation_unit) |
||||
arbitrary_sends.detect() |
||||
for node in arbitrary_sends.permit_results: |
||||
func = node.function |
||||
info = [ |
||||
func, |
||||
" uses arbitrary from in transferFrom in combination with permit: ", |
||||
node, |
||||
"\n", |
||||
] |
||||
res = self.generate_result(info) |
||||
results.append(res) |
||||
|
||||
return results |
@ -1,144 +0,0 @@ |
||||
""" |
||||
Module detecting send to arbitrary address |
||||
|
||||
To avoid FP, it does not report: |
||||
- If msg.sender is used as index (withdraw situation) |
||||
- If the function is protected |
||||
- If the value sent is msg.value (repay situation) |
||||
- If there is a call to transferFrom |
||||
|
||||
TODO: dont report if the value is tainted by msg.value |
||||
""" |
||||
from typing import List |
||||
|
||||
from slither.core.cfg.node import Node |
||||
from slither.core.declarations import Function, Contract |
||||
from slither.analyses.data_dependency.data_dependency import is_tainted, is_dependent |
||||
from slither.core.declarations.solidity_variables import ( |
||||
SolidityFunction, |
||||
SolidityVariableComposed, |
||||
) |
||||
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification |
||||
from slither.slithir.operations import ( |
||||
HighLevelCall, |
||||
Index, |
||||
LowLevelCall, |
||||
Send, |
||||
SolidityCall, |
||||
Transfer, |
||||
) |
||||
|
||||
|
||||
# pylint: disable=too-many-nested-blocks,too-many-branches |
||||
from slither.utils.output import Output |
||||
|
||||
|
||||
def arbitrary_send(func: Function): |
||||
if func.is_protected(): |
||||
return [] |
||||
|
||||
ret: List[Node] = [] |
||||
for node in func.nodes: |
||||
for ir in node.irs: |
||||
if isinstance(ir, SolidityCall): |
||||
if ir.function == SolidityFunction("ecrecover(bytes32,uint8,bytes32,bytes32)"): |
||||
return False |
||||
if isinstance(ir, Index): |
||||
if ir.variable_right == SolidityVariableComposed("msg.sender"): |
||||
return False |
||||
if is_dependent( |
||||
ir.variable_right, |
||||
SolidityVariableComposed("msg.sender"), |
||||
func.contract, |
||||
): |
||||
return False |
||||
if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)): |
||||
if isinstance(ir, (HighLevelCall)): |
||||
if isinstance(ir.function, Function): |
||||
if ir.function.full_name == "transferFrom(address,address,uint256)": |
||||
return False |
||||
if ir.call_value is None: |
||||
continue |
||||
if ir.call_value == SolidityVariableComposed("msg.value"): |
||||
continue |
||||
if is_dependent( |
||||
ir.call_value, |
||||
SolidityVariableComposed("msg.value"), |
||||
func.contract, |
||||
): |
||||
continue |
||||
|
||||
if is_tainted(ir.destination, func.contract): |
||||
ret.append(node) |
||||
|
||||
return ret |
||||
|
||||
|
||||
def detect_arbitrary_send(contract: Contract): |
||||
""" |
||||
Detect arbitrary send |
||||
Args: |
||||
contract (Contract) |
||||
Returns: |
||||
list((Function), (list (Node))) |
||||
""" |
||||
ret = [] |
||||
for f in [f for f in contract.functions if f.contract_declarer == contract]: |
||||
nodes = arbitrary_send(f) |
||||
if nodes: |
||||
ret.append((f, nodes)) |
||||
return ret |
||||
|
||||
|
||||
class ArbitrarySend(AbstractDetector): |
||||
ARGUMENT = "arbitrary-send" |
||||
HELP = "Functions that send Ether to arbitrary destinations" |
||||
IMPACT = DetectorClassification.HIGH |
||||
CONFIDENCE = DetectorClassification.MEDIUM |
||||
|
||||
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#functions-that-send-ether-to-arbitrary-destinations" |
||||
|
||||
WIKI_TITLE = "Functions that send Ether to arbitrary destinations" |
||||
WIKI_DESCRIPTION = "Unprotected call to a function sending Ether to an arbitrary address." |
||||
|
||||
# region wiki_exploit_scenario |
||||
WIKI_EXPLOIT_SCENARIO = """ |
||||
```solidity |
||||
contract ArbitrarySend{ |
||||
address destination; |
||||
function setDestination(){ |
||||
destination = msg.sender; |
||||
} |
||||
|
||||
function withdraw() public{ |
||||
destination.transfer(this.balance); |
||||
} |
||||
} |
||||
``` |
||||
Bob calls `setDestination` and `withdraw`. As a result he withdraws the contract's balance.""" |
||||
# endregion wiki_exploit_scenario |
||||
|
||||
WIKI_RECOMMENDATION = "Ensure that an arbitrary user cannot withdraw unauthorized funds." |
||||
|
||||
def _detect(self) -> List[Output]: |
||||
"""""" |
||||
results = [] |
||||
|
||||
for c in self.contracts: |
||||
arbitrary_send_result = detect_arbitrary_send(c) |
||||
for (func, nodes) in arbitrary_send_result: |
||||
|
||||
info = [func, " sends eth to arbitrary user\n"] |
||||
info += ["\tDangerous calls:\n"] |
||||
|
||||
# sort the nodes to get deterministic results |
||||
nodes.sort(key=lambda x: x.node_id) |
||||
|
||||
for node in nodes: |
||||
info += ["\t- ", node, "\n"] |
||||
|
||||
res = self.generate_result(info) |
||||
|
||||
results.append(res) |
||||
|
||||
return results |
@ -0,0 +1,144 @@ |
||||
""" |
||||
Module detecting send to arbitrary address |
||||
|
||||
To avoid FP, it does not report: |
||||
- If msg.sender is used as index (withdraw situation) |
||||
- If the function is protected |
||||
- If the value sent is msg.value (repay situation) |
||||
- If there is a call to transferFrom |
||||
|
||||
TODO: dont report if the value is tainted by msg.value |
||||
""" |
||||
from typing import List |
||||
|
||||
from slither.core.cfg.node import Node |
||||
from slither.core.declarations import Function, Contract |
||||
from slither.analyses.data_dependency.data_dependency import is_tainted, is_dependent |
||||
from slither.core.declarations.solidity_variables import ( |
||||
SolidityFunction, |
||||
SolidityVariableComposed, |
||||
) |
||||
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification |
||||
from slither.slithir.operations import ( |
||||
HighLevelCall, |
||||
Index, |
||||
LowLevelCall, |
||||
Send, |
||||
SolidityCall, |
||||
Transfer, |
||||
) |
||||
|
||||
|
||||
# pylint: disable=too-many-nested-blocks,too-many-branches |
||||
from slither.utils.output import Output |
||||
|
||||
|
||||
def arbitrary_send(func: Function): |
||||
if func.is_protected(): |
||||
return [] |
||||
|
||||
ret: List[Node] = [] |
||||
for node in func.nodes: |
||||
for ir in node.irs: |
||||
if isinstance(ir, SolidityCall): |
||||
if ir.function == SolidityFunction("ecrecover(bytes32,uint8,bytes32,bytes32)"): |
||||
return False |
||||
if isinstance(ir, Index): |
||||
if ir.variable_right == SolidityVariableComposed("msg.sender"): |
||||
return False |
||||
if is_dependent( |
||||
ir.variable_right, |
||||
SolidityVariableComposed("msg.sender"), |
||||
func.contract, |
||||
): |
||||
return False |
||||
if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)): |
||||
if isinstance(ir, (HighLevelCall)): |
||||
if isinstance(ir.function, Function): |
||||
if ir.function.full_name == "transferFrom(address,address,uint256)": |
||||
return False |
||||
if ir.call_value is None: |
||||
continue |
||||
if ir.call_value == SolidityVariableComposed("msg.value"): |
||||
continue |
||||
if is_dependent( |
||||
ir.call_value, |
||||
SolidityVariableComposed("msg.value"), |
||||
func.contract, |
||||
): |
||||
continue |
||||
|
||||
if is_tainted(ir.destination, func.contract): |
||||
ret.append(node) |
||||
|
||||
return ret |
||||
|
||||
|
||||
def detect_arbitrary_send(contract: Contract): |
||||
""" |
||||
Detect arbitrary send |
||||
Args: |
||||
contract (Contract) |
||||
Returns: |
||||
list((Function), (list (Node))) |
||||
""" |
||||
ret = [] |
||||
for f in [f for f in contract.functions if f.contract_declarer == contract]: |
||||
nodes = arbitrary_send(f) |
||||
if nodes: |
||||
ret.append((f, nodes)) |
||||
return ret |
||||
|
||||
|
||||
class ArbitrarySendEth(AbstractDetector): |
||||
ARGUMENT = "arbitrary-send-eth" |
||||
HELP = "Functions that send Ether to arbitrary destinations" |
||||
IMPACT = DetectorClassification.HIGH |
||||
CONFIDENCE = DetectorClassification.MEDIUM |
||||
|
||||
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#functions-that-send-ether-to-arbitrary-destinations" |
||||
|
||||
WIKI_TITLE = "Functions that send Ether to arbitrary destinations" |
||||
WIKI_DESCRIPTION = "Unprotected call to a function sending Ether to an arbitrary address." |
||||
|
||||
# region wiki_exploit_scenario |
||||
WIKI_EXPLOIT_SCENARIO = """ |
||||
```solidity |
||||
contract ArbitrarySendEth{ |
||||
address destination; |
||||
function setDestination(){ |
||||
destination = msg.sender; |
||||
} |
||||
|
||||
function withdraw() public{ |
||||
destination.transfer(this.balance); |
||||
} |
||||
} |
||||
``` |
||||
Bob calls `setDestination` and `withdraw`. As a result he withdraws the contract's balance.""" |
||||
# endregion wiki_exploit_scenario |
||||
|
||||
WIKI_RECOMMENDATION = "Ensure that an arbitrary user cannot withdraw unauthorized funds." |
||||
|
||||
def _detect(self) -> List[Output]: |
||||
"""""" |
||||
results = [] |
||||
|
||||
for c in self.contracts: |
||||
arbitrary_send_result = detect_arbitrary_send(c) |
||||
for (func, nodes) in arbitrary_send_result: |
||||
|
||||
info = [func, " sends eth to arbitrary user\n"] |
||||
info += ["\tDangerous calls:\n"] |
||||
|
||||
# sort the nodes to get deterministic results |
||||
nodes.sort(key=lambda x: x.node_id) |
||||
|
||||
for node in nodes: |
||||
info += ["\t- ", node, "\n"] |
||||
|
||||
res = self.generate_result(info) |
||||
|
||||
results.append(res) |
||||
|
||||
return results |
@ -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,30 @@ |
||||
from slither.core.declarations import Contract |
||||
from slither.core.variables.state_variable import StateVariable |
||||
from slither.core.solidity_types import ArrayType, ElementaryType |
||||
|
||||
|
||||
def is_upgradable_gap_variable(contract: Contract, variable: StateVariable) -> bool: |
||||
"""Helper function that returns true if 'variable' is a gap variable used |
||||
for upgradable contracts. More specifically, the function returns true if: |
||||
- variable is named "__gap" |
||||
- it is a uint256 array declared at the end of the contract |
||||
- it has private visibility""" |
||||
|
||||
# Return early on if the variable name is != gap to avoid iterating over all the state variables |
||||
if variable.name != "__gap": |
||||
return False |
||||
|
||||
declared_variable_ordered = [ |
||||
v for v in contract.state_variables_ordered if v in contract.state_variables_declared |
||||
] |
||||
|
||||
if not declared_variable_ordered: |
||||
return False |
||||
|
||||
variable_type = variable.type |
||||
return ( |
||||
declared_variable_ordered[-1] is variable |
||||
and isinstance(variable_type, ArrayType) |
||||
and variable_type.type == ElementaryType("uint256") |
||||
and variable.visibility == "private" |
||||
) |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue