code-423n4 / 2024-07-reserve-findings

5 stars 4 forks source link

Potential Manipulation of Draft RSR During Mass Unstaking Events #73

Closed howlbot-integration[bot] closed 2 months ago

howlbot-integration[bot] commented 3 months ago

Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSR.sol#L614-L623 https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSR.sol#L490-L499 https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSR.sol#L693-L695

Vulnerability details

Impact

When stakeRate is no longer secure, stRSR can initiate a new era and draft era through the resetStakes() function. However, this function only checks the stakeRate, leaving an opportunity for attackers to exploit draftRSR.

Considering that RSR exists as an over-collateralized asset for rToken, in the event of rToken price de-pegging, RSR stakers are likely to unstake in large quantities. Due to the unstakingDelay, a significant amount of stakeRSR would be treated as draftRSR. Moreover, when these users unstake, their stRSR is burned, resulting in a loss of voting rights.

This situation can lead to a scenario where a small amount of stRSR with voting rights can influence a large amount of draftRSR. Furthermore, when stakeRSR is low, it becomes easy to manipulate the stakeRate. Attackers can exploit the vulnerability in resetStake(), which only checks stakeRate, to forcefully execute resetStakes() and subsequently beginDraftEra(). This action would cause a large amount of draftRSR to be considered as distributable rewardsRSR in the new Era, preventing stakers from withdrawing their draftRSR.

The potential impact of this vulnerability is severe:

  1. Loss of funds: Stakers may permanently lose access to their unstaked RSR.
  2. Governance manipulation: A small group of stakers could disproportionately influence the protocol's decisions.
  3. Economic imbalance: The misallocation of draftRSR as rewardsRSR could significantly disrupt the protocol's economic model.

Proof of Concept

Assume the stRSR contract initially has 1M DAI staked. Due to market volatility or discovery of a protocol bug, a large amount of funds are unstaked, leaving only 0.01 DAI staked in the protocol:

  1. From the system's perspective, stakeRSR is 0.01, corresponding to 0.01 stRSR (assuming stakeRate is still FIX_ONE), while draftRSR is approximately 1M. Since draftRSR has already burned stRSR, this portion of funds has no voting rights.

  2. An attacker donates 110,000 USDC to the stRSR contract, which will be treated as rewardsRSR.

  3. Assuming rewardRatio is MAX_REWARD_RATIO (0.01), after approximately 1006 seconds (16 minutes 48 seconds), about 11,000 USDC will be _payoutRewards(), reducing the stakeRate to < 1e12.

  4. At this point, the attacker can stake some RSR to gain voting rights and force the resetStakes() proposal to pass.

  5. Due to beginDraftEra(), any Drafts that haven't been withdrawn in time will be treated as rewardsRSR in the new Era.

  6. Users will never be able to withdraw their RSR again.

Further analysis reveals additional concerns:

a) Operator vulnerability: Since the Reserve project only provides a mechanism for creating stablecoins, anyone can be an operator, including potential attackers. This increases the risk of exploitation.

b) Easy manipulation with low stakeRSR: The less stakeRSR left in the protocol, the easier it is to manipulate. An attacker could even exploit this vulnerability immediately after protocol deployment by staking a minimal amount of RSR and donating RSR to keep the initial stakeRate low, facilitating subsequent attacks.

c) Profit motivation: Beyond purely malicious attacks, an attacker could profit from this exploit by staking a large amount of RSR in the new Era to receive the majority of rewards from _payoutRewards().

Tools Used

Manual Review

Recommended Mitigation Steps

  1. Limit the maximum value of unstakingDelay. It should not exceed the minimum time required for a new proposal to be submitted and executed.

  2. Either modify resetStakes() to check both stakeRate and draftRate, or separate beginEra() and beginDraftEra() into different functions.

  3. Require the protocol operator to stake a significant amount of RSR and set minimum stake requirement. This would substantially increase the threshold for manipulating the stakeRate, making the attack much more difficult and costly to execute.

Assessed type

Governance

tbrent commented 3 months ago

Known issue around instability at era-change boundaries -- see page 47: https://github.com/reserve-protocol/protocol/blob/72fc1f6e41da01e733c0a7e96cdb8ebb45bf1065/audits/Trust%20Security%20-%20Reserve%20Audit%20Report%203_1_0.pdf

c4-judge commented 2 months ago

thereksfour marked the issue as unsatisfactory: Out of scope