Liquid Staking

Stakelink
DeFiHardhatOracle
50,000 USDC
View results
Submission Details
Severity: medium
Invalid

Insufficient Validation of Vault Groups

Summary

The FundFlowController.sol contract lacks proper validation for vault group indices in its updateOperatorVaultGroupAccounting function. This deficiency allows external users to supply invalid or out-of-bounds vault group indices, potentially causing the contract to revert or behave unexpectedly. Such behavior can lead to Denial-of-Service (DoS) attacks, disrupting the normal operations of the staking platform and affecting its reliability.

Vulnerability Details

Lack of Input Validation in updateOperatorVaultGroupAccounting

function updateOperatorVaultGroupAccounting(uint256[] calldata _vaultGroups) external {
address[] memory vaults = operatorVCS.getVaults();
(, uint256 maxDeposits) = operatorVCS.getVaultDepositLimits();
(, , , uint64 depositIndex) = operatorVCS.globalVaultState();
uint256[] memory totalDepositRoom = new uint256[]();
uint256 totalUnbonded = operatorVCS.totalUnbonded();
for (uint256 i = 0; i < _vaultGroups.length; ++i) {
(uint256 depositRoom, ) = _getTotalDepositRoom(
vaults,
numVaultGroups,
_vaultGroups[i],
maxDeposits,
depositIndex
);
totalDepositRoom[i] = depositRoom;
if (_vaultGroups[i] == curUnbondedVaultGroup) {
totalUnbonded = _getTotalUnbonded(vaults, numVaultGroups, _vaultGroups[i]);
}
}
IOperatorVCS(address(operatorVCS)).updateVaultGroupAccounting(
_vaultGroups,
totalDepositRoom,
totalUnbonded,
maxDeposits
);
}

Explanation:

The updateOperatorVaultGroupAccounting function processes an array of vault group indices (_vaultGroups) provided by external callers. However, it does not validate whether each index in _vaultGroups is within the valid range (i.e., less than numVaultGroups). This omission allows an attacker to supply an index that exceeds the bounds of the vaultGroups array, leading to a revert when the contract attempts to access an invalid array position.

Absence of Access Control on Critical Function

function updateOperatorVaultGroupAccounting(uint256[] calldata _vaultGroups) external {
// Function body...
}

Explanation:

The updateOperatorVaultGroupAccounting function is marked as external without any access control modifiers such as onlyOwner or onlyAuthorized. This means any external account can invoke this function with arbitrary vault group indices, including those that are invalid or out-of-bounds. The lack of restrictions makes the contract susceptible to unauthorized manipulation and potential exploitation.

Potential for Denial-of-Service (DoS)

require(_vaultGroups[i] < numVaultGroups, "Invalid vault group index");

Explanation:

Without proper validation, supplying invalid vault group indices causes the function to revert. An attacker can repeatedly call updateOperatorVaultGroupAccounting with such invalid indices, leading to continuous reverts. This behavior can effectively halt critical contract operations that rely on this function, resulting in a Denial-of-Service (DoS) condition where legitimate users are unable to interact with the staking platform as intended.

Example of Malicious Input Causing Reversion

uint256[] memory maliciousIndices = new uint256[]();
maliciousIndices[0] = numVaultGroups; // Invalid index, as valid indices are 0 to numVaultGroups - 1
fundFlowController.updateOperatorVaultGroupAccounting(maliciousIndices);

Explanation:

In this example, an attacker constructs an array containing a single vault group index equal to numVaultGroups. Since array indices are zero-based, the highest valid index is numVaultGroups - 1. Attempting to access vaultGroups[numVaultGroups] triggers a revert, as the index is out-of-bounds. Repeated execution of this malicious input can disrupt the contract's normal functionality.

Impact

  • Operational Disruption: Critical functions such as vault group accounting become unusable, halting staking operations and rewards distribution.

  • Denial-of-Service (DoS): Repeated malicious calls with invalid indices can keep the contract in a reverted state, denying service to legitimate users.

  • User Trust Erosion: Continuous disruptions can erode user confidence in the platform's reliability and security, potentially leading to reduced usage and financial losses.

Tools Used

  • Manual Review

  • Foundry

Recommendations

To mitigate this vulnerability, implement comprehensive input validation and access control mechanisms within the FundFlowController.sol contract. Below are detailed steps and code fixes to address the identified issues.

1. Implement Input Validation

Add require statements to ensure that each vault group index provided in _vaultGroups is within the valid range (0 to numVaultGroups - 1).

function updateOperatorVaultGroupAccounting(uint256[] calldata _vaultGroups) external {
// Existing code...
for (uint256 i = 0; i < _vaultGroups.length; ++i) {
require(_vaultGroups[i] < numVaultGroups, "FundFlowController: Vault group index out of bounds");
(uint256 depositRoom, ) = _getTotalDepositRoom(
vaults,
numVaultGroups,
_vaultGroups[i],
maxDeposits,
depositIndex
);
totalDepositRoom[i] = depositRoom;
if (_vaultGroups[i] == curUnbondedVaultGroup) {
totalUnbonded = _getTotalUnbonded(vaults, numVaultGroups, _vaultGroups[i]);
}
}
// Existing code...
}

Explanation:

The require statement ensures that each vault group index is less than numVaultGroups. If an invalid index is detected, the transaction reverts with a clear and descriptive error message, preventing further execution of the function with compromised data.

2. Add Access Control

Restrict access to the updateOperatorVaultGroupAccounting function to authorized entities only, such as the contract owner or a designated role.

function updateOperatorVaultGroupAccounting(uint256[] calldata _vaultGroups) external onlyOwner {
// Function body...
}

Explanation:

By adding the onlyOwner modifier, only the contract owner can invoke this function. This restriction prevents unauthorized accounts from manipulating vault group indices, thereby safeguarding the contract against malicious exploitation.

Alternatively, for Role-Based Access Control:

import "@openzeppelin/contracts/access/AccessControl.sol";
contract FundFlowController is UUPSUpgradeable, OwnableUpgradeable, AccessControl {
bytes32 public constant VAULT_GROUP_ADMIN = keccak256("VAULT_GROUP_ADMIN");
function initialize(
// existing parameters...
) public initializer {
__UUPSUpgradeable_init();
__Ownable_init();
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
_setupRole(VAULT_GROUP_ADMIN, msg.sender);
// existing initialization...
}
function updateOperatorVaultGroupAccounting(uint256[] calldata _vaultGroups) external onlyRole(VAULT_GROUP_ADMIN) {
// Function body...
}
}

Explanation:

Implementing role-based access control using OpenZeppelin's AccessControl allows for more granular permission management. Specific roles (e.g., VAULT_GROUP_ADMIN) can be assigned to authorized accounts, providing flexibility and enhanced security.

Updates

Lead Judging Commences

inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.