DittoETH

Ditto
DeFiFoundryOracle
55,000 USDC
View results
Submission Details
Severity: low
Valid

DoS in primary margin call

Summary

The flaggerIdCounter is capped at type(uint16).max (65,535) instead of type(uint24).max (16,777,215). This opens a potential attack vector where an attacker can create and flag a large number of short records, creating a DoS attack on the primary margin call system.

Vulnerability Details

In the technical documentation of the protocol we can read:

flaggerId: uint24 is a max of 16m.

While flaggerIdCounter is indeed declared in AppStorage.sol as an uint24, we can find the following code in LibShortRecord.sol:setFlagger():

File: contracts/libraries/LibShortRecord.sol
393 //@dev re-use an inactive flaggerId
394 if (timeDiff > LibAsset.firstLiquidationTime(cusd)) {
395 delete s.assetUser[cusd][flaggerToReplace].g_flaggerId;
396 short.flaggerId = flagStorage.g_flaggerId = flaggerHint;
397 } else if (s.flaggerIdCounter < type(uint16).max) {
398 //@dev generate brand new flaggerId
399 short.flaggerId = flagStorage.g_flaggerId = s.flaggerIdCounter;
400 s.flaggerIdCounter++;
401 } else {
402 revert Errors.InvalidFlaggerHint();
403 }

As we can see in line 397 flaggerIdCounter is effectively capped at type(uint16).max (65,535), and not type(uint24).max (16,777,215), as stated in the documentation. This opens a potential attack vector where an attacker can create and flag a large number of short records, creating a DoS attack on the primary margin call system.

Although performing such an attack will be costly, an attacker can be motivated to do so if he has a large short position at risk of being liquidated and wants to buy time to either obtain more collateral or have the price of the asset vary in his favor. Also, other parties like protocols of the competition might be interested in damaging the protocol by performing such an attack.

Proof of Concept

  • The attacker detects off-chain a great variation of the asset price that will trigger a liquidation of their short position.

  • Before the price oracle is updated, the attacker creates 65,535 short records for the minimum amount (0.001 ether).

  • Once the price oracle is updated, the attacker calls setFlagger() on all the short records using different accounts for each call.

  • Given flaggerIdCounter has reached its maximum value nobody can flag more shorts under 12 hours have passed.

This operation would require leaving 65.514 ETH as collateral.

These are the gas estimations taken from the protocol's test suite:

  • create short orders reusing id: 75,633

  • create a bid that matches with 100 short orders: 65,514

  • flag a short record: 71,101

That gives us a total gas cost of:

(75,633 + 71,101 + 65,514 / 100) * 65535 = 9,659,147,289

Taking a gas price of 10 gwei, the attacker would spend 96.59 ETH in gas fees.

Impact

The primary margin call system can be DoSed, delaying the beginning of the liquidation process for 12 hours. This can provoke short positions not being liquidated and reputational damage to the protocol.

Tools Used

Manual review.

Recommendations

File: contracts/libraries/LibShortRecord.sol
- } else if (s.flaggerIdCounter < type(uint16).max) {
+ } else if (s.flaggerIdCounter < type(uint24).max) {
//@dev generate brand new flaggerId
short.flaggerId = flagStorage.g_flaggerId = s.flaggerIdCounter;
s.flaggerIdCounter++;
} else {
revert Errors.InvalidFlaggerHint();
}
Updates

Lead Judging Commences

0xnevi Lead Judge almost 2 years ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-241

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.