Summary
In the editPool
function of the Distribution contract, the vulnerability allows the admin to set the payoutStart
parameter to a timestamp in the past, potentially enabling users to withdraw and claim rewards prematurely. The proposed fix involves introducing a validation check to ensure that payoutStart
is always set to a future timestamp.
Vulnerability Details
In the editPool
function, the absence of a validation check allows the admin to manipulate the payoutStart
parameter, potentially setting it to a timestamp in the past. This vulnerability has a significant impact on the contract's security, as users may exploit the lower payoutStart
value to withdraw and claim rewards before the intended timeframe.
Impact
The impact of this vulnerability is critical, as a lower or past value of payoutStart
may lead to premature withdrawal and claiming of rewards by users.
However, the likelihood is low. Because the edit is controlled by admin only.
POC
Copy the below test and run it via forge test --match-test testEditPool -vvvv
In the result you can see when pool was created the payoutStart: 86400
and when the edit was done it is lowered to payoutStart: 56400
function testEditPool() public {
vm.prank(address(distribution.owner()));
distribution.createPool(myPool);
vm.prank(address(distribution.owner()));
distribution.editPool(0, myPoolEdit);
}
Result:
├─ [184054] Distribution::createPool(Pool({ payoutStart: 86400 [8.64e4], decreaseInterval: 86400 [8.64e4], withdrawLockPeriod: 43200 [4.32e4], claimLockPeriod: 43200 [4.32e4], withdrawLockPeriodAfterStake: 86400 [8.64e4], initialReward: 100000000000000000000 [1e20], rewardDecrease: 20000000000000000000 [2e19], minimalStake: 10000000000000000000 [1e19], isPublic: true }))
│ ├─ emit PoolCreated(poolId: 0, pool: Pool({ payoutStart: 86400 [8.64e4], decreaseInterval: 86400 [8.64e4], withdrawLockPeriod: 43200 [4.32e4], claimLockPeriod: 43200 [4.32e4], withdrawLockPeriodAfterStake: 86400 [8.64e4], initialReward: 100000000000000000000 [1e20], rewardDecrease: 20000000000000000000 [2e19], minimalStake: 10000000000000000000 [1e19], isPublic: true }))
│ └─ ← ()
├─ [365] Distribution::owner() [staticcall]
│ └─ ← 0x0000000000000000000000000000000000000000
├─ [0] VM::prank(0x0000000000000000000000000000000000000000)
│ └─ ← ()
├─ [35540] Distribution::editPool(0, Pool({ payoutStart: 56400 [5.64e4], decreaseInterval: 86400 [8.64e4], withdrawLockPeriod: 43200 [4.32e4], claimLockPeriod: 43200 [4.32e4], withdrawLockPeriodAfterStake: 86400 [8.64e4], initialReward: 100000000000000000000 [1e20], rewardDecrease: 20000000000000000000 [2e19], minimalStake: 10000000000000000000 [1e19], isPublic: true }))
│ ├─ emit PoolEdited(poolId: 0, pool: Pool({ payoutStart: 56400 [5.64e4], decreaseInterval: 86400 [8.64e4], withdrawLockPeriod: 43200 [4.32e4], claimLockPeriod: 43200 [4.32e4], withdrawLockPeriodAfterStake: 86400 [8.64e4], initialReward: 100000000000000000000 [1e20], rewardDecrease: 20000000000000000000 [2e19], minimalStake: 10000000000000000000 [1e19], isPublic: true }))
│ └─ ← ()
└─ ← ()
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 13.15ms
Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)
Recommendations
Introduce a validation check within the editPool
function. Specifically, consider adding the following line of code to ensure that payoutStart
is always set to a timestamp greater than the current block timestamp:
require(pool_.payoutStart > block.timestamp, "DS: invalid payout start value");