code-423n4 / 2023-12-revolutionprotocol-findings

3 stars 2 forks source link

Securing the _authorizeUpgrade, _setQuorumVotesBPSfunctions, voteForManyWithSig and the batchVoteForManyWithSig Functions in the CultureIndex contract #63

Closed c4-bot-4 closed 8 months ago

c4-bot-4 commented 8 months ago

Lines of code

https://github.com/code-423n4/2023-12-revolutionprotocol/blob/main/packages/revolution/src/CultureIndex.sol#L543 https://github.com/code-423n4/2023-12-revolutionprotocol/blob/main/packages/revolution/src/CultureIndex.sol#L498 https://github.com/code-423n4/2023-12-revolutionprotocol/blob/main/packages/revolution/src/CultureIndex.sol#L367 https://github.com/code-423n4/2023-12-revolutionprotocol/blob/main/packages/revolution/src/CultureIndex.sol#L389

Vulnerability details

Impact

These checks collectively ensure the integrity and security of key functionalities within your CultureIndex contract, guarding against unauthorized actions and ensuring that operations are carried out as intended. Such as an attacker that attempts to vote on behalf of another user by forging their signature.

Proof of Concept

In the CultureIndex contract, these functions are particularly sensitive due to their potential impact on the contract's behavior and state. Let's explore hypothetical attack scenarios for the _authorizeUpgrade, _setQuorumVotesBPS, voteForManyWithSig, and batchVoteForManyWithSig functions and understand how the implemented security checks defend against these scenarios:

Attack Scenarios on _authorizeUpgrade Function:

  1. Unauthorized Contract Upgrade: Scenario: An attacker attempts to upgrade the contract to a malicious implementation. Defense:

require(manager.isRegisteredUpgrade(_getImplementation(), _newImpl), "Unauthorized upgrade");

This check ensures that any new implementation address must be registered and approved, preventing unauthorized or potentially harmful upgrades.

Attack Scenarios on _setQuorumVotesBPS Function:

  1. Unauthorized Access: Scenario: An unauthorized user tries to change the quorum votes basis points, potentially disrupting the voting process. Defense: onlyOwner The onlyOwner modifier restricts access to this critical function to the owner of the contract only.

  2. Setting Impractical Quorum Values: Scenario: Setting an unreasonably high or low quorum votes threshold, which could either block the dropping of any piece or allow drops with minimal support. Defense:

require(newQuorumVotesBPS <= MAX_QUORUM_VOTES_BPS, "Quorum BPS too high");

This validation ensures that the quorum votes basis points are set within a sensible range.

Attack Scenarios on voteForManyWithSig and batchVoteForManyWithSig Functions:

  1. Expired or Invalid Signatures: Scenario: Attackers use expired or forged signatures to cast votes. Defense:

require(deadline >= block.timestamp, "Signature expired");

This check ensures that each vote is cast within the valid time frame, as indicated by the deadline in the signature.

  1. Signature Forgery: Scenario: An attacker attempts to vote on behalf of another user by forging their signature. Defense:

address recoveredAddress = ecrecover(digest, v, r, s); require(recoveredAddress != address(0) && recoveredAddress == from, "Invalid signature");

Signature components (v, r, s) are used in ecrecover to ensure the signature is valid and indeed comes from the authorized voter. This prevents signature forgery and unauthorized vote casting.

  1. Voting with Invalid Data: Scenario: Attackers try to vote on non-existent pieces or manipulate the voting process with invalid data. Defense: Similar input validation as in the vote and voteForMany functions, ensuring piece validity and voting power requirements are met.

Summary of Defenses:

  1. Upgrade Security: Protects the contract from unauthorized upgrades, ensuring only vetted implementations are deployed.
  2. Access Control and Input Validation: Guards critical governance functions against unauthorized access and manipulation of key parameters.
  3. Signature Verification: Ensures the authenticity and timeliness of votes cast through signatures, preserving the integrity of the voting process.

These security measures collectively protect the contract from a range of potential attacks, ensuring its reliable and secure operation in line with its intended governance and operational logic.

Let's integrate the defensive checks directly into the _authorizeUpgrade, _setQuorumVotesBPS, voteForManyWithSig, and batchVoteForManyWithSig functions in your CultureIndex contract. These checks will enhance the security and robustness of the contract.

  1. _authorizeUpgrade Function with Defensive Checks:

function _authorizeUpgrade(address _newImpl) internal view override onlyOwner { // Ensure the new implementation is a registered upgrade require(manager.isRegisteredUpgrade(_getImplementation(), _newImpl), "Unauthorized upgrade");

// Existing authorization logic...

}

  1. _setQuorumVotesBPS Function with Defensive Checks:

function _setQuorumVotesBPS(uint256 newQuorumVotesBPS) external onlyOwner { // Validate the new quorum votes BPS is within acceptable limits require(newQuorumVotesBPS <= MAX_QUORUM_VOTES_BPS, "Quorum BPS too high");

quorumVotesBPS = newQuorumVotesBPS;
emit QuorumVotesBPSSet(quorumVotesBPS, newQuorumVotesBPS);

}

  1. voteForManyWithSig Function with Defensive Checks:

