Open howlbot-integration[bot] opened 4 months ago
The address randomization need is correctly pointed out.
FYI the suggested mitigation 1 is not possible as nominees come from any chain, and there is no way to see if the contract exist on other chains.
kupermind (sponsor) acknowledged
0xA5DF marked the issue as selected for report
0xA5DF marked the issue as satisfactory
Requiring more gas would probably be low, but it seems like due to MAX_NUM_WEEKS
the nominee would become unusable at some point.
Marking as med
Fixed
Lines of code
https://github.com/code-423n4/2024-05-olas/blob/main/governance/contracts/VoteWeighting.sol#L292-L344
Vulnerability details
Impact
It is possible to add a staking instance as a nominee before it is being created. This can lead to a situation that when this instance is going to claim its staking incentives, it should go over all the epochs from the time it was added as nominee to the epoch it was created. This range of epochs is useless for this instance, because no incentives can be assigned to that instance during the epoch it is added as nominee until the epoch it is created. By doing so, the attacker can force users (whoever claim the staking incentives for an instance) to consume much more gas.
Proof of Concept
When creating a proxy staking instance, the address of the to-be-created proxy instance is predictable as follows.
https://github.com/code-423n4/2024-05-olas/blob/main/registries/contracts/staking/StakingFactory.sol#L141
It shows that the address is dependent on two dynamic parameters
nonce
as well as the address of theimplementation
.The
nonce
is incremented by one each time an instance is created, so it can be tracked easily.The
implementation
address is assumed to be the address of the contractStakingToken
orStakingNativeToken
for most instances. In other words, it is more probable that users create the proxy instance based on those default implementations instead of a customized one, and users usually only change theinitPayload
to create an instance with different configurations. As can be seen in the following code, the address of an instance is independent ofinitPayload
.https://github.com/code-423n4/2024-05-olas/blob/main/registries/contracts/staking/StakingFactory.sol#L208
There is an opportunity for the attacker to act maliciously as explained in the following scenario:
In the early days of the protocol launch, the attacker predicts the address of many instances. As explained above the accuracy of this prediction would be very high because the nonce range is limited, and the implementation address is usually the address of default staking instances
StakingToken
orStakingNativeToken
.After that, the attacker adds the predicted addresses as nominee.
https://github.com/code-423n4/2024-05-olas/blob/main/governance/contracts/VoteWeighting.sol#L292-L344
By doing so, when an instance is added as nominee, its
mapLastClaimedStakingEpochs
is set at the current epoch in the contractDispenser
. Assuming we are in the early days of the protocol launch, for example the current epoch is 2.https://github.com/code-423n4/2024-05-olas/blob/main/tokenomics/contracts/Dispenser.sol#L745
By doing so, many instances are added as nominee and their
mapLastClaimedStakingEpochs
are set to 2, while they are not yet created inStakingFactory
.Suppose, after some time (for example in epoch 100), these instances are created through calling
createStakingInstance
.After these instances being active and receiving incentives, they are going to claim their incentives by calling the function
calculateStakingIncentives
. This function invokes the internal function_checkpointNomineeAndGetClaimedEpochCounters
to see the range of epochs it should go over.https://github.com/code-423n4/2024-05-olas/blob/main/tokenomics/contracts/Dispenser.sol#L874
In this function, it returns
firstClaimedEpoch = 2
(the time it was added as nominee by the attacker), andlastClaimedEpoch = 2 + numClaimedEpochs
.https://github.com/code-423n4/2024-05-olas/blob/main/tokenomics/contracts/Dispenser.sol#L356
The issue is that this instance was added as nominee in epoch 2 (so
mapLastClaimedStakingEpochs
was set to 2), but it was created at epoch 100. So, from epoch 2 to 100, the instance was not created yet, thus obviously there is no reward to claim during these epochs. But, the for-loop in the functioncalculateStakingIncentives
goes over epoch 2 to2 + numClaimedEpochs
, among that, epoch 2 to 100 are uselss for this instance. If the gap between the time that the instance is added as nominee and the time that it is created is large, it will be a large gas consumption for going over epochs that are useless for that instance.https://github.com/code-423n4/2024-05-olas/blob/main/tokenomics/contracts/Dispenser.sol#L881
The following code shows the code that the attacker use to executed the attack by calling the function
run
. It sets the nonce from 0 to 10000, and predict the address of instances if it is created with implementation ofStakingToken
orStakingNativeToken
. Then the attacker adds these predicted addresses as nominee.Tests
Test1 (Normal case):
In the following test, it is assumed
epochLen = 10 days
. The nominee is added and voted for at epoch 102, and the functioncalculateStakingIncentives
is called at epoch 103. It shows the gas consumed to callcalculateStakingIncentives
is equal to326_733
, because it iterates over only one epoch (from 102 to 103).The result is:
Test2 (Malicious case):
In the following test, it is assumed
epochLen = 10 days
. The nominee is added at epoch 2 and voted for at epoch 102, and the functioncalculateStakingIncentives
is called at epoch 103. It shows the gas consumed to callcalculateStakingIncentives
is equal to1_439_295
, because it iterates over 101 epoch (from 2 to 103).The result is:
Test 1 and 2 show that if a nominee is added long before it is created, calculating the incentives allocated to it will be highly gas consuming because it iterates over many epochs from the epoch it is added to the epoch it is created (which are useless since no incentives are allocated during this range to the nominee). Test 2 shows that the consumed gas is 5x higher than test 1.
Tools Used
Recommended Mitigation Steps
There are two recommendations:
First: when adding a nominee, it checks that the nominee is created already or not, so a malicious user can not add nominee long before it is created.
Second: include the
initPayload
into thesalt
, so that the address of the instance would depend on that.Assessed type
Context