mirror of https://github.com/crytic/slither
Merge pull request #1653 from bart1e/detector_for_invalid_using_for
Detector for invalid using for at the top levelpull/1936/head
commit
e8e1681f9f
@ -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 |
@ -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). |
||||
|
@ -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 not shown.
Loading…
Reference in new issue