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

Conversation

QiuhaoLi
Copy link
Contributor

When upgrading a proxy's implementation with reinitializer, developers could errorly write multiple new reinitializers. If the new reinitializer with a higher version is called first, the reinitializer with a lower version can't be called later (you can't call a reinitializer with a lower version) and may leave uninitalized state variables.

This could lead to severe results in the real world. For example, the Ronin Network prepared two upgrades for their gateway contract and wrote two reinitializers (initializeV3() and initializeV4()). However, the developer calls the initializeV4() first and leaves the state variables _operatorWeight and _totalOperatorWeight uninitialized, which should be set in the initializeV3(). So attackers can bypass the vote weight and withdraw assets directly [1] [2] [3].

ronin-exploit1

The best practice for this issue is never to introduce more than one reinitializer in one updated implementation. This PR will add a detector to the slither-check-upgradeability to check if multiple new reinitializes are introduced and report a low-severity warning. For example:

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.24;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
contract Counter 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 CounterV2 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;
    }
}
qiuhao@laptop:~/tmp/foundry_test/upgrade$ slither-check-upgradeability src/Counter.sol Counter --new-contract-name CounterV2
...
INFO:Slither:
CounterV2.initializeV3(uint256) (src/Counter.sol#33-35) multiple new reinitializers.
CounterV2.initializeV4(uint256) (src/Counter.sol#37-39) multiple new reinitializers.
Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#multiple-new-reinitializers
qiuhao@laptop:~/tmp/foundry_test/upgrade$ 


# region wiki_recommendation
WIKI_RECOMMENDATION = """
Do not use multiple reinitializers with higher versions in the updated contract.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add what should be done instead? I think they should be combined into a single re-initializer per upgrade based off the description of the issue, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I added a suggestion, thanks.

results = []
if len(new_reinitializer_funcs) > 1:
for f in new_reinitializer_funcs:
info = [f, " multiple new reinitializers.\n"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
info = [f, " multiple new reinitializers.\n"]
info = [f, " multiple new reinitializers which should be combined into one per upgrade.\n"]

Same general idea as https://github.com/crytic/slither/pull/2536/files#r1722628467

@0xalpharush
Copy link
Contributor

Can you run make reformat && make lint and fix the lint warnings please?

@0xalpharush
Copy link
Contributor

I am trying to fix the CI failure in #2537

@0xalpharush 0xalpharush merged commit e4657f5 into crytic:dev Aug 23, 2024
70 of 74 checks passed
@0xalpharush
Copy link
Contributor

Thanks @QiuhaoLi, these are great additions!

@QiuhaoLi
Copy link
Contributor Author

@0xalpharush Thanks for the review :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants