Staking on behalf of another user can be exploited by an attacker who adds enough items to the userValidators mapping of any user to cause the gas consumption of function calls to exceed the block gas limit. This results in a denial of service for several functions, preventing them from being called.
Vulnerability Details
When StakingFacet::stakeOnBehalf() is called, which can be done by anyone to stake PLUME to a specific validator on behalf of another user, _performStakeSetup() is called within this function:
functionstakeOnBehalf(uint16validatorId,addressstaker)externalpayablereturns(uint256){......// Perform all common staking setup for the beneficiary>>bool isNewStake =_performStakeSetup(staker, validatorId, stakeAmount);......}
This function calls PlumeValidatorLogic::addStakerToValidator(), which then pushes the validatorId onto the userValidator mapping:
This can be done for any existing validatorId, which means the mapping can be flooded with entries. As a result, when looping over the mapping, it could cause a revert due to the transaction consuming more gas than the block gas limit. This could happen, for example, in StakingFacet::_calculateAndClaimAllRewardsWithCleanup(), which is called within StakingFacet::restakeRewards():
Impact Details
This issue causes a denial of service for several functions, including StakingFacet::restakeRewards() and StakingFacet::withdraw().
References
Code references are provided throughout the report (links above).
Proof of Concept
1
Stake on behalf to populate user's validators
An attacker calls StakingFacet::stakeOnBehalf() to stake PLUME to a specific validator on behalf of Alice. Within this function, _performStakeSetup() is called.
When StakingFacet::restakeRewards() is called, it invokes _calculateAndClaimAllRewardsWithCleanup(), which loops over $.userValidators[user]. If that array is large enough (flooded by the attacker), the loop can consume more gas than the block gas limit and revert, causing a denial of service.