Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tool: add detector for multiple new reinitializers #2536

Merged
merged 5 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion scripts/ci_test_upgradability.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

### Test slither-check-upgradeability

DIR_TESTS="tests/check-upgradeability"
DIR_TESTS="tests/tools/check_upgradeability"
solc-select install "0.5.0"
solc-select use "0.5.0"

slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy > test_1.txt 2>&1
Expand Down Expand Up @@ -181,6 +182,32 @@ then
exit 255
fi

slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_no_bug_reinitializer --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy > test_14.txt 2>&1
DIFF=$(diff test_14.txt "$DIR_TESTS/test_14.txt")
if [ "$DIFF" != "" ]
then
echo "slither-check-upgradeability 14 failed"
cat test_14.txt
echo ""
cat "$DIR_TESTS/test_14.txt"
echo ""
echo "$DIFF"
exit 255
fi

slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_reinitializer_V2 --new-contract-name Counter_reinitializer_V3_V4 > test_15.txt 2>&1
DIFF=$(diff test_15.txt "$DIR_TESTS/test_15.txt")
if [ "$DIFF" != "" ]
then
echo "slither-check-upgradeability 14 failed"
cat test_15.txt
echo ""
cat "$DIR_TESTS/test_15.txt"
echo ""
echo "$DIFF"
exit 255
fi

rm test_1.txt
rm test_2.txt
rm test_3.txt
Expand All @@ -194,3 +221,5 @@ rm test_10.txt
rm test_11.txt
rm test_12.txt
rm test_13.txt
rm test_14.txt
rm test_15.txt
1 change: 1 addition & 0 deletions slither/tools/upgradeability/checks/all_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
MissingCalls,
MultipleCalls,
InitializeTarget,
MultipleReinitializers,
)

from slither.tools.upgradeability.checks.functions_ids import IDCollision, FunctionShadowing
Expand Down
122 changes: 116 additions & 6 deletions slither/tools/upgradeability/checks/initialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
CheckClassification,
)
from slither.utils.colors import red
from slither.exceptions import SlitherError

logger = logging.getLogger("Slither-check-upgradeability")

Expand All @@ -18,7 +19,7 @@ class MultipleInitTarget(Exception):
def _has_initialize_modifier(function: Function):
if not function.modifiers:
return False
return any((m.name == "initializer") for m in function.modifiers)
return any((m.name in ("initializer", "reinitializer")) for m in function.modifiers)


def _get_initialize_functions(contract):
Expand Down Expand Up @@ -164,7 +165,7 @@ class MissingInitializerModifier(AbstractCheck):

# region wiki_description
WIKI_DESCRIPTION = """
Detect if `Initializable.initializer()` is called.
Detect if `Initializable.initializer()` or `Initializable.reinitializer(uint64)` is called.
"""
# endregion wiki_description

Expand All @@ -184,7 +185,7 @@ class MissingInitializerModifier(AbstractCheck):

# region wiki_recommendation
WIKI_RECOMMENDATION = """
Use `Initializable.initializer()`.
Use `Initializable.initializer()` or `Initializable.reinitializer(uint64)`.
"""
# endregion wiki_recommendation

Expand All @@ -199,15 +200,18 @@ def _check(self):
if initializable not in self.contract.inheritance:
return []
initializer = self.contract.get_modifier_from_canonical_name("Initializable.initializer()")
reinitializer = self.contract.get_modifier_from_canonical_name(
"Initializable.reinitializer(uint64)"
)
# InitializableInitializer
if initializer is None:
if initializer is None and reinitializer is None:
return []

results = []
all_init_functions = _get_initialize_functions(self.contract)
for f in all_init_functions:
if initializer not in f.modifiers:
info = [f, " does not call the initializer modifier.\n"]
if initializer not in f.modifiers and reinitializer not in f.modifiers:
info = [f, " does not call the initializer or reinitializer modifier.\n"]
json = self.generate_result(info)
results.append(json)
return results
Expand Down Expand Up @@ -271,6 +275,9 @@ def _check(self):
all_init_functions_called = _get_all_internal_calls(most_derived_init) + [most_derived_init]
missing_calls = [f for f in all_init_functions if not f in all_init_functions_called]
for f in missing_calls:
# we don't account reinitializers
if any((m.name == "reinitializer") for m in f.modifiers):
continue
info = ["Missing call to ", f, " in ", most_derived_init, ".\n"]
json = self.generate_result(info)
results.append(json)
Expand Down Expand Up @@ -396,3 +403,106 @@ def _check(self):
]
json = self.generate_result(info)
return [json]


class MultipleReinitializers(AbstractCheck):
ARGUMENT = "multiple-new-reinitializers"
IMPACT = CheckClassification.LOW

HELP = "Multiple new reinitializers in the updated contract"
WIKI = (
"https://github.com/crytic/slither/wiki/Upgradeability-Checks#multiple-new-reinitializers"
)
WIKI_TITLE = "Multiple new reinitializers in the updated contract"

# region wiki_description
WIKI_DESCRIPTION = """
Detect multiple new reinitializers in the updated contract`.
"""
# endregion wiki_description

# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Counter is Initializable {
uint256 public x;

function initialize(uint256 _x) public initializer {
x = _x;
}

function changeX() public {
x++;
}
}

contract CounterV2 is Initializable {
uint256 public x;
uint256 public y;
uint256 public z;

function initializeV2(uint256 _y) public reinitializer(2) {
y = _y;
}

function initializeV3(uint256 _z) public reinitializer(3) {
z = _z;
}

function changeX() public {
x = x + y + z;
}
}
```
`CounterV2` has two reinitializers with new versions `2` and `3`. If `initializeV3()` is called first, the `initializeV2()` can't be called to initialize `y`, which may brings security risks.
"""
# endregion wiki_exploit_scenario

# region wiki_recommendation
WIKI_RECOMMENDATION = """
Do not use multiple reinitializers with higher versions in the updated contract. Please consider combining new reinitializers into a single one.
"""
# endregion wiki_recommendation

REQUIRE_CONTRACT = True
REQUIRE_CONTRACT_V2 = True

def _check(self):
contract_v1 = self.contract
contract_v2 = self.contract_v2

if contract_v2 is None:
raise SlitherError("multiple-new-reinitializers requires a V2 contract")

initializerV1 = contract_v1.get_modifier_from_canonical_name("Initializable.initializer()")
reinitializerV1 = contract_v1.get_modifier_from_canonical_name(
"Initializable.reinitializer(uint64)"
)
reinitializerV2 = contract_v2.get_modifier_from_canonical_name(
"Initializable.reinitializer(uint64)"
)

# contractV1 has initializer or reinitializer
if initializerV1 is None and reinitializerV1 is None:
return []
# contractV2 has reinitializer
if reinitializerV2 is None:
return []

initializer_funcs_v1 = _get_initialize_functions(contract_v1)
initializer_funcs_v2 = _get_initialize_functions(contract_v2)
new_reinitializer_funcs = []
for fv2 in initializer_funcs_v2:
if not any((fv2.full_name == fv1.full_name) for fv1 in initializer_funcs_v1):
new_reinitializer_funcs.append(fv2)

results = []
if len(new_reinitializer_funcs) > 1:
for f in new_reinitializer_funcs:
info = [
f,
" multiple new reinitializers which should be combined into one per upgrade.\n",
]
json = self.generate_result(info)
results.append(json)
return results
54 changes: 54 additions & 0 deletions tests/tools/check_upgradeability/contract_initialization.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ contract Initializable{
_;
}

modifier reinitializer(uint64 version){
_;
}

}

contract Contract_no_bug is Initializable{
Expand All @@ -16,6 +20,14 @@ contract Contract_no_bug is Initializable{

}

contract Contract_no_bug_reinitializer is Initializable{

function initialize() public reinitializer(2){

}

}

contract Contract_lack_to_call_modifier is Initializable{

function initialize() public {
Expand Down Expand Up @@ -47,3 +59,45 @@ contract Contract_double_call is Contract_no_bug, Contract_no_bug_inherits{
}

}

contract Contract_reinitializer_V2 is Initializable {
uint256 public x;

function initialize(uint256 _x) public initializer {
x = _x;
}

function initializeV2(uint256 _x) public reinitializer(2) {
x = _x;
}

function changeX() public {
x++;
}
}

contract Counter_reinitializer_V3_V4 is Initializable {
uint256 public x;
uint256 public y;
uint256 public z;

function initialize(uint256 _x) public initializer {
x = _x;
}

function initializeV2(uint256 _x) public reinitializer(2) {
x = _x;
}

function initializeV3(uint256 _y) public reinitializer(3) {
y = _y;
}

function initializeV4(uint256 _z) public reinitializer(4) {
z = _z;
}

function changeX() public {
x = x + y + z;
}
}
6 changes: 3 additions & 3 deletions tests/tools/check_upgradeability/test_10.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ INFO:Slither:
Initializable contract not found, the contract does not follow a standard initalization schema.
Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#initializable-is-missing
INFO:Slither:
ContractV1.destination (tests/check-upgradeability/contractV1.sol#2) was not constant but ContractV2.destination (tests/check-upgradeability/contract_v2_constant.sol#2) is.
ContractV1.destination (tests/tools/check_upgradeability/contractV1.sol#2) was not constant but ContractV2.destination (tests/tools/check_upgradeability/contract_v2_constant.sol#2) is.
Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-that-should-not-be-constant
INFO:Slither:
Variable missing in ContractV2 (tests/check-upgradeability/contract_v2_constant.sol#1-3): ContractV1.destination (tests/check-upgradeability/contractV1.sol#2)
Variable missing in ContractV2 (tests/tools/check_upgradeability/contract_v2_constant.sol#1-3): ContractV1.destination (tests/tools/check_upgradeability/contractV1.sol#2)
Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#missing-variables
INFO:Slither:
Initializable contract not found, the contract does not follow a standard initalization schema.
Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#initializable-is-missing
INFO:Slither:4 findings, 21 detectors run
INFO:Slither:4 findings, 22 detectors run
2 changes: 1 addition & 1 deletion tests/tools/check_upgradeability/test_11.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ INFO:Slither:
Initializable contract not found, the contract does not follow a standard initalization schema.
Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#initializable-is-missing
INFO:Slither:
ContractV1.destination (tests/check-upgradeability/contract_v1_var_init.sol#2) is a state variable with an initial value.
ContractV1.destination (tests/tools/check_upgradeability/contract_v1_var_init.sol#2) is a state variable with an initial value.
Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#state-variable-initialized
INFO:Slither:2 findings, 8 detectors run
2 changes: 1 addition & 1 deletion tests/tools/check_upgradeability/test_12.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#initiali
INFO:Slither:
Initializable contract not found, the contract does not follow a standard initalization schema.
Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#initializable-is-missing
INFO:Slither:2 findings, 21 detectors run
INFO:Slither:2 findings, 22 detectors run
8 changes: 4 additions & 4 deletions tests/tools/check_upgradeability/test_13.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ INFO:Slither:
Initializable contract not found, the contract does not follow a standard initalization schema.
Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#initializable-is-missing
INFO:Slither:
Different variables between ContractV1 (tests/check-upgradeability/contractV1_struct.sol#1-8) and ContractV2 (tests/check-upgradeability/contractV2_struct_bug.sol#1-8)
ContractV1.foo (tests/check-upgradeability/contractV1_struct.sol#7)
ContractV2.foo (tests/check-upgradeability/contractV2_struct_bug.sol#7)
Different variables between ContractV1 (tests/tools/check_upgradeability/contractV1_struct.sol#1-8) and ContractV2 (tests/tools/check_upgradeability/contractV2_struct_bug.sol#1-8)
ContractV1.foo (tests/tools/check_upgradeability/contractV1_struct.sol#7)
ContractV2.foo (tests/tools/check_upgradeability/contractV2_struct_bug.sol#7)
Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#incorrect-variables-with-the-v2
INFO:Slither:
Initializable contract not found, the contract does not follow a standard initalization schema.
Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#initializable-is-missing
INFO:Slither:3 findings, 21 detectors run
INFO:Slither:3 findings, 22 detectors run
4 changes: 4 additions & 0 deletions tests/tools/check_upgradeability/test_14.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
INFO:Slither:
Contract_no_bug_reinitializer (tests/tools/check_upgradeability/contract_initialization.sol#23-29) needs to be initialized by Contract_no_bug_reinitializer.initialize() (tests/tools/check_upgradeability/contract_initialization.sol#25-27).
Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialize-function
INFO:Slither:1 findings, 12 detectors run
15 changes: 15 additions & 0 deletions tests/tools/check_upgradeability/test_15.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
INFO:Slither:
Contract_reinitializer_V2 (tests/tools/check_upgradeability/contract_initialization.sol#63-77) needs to be initialized by Contract_reinitializer_V2.initialize(uint256) (tests/tools/check_upgradeability/contract_initialization.sol#66-68).
Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialize-function
INFO:Slither:
Extra variables in Counter_reinitializer_V3_V4 (tests/tools/check_upgradeability/contract_initialization.sol#79-104): Counter_reinitializer_V3_V4.y (tests/tools/check_upgradeability/contract_initialization.sol#81)
Extra variables in Counter_reinitializer_V3_V4 (tests/tools/check_upgradeability/contract_initialization.sol#79-104): Counter_reinitializer_V3_V4.z (tests/tools/check_upgradeability/contract_initialization.sol#82)
Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#extra-variables-in-the-v2
INFO:Slither:
Counter_reinitializer_V3_V4.initializeV3(uint256) (tests/tools/check_upgradeability/contract_initialization.sol#92-94) multiple new reinitializers which should be combined into one per upgrade.
Counter_reinitializer_V3_V4.initializeV4(uint256) (tests/tools/check_upgradeability/contract_initialization.sol#96-98) multiple new reinitializers which should be combined into one per upgrade.
Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#multiple-new-reinitializers
INFO:Slither:
Counter_reinitializer_V3_V4 (tests/tools/check_upgradeability/contract_initialization.sol#79-104) needs to be initialized by Counter_reinitializer_V3_V4.initialize(uint256) (tests/tools/check_upgradeability/contract_initialization.sol#84-86).
Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialize-function
INFO:Slither:6 findings, 22 detectors run
2 changes: 1 addition & 1 deletion tests/tools/check_upgradeability/test_2.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#initiali
INFO:Slither:
Initializable contract not found, the contract does not follow a standard initalization schema.
Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#initializable-is-missing
INFO:Slither:2 findings, 25 detectors run
INFO:Slither:2 findings, 26 detectors run
Loading
Loading