Moonwell - bin2chen's results

An open lending and borrowing DeFi protocol.

General Information

Platform: Code4rena

Start Date: 24/07/2023

Pot Size: $100,000 USDC

Total HM: 18

Participants: 73

Period: 7 days

Judge: alcueca

Total Solo HM: 8

Id: 267

League: ETH

Moonwell

Findings Distribution

Researcher Performance

Rank: 29/73

Findings: 2

Award: $162.72

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: immeas

Also found by: 0xkazim, ABAIKUNANBAEV, T1MOH, berlin-101, bin2chen, kutugu, markus_ether

Labels

bug
2 (Med Risk)
partial-50
duplicate-314

Awards

119.3545 USDC - $119.35

External Links

Lines of code

https://github.com/code-423n4/2023-07-moonwell/blob/fced18035107a345c31c9a9497d0da09105df4df/src/core/Governance/TemporalGovernor.sol#L188

Vulnerability details

Impact

maybe brick the temporalGovernor

Proof of Concept

when execute grantGuardiansPause() , it will set guardianPauseAllowed to true , but don't execute _unpause()

grantGuardiansPause code:

    function grantGuardiansPause() external {
        require(
            msg.sender == address(this),
            "TemporalGovernor: Only this contract can update grant guardian pause"
        );

@>      guardianPauseAllowed = true;
        lastPauseTime = 0;

        emit GuardianPauseGranted(block.timestamp);
    }

This way there may exist a path when Guardian is held hostage, can putting the contract into a state where it cannot be updated.

Example: Suppose there is currently a legitimate pending proposal: VAA_1.calldatas = grantGuardiansPause() If Guardian is being held hostage at this point, then a malicious Guardian could perform the following steps,putting the contract into a state where it cannot be updated.

  1. Guardian execute togglePause() -> change to: _paused = true guardianPauseAllowed = false

  2. Guardian execute fastTrackProposalExecution(VAA_1),Since VAA_1 is legal, it will be executed successfully -> change to: guardianPauseAllowed = true

  3. Guardian execute Ownable().renounceOwnership() -> change to: owner = address(0)

After three steps, the state of the contract will become: _paused = true guardianPauseAllowed = true owner = address(0)

These three states cause all contract methods to fail to execute Contains: permissionlessUnpause() executeProposal() fastTrackProposalExecution() togglePause() ...

Since not disabling renounceOwnership() is also one of the main reasons.

Tools Used

grantGuardiansPause() enforce _unpause(), and disable renounceOwnership()/transferOwnership()

    function grantGuardiansPause() external {
        require(
            msg.sender == address(this),
            "TemporalGovernor: Only this contract can update grant guardian pause"
        );

        guardianPauseAllowed = true;
        lastPauseTime = 0;

+       if (paused()) {
+          _unpause();
+       }

        emit GuardianPauseGranted(block.timestamp);
    }

Assessed type

Context

#0 - c4-pre-sort

2023-08-03T13:29:21Z

0xSorryNotSorry marked the issue as duplicate of #232

#1 - c4-judge

2023-08-12T20:50:39Z

alcueca marked the issue as satisfactory

#2 - c4-judge

2023-08-12T20:50:43Z

alcueca marked the issue as partial-50

Findings Information

Labels

bug
2 (Med Risk)
partial-50
duplicate-268

Awards

43.3709 USDC - $43.37

External Links

Lines of code

https://github.com/code-423n4/2023-07-moonwell/blob/fced18035107a345c31c9a9497d0da09105df4df/src/core/Governance/TemporalGovernor.sol#L237

Vulnerability details

Impact

Missing payable, making it impossible to execute proposals that need eth.

Proof of Concept

When the proposal is executed, it takes the value with it The code is as follows.

    function _executeProposal(bytes memory VAA, bool overrideDelay) private {
...

        for (uint256 i = 0; i < targets.length; i++) {
            address target = targets[i];
            uint256 value = values[i];
            bytes memory data = calldatas[i];

            // Go make our call, and if it is not successful revert with the error bubbling up
@>          (bool success, bytes memory returnData) = target.call{value: value}(
                data
            );

            /// revert on failure with error message if any
            require(success, string(returnData));

            emit ExecutedTransaction(target, value, data);
        }
    }    

However, the current contract does not have a receive() method, and executeProposal() and other methods does not have a modifier payable.

This prevents the contract from receiving any eth.

proposals that need eth will never be successfully executed.

Tools Used

executeProposal()/fastTrackProposalExecution() add payable

-   function executeProposal(bytes memory VAA) public whenNotPaused {
+   function executeProposal(bytes memory VAA) public payable whenNotPaused {
        _executeProposal(VAA, false);
    }


-   function fastTrackProposalExecution(bytes memory VAA) external onlyOwner {
+   function fastTrackProposalExecution(bytes memory VAA) external payable onlyOwner {
        _executeProposal(VAA, true); /// override timestamp checks and execute
    }    

Assessed type

Context

#0 - c4-pre-sort

2023-08-03T13:20:52Z

0xSorryNotSorry marked the issue as duplicate of #268

#1 - c4-judge

2023-08-12T20:35:52Z

alcueca marked the issue as satisfactory

#2 - c4-judge

2023-08-12T20:35:56Z

alcueca marked the issue as partial-50

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter