-
Notifications
You must be signed in to change notification settings - Fork 996
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1653 from bart1e/detector_for_invalid_using_for
Detector for invalid using for at the top level
- Loading branch information
Showing
6 changed files
with
347 additions
and
0 deletions.
There are no files selected for viewing
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
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,223 @@ | ||
from typing import List | ||
|
||
from slither.core.declarations import Contract, Structure, Enum | ||
from slither.core.declarations.using_for_top_level import UsingForTopLevel | ||
from slither.core.solidity_types import ( | ||
UserDefinedType, | ||
Type, | ||
ElementaryType, | ||
TypeAlias, | ||
MappingType, | ||
ArrayType, | ||
) | ||
from slither.core.solidity_types.elementary_type import Uint, Int, Byte | ||
from slither.detectors.abstract_detector import ( | ||
AbstractDetector, | ||
DetectorClassification, | ||
DETECTOR_INFO, | ||
) | ||
from slither.utils.output import Output | ||
|
||
|
||
def _is_correctly_used(type_: Type, library: Contract) -> bool: | ||
""" | ||
Checks if a `using library for type_` statement is used correctly (that is, does library contain any function | ||
with type_ as the first argument). | ||
""" | ||
for f in library.functions: | ||
if len(f.parameters) == 0: | ||
continue | ||
if f.parameters[0].type and not _implicitly_convertible_to(type_, f.parameters[0].type): | ||
continue | ||
return True | ||
return False | ||
|
||
|
||
def _implicitly_convertible_to(type1: Type, type2: Type) -> bool: | ||
""" | ||
Returns True if type1 may be implicitly converted to type2. | ||
""" | ||
if isinstance(type1, TypeAlias) or isinstance(type2, TypeAlias): | ||
if isinstance(type1, TypeAlias) and isinstance(type2, TypeAlias): | ||
return type1.type == type2.type | ||
return False | ||
|
||
if isinstance(type1, UserDefinedType) and isinstance(type2, UserDefinedType): | ||
if isinstance(type1.type, Contract) and isinstance(type2.type, Contract): | ||
return _implicitly_convertible_to_for_contracts(type1.type, type2.type) | ||
|
||
if isinstance(type1.type, Structure) and isinstance(type2.type, Structure): | ||
return type1.type.canonical_name == type2.type.canonical_name | ||
|
||
if isinstance(type1.type, Enum) and isinstance(type2.type, Enum): | ||
return type1.type.canonical_name == type2.type.canonical_name | ||
|
||
if isinstance(type1, ElementaryType) and isinstance(type2, ElementaryType): | ||
return _implicitly_convertible_to_for_elementary_types(type1, type2) | ||
|
||
if isinstance(type1, MappingType) and isinstance(type2, MappingType): | ||
return _implicitly_convertible_to_for_mappings(type1, type2) | ||
|
||
if isinstance(type1, ArrayType) and isinstance(type2, ArrayType): | ||
return _implicitly_convertible_to_for_arrays(type1, type2) | ||
|
||
return False | ||
|
||
|
||
def _implicitly_convertible_to_for_arrays(type1: ArrayType, type2: ArrayType) -> bool: | ||
""" | ||
Returns True if type1 may be implicitly converted to type2. | ||
""" | ||
return _implicitly_convertible_to(type1.type, type2.type) | ||
|
||
|
||
def _implicitly_convertible_to_for_mappings(type1: MappingType, type2: MappingType) -> bool: | ||
""" | ||
Returns True if type1 may be implicitly converted to type2. | ||
""" | ||
return type1.type_from == type2.type_from and type1.type_to == type2.type_to | ||
|
||
|
||
def _implicitly_convertible_to_for_elementary_types( | ||
type1: ElementaryType, type2: ElementaryType | ||
) -> bool: | ||
""" | ||
Returns True if type1 may be implicitly converted to type2. | ||
""" | ||
if type1.type == "bool" and type2.type == "bool": | ||
return True | ||
if type1.type == "string" and type2.type == "string": | ||
return True | ||
if type1.type == "bytes" and type2.type == "bytes": | ||
return True | ||
if type1.type == "address" and type2.type == "address": | ||
return _implicitly_convertible_to_for_addresses(type1, type2) | ||
if type1.type in Uint and type2.type in Uint: | ||
return _implicitly_convertible_to_for_uints(type1, type2) | ||
if type1.type in Int and type2.type in Int: | ||
return _implicitly_convertible_to_for_ints(type1, type2) | ||
if ( | ||
type1.type != "bytes" | ||
and type2.type != "bytes" | ||
and type1.type in Byte | ||
and type2.type in Byte | ||
): | ||
return _implicitly_convertible_to_for_bytes(type1, type2) | ||
return False | ||
|
||
|
||
def _implicitly_convertible_to_for_bytes(type1: ElementaryType, type2: ElementaryType) -> bool: | ||
""" | ||
Returns True if type1 may be implicitly converted to type2 assuming they are both bytes. | ||
""" | ||
assert type1.type in Byte and type2.type in Byte | ||
assert type1.size is not None | ||
assert type2.size is not None | ||
|
||
return type1.size <= type2.size | ||
|
||
|
||
def _implicitly_convertible_to_for_addresses(type1: ElementaryType, type2: ElementaryType) -> bool: | ||
""" | ||
Returns True if type1 may be implicitly converted to type2 assuming they are both addresses. | ||
""" | ||
assert type1.type == "address" and type2.type == "address" | ||
# payable attribute to be implemented; for now, always return True | ||
return True | ||
|
||
|
||
def _implicitly_convertible_to_for_ints(type1: ElementaryType, type2: ElementaryType) -> bool: | ||
""" | ||
Returns True if type1 may be implicitly converted to type2 assuming they are both ints. | ||
""" | ||
assert type1.type in Int and type2.type in Int | ||
assert type1.size is not None | ||
assert type2.size is not None | ||
|
||
return type1.size <= type2.size | ||
|
||
|
||
def _implicitly_convertible_to_for_uints(type1: ElementaryType, type2: ElementaryType) -> bool: | ||
""" | ||
Returns True if type1 may be implicitly converted to type2 assuming they are both uints. | ||
""" | ||
assert type1.type in Uint and type2.type in Uint | ||
assert type1.size is not None | ||
assert type2.size is not None | ||
|
||
return type1.size <= type2.size | ||
|
||
|
||
def _implicitly_convertible_to_for_contracts(contract1: Contract, contract2: Contract) -> bool: | ||
""" | ||
Returns True if contract1 may be implicitly converted to contract2. | ||
""" | ||
return contract1 == contract2 or contract2 in contract1.inheritance | ||
|
||
|
||
class IncorrectUsingFor(AbstractDetector): | ||
""" | ||
Detector for incorrect using-for statement usage. | ||
""" | ||
|
||
ARGUMENT = "incorrect-using-for" | ||
HELP = "Detects using-for statement usage when no function from a given library matches a given type" | ||
IMPACT = DetectorClassification.INFORMATIONAL | ||
CONFIDENCE = DetectorClassification.HIGH | ||
|
||
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-using-for-usage" | ||
|
||
WIKI_TITLE = "Incorrect usage of using-for statement" | ||
WIKI_DESCRIPTION = ( | ||
"In Solidity, it is possible to use libraries for certain types, by the `using-for` statement " | ||
"(`using <library> for <type>`). However, the Solidity compiler doesn't check whether a given " | ||
"library has at least one function matching a given type. If it doesn't, such a statement has " | ||
"no effect and may be confusing. " | ||
) | ||
|
||
# region wiki_exploit_scenario | ||
WIKI_EXPLOIT_SCENARIO = """ | ||
```solidity | ||
library L { | ||
function f(bool) public pure {} | ||
} | ||
using L for uint; | ||
``` | ||
Such a code will compile despite the fact that `L` has no function with `uint` as its first argument.""" | ||
# endregion wiki_exploit_scenario | ||
WIKI_RECOMMENDATION = ( | ||
"Make sure that the libraries used in `using-for` statements have at least one function " | ||
"matching a type used in these statements. " | ||
) | ||
|
||
def _append_result( | ||
self, results: List[Output], uf: UsingForTopLevel, type_: Type, library: Contract | ||
) -> None: | ||
info: DETECTOR_INFO = [ | ||
f"using-for statement at {uf.source_mapping} is incorrect - no matching function for {type_} found in ", | ||
library, | ||
".\n", | ||
] | ||
res = self.generate_result(info) | ||
results.append(res) | ||
|
||
def _detect(self) -> List[Output]: | ||
results: List[Output] = [] | ||
|
||
for uf in self.compilation_unit.using_for_top_level: | ||
# UsingForTopLevel.using_for is a dict with a single entry, which is mapped to a list of functions/libraries | ||
# the following code extracts the type from using-for and skips using-for statements with functions | ||
type_ = list(uf.using_for.keys())[0] | ||
for lib_or_fcn in uf.using_for[type_]: | ||
# checking for using-for with functions is already performed by the compiler; we only consider libraries | ||
if isinstance(lib_or_fcn, UserDefinedType): | ||
lib_or_fcn_type = lib_or_fcn.type | ||
if ( | ||
isinstance(type_, Type) | ||
and isinstance(lib_or_fcn_type, Contract) | ||
and not _is_correctly_used(type_, lib_or_fcn_type) | ||
): | ||
self._append_result(results, uf, type_, lib_or_fcn_type) | ||
|
||
return results |
24 changes: 24 additions & 0 deletions
24
...apshots/detectors__detector_IncorrectUsingFor_0_8_17_IncorrectUsingForTopLevel_sol__0.txt
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,24 @@ | ||
using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#84 is incorrect - no matching function for bytes17[] found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). | ||
|
||
using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#85 is incorrect - no matching function for uint256 found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). | ||
|
||
using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#90 is incorrect - no matching function for mapping(int256 => uint128) found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). | ||
|
||
using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#86 is incorrect - no matching function for int256 found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). | ||
|
||
using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#89 is incorrect - no matching function for E2 found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). | ||
|
||
using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#93 is incorrect - no matching function for bytes[][] found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). | ||
|
||
using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#92 is incorrect - no matching function for string[][] found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). | ||
|
||
using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#91 is incorrect - no matching function for mapping(int128 => uint256) found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). | ||
|
||
using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#87 is incorrect - no matching function for bytes18 found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). | ||
|
||
using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#88 is incorrect - no matching function for S2 found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). | ||
|
||
using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#83 is incorrect - no matching function for C3 found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). | ||
|
||
using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#94 is incorrect - no matching function for custom_int found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). | ||
|
94 changes: 94 additions & 0 deletions
94
tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol
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,94 @@ | ||
pragma solidity 0.8.17; | ||
|
||
struct S1 | ||
{ | ||
uint __; | ||
} | ||
|
||
struct S2 | ||
{ | ||
uint128 __; | ||
} | ||
|
||
enum E1 | ||
{ | ||
A, | ||
B | ||
} | ||
|
||
enum E2 | ||
{ | ||
A, | ||
B | ||
} | ||
|
||
contract C0 | ||
{ | ||
|
||
} | ||
|
||
contract C1 is C0 | ||
{ | ||
|
||
} | ||
|
||
contract C2 is C1 | ||
{ | ||
|
||
} | ||
|
||
contract C3 | ||
{ | ||
|
||
} | ||
|
||
type custom_uint is uint248; | ||
type custom_int is int248; | ||
|
||
library L | ||
{ | ||
function f0(C0) public pure {} | ||
function f1(bool) public pure {} | ||
function f2(string memory) public pure {} | ||
function f3(bytes memory) public pure {} | ||
function f4(uint248) public pure {} | ||
function f5(int248) public pure {} | ||
function f6(address) public pure {} | ||
function f7(bytes17) public pure {} | ||
function f8(S1 memory) public pure {} | ||
function f9(E1) public pure {} | ||
function f10(mapping(int => uint) storage) public pure {} | ||
function f11(string[] memory) public pure {} | ||
function f12(bytes[][][] memory) public pure {} | ||
function f13(custom_uint) public pure {} | ||
} | ||
|
||
// the following statements are correct | ||
using L for C2; | ||
using L for bool; | ||
using L for string; | ||
using L for bytes; | ||
using L for uint240; | ||
using L for int16; | ||
using L for address; | ||
using L for bytes16; | ||
using L for S1; | ||
using L for E1; | ||
using L for mapping(int => uint); | ||
using L for string[]; | ||
using L for bytes[][][]; | ||
using L for custom_uint; | ||
|
||
// the following statements are incorrect | ||
using L for C3; | ||
using L for bytes17[]; | ||
using L for uint; | ||
using L for int; | ||
using L for bytes18; | ||
using L for S2; | ||
using L for E2; | ||
using L for mapping(int => uint128); | ||
using L for mapping(int128 => uint); | ||
using L for string[][]; | ||
using L for bytes[][]; | ||
using L for custom_int; |
Binary file added
BIN
+9.84 KB
...e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol-0.8.17.zip
Binary file not shown.
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