-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
_validatePositionList()
does not check for duplicate tokenIds, allowing attackers to bypass solvency checks
#498
Comments
Picodes marked the issue as primary issue |
Picodes marked the issue as satisfactory |
Picodes marked the issue as selected for report |
Hello judge, I'd like to dispute the severity of this report based on the following reasons:
Because of the above points, I'd like to ask for a reconsideration of the severity assessed in this report, since the most impactful consequence is that users could mint 1 position above their limit, but as a result of doing it, their own accounts will fall into liquidation, thus, they will end up losing their collateral and gaining nothing in return. |
Hi @stalinMacias. For point 1, allowing to mint/burn a position for a insolvent account contradicts one of the main invariant in contest readme: For point 2, the report never stated that an attacker can liquidate a solvent account. I am aware of what you are saying. For point 3, the point is the user can pass a forged positionIdListExercisee and force exercise the position even though the exercisee is insolvent. For point 4, allowing a non solvent account to be |
Hey @pkqs90 Not because an invariant is broken, a report should automatically be awarded as a high severity, it should be assessed what is the impact of breaking such an invariant that determines the final severity. The fact that an invariant is broken makes the report a valid concern, but that does not warrant a high sev automatically.
|
Hey @stalinMacias, For point 1, I think something to consider here is that the liquidation does not happen automatically, as does in a CEX. Since the protocol is onchain and relies on liquidation bots, the liquidation may not happen in some period of time. The user can use this time period to open another position and increase the leverage, and as the price moves, the position will be healthy again, and the user "saves" himself from liquidation. To make a comparison in CEX, this is like when a user is supposed to be liquidated, he instead creates more long/short positions to increase leverage, and when the price moves in the correct direction and account is healthy again, he sells his position to avoid being liquidated. The difference is this cannot actually happen in a CEX because liquidation is done automatically, but can happen here. |
You've just listed a couple of external requirements for this problem to actually have an impact.
This seems to match more the description of a medium severity rather than a high sev.
I've made my points and will let the judge take the final say in this. |
@stalinMacias thanks for your points. I don't think here "liquidation bots not liquidating the insolvent position during a period of time" is an external requirement considering how critical liquidations are to Panoptic. My reasoning was that even if the loss of funds is not "direct", being able to properly control when positions can be opened is a key feature, and the fact that you can "increase your leverage" while being insolvent prevents proper risk control. However, it's true that this is not strictly speaking a scenario where "assets can be stolen/lost/compromised directly" so I'll downgrade to Med. |
Picodes changed the severity to 2 (Med Risk) |
Hi @Picodes you have made the following comment:
But as per the The following is stated in the Panoptic documentation :
Since the liquidation bots are not programmed to liquidate the positions but to alert the liquidators at regular time intervals, of the I would kindly like to know your opinion on the above. Thank you |
@udsene I am not sure to understand what additional facts you are bringing. As I said above I agree with you that it's not an external requirement as it's meant to happen, but the loss of funds isn't direct |
@Picodes sorry it seems I had misunderstood your comment. Thanks for the clarification. |
Lines of code
https://github.com/code-423n4/2024-04-panoptic/blob/main/contracts/PanopticPool.sol#L1367-L1391
https://github.com/code-423n4/2024-04-panoptic/blob/main/contracts/PanopticPool.sol#L887-L893
Vulnerability details
Impact
The underlying issue is that
_validatePositionList()
does not check for duplicate tokenIds. Attackers can use this issue to bypass solvency checks, which leads to several impacts:This also conflicts a main invariant stated in contest readme:
Users should not be allowed to mint/burn options or pay premium if their end state is insolvent
.Bug Description
First, let's see why
_validatePositionList
does not check for duplicate tokenIds. For a user position hash, the first 8 bits is the length of tokenIds which overflows, last 248 bits is the xor hash. However, we can easily add 256 tokenIds of the same kind to create a duplicate positionHash.For example:
Hash(key0, key1, key2) == Hash(key0, key1, key2, key0, key0, ..., 256 more key0)
. This way, we can add any tokenId we want while still arriving the same position hash.PanopticPool.sol
PanopticMath.sol
Then, let's see how duplicate ids can bypass solvency check. The solvency check is in
_validateSolvency()
, which is called by all the user interaction functions, such as mint/burn/liquidate/forceExercise/settleLongPremium. This function first checks for a user passed inpositionIdList
(which we already proved can include duplicates), then calls_checkSolvencyAtTick()
to calculate thebalanceCross
(collateral balance) andthresholdCross
(required collateral) for all tokens.The key is the collateral balance includes the premium that is collected for each of the positions. For most of the positions, the collected premium should be less than required collateral to keep this position open. However, if a position has been open for a long enough time, the fees it accumulated may be larger than required collateral.
For this kind of position, we can duplicate it 256 times (or multiple of 256 times, as long as gas fee is enough), and make our collateral balance grow faster than required collateral. This can make a insolvent account "solvent", by duplicating the key tokenId multiple times.
CollateralTracker.sol
Now we have shown how to bypass the
_validateSolvency()
, we can bypass all related checks. Listing them here:Proof of Concept
In this report, we will only prove the core issue: duplicate tokenIds are allowed, and won't craft complicated scenarios for relevant impacts.
Add the following test code in
PanopticPool.t.sol
, we can see that passing 257 of the same token ids can still work formintOptions()
.Tools Used
Manual review
Recommended Mitigation Steps
Add a check in
_validatePositionList
that the length is shorter thanMAX_POSITIONS
(32).Assessed type
Invalid Validation
The text was updated successfully, but these errors were encountered: