This repository has been archived by the owner on Sep 29, 2024. It is now read-only.
pkqs90 - Validator threshold can be bypassed: a single compromised validator can update minter's state to historical state #46
Labels
Has Duplicates
A valid issue with 1+ other issues describing the same vulnerability
Medium
A valid Medium severity issue
Reward
A payout will be made for this issue
Sponsor Confirmed
The sponsor acknowledged this issue is valid
Will Fix
The sponsor confirmed this issue will be fixed
pkqs90
medium
Validator threshold can be bypassed: a single compromised validator can update minter's state to historical state
Summary
The
updateCollateralValidatorThreshold
specifies the minimum number of validators needed to confirm the validity ofupdateCollateral
data. However, just one compromised validator is enough to alter a minter's collateral status. In particular, this vulnerability allows the compromised validator to set the minter's state back to a historical state, allowing malicious minters to increase their collateral.Vulnerability Detail
The
updateCollateral()
function calls the_verifyValidatorSignatures()
function, which calculates the minimum timestamp signed by all validators. This timestamp is then used to update the minter state's_minterStates[minter_].updateTimestamp
. The constraint during this process is that the_minterStates[minter_].updateTimestamp
must always be increasing.Function
updateCollateral()
:Function
_updateCollateral()
:If we have 1 compromised validator, its signature can be manipulated to any chosen timestamp. Consequently, this allows for control over the timestamp in
_minterStates[minter_].updateTimestamp
making it possible to update the minter's state to a historical state. An example is given in the following proof of concept. The key here is that even thoughupdateCollateralValidatorThreshold
may be set to 2 or even 3, as long as 1 validator is compromised, the attack vector would work, thus defeating the purpose of having a validator threshold.Proof Of Concept
In this unit test,
updateCollateralInterval
is set to 2000 (default value). TheupdateCollateralValidatorThreshold
is set to 2, and the_validator1
is compromised. Following the steps below, we show how we update minter to a historical state:T0
.T0+100
. Deposit 100e6 collateral atT0+100
._validator0
signs signature atT0+100
, and_validator1
signs signature atT0+1
. AfterupdateCollateral()
, minter state collateral = 100e6, and updateTimestamp =T0+1
.T0+200
. Propose retrieval for all collateral, and perform the retrieval offchain._validator0
signs signature atT0+200
, and_validator1
signs signature atT0+2
. AfterupdateCollateral()
, minter state collateral = 0, and updateTimestamp =T0+2
.T0+300
. Reuse_validator0
signature from step 1, it is signed on timestampT0+100
._validator1
signs collateral=100e6 atT0+3
. AfterupdateCollateral()
, minter state collateral = 100e6, and updateTimestamp =T0+3
.Now, the minter is free to perform minting actions since his state claims collateral is 100e6, even though he has already retrieved it back in step 2. The mint proposal may even be proposed between step 1 and step 2 to reduce the mintDelay the minter has to wait.
Add the following testing code to
MinterGateway.t.sol
. See more description in code comments.Impact
As shown in the proof of concept, the minter can use the extra collateral to mint M tokens for free.
One may claim that during minting, the
collateralOf()
function checks forblock.timestamp < collateralExpiryTimestampOf(minter_)
, however, since during deploymentupdateCollateralInterval
is set to 86400, that gives us enough time to perform the attack vector before "fake" collateral expires.Code Snippet
Tool used
Foundary
Recommendation
Use the maximum timestamp of all validators instead of minimum, or take the
threshold
-last minimum instead of the most minimum.The text was updated successfully, but these errors were encountered: