Open c4-bot-4 opened 11 months ago
Picodes marked the issue as primary issue
dyedm1 marked the issue as disagree with severity
dyedm1 (sponsor) confirmed
Any upstream protocol integerating with panoptic will not be able to find any such errors.
agreed this might be a little confusing, but Panoptic at least always calls initialize before using a pool. Also, it is extremely difficult to find a pool with such a poolId (no birthday attack possible) and could only be achieved with a large amount of computing power and ample time. Even if someone were to create such a pool, it wouldn't be a relevant one because at least one of the tokens would have to have a randomized address.
Id say low/QA or nothing for this just because it isn't really feasible in the wild/has low/no impact even if achieved.
Picodes changed the severity to QA (Quality Assurance)
In the described scenario there is no loss of funds but a broken functionality which is very unlikely to happen, so Low severity seems appropriate.
Picodes marked the issue as grade-b
The issue #128 has details of generating address collisions in the section "Feasibility of the Attack" https://github.com/code-423n4/2023-11-panoptic-findings/issues/128
The issue @260 has details of generating an token address with collision using rust/go program https://github.com/code-423n4/2023-11-panoptic-findings/issues/260
@dyedm1 has commented that "could only be achieved with a large amount of computing power and ample time", but with details given in both the above issues, the comment wrt feasibility is wrong. Also, in the issue#128 @dyedm1 seems to agree with the feasibility, but disagrees about the feasibility in this issue#594
For extra safety and logical correctness, it makes sense to never have a situration when the pool-id = 0
Regarding the impact, as i have mentioned in the submission, the users will be minting/burning positions in wrong set of pool, which may result in loss against their investment strategy. So user funds are affected.
The key point is not that a pool with 8 trailing zero bytes is impossible to compute, but that it is very expensive and there are no significant incentives/impacts. To date, no one has ever mined an address with 8 leading or trailing zero bytes (even though there is a gas/vanity incentive to do so). To create the situation described, you would have to spend a lot of time and money on compute to create an impact that might, at worst, confuse users about the poolId to use, which they should be verifying by getting the address from the returned poolId to make sure it's the pool they want to deposit in. Issue 128 is actually harder (compute-wise) than this one to create, so much so that it's also present in official Uniswap router contracts considered "safe", but there are large-scale incentives since all the approved funds could be drained.
Lines of code
https://github.com/code-423n4/2023-11-panoptic/blob/aa86461c9d6e60ef75ed5a1fe36a748b952c8666/contracts/SemiFungiblePositionManager.sol#L368 https://github.com/code-423n4/2023-11-panoptic/blob/aa86461c9d6e60ef75ed5a1fe36a748b952c8666/contracts/SemiFungiblePositionManager.sol#L1468-L1470
Vulnerability details
In initializeAMMPool() there may be a combination for univ3pool which will give a poolId value of 0. Although the s_AddrToPoolIdData[univ3pool] will store a non-zero value to make a distinction between zero and uninitialized univ3pool, there is still a confusion while fetching the poolId via getPoolId(address univ3pool). This function getPoolId() will return 0 to both an initialized(with zero) poolId and for an uninitialized univ3pool.
Impact
Assume two pools in UniswapV3
Both are having same token0 and token1 but different fees. Now there is a possibility that for the first univ3pool USDC_WETH_5 during initializeAMMPool() in panoptic SemiFungiblePositionManager, the poolId value returned by getPoolId() is zero. The second univ3pool USDC_WETH_30 is still not initialized in SemiFungiblePositionManager. For both the initialized and uninitialized pools, the tokenId value will be same i.e., the 64 bits of Univ3 Pool Address, as well as token0 and token1. So if a user wishes to create a new position for the uninitialized pool (USDC_WETH_30) using mintTokenizedPosition, the position gets actually created against the initialized pool(USDC_WETH_5), since all the relevant parameters are same. This is because both getPoolId(USDC_WETH_5) and getPoolId(USDC_WETH_30) returns same value of 0, which can cause confusion in tokenId structure. The same holds while using burnTokenizedPosition()
Any upstream protocol integerating with panoptic will not be able to find any such errors.
Proof of Concept
The below test code shows how both initialized(to zero) and uninitialized pool gets value of 0 from getPoolId() Assume two pools in UniswapV3
The first pool USDC_WETH_5 is initialized and USDC_WETH_30 is uninitialzed in SemiFungiblePositionManager. Also assume that this pool USDC_WETH_5 returns a poolId of 0. For this the PanopticMath.sol needs to be Mocked as below
Add the below sample foundry test into SemiFungiblePositionManager.t.sol and run
The run logs output is as follows which shows that both have getPoolId() = 0
Recommended Mitigation Steps
Donot allow the value of poolId equal to 0; Add a check as below in function initializeAMMPool()
Assessed type
Other