Revert Lend - zaevlad's results

A lending protocol specifically designed for liquidity providers on Uniswap v3.

General Information

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

Revert

Findings Distribution

Researcher Performance

Rank: 75/105

Findings: 1

Award: $42.78

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

42.7786 USDC - $42.78

Labels

bug
downgraded by judge
grade-a
insufficient quality report
QA (Quality Assurance)
duplicate-188
Q-19

External Links

Lines of code

https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/utils/Swapper.sol#L172 https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/utils/Swapper.sol#L161 https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/PoolAddress.sol#L33

Vulnerability details

Impact

A malicious user can steal tokens from the Swapper by providing data with a fake token

Proof of Concept

Swapper contract accept callbacks from uniswap pools where amount for swap is payed:

    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);
    }

It tries to verify that a call was made from the real pool by calling _getPool and calculate the real address:

function _getPool(address tokenA, address tokenB, uint24 fee) internal view returns (IUniswapV3Pool) { return IUniswapV3Pool(PoolAddress.computeAddress(address(factory), PoolAddress.getPoolKey(tokenA, tokenB, fee))); }

The problem is it never verifies that the pool exist, but only tries to get the address:

struct PoolKey { address token0; address token1; uint24 fee; } function getPoolKey( address tokenA, address tokenB, uint24 fee ) internal pure returns (PoolKey memory) { if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); return PoolKey({token0: tokenA, token1: tokenB, fee: fee}); } function computeAddress(address factory, PoolKey memory key) internal pure returns (address pool) { require(key.token0 < key.token1); pool = address( uint160( uint256( keccak256( abi.encodePacked( hex'ff', factory, keccak256(abi.encode(key.token0, key.token1, key.fee)), POOL_INIT_CODE_HASH ) ) ) ) ); }

So a malicious user can calculate and delopy the contract that will match the calculated address with the information about a token he wants to steal and a fake token.

It will require some serious computaions for the hacker to break the right hash and build the valid address, but it is still possible with modern tools.

Tools Used

Manual review

Verify with the factory that msg.sender is a valid pool

Assessed type

Invalid Validation

#0 - c4-pre-sort

2024-03-22T08:52:52Z

0xEVom marked the issue as duplicate of #188

#1 - c4-pre-sort

2024-03-22T08:52:58Z

0xEVom marked the issue as insufficient quality report

#2 - 0xEVom

2024-03-22T08:53:17Z

Insufficient proof

#3 - c4-judge

2024-04-01T11:49:21Z

jhsagd76 changed the severity to QA (Quality Assurance)

#4 - c4-judge

2024-04-01T11:50:08Z

jhsagd76 marked the issue as grade-a

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