Sushi Trident contest phase 2 - broccoli's results

Community-driven DeFi platform

General Information

Platform: Code4rena

Start Date: 30/09/2021

Pot Size: $100,000 SUSHI

Total HM: 24

Participants: 7

Period: 7 days

Judge: alcueca

Total Solo HM: 16

Id: 35

League: ETH

Sushi

Findings Distribution

Researcher Performance

Rank: 2/7

Findings: 10

Award: $22,095.11

🌟 Selected for report: 9

🚀 Solo Findings: 4

Findings Information

🌟 Selected for report: cmichel

Also found by: broccoli

Labels

bug
duplicate
3 (High Risk)
sponsor confirmed

Awards

169.9968 SUSHI - $2,124.96

External Links

Handle

broccoli

Vulnerability details

Impact

In the burn function of ConcentratedLiquidityPool, when calling _updatePosition, the amount of liquidity to burn is explicitly converted from uint128 to int128, which could result in a positive integer if amount is larger than (1 << 127) and less than (1 << 128) (i.e., the MSB of amount is set). As a result, the pool's liquidity decreases, but the attacker's liquidity at a specific location increases. The incorrect liquidity could affect all trades in the pool afterward.

Proof of Concept

  1. Suppose that the current price is at the tick 500000. Consider an attacker calling the burn function with lower = upper = 50000, and amount = (1 << 128) - 100000.
  2. Since the current price, the lower price, and the upper price are all the same (there's no check to ensure that the upper price should be greater than the lower price), the amount0 and amount1 to be withdrawn from the pool will both be 0. The pool's liquidity is decreased by amount (and could underflow to a large number).
  3. However, when the _updatePosition function is called, the liquidity to update is calculated by -int128(amount) = -int128((1 << 128) - 100000) = -(-100000) = 100000, which causes the attacker's liquidity at positions[attacker][lower][lower] to be increased by 100000.
  4. In short, the attacker does not have to prepare any liquidity at any position to control (pump or decrease) the pool's liquidity. Due to the incorrect liquidity value, all swaps afterward are affected.
  5. Moreover, the attacker could potentially exploit the controlled liquidity value to profit from the pool.

Referenced code: ConcentratedLiquidityPool.sol#L252

In the burn function, check the amount exceeds type(uint128).max or not. If so, then revert the transaction. Or consider using the SafeCast library from OpenZeppelin instead.

#0 - sarangparikh22

2021-10-11T13:50:24Z

Duplicate of #50

Findings Information

🌟 Selected for report: broccoli

Also found by: 0xsanson, cmichel, hickuphh3, pauliax

Labels

bug
duplicate
3 (High Risk)
sponsor disputed

Awards

49.5711 SUSHI - $619.64

External Links

Handle

broccoli

Vulnerability details

Impact

In the subscribe function of ConcentratedLiquidityPoolManager, the incentive to subscribed is determined as follows:

Incentive memory incentive = incentives[pool][positionId];

However, positionId should be incentiveId, a counter that increases by one whenever a new incentive is added to the pool. The usage of positionId could cause the wrong incentive to be used, and in general, the incentive is not found, and the transaction reverts (the condition block.timestamp < incentive.endTime is not met). The getReward and claimReward functions have the bug of misusing positionId as the index of incentives.

Proof of Concept

Referenced code: ConcentratedLiquidityPoolManager.sol#L68 ConcentratedLiquidityPoolManager.sol#L87 ConcentratedLiquidityPoolManager.sol#L105

Change positionId to incentiveId in the referenced lines of code.

#0 - sarangparikh22

2021-10-12T09:44:56Z

Duplicate of #39. Disagree with severity, it should be 2.

#1 - alcueca

2021-11-12T10:44:22Z

I'm choosing this one as the main description, since it mentions the three lines in which the error can be found.

Findings Information

🌟 Selected for report: cmichel

Also found by: WatchPug, broccoli, hickuphh3, pauliax

Labels

bug
duplicate
3 (High Risk)
sponsor confirmed

Awards

49.5711 SUSHI - $619.64

External Links

Handle

broccoli

Vulnerability details

Impact

The reclaimIncentive function of ConcentratedLiquidityPoolManager does not update the rewardsUnclaimed variable after some rewards are reclaimed. Thus, an attacker could add an incentive with a corresponding token, such as DAI, and reclaim the incentive multiple times to drain all the DAI within the manager contract.

Proof of Concept

Referenced code: ConcentratedLiquidityPoolManager.sol#L59-L60

Subtract incentive.rewardsUnclaimed by amount before the _transfer (which prevents reentrancy attacks).

#0 - sarangparikh22

2021-10-11T13:54:41Z

Duplicate of #37

Findings Information

🌟 Selected for report: broccoli

Labels

bug
3 (High Risk)
sponsor disputed

Awards

377.7706 SUSHI - $4,722.13

External Links

Handle

broccoli

Vulnerability details

Impact

Similar to a previous finding in the IndexPool contract, the mint function of ConcentratedLiquidityPool allows integer overflows when checking the balance is larger or equal to the received amount of token plus the reserve. As a result, the attacker could get a large amount of liquidity but only provide a small number of tokens to the pool, effectively stealing other LPs' funds when burning his liquidity.

Notice that this bug is independent of another bug of incorrect casting uint256 type to uint128 in the _getAmountsForLiquidity function. Even if the previously mentioned bug does not exist, the attacker could still steal the funds in the pool by exploiting this bug.

Proof of Concept

  1. Suppose that the current price is at the tick 500000, an attacker calls the mint function with the following parameters:
mintParams.lower = 100000 mintParams.upper = 500000 mintParams.amount1Desired = (1 << 128) - 47541305835 # a carefully chosen number mintParams.amount0Desired = 0
  1. Since the current price is equal to the upper price, we have
_liquidity = mintParams.amount1Desired * (1 << 96) // (priceUpper - priceLower) = 4731732988155153573010127839
  1. The amounts of token0 and token1 that the attacker has to pay is
amount0Actual = 0 amount1Actual = uint128(DyDxMath.getDy(_liquidity, priceLower, priceUpper, true)) = uint128(_liquidity * (priceUpper - priceLower) // (1 << 96)) # round up = uint128(340282366920938463463374607384226905622) = 340282366920938463463374607384226905622 = (1 << 128) - 47541305834
  1. As long as reserve1 is greater than 47541305834, the addition amount1Actual + reserve1 overflows to a small number, causing the attacker to pass the balance check.

Referenced code: ConcentratedLiquidityPool.sol#L204 ConcentratedLiquidityPool.sol#L209

Consider removing the unchecked statement to check for integer overflow or casting both amount1Actual and reserve1 to type uint256 before adding them and comparing to the _balance(token).

#0 - sarangparikh22

2021-10-28T21:26:06Z

The example is wrong, you can't add use upper tick as odd, correct the example and resubmit please.

Findings Information

🌟 Selected for report: broccoli

Labels

bug
3 (High Risk)
sponsor confirmed

Awards

377.7706 SUSHI - $4,722.13

External Links

Handle

broccoli

Vulnerability details

Impact

The _getAmountsForLiquidity function of ConcentratedLiquidityPool explicitly converts the result of DyDxMath.getDy and DyDxMath.getDx from type uint256 to type uint128. The explicit casting without checking whether the integer exceeds the maximum number (i.e., type(uint128).max) could cause incorrect results being used. Specifically, an attacker could exploit this bug to mint a large amount of liquidity but only pay a little of token0 or token1 to the pool and effectively steal other's funds when burning his liquidity.

Proof of Concept

  1. Suppose that the current price is at the tick 500000, an attacker calls the mint function with the following parameters:
mintParams.lower = 100000 mintParams.upper = 500000 mintParams.amount1Desired = (1 << 128) + 71914955423 # a carefully chosen number mintParams.amount0Desired = 0
  1. Since the current price is equal to the upper price, we have
_liquidity = mintParams.amount1Desired * (1 << 96) // (priceUpper - priceLower) = 4731732988155153573010127840
  1. The amounts of token0 and token1 that the attacker has to pay is
amount0Actual = 0 amount1Actual = uint128(DyDxMath.getDy(_liquidity, priceLower, priceUpper, true)) = uint128(_liquidity * (priceUpper - priceLower) // (1 << 96)) # round up = uint128(340282366920938463463374607456141861046) # exceed the max = 24373649590 # truncated
  1. The attacker only pays 24373649590 of token1 to get 4731732988155153573010127840 of the liquidity, which he could burn to get more token1. As a result, the attacker is stealing the funds from the pool and could potentially drain it.

Referenced code: ConcentratedLiquidityPool.sol#L480 concentratedPool/DyDxMath.sol#L15 concentratedPool/DyDxMath.sol#L30

Check whether the result of DyDxMath.getDy or DyDxMath.getDx exceeds type(uint128).max or not. If so, then revert the transaction. Or consider using the SafeCast library from OpenZeppelin instead.

#0 - sarangparikh22

2021-10-28T21:27:11Z

The example is wrong, you can't add use upper tick as odd, correct the example and resubmit please.

#1 - alcueca

2021-11-12T11:06:48Z

@sarangparikh22, could you confirm whether the casting to uint128 is known to be safe? Are you unconvinced of the issue?

#2 - sarangparikh22

2021-11-16T23:19:23Z

@alcueca I can confirm casting to uint128 is not safe, and will lead to overflow. However, the example mentioned is wrong.

#3 - alcueca

2021-11-18T06:53:42Z

Understood. I will uphold the severity 3 because the overflow happens in a critical function for the management of funds and an incorrect execution will likely lead to loss of funds.

Findings Information

🌟 Selected for report: broccoli

Labels

bug
3 (High Risk)
sponsor confirmed

Awards

377.7706 SUSHI - $4,722.13

External Links

Handle

broccoli

Vulnerability details

range fee growth underflow

Impact

The function RangeFeeGrowth ConcentratedLiquidityPool.sol#L601-L633 would revert the transaction in some cases.

When a pool cross a tick, it only updates either feeGrowthOutside0 or feeGrowthOutside1. Ticks.sol#L23-L53

RangeFeeGrowth calculates the fee as follow:

            feeGrowthInside0 = _feeGrowthGlobal0 - feeGrowthBelow0 - feeGrowthAbove0;
            feeGrowthInside1 = _feeGrowthGlobal1 - feeGrowthBelow1 - feeGrowthAbove1;

feeGrowthBelow + feeGrowthAbove is not necessary smaller than _feeGrowthGlobal. Please see POC.

Users can not provide liquidity or burn liquidity. Fund will get stocked in the contract. I consider this is a high-risk issue.

Proof of Concept

    # This is the wrapper.
    # def add_liquidity(pool, amount, lower, upper)
    # def swap(pool, buy, amount)

    add_liquidity(pool, deposit_amount, -800, 500)
    add_liquidity(pool, deposit_amount, 400, 700)
    # We cross the tick here to trigger the bug.

    swap(pool, False, deposit_amount)
    # Only tick 700's feeGrowthOutside1 is updated

    swap(pool, True, deposit_amount)
    # Only tick 500's feeGrowthOutside0 is updated
    
    # current tick at -800

    # this would revert
    # feeGrowthBelow1 = feeGrowthGlobal1
    # feeGrowthGlobal1 - feeGrowthBelow1 - feeGrowthAbove1 would revert
    # user would not be able to mint/withdraw/cross this tick. The pool is broken
    add_liquidity(pool, deposit_amount, 400, 700)

Tools Used

Hardhat

It's either modify the tick's algo or RangeFeeGrowth. The quick-fix I come up with is to deal with the fee in RangeFeeGrowth. However, I recommend the team to go through tick's logic again.

#0 - sarangparikh22

2021-10-13T23:40:02Z

The example is wrong, you can't add use upper tick as odd, correct the example and resubmit please.

#1 - alcueca

2021-11-12T13:44:49Z

@sarangparikh22, is the example invalid, or the whole issue? Is this something that you would consider fixing?

#2 - sarangparikh22

2021-11-17T23:09:11Z

@alcueca The example is invalid, but the issue is valid, the fix is to swap the condition of feeGrowthGlobal

Findings Information

🌟 Selected for report: cmichel

Also found by: 0xsanson, broccoli, pauliax

Labels

bug
duplicate
3 (High Risk)
sponsor confirmed

Awards

68.8487 SUSHI - $860.61

External Links

Handle

broccoli

Vulnerability details

burn do not burn reserve

Impact

ConcentratedLiquidityPool.sol#L263-L266

The dev mistakenly write:

            reserve0 -= uint128(amount0fees);
            reserve1 -= uint128(amount1fees);

It should be

            reserve0 -= uint128(amount0fees);
            reserve1 -= uint128(amount1fees);

Other users can't mint lp unless they first send the extra tokens to match the reserve. I consider this is a high-risk issue.

Proof of Concept

None

Tools Used

None

I believe it's a mistake.

            reserve0 -= uint128(amount0fees);
            reserve1 -= uint128(amount1fees);

#0 - sarangparikh22

2021-10-11T14:07:00Z

The solution advised is same as the bug. It should be:

unchecked { amount0 += amount0fees; amount1 += amount1fees; } unchecked { reserve0 -= uint128(amount0); reserve1 -= uint128(amount1); }

However, I'm still approving this issue, as the title reflects the correct issue.

Duplicate of #51

Findings Information

🌟 Selected for report: cmichel

Also found by: 0xsanson, WatchPug, broccoli, hickuphh3

Labels

bug
duplicate
2 (Med Risk)
sponsor disputed

Awards

14.8713 SUSHI - $185.89

External Links

Handle

broccoli

Vulnerability details

Impact

The claimReward function of ConcentratedLiquidityPoolManager calculates the secondsUnclaimed variable using a formula with an unclear intention:

uint256 secondsUnclaimed = (maxTime - incentive.startTime) << (128 - incentive.secondsClaimed);

This formula causes an integer underflow error when incentive.secondsClaimed is greater than 128, which generally happens in most cases since incentive.secondsClaimed is scaled by a factor of 1 << 128. The integer underflow reverts the transaction and prevents anyone from claiming the reward.

Proof of Concept

Referenced code: ConcentratedLiquidityPoolManager.sol#L93 ConcentratedLiquidityPoolManager.sol#L110

Fix the calculation of secondsUnclaimed.

#0 - sarangparikh22

2021-10-12T09:47:09Z

Duplicate of #41. Disagree with severity, should be 2.

#1 - alcueca

2021-11-12T10:37:59Z

Assets are not at risk of being lost, but functionality is impacted. Severity 2.

Findings Information

🌟 Selected for report: broccoli

Labels

bug
2 (Med Risk)
sponsor acknowledged

Awards

113.3312 SUSHI - $1,416.64

External Links

Handle

broccoli

Vulnerability details

Impact

There could be an integer underflow error when the reward of an incentive is claimed, forcing users to wait for a sufficient period or reduce their liquidity to claim the rewards.

Proof of Concept

The unclaimed reward that a user could claim is proportional to the secondsInside, which is, in fact, proportional to the position's liquidity. It is possible that the liquidity is too large and causes secondsInside to be larger than secondsUnclaimed. As a result, the rewards that the user wants to claim exceed the incentive.rewardsUnclaimed and causes an integer underflow error, which prevents him from getting the rewards.

Referenced code: ConcentratedLiquidityPoolManager.sol#L94-L95

Check whether the rewards exceeds the incentive.rewardsUnclaimed. If so, then send only incentive.rewardsUnclaimed amount of rewards to the user.

#0 - sarangparikh22

2021-10-13T20:43:37Z

The problem seems very unlikely to happen, would be great to see a POC.

Findings Information

🌟 Selected for report: tensors

Also found by: broccoli

Labels

bug
duplicate
1 (Low Risk)
sponsor disputed

Awards

16.9997 SUSHI - $212.50

External Links

Handle

broccoli

Vulnerability details

Impact

Unlocked/floating pragmas are used in the contracts (i.e., pragma solidity >=0.8.0;). Locking the pragma ensures that the contracts are not accidentally deployed using an outdated compiler version with unfixed bugs.

Proof of Concept

Referenced code: Please use grep -R "pragma" . to find the unlocked pragmas.

Lock pragmas to a specific Solidity version. Consider the compiler bugs in the following links and ensure that they do not affect the contracts. It is also recommended to use the latest version of Solidity when writing and deploying contracts (see Solidity docs).

Related links: Solidity repo - known bugs Solidity repo - bugs by version

#0 - sarangparikh22

2021-10-11T13:04:37Z

Duplicate of #3

#1 - alcueca

2021-11-12T10:53:28Z

Severity 1 is correct

Findings Information

🌟 Selected for report: broccoli

Labels

bug
1 (Low Risk)
sponsor confirmed

Awards

37.7771 SUSHI - $472.21

External Links

Handle

broccoli

Vulnerability details

Impact

In the burn and swap functions of ConcentratedLiquidityPool, the lower tick is not explicitly checked to be less than the upper tick. Besides, the ticks are not checked to be at least the minimum tick and at most the maximum tick.

Proof of Concept

Referenced code: Ticks.sol#L68-L70

Add sanity checks on the lower and upper ticks in critical functions (see the referenced line of code, for example).

#0 - sarangparikh22

2021-10-13T22:25:59Z

This doesn't seem reachable, can you give a POC?

#1 - alcueca

2021-11-13T06:38:17Z

@sarangparikh22, I don't understand what it is that you mean by not reachable, could you please elaborate?

#2 - sarangparikh22

2021-11-16T21:38:17Z

If we provide an invalid tick condition, such as lower is higher than upper, it would fail at the FullMath check, as it would overflow and we would get a transaction revert. However, it is better to fail early with a proper message. What I meant by unreachable is, I am unable to find a condition where this condition holds true and we are able to swap or burn with wrong tick specs. We'll make the changes to fail early by checking ticks, and will mark this issue as valid, since it makes sense to just check it early, even though it would fail. @alcueca

Findings Information

🌟 Selected for report: broccoli

Labels

bug
1 (Low Risk)
sponsor acknowledged

Awards

37.7771 SUSHI - $472.21

External Links

Handle

broccoli

Vulnerability details

Impact

The _updatePosition function of ConcentratedLiquidityPool uses the < operator to ensure a user's liquidity does not exceed the maximum. However, we should use the <= operator instead.

Proof of Concept

Referenced code: ConcentratedLiquidityPool.sol#L549

Change < to <= in the referenced line of code.

Findings Information

🌟 Selected for report: broccoli

Labels

bug
1 (Low Risk)
sponsor acknowledged

Awards

37.7771 SUSHI - $472.21

External Links

Handle

broccoli

Vulnerability details

Impact

In the swap function of ConcentratedLiquidityPool, the following line of code seems to handle the timestamp overflow problem happening in year 2106:

uint256 diff = timestamp - uint256(lastObservation); /// @dev Underflow in 2106.

However, the subtraction is not within an unchecked statement. Thus an integer underflow still occurs when the block.timestamp overflows the maximum of uint256.

Proof of Concept

Referenced code: ConcentratedLiquidityPool.sol#L314

Add an unchecked statement around the referenced line of code.

Findings Information

🌟 Selected for report: broccoli

Labels

bug
1 (Low Risk)
sponsor disputed

Awards

37.7771 SUSHI - $472.21

External Links

Handle

broccoli

Vulnerability details

No sanity check of _price in the constructor

Impact

ConcentratedLiquidityPool.sol#L111-L137

The pool can be deployed even it's given an invalid initial price.

Proof of Concept

None

Tools Used

None

Do the sanity check in the constructor.

#0 - sarangparikh22

2021-10-13T20:23:05Z

There is no way to actually sanity check the price at the time of deployment,whatever price is set, the market would make sure it gets balanced. If you have an attack vector from this, please let us know.

#1 - alcueca

2021-11-12T13:41:51Z

Awaiting for warden to provide an example of an invalid initial price

#2 - sarangparikh22

2021-11-16T22:53:08Z

@alcueca as described earlier, there is no way to control the inital price of the pool, however, we are adding checks to make sure that the initial price is in the specific range according to the specs. if (TickMath.MIN_SQRT_RATIO > _price || _price >= TickMath.MAX_SQRT_RATIO) revert BadPrice(); . Not sure, if warden meant this, but would like to give benefit of doubt and would accept this as a valid finding.

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter