Submitted by b0g0, also found by 0x175, 14si2o_Flint, kfx, Fitro, Giorgio, grearlake, 0xblackskull (1, 2), crypticdefense, Silvermist, 0xspryon, MohammedRizwan, y0ng0p3, 0xAlix2, MSaptarshi, boredpukar, and maxim371
V3Oracle::getValue() is used to calculate the value of a position. The value is the product of the oracle price * the amounts held in the position. Price manipulation is prevented by checking for differences between Chainlink oracle and Uniswap TWAP.
However, the amounts (amount0 and amount1) of the tokens in the position are calculated based on the current pool price (pool.spot0()), which means they can be manipulated. Since the value of the total position is calculated from amount0 and amount1 it can be manipulated as well.
Invoking V3Oracle::getValue() first calls the getPositionBreakdown() to calculate the amount0 and amount1 of the tokens in the position based on spot price:
https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Oracle.sol#L102
Under the hood this calls _initializeState() which gets the current price in the pool: 
https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Oracle.sol#L395
Based on this value the amount0 and amount1 (returned from getPositionBreakdown() are deduced:
https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Oracle.sol#L426
After that, the prices are fetched from Uniswap & Chainlink and compared.
Finally, the value of the positions tokens and fees are calculated in the following formula:
Basically the position value is a product of 2 parameters price0X96/price1X96 and amount0/amount1:
Since amount0 and amount1 can be increased/decreased if a malicious user decides to distort the pool price in the current block (through a flash loan for example), this will directly impact the calculated value, even though the price itself cannot be manipulated since it is protected against manipulation.
The check in the end _checkPoolPrice() only verifies that the price from the oracles is in the the acceptable ranges. However, this does not safeguard the value calculation, which as explained above also includes the amounts parameters.
It should be noted that _checkPoolPrice uses the uniswap TWAP price for comparison, which is the price over an extended period of time making it very hard to manipulate in a single block. And exactly this property of the TWAP price can allow an attacker to manipulate the spot price significantly, without affecting the TWAP much; which means the price difference wont change much and _checkPoolPrice will pass.
A short example:
Consider calculating amount0 & amount1 based on the oracle price and not on the spot price taken from slot0(). This way the above exploit will be mitigated.
Uniswap
kalinbas (Revert) commented:
ronnyx2017 (judge) decreased severity to Medium and commented:
kalinbas (Revert) confirmed and commented:
Revert mitigated:
Status: Mitigation Confirmed. Full details in reports from thank_you, b0g0 and ktg.
