You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository has been archived by the owner on Jan 7, 2024. It is now read-only.
sherlock-admin opened this issue
Jul 3, 2023
· 0 comments
Labels
DuplicateA valid issue that is a duplicate of an issue with `Has Duplicates` labelHighA valid High severity issueRewardA payout will be made for this issue
The liquidator can set any stale price data by setSymbolsPrice and others can't override it
Summary
setSymbolsPrice not checking whether the price is stale, the liquidator can set the stale price to liquidate.
Vulnerability Detail
function verifyPrices(PriceSig memorypriceSig, addresspartyA) internalview {
MuonStorage.Layout storage muonLayout = MuonStorage.layout();
require(priceSig.prices.length== priceSig.symbolIds.length, "LibMuon: Invalid length");
bytes32 hash =keccak256(
abi.encodePacked(
muonLayout.muonAppId,
priceSig.reqId,
address(this),
partyA,
priceSig.upnl,
priceSig.totalUnrealizedLoss,
priceSig.symbolIds,
priceSig.prices,
priceSig.timestamp,
getChainId()
)
);
verifyTSSAndGateway(hash, priceSig.sigs, priceSig.gatewaySignature);
}
function setSymbolsPrice(addresspartyA, PriceSig memorypriceSig) internal {
MAStorage.Layout storage maLayout = MAStorage.layout();
AccountStorage.Layout storage accountLayout = AccountStorage.layout();
// @audit Here not check priceSig.timestamp
LibMuon.verifyPrices(priceSig, partyA);
require(maLayout.liquidationStatus[partyA], "LiquidationFacet: PartyA is solvent");
// @audit Here only check whether priceSig is younger than the liquidation expiration time, not whether it is older than the liquidation setting timerequire(
priceSig.timestamp <=
maLayout.liquidationTimestamp[partyA] + maLayout.liquidationTimeout,
"LiquidationFacet: Expired signature"
);
for (uint256 index =0; index < priceSig.symbolIds.length; index++) {
accountLayout.symbolsPrices[partyA][priceSig.symbolIds[index]] =Price(
priceSig.prices[index],
maLayout.liquidationTimestamp[partyA]
);
}
int256 availableBalance = LibAccount.partyAAvailableBalanceForLiquidation(
priceSig.upnl,
partyA
);
// @audit only first timeif (accountLayout.liquidationDetails[partyA].liquidationType == LiquidationType.NONE) {
accountLayout.liquidationDetails[partyA] =LiquidationDetail({
liquidationType: LiquidationType.NONE,
upnl: priceSig.upnl,
totalUnrealizedLoss: priceSig.totalUnrealizedLoss,
deficit: 0,
liquidationFee: 0
});
if (availableBalance >=0) {
uint256 remainingLf = accountLayout.lockedBalances[partyA].lf;
accountLayout.liquidationDetails[partyA].liquidationType = LiquidationType.NORMAL;
accountLayout.liquidationDetails[partyA].liquidationFee = remainingLf;
} elseif (uint256(-availableBalance) < accountLayout.lockedBalances[partyA].lf) {
uint256 remainingLf = accountLayout.lockedBalances[partyA].lf -uint256(-availableBalance);
accountLayout.liquidationDetails[partyA].liquidationType = LiquidationType.NORMAL;
accountLayout.liquidationDetails[partyA].liquidationFee = remainingLf;
} elseif (
uint256(-availableBalance) <=
accountLayout.lockedBalances[partyA].lf + accountLayout.lockedBalances[partyA].cva
) {
uint256 deficit =uint256(-availableBalance) -
accountLayout.lockedBalances[partyA].lf;
accountLayout.liquidationDetails[partyA].liquidationType = LiquidationType.LATE;
accountLayout.liquidationDetails[partyA].deficit = deficit;
} else {
uint256 deficit =uint256(-availableBalance) -
accountLayout.lockedBalances[partyA].lf -
accountLayout.lockedBalances[partyA].cva;
accountLayout.liquidationDetails[partyA].liquidationType = LiquidationType.OVERDUE;
accountLayout.liquidationDetails[partyA].deficit = deficit;
}
AccountStorage.layout().liquidators[partyA].push(msg.sender);
// @audit When invoke setSymbolsPrice again, the pnl and loss are required to be equal to the first time, it can be assumed that others cannot modify the price again
} else {
require(
accountLayout.liquidationDetails[partyA].upnl == priceSig.upnl &&
accountLayout.liquidationDetails[partyA].totalUnrealizedLoss ==
priceSig.totalUnrealizedLoss,
"LiquidationFacet: Invalid upnl sig"
);
}
}
LibMuon.verifyPrices not check whether it is stale price and setSymbolsPrice only check that priceSig doesn't timeout.
So the liquidator can set a stale price data, and because setSymbolsPrice requires pnl and loss to be the same as the first time, it is difficult for others to override with the correct price. See my comment in the code for details.
Even though pnl and loss may equal, there is no time for others to overwrite because malicious liquidator can pack several liquidate txs into one block.
For comparison you can see that liquidatePositionsPartyB has two time checks:
The liquidator can use the stale price to liquidate. In particular, given that one side of the party may be the liquidator, they can use this price to arbitrage.
Sign up for freeto subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Labels
DuplicateA valid issue that is a duplicate of an issue with `Has Duplicates` labelHighA valid High severity issueRewardA payout will be made for this issue
kutugu
high
The liquidator can set any stale price data by setSymbolsPrice and others can't override it
Summary
setSymbolsPrice
not checking whether the price is stale, the liquidator can set the stale price to liquidate.Vulnerability Detail
LibMuon.verifyPrices
not check whether it is stale price andsetSymbolsPrice
only check that priceSig doesn't timeout.So the liquidator can set a stale price data, and because setSymbolsPrice requires pnl and loss to be the same as the first time, it is difficult for others to override with the correct price. See my comment in the code for details.
Even though pnl and loss may equal, there is no time for others to overwrite because malicious liquidator can pack several liquidate txs into one block.
For comparison you can see that
liquidatePositionsPartyB
has two time checks:Impact
The liquidator can use the stale price to liquidate. In particular, given that one side of the party may be the liquidator, they can use this price to arbitrage.
Code Snippet
Tool used
Manual Review
Recommendation
Added freshness check for price data
Duplicate of #113
The text was updated successfully, but these errors were encountered: