Open sherlock-admin3 opened 3 months ago
Thanks for for bringing this up! This issue's fix will probably be the same as #46, with better proposal filtering.
This and #46 have the same fix, which is included in the #51 recommendation (3rd code snippet).
I.e. for #32, #46 and #51 set please use the fix from #51 as it covers them all.
@dmitriia want to confirm that these three issues are not dups, even though they have the same fix?
@WangSecurity They are different: 32 is for the avoidance of minimal reward period, 46 is for extending auction revenue span, while 51 is for manipulation with the help of proposal canceling.
They can have different fixes, but the recommendation is just unified to minimize code changes. Although, the base recommendation for 51 by itself doesn't include the 32 and 46 fixes, but there is an expanded write up of the cumulative change put there, that includes some logic optimization along with these fixes.
The protocol team fixed this issue in the following PRs/commits: https://github.com/nounsDAO/nouns-monorepo/pull/839
Fix looks ok.
The Lead Senior Watson signed off on the fix.
hyh
medium
Rewards can be allocated for less than minimal reward period with the help of bogus proposal
Summary
Anyone controlling more than
proposalThresholdBPS
share of votes can ignore the minimum reward period to allocate the rewards for the proposal they benefit from.Vulnerability Detail
The
minimumRewardPeriod
check can be surpassed with the help of any bogus proposal since the check comes before proposal eligibility control. I.e. once currentblock.timestamp
is big enough an attacker can just create a proposal only to get past the check by later using that proposal as a anchor for reward distribution call.This proposal doesn't have to be eligible (can be heavily voted against, can even be vetoed), the only requirement is that it should have ended (
require(block.number > endBlock, ...
check), i.e. the attacker needs to create it beforehand, but the timing is exact and is known in advance, so this can be done all the times. Note thatobjectionPeriodEndBlock
is activated only for flipped proposals, which is higly unlikely for bogus ones, soendBlock = max(proposals[i].endBlock, proposals[i].objectionPeriodEndBlock) = proposals[i].endBlock
, which is known as of time of proposal creation.Impact
The attack can be carried deterministically, there are no direct prerequisites, while the total cost is additional proposal creation gas cost only (it's not compensated). Surpassing the
minimumRewardPeriod
check can be used for gaming reward allocation, i.e. placing good auctions to the proposal attacker benefit ahead of other proposals, who have to wait for the period to expire.I.e. in a situation when there are only few active proposals that are about to end, while attacker's one is ended,
$.params.numProposalsEnoughForReward
isn't met, while current auctions have good revenue, the attacker can steal the rewards from other proposals by not waiting for them with the help of the anchor bogus one. Overall, this setup has medium probability of occurring, while the reward loss can be material, so the impact can be estimated as medium as well.Likelihood: Medium + Impact: Medium = Severity: Medium.
Code Snippet
t.lastProposal.creationTimestamp
comes from user suppliedlastProposalId
:https://github.com/sherlock-audit/2024-03-nouns-dao-2/blob/main/nouns-monorepo/packages/nouns-contracts/contracts/client-incentives/Rewards.sol#L370-L383
With the only checks being
nounsDAO.proposalCount() >= lastProposalId >= t.nextProposalIdToReward
, sinceproposalDataForRewards
doesn't filter the proposals, returning all the results sequentially:https://github.com/sherlock-audit/2024-03-nouns-dao-2/blob/main/nouns-monorepo/packages/nouns-contracts/contracts/client-incentives/Rewards.sol#L319-L330
User that can create proposals can also anyhow time the bogus one:
https://github.com/sherlock-audit/2024-03-nouns-dao-2/blob/main/nouns-monorepo/packages/nouns-contracts/contracts/governance/NounsDAOProposals.sol#L871-L881
Tool used
Manual Review
Recommendation
Consider basing the check on the last eligible proposal, not just the user supplied one, e.g. by filtering them out in
proposalDataForRewards()
, which isn't used outside reward logic:https://github.com/sherlock-audit/2024-03-nouns-dao-2/blob/main/nouns-monorepo/packages/nouns-contracts/contracts/governance/NounsDAOProposals.sol#L719-L737
In order to minimize the changes this suggestion is shared with another issues.