Lucene search

K
code423n4Code4renaCODE423N4:2023-10-CANTO-FINDINGS-ISSUES-259
HistoryOct 06, 2023 - 12:00 a.m.

Lack of tick range validation allows initialization of invalid ticks.

2023-10-0600:00:00
Code4rena
github.com
3
initialization
tick range
validation
vulnerability
proof of concept
structure
contract
exploit
data corruption

6.8 Medium

AI Score

Confidence

High

Lines of code

Vulnerability details

Impact

Function initTickTracking initializes the tick tracking data structure, but does not validate that tick is within the min/max tick range for the pool. This could allow initializing invalid tick values.

Proof of Concept

Here is the line in initTickTracking that could initialize invalid tick values.

function initTickTracking(bytes32 poolIdx, int24 tick) internal {
  StorageLayout.TickTracking memory tickTrackingData = StorageLayout
            .TickTracking(uint32(block.timestamp), 0);  
  tickTracking_[poolIdx][tick].push(tickTrackingData);
}

The tick parameter is used to index into the tickTracking_ mapping without validating it is within the min/max tick range for the pool.

The initTickTracking function is used to initialize the tick tracking data structure for a specific tick in a pool. This data structure tracks when ticks become activated/deactivated over time.

It is defined as:

function initTickTracking(bytes32 poolIdx, int24 tick) internal {

  //...

}

The key parameters are:

  • poolIdx: The unique ID of the pool
  • tick: The specific tick that we want to start tracking

Within the function, it does the following:

  1. Creates a TickTracking struct to represent the initial data for this tick:

    StorageLayout.TickTracking memory tickTrackingData = StorageLayout
    .TickTracking(uint32(block.timestamp), 0);

  2. Pushes this tick tracking data into the mapping that stores the info per tick:

    tickTracking_[poolIdx][tick].push(tickTrackingData);

The issue here is that tick is used to index directly into the tickTracking_ mapping without validating it is within the min/max range for the pool.

The min/max ticks are set for each pool based on the tick spacing (number of ticks in the entire range). This range is stored in the PoolRegistry.

By not validating tick is within range, it is possible to initialize tick tracking for arbitrary tick values, even those outside the min/max bounds for the pool.

This could then corrupt the tick tracking data structure by inserting invalid entries. Later accounting logic relies on this structure being valid.

> Here is a simple proof of concept that demonstrates initializing the tick tracking for an invalid, out-of-range tick value in the initTickTracking function:

contract Exploit {

  address constant pool = 0x...; // some pool address

  function exploitInitTickTracking() external {

    IUniswapV3Pool poolContract = IUniswapV3Pool(pool);

    bytes32 poolKey = poolContract.poolId();
    
    // Let's say this pool has a min tick of -887272 and max tick of -887270

    // Calling initTickTracking with a tick below the min 
    LiquidityMining(pool).initTickTracking(poolKey, -887300);
    
    // This initializes tick tracking for an invalid tick below the min

    // Now the tickTracking mapping has an invalid entry for tick -887300

  }

}

Key points:

  • I first get the poolId for the target pool contract
  • I assume a hypothetical min/max tick range that is valid for the pool
  • Then call the initTickTracking function with a tick value -887300 that is lower than the min tick
  • This will successfully initialize tick tracking data for that invalid tick
  • Resulting in a corrupted tickTracking_ mapping containing invalid data

This simple exploit shows how failing to validate the tick input can lead to improper state in the contract’s data structures.

Tools Used

Vs

Recommended Mitigation Steps

The min/max tick values for a pool can be obtained from the PoolRegistry contract using PoolRegistry.getPoolData(poolIdx). So to validate, you could add something like:

function initTickTracking(bytes32 poolIdx, int24 tick) internal {

  (,,int24 tickSpacing,, int24 minTick, int24 maxTick,) = PoolRegistry.getPoolData(poolIdx);

  require(tick >= minTick && tick < maxTick, "Invalid tick");
  
  //...rest of function
}

This would prevent initializing the tick tracking data structure for invalid tick values outside the min/max range.

Assessed type

Invalid Validation


The text was updated successfully, but these errors were encountered:

All reactions

6.8 Medium

AI Score

Confidence

High