code-423n4 / 2023-08-arbitrum-findings

3 stars 3 forks source link

Nominee vetting period can change for existing not yet executed proposals #231

Closed code423n4 closed 1 year ago

code423n4 commented 1 year ago

Lines of code

https://github.com/ArbitrumFoundation/governance/blob/c18de53820c505fc459f766c1b224810eaeaabc5/src/security-council-mgmt/governors/modules/SecurityCouncilNomineeElectionGovernorTiming.sol#L69-L71 https://github.com/ArbitrumFoundation/governance/blob/c18de53820c505fc459f766c1b224810eaeaabc5/src/security-council-mgmt/governors/SecurityCouncilNomineeElectionGovernor.sol#L332 https://github.com/ArbitrumFoundation/governance/blob/c18de53820c505fc459f766c1b224810eaeaabc5/src/security-council-mgmt/governors/SecurityCouncilNomineeElectionGovernor.sol#L142-L150 https://github.com/ArbitrumFoundation/governance/blob/c18de53820c505fc459f766c1b224810eaeaabc5/src/security-council-mgmt/governors/SecurityCouncilNomineeElectionGovernor.sol#L266-L269

Vulnerability details

Impact

The SecurityCouncilNomineeElectionGovernor contract inherits from SecurityCouncilNomineeElectionGovernorTiming which implements proposalVettingDeadline to retrieve the deadline for the nominee vetting period for a given proposalId. While the GovernorUpgradeable's proposalDeadline correctly stores the proposal parameters with the proposal/proposalId itself, the proposalVettingDeadline uses the nomineVettingDuration (as proposalDeadline(proposalId) + nomineVettingDuration) of the SecurityCouncilNomineeElectionGovernorTiming contract which is initialized at the end of its __SecurityCouncilNomineeElectionGovernorTiming_init initializer and therefore can mutate globally during upgrades affecting all existing proposals (for both the _execute function and the onlyVettingPetiod).

Proof of Concept

From https://github.com/ArbitrumFoundation/governance/blob/c18de53820c505fc459f766c1b224810eaeaabc5/src/security-council-mgmt/governors/modules/SecurityCouncilNomineeElectionGovernorTiming.sol

    function __SecurityCouncilNomineeElectionGovernorTiming_init(
        Date memory _firstNominationStartDate,
        uint256 _nomineeVettingDuration
    ) internal onlyInitializing {

        ...

        firstNominationStartDate = _firstNominationStartDate;
        nomineeVettingDuration = _nomineeVettingDuration;
    }

    ...

    /// @notice Deadline for the nominee vetting period for a given `proposalId`
    function proposalVettingDeadline(uint256 proposalId) public view returns (uint256) {
        return proposalDeadline(proposalId) + nomineeVettingDuration;
    }

From

    /// @dev    `GovernorUpgradeable` function to execute a proposal overridden to handle nominee elections.
    ///         Can be called by anyone via `execute` after voting and nominee vetting periods have ended.
    ///         If the number of compliant nominees is > the target number of nominees,
    ///         we move on to the next phase by calling the SecurityCouncilMemberElectionGovernor.
    /// @param  proposalId The id of the proposal
    function _execute(
        uint256 proposalId,
        address[] memory, /* targets */
        uint256[] memory, /* values */
        bytes[] memory callDatas,
        bytes32 /*descriptionHash*/
    ) internal virtual override {
        // we can only execute when the vetting deadline has passed
        uint256 vettingDeadline = proposalVettingDeadline(proposalId);
        if (block.number <= vettingDeadline) {
            revert ProposalInVettingPeriod(block.number, vettingDeadline);
        }

        ...

    }

Tools Used

n/a

Recommended Mitigation Steps

Store nomineeVettingDuration with the proposal / proposalId.

Assessed type

Governance

0xSorryNotSorry commented 1 year ago

This looks like the intended behaviour even giving more room in setting of the proposal deadlines via nomineeVettingDuration during upgrades.

Could be QA.

c4-pre-sort commented 1 year ago

0xSorryNotSorry marked the issue as low quality report

c4-judge commented 1 year ago

0xean changed the severity to QA (Quality Assurance)

eierina commented 1 year ago

Hey @0xean I think there was an oversight on this one, can you please reconsider?

I don't think we can say this is the "intended behaviour" as this vulnerability can have at least the following impacts:

The base OZ GovernorUpgradeable base class is implemented correctly where the state/parameters of each proposal are stored with the proposalId to guarantee the proposal's immutability (including timing) while SecurityCouncilNomineeElectionGovernorTiming's timing is mutable.

0xean commented 1 year ago

I don't believe this qualifies as M, I don't see how an "attacker" can change nomineeVettingDuration. Broadly, this could fall under some "Centralization concerns" that should be OOS based on the bot report. I believe QA is correct here.

eierina commented 1 year ago

@0xean couple of points here:

0xean commented 1 year ago

QA has ended, thanks for your comments. This will remain as judged.