Platform: Code4rena
Start Date: 04/03/2024
Pot Size: $88,500 USDC
Total HM: 31
Participants: 105
Period: 11 days
Judge: ronnyx2017
Total Solo HM: 7
Id: 342
League: ETH
Rank: 67/105
Findings: 2
Award: $49.39
π Selected for report: 0
π Solo Findings: 0
π Selected for report: b0g0
Also found by: 0x175, 0xAlix2, 0xblackskull, 0xspryon, 14si2o_Flint, Fitro, Giorgio, MSaptarshi, MohammedRizwan, Silvermist, boredpukar, crypticdefense, grearlake, kfx, maxim371, y0ng0p3
6.6125 USDC - $6.61
https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/automators/Automator.sol#L148 https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/transformers/AutoCompound.sol#L129 https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Oracle.sol#L363 https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Oracle.sol#L422
Tokens value can be manipulated to cause loss of funds for the protocol and other users via flash loans, MEV searchers etc. This allows a malicious user to manipulate the valuation of the LP. An example of this kind of manipulation would be to use large buys/sells to alter the composition of the LP to make it worth less or more. A big swap using a flash loan can push the liquidity to one side only.
Revert lend contracts like Automator.sol
, AutoCompound.sol
and V3Oracle.sol
has used slot0 data of uniswap v3 pool.
File: src/automators/Automator.sol 148 (sqrtPriceX96, currentTick,,,,,) = pool.slot0();
File: src/transformers/AutoCompound.sol 128 IUniswapV3Pool pool = _getPool(state.token0, state.token1, state.fee); 129 (state.sqrtPriceX96, state.tick,,,,,) = pool.slot0();
File: src/transformers/AutoCompound.sol 363 (sqrtPriceX96,,,,,,) = pool.slot0(); 421 state.pool = _getPool(token0, token1, fee); 422 (state.sqrtPriceX96, state.tick,,,,,) = state.pool.slot0();
The slot0 function returns various parameters of the pool, including the sqrtPriceX96
value and slot0
is the most recent data point and is therefore extremely easy to manipulate. More info on slot0 can be checked here
All of above use the function sqrtPriceX96
pulled from Uniswap.slot0
. An attacker can simply manipulate the sqrtPriceX96
and if the Uniswap.swap function is called with the sqrtPriceX96
, the token will be bought at a higher price and the attacker would run the transaction to sell; thereby earning gains but causing a loss of funds to whoever called those functions.
Manual review
Use TWAP instead of slot0
Uniswap
#0 - c4-pre-sort
2024-03-22T08:01:03Z
0xEVom marked the issue as duplicate of #191
#1 - c4-pre-sort
2024-03-22T08:01:06Z
0xEVom marked the issue as insufficient quality report
#2 - c4-judge
2024-03-31T14:28:15Z
jhsagd76 marked the issue as duplicate of #175
#3 - c4-judge
2024-03-31T14:44:46Z
jhsagd76 marked the issue as partial-50
#4 - c4-judge
2024-04-01T15:43:40Z
jhsagd76 changed the severity to 2 (Med Risk)
π Selected for report: Bauchibred
Also found by: 0x11singh99, 0x175, 0xAlix2, 0xDemon, 0xGreyWolf, 0xPhantom, 0xspryon, 14si2o_Flint, Arabadzhiev, Aymen0909, Bigsam, BowTiedOriole, CRYP70, DanielArmstrong, FastChecker, JecikPo, KupiaSec, MohammedRizwan, Norah, Timenov, Topmark, VAD37, adeolu, btk, crypticdefense, cryptphi, givn, grearlake, jnforja, kennedy1030, kfx, ktg, lanrebayode77, n1punp, santiellena, stonejiajia, t4sk, thank_you, tpiliposian, wangxx2026, y0ng0p3, zaevlad
42.7786 USDC - $42.78
uniswapV3SwapCallback()
never verifies that the callback msg.sender is actually a deployed pool. This allows for a provable address collision that can be used to drain all allowances to the router.
Before going in details, This issue is referenced from this issue which was also found in Kyberswap audit at sherlock.
The pool address check in the the callback function isn't strict enough and can suffer issues with collision. Due to the truncated nature of the create2
opcode the collision resistance is already impaired to 2^160
as that is total number of possible hashes after truncation. Obviously if you are searching for a single hash, this is (basically) impossible. The issue here is that one does not need to search for a single address as the router never verifies that the pool actually exists. This is the crux of the problem,
For more details, refer this article on The probability of a hash collision
. Also refer this issue.
Swapper.sol
is an abstract contract which acts as base contract for FlashloanLiquidator.sol
, V3Utils.sol
, LeverageTransformer.sol
, Automator.sol
, AutoRange.sol
and so on.
uniswapV3SwapCallback()
is an external function and anyone can call it.
uniswapV3SwapCallback() calls _getPool() to check the call is really from pool.
function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external override { require(amount0Delta > 0 || amount1Delta > 0); // swaps entirely within 0-liquidity regions are not supported // check if really called from pool (address tokenIn, address tokenOut, uint24 fee) = abi.decode(data, (address, address, uint24)); @> if (address(_getPool(tokenIn, tokenOut, fee)) != msg.sender) { revert Unauthorized(); } // transfer needed amount of tokenIn uint256 amountToPay = amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta); SafeERC20.safeTransfer(IERC20(tokenIn), msg.sender, amountToPay); }
_getPool()
is given as,
function _getPool(address tokenA, address tokenB, uint24 fee) internal view returns (IUniswapV3Pool) { @> return IUniswapV3Pool(PoolAddress.computeAddress(address(factory), PoolAddress.getPoolKey(tokenA, tokenB, fee))); }
The _getPool() used in uniswapV3SwapCallback()
function is used to verify that msg.sender is the address of the pool and only a require check is performed to verify if msg.sender has been computed via computeAddress().
According to the UniswapV3 Doc,
when using uniswapV3SwapCallback, the caller of this method must be verified to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
However, the function never checks with the factory that the pool exists or any of the inputs are valid in any way. tokenIn can be constant and we can achieve the variation in the hash by changing tokenOut. The attacker could use tokenIn = WETH and vary tokenOut. This would allow them to steal all allowances of WETH. Since allowances are forever until revoked, this could put hundreds of millions of dollars at risk.
This comment during kyberswap issue discussion further proves the attack is possible.
Vitalik Buterin in November 2021 claims that: βeven without address space extension, collisions today take 2^80 time to compute, and that length of time is already within range of nation-state-level actors willing to spend huge amounts of resources. For reference, the Bitcoin network performs 2^80 SHA256 hashes once every two hours.β https://ethresear.ch/t/what-would-break-if-we-lose-address-collision-resistance/11356
Mysten Labs have recently (October 2023) estimated the cost of a collision attack. They claim that βit's not a surprise that today, a 2^80 attack would cost a few million dollarsβ. https://mystenlabs.com/blog/ambush-attacks-on-160bit-objectids-addresses
Manual review
Verify with the factory that msg.sender is a valid pool.
Access Control
#0 - c4-pre-sort
2024-03-21T14:03:31Z
0xEVom marked the issue as high quality report
#1 - c4-pre-sort
2024-03-21T14:03:33Z
0xEVom marked the issue as primary issue
#2 - c4-sponsor
2024-03-26T21:48:58Z
kalinbas (sponsor) confirmed
#3 - jhsagd76
2024-04-01T11:48:01Z
Technically there is no misunderstanding, but for me, this does not qualify as a medium.
#4 - jhsagd76
2024-04-01T11:49:13Z
Will discuss further with other judges. But now, L-A
#5 - c4-judge
2024-04-01T11:49:23Z
jhsagd76 changed the severity to QA (Quality Assurance)
#6 - c4-judge
2024-04-01T11:49:43Z
jhsagd76 marked the issue as grade-a
#7 - Arabadzhiew
2024-04-02T17:46:29Z
Hey @jhsagd76, thank you for your work.
Historically, such issues have been judged as medium severity ones. Such is the case with this issue from the last Panoptic contest here at Code4rena. Because of that, I believe that this issue also deserves to be judged as a medium severity one.
On a side note, since the feasibility of this issue being exploited will likely be the biggest determining factor when deciding on its severity, I am also attaching this issue of the same type from a recent Sherlock contest, under which a pretty lengthy discussion was held in regards to that. The TLDL of the whole discussion is that this issue will cost a few million dollars to be exploited as of today, and this price will be dropping with time, proportionally to the advancement in computing capabilities.
#8 - jhsagd76
2024-04-04T03:37:20Z
An internal discussion has already taken place in the judge channel, with two other judges participating, and we unanimously believe this does not meet the criteria for M. If you want to open a discussion about these diff precedent, please issue your proposal(a demo https://github.com/code-423n4/org/issues/72) for public discussion and to set standard precedents for future competitions.