Skip to content

Commit

Permalink
Release 2 new detectors
Browse files Browse the repository at this point in the history
  • Loading branch information
montyly committed Oct 5, 2023
1 parent 1eaec64 commit 7007949
Show file tree
Hide file tree
Showing 11 changed files with 221 additions and 4 deletions.
2 changes: 2 additions & 0 deletions slither/detectors/all_detectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,5 @@
from .operations.cache_array_length import CacheArrayLength
from .statements.incorrect_using_for import IncorrectUsingFor
from .operations.encode_packed import EncodePackedCollision
from .assembly.incorrect_return import IncorrectReturn
from .assembly.return_instead_of_leave import ReturnInsteadOfLeave
91 changes: 91 additions & 0 deletions slither/detectors/assembly/incorrect_return.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from typing import List, Optional

from slither.core.declarations import SolidityFunction, Function
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import SolidityCall
from slither.utils.output import Output


def _assembly_node(function: Function) -> Optional[SolidityCall]:
"""
Check if there is a node that use return in assembly
Args:
function:
Returns:
"""

for ir in function.all_slithir_operations():
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction(
"return(uint256,uint256)"
):
return ir
return None


class IncorrectReturn(AbstractDetector):
"""
Check for cases where a return(a,b) is used in an assembly function
"""

ARGUMENT = "incorrect-return"
HELP = "If a `return` is incorrectly used in assembly mode."
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM

WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-assembly-return"

WIKI_TITLE = "Incorrect return in assembly"
WIKI_DESCRIPTION = "Detect if `return` in an assembly block halts unexpectedly the execution."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract C {
function f() internal returns (uint a, uint b) {
assembly {
return (5, 6)
}
}
function g() returns (bool){
f();
return true;
}
}
```
The return statement in `f` will cause execution in `g` to halt.
The function will return 6 bytes starting from offset 5, instead of returning a boolean."""

WIKI_RECOMMENDATION = "Use the `leave` statement."

# pylint: disable=too-many-nested-blocks
def _detect(self) -> List[Output]:
results: List[Output] = []
for c in self.contracts:
for f in c.functions_declared:

for node in f.nodes:
if node.sons:
for function_called in node.internal_calls:
if isinstance(function_called, Function):
found = _assembly_node(function_called)
if found:

info: DETECTOR_INFO = [
f,
" calls ",
function_called,
" which halt the execution ",
found.node,
"\n",
]
json = self.generate_result(info)

results.append(json)

return results
64 changes: 64 additions & 0 deletions slither/detectors/assembly/return_instead_of_leave.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from typing import List

from slither.core.declarations import SolidityFunction, Function
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import SolidityCall
from slither.utils.output import Output


class ReturnInsteadOfLeave(AbstractDetector):
"""
Check for cases where a return(a,b) is used in an assembly function that also returns two variables
"""

ARGUMENT = "return-leave"
HELP = "If a `return` is used instead of a `leave`."
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM

WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-assembly-return"

WIKI_TITLE = "Return instead of leave in assembly"
WIKI_DESCRIPTION = "Detect if a `return` is used where a `leave` should be used."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract C {
function f() internal returns (uint a, uint b) {
assembly {
return (5, 6)
}
}
}
```
The function will halt the execution, instead of returning a two uint."""

WIKI_RECOMMENDATION = "Use the `leave` statement."

def _check_function(self, f: Function) -> List[Output]:
results: List[Output] = []

for node in f.nodes:
for ir in node.irs:
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction(
"return(uint256,uint256)"
):
info: DETECTOR_INFO = [f, " contains an incorrect call to return: ", node, "\n"]
json = self.generate_result(info)

results.append(json)
return results

def _detect(self) -> List[Output]:
results: List[Output] = []
for c in self.contracts:
for f in c.functions_declared:

if len(f.returns) == 2 and f.contains_assembly:
results += self._check_function(f)

return results
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
C.bad1() (tests/e2e/detectors/test_data/incorrect-return/0.8.10/incorrect_return.sol#21-24) calls C.indirect() (tests/e2e/detectors/test_data/incorrect-return/0.8.10/incorrect_return.sol#17-19) which halt the execution return(uint256,uint256)(5,6) (tests/e2e/detectors/test_data/incorrect-return/0.8.10/incorrect_return.sol#4)

C.bad0() (tests/e2e/detectors/test_data/incorrect-return/0.8.10/incorrect_return.sol#8-11) calls C.internal_return() (tests/e2e/detectors/test_data/incorrect-return/0.8.10/incorrect_return.sol#2-6) which halt the execution return(uint256,uint256)(5,6) (tests/e2e/detectors/test_data/incorrect-return/0.8.10/incorrect_return.sol#4)

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
C.f() (tests/e2e/detectors/test_data/return-leave/0.8.10/incorrect_return.sol#3-7) contains an incorrect call to return: return(uint256,uint256)(5,6) (tests/e2e/detectors/test_data/return-leave/0.8.10/incorrect_return.sol#5)

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
contract C {
function internal_return() internal{
assembly {
return (5, 6)
}
}

function bad0() public returns (bool){
internal_return();
return true;
}

function indirect2() internal {
internal_return();
}

function indirect() internal {
indirect2();
}

function bad1() public returns (bool){
indirect();
return true;
}

function good0() public{
// Dont report if there is no following operation
internal_return();
}

function good1() public returns (uint a, uint b){
assembly {
return (5, 6)
}
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
contract C {

function f() internal returns (uint a, uint b){
assembly {
return (5, 6)
}
}
}
Binary file not shown.
10 changes: 10 additions & 0 deletions tests/e2e/detectors/test_detectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -1654,6 +1654,16 @@ def id_test(test_item: Test):
"encode_packed_collision.sol",
"0.7.6",
),
Test(
all_detectors.IncorrectReturn,
"incorrect_return.sol",
"0.8.10",
),
Test(
all_detectors.ReturnInsteadOfLeave,
"incorrect_return.sol",
"0.8.10",
),
]

GENERIC_PATH = "/GENERIC_PATH"
Expand Down
8 changes: 4 additions & 4 deletions tests/e2e/solc_parsing/test_ast_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
import sys
from pathlib import Path
from typing import List, Dict, Tuple
from packaging.version import parse as parse_version
import pytest

from crytic_compile import CryticCompile, save_to_zip
from crytic_compile.utils.zip import load_from_zip
from deepdiff import DeepDiff
from packaging.version import parse as parse_version
from solc_select.solc_select import install_artifacts as install_solc_versions
from solc_select.solc_select import installed_versions as get_installed_solc_versions
from crytic_compile import CryticCompile, save_to_zip
from crytic_compile.utils.zip import load_from_zip

from slither import Slither
from slither.printers.guidance.echidna import Echidna
Expand Down

0 comments on commit 7007949

Please sign in to comment.