function voteForManyWithSig( address from, uint256[] calldata pieceIds, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external nonReentrant { // Validate deadline and signature components require(deadline >= block.timestamp, "Signature expired"); require(from != address(0), "Invalid address");

bytes32 digest = _createVoteDigest(from, pieceIds, deadline);
address recoveredAddress = ecrecover(digest, v, r, s);
require(recoveredAddress != address(0) && recoveredAddress == from, "Invalid signature");

_voteForMany(pieceIds, from);

}

function _createVoteDigest(address from, uint256[] memory pieceIds, uint256 deadline) internal view returns (bytes32) { bytes32 voteHash = keccak256(abi.encode(VOTE_TYPEHASH, from, keccak256(abi.encodePacked(pieceIds)), nonces[from]++, deadline)); return _hashTypedDataV4(voteHash); }

  1. batchVoteForManyWithSig Function with Defensive Checks:

function batchVoteForManyWithSig( address[] memory from, uint256[][] calldata pieceIds, uint256[] memory deadline, uint8[] memory v, bytes32[] memory r, bytes32[] memory s ) external nonReentrant { for (uint256 i = 0; i < from.length; i++) { // Validate deadline and signature components for each vote require(deadline[i] >= block.timestamp, "Signature expired"); require(from[i] != address(0), "Invalid address");

    bytes32 digest = _createVoteDigest(from[i], pieceIds[i], deadline[i]);
    address recoveredAddress = ecrecover(digest, v[i], r[i], s[i]);
    require(recoveredAddress != address(0) && recoveredAddress == from[i], "Invalid signature");

    _voteForMany(pieceIds[i], from[i]);
}

}

Explanation of the Defensive Checks:

  1. _authorizeUpgrade: Ensures that only approved new implementations can be used to upgrade the contract, preventing unauthorized upgrades.
  2. _setQuorumVotesBPS: Restricts this governance-related function to the owner and checks that the new quorum votes BPS is within a sensible range.
  3. voteForManyWithSig and batchVoteForManyWithSig: Validate the deadline and signature components to ensure each vote is authentic and timely. They prevent expired, forged, or invalid signatures from being used to cast votes.

Tools Used

VS Code

Recommended Mitigation Steps

To enhance the security of the _authorizeUpgrade, _setQuorumVotesBPS, voteForManyWithSig, and batchVoteForManyWithSig functions in the CultureIndex contract, we can implement the recommended security checks as follows:

  1. _authorizeUpgrade Function: Upgrade Security: Check that the new implementation address is registered as a valid upgrade in the manager.

function _authorizeUpgrade(address _newImpl) internal view override onlyOwner { // Check if the new implementation is a registered upgrade require(manager.isRegisteredUpgrade(_getImplementation(), _newImpl), "Unauthorized upgrade"); // Existing authorization logic... }

  1. _setQuorumVotesBPS Function: Access Control: Restrict this function to the owner or specific authorized roles (using OpenZeppelin's onlyOwner or similar access control mechanisms). Input Validation: Validate that newQuorumVotesBPS is within acceptable limits.

function _setQuorumVotesBPS(uint256 newQuorumVotesBPS) external onlyOwner { require(newQuorumVotesBPS <= MAX_QUORUM_VOTES_BPS, "Quorum BPS too high");

quorumVotesBPS = newQuorumVotesBPS;
emit QuorumVotesBPSSet(quorumVotesBPS, newQuorumVotesBPS);

}

  1. voteForManyWithSig Function: Input Validation: Validate deadline, pieceIds, and signature components (v, r, s). Signature Verification: Ensure the signature is valid and comes from an authorized voter.

function voteForManyWithSig( address from, uint256[] calldata pieceIds, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external nonReentrant { require(deadline >= block.timestamp, "Signature expired"); require(from != address(0), "Invalid address");

bytes32 digest = _createVoteDigest(from, pieceIds, deadline);
address recoveredAddress = ecrecover(digest, v, r, s);
require(recoveredAddress != address(0) && recoveredAddress == from, "Invalid signature");

_voteForMany(pieceIds, from);

}

function _createVoteDigest(address from, uint256[] memory pieceIds, uint256 deadline) internal view returns (bytes32) { bytes32 voteHash = keccak256(abi.encode(VOTE_TYPEHASH, from, keccak256(abi.encodePacked(pieceIds)), nonces[from]++, deadline)); return _hashTypedDataV4(voteHash); }

  1. batchVoteForManyWithSig Function: Apply similar checks as in voteForManyWithSig, but for each vote in the batch.

function batchVoteForManyWithSig( address[] memory from, uint256[][] calldata pieceIds, uint256[] memory deadline, uint8[] memory v, bytes32[] memory r, bytes32[] memory s ) external nonReentrant { for (uint256 i = 0; i < from.length; i++) { require(deadline[i] >= block.timestamp, "Signature expired"); require(from[i] != address(0), "Invalid address");

    bytes32 digest = _createVoteDigest(from[i], pieceIds[i], deadline[i]);
    address recoveredAddress = ecrecover(digest, v[i], r[i], s[i]);
    require(recoveredAddress != address(0) && recoveredAddress == from[i], "Invalid signature");

    _voteForMany(pieceIds[i], from[i]);
}

}

Rationale Behind the Changes:

  1. Upgrade Security in _authorizeUpgrade: This check ensures that only legitimate and verified new implementations are used for upgrades, preventing unauthorized or malicious upgrades.
  2. Access Control and Input Validation in _setQuorumVotesBPS: By restricting this function to authorized roles and validating inputs, it prevents unauthorized manipulation of voting quorums and ensures that set values are within reasonable limits.
  3. Validation and Signature Verification in Voting Functions: These checks ensure that the votes are cast within the deadline, are for valid pieces, and are from a verified and authorized voter, thus maintaining the integrity and authenticity of the voting process.

These security measures are designed to protect the contract against common vulnerabilities and unauthorized actions, ensuring that it operates securely and as intended.

Assessed type

Access Control

c4-pre-sort commented 8 months ago

raymondfam marked the issue as insufficient quality report

c4-pre-sort commented 8 months ago

raymondfam marked the issue as duplicate of #47

c4-judge commented 8 months ago

MarioPoneder changed the severity to QA (Quality Assurance)

c4-judge commented 8 months ago

MarioPoneder marked the issue as grade-c