CVE-2022-48216 (Other, published 2023-01-04T16:15Z)
Description: value: Uniswap Universal Router before 1.1.0 mishandles reentrancy. This would have allowed theft of funds.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-667 \u2014 Improper Locking
The product does not properly acquire or release a lock on a resource, leading to unexpected resource state changes and behaviors.
Repository: https://github.com/Uniswap/universal-router
Files changed:
contracts/UniversalRouter.sol
contracts/base/ReentrancyLock.sol
contracts/test/ReenteringProtocol.sol
test/integration-tests/UniversalRouter.test.ts
test/integration-tests/gas-tests/__snapshots__/CheckOwnership.gas.test.ts.snap
test/integration-tests/gas-tests/__snapshots__/CryptoPunk.gas.test.ts.snap
test/integration-tests/gas-tests/__snapshots__/Foundation.gas.test.ts.snap
test/integration-tests/gas-tests/__snapshots__/LooksRare.gas.test.ts.snap
test/integration-tests/gas-tests/__snapshots__/NFT20.gas.test.ts.snap
test/integration-tests/gas-tests/__snapshots__/NFTX.gas.test.ts.snap
test/integration-tests/gas-tests/__snapshots__/Payments.gas.test.ts.snap
test/integration-tests/gas-tests/__snapshots__/Seaport.gas.test.ts.snap
test/integration-tests/gas-tests/__snapshots__/Sudoswap.gas.test.ts.snap
test/integration-tests/gas-tests/__snapshots__/Uniswap.gas.test.ts.snap
test/integration-tests/gas-tests/__snapshots__/UniversalRouter.gas.test.ts.snap
test/integration-tests/gas-tests/__snapshots__/UniversalVSSwapRouter.gas.test.ts.snap
test/integration-tests/gas-tests/__snapshots__/X2Y2.gas.test.ts.snap
test/integration-tests/shared/deployUniversalRouter.ts
Commit message: [to consider] dirty reentrancy lock (#189)

* Gas costs for clean reentrancy lock

* Gas costs for direct reentrancy lock

* Reentrancy test

* bytecode increase

Vulnerable code:
contract UniversalRouter is RouterImmutables, IUniversalRouter, Dispatcher, RewardsCollector {
    function execute(bytes calldata commands, bytes[] calldata inputs) public payable {
  "gasUsed": 203708,
  "gasUsed": 339196,
  "gasUsed": 200173,
  "gasUsed": 30615,
  "gasUsed": 105779,
  "gasUsed": 179658,
  "gasUsed": 246469,
  "gasUsed": 266494,
  "gasUsed": 391360,
  "gasUsed": 490835,
  "gasUsed": 37194,
  "gasUsed": 66436,
  "gasUsed": 36168,
  "gasUsed": 31759,
  "gasUsed": 44647,
  "gasUsed": 52003,
  "gasUsed": 53491,
// Just Snapshot v1, https://goo.gl/fbAQLP

exports[`Router Gas Tests gas: bytecode size 1`] = `17011`;

exports[`Router Gas Tests trading for NFTs gas: ERC20 --> ETH --> Seaport NFT 1`] = `
Object {
  "calldataByteLength": 2500,
  "gasUsed": 301927,
}
`;

exports[`Router Gas Tests trading for NFTs gas: ETH --> Seaport NFT 1`] = `
Object {
  "calldataByteLength": 2052,
  "gasUsed": 200173,
}
`;
  "gasUsed": 200173,
  "gasUsed": 332120,
  "gasUsed": 190323,
  "gasUsed": 197181,
  "gasUsed": 189735,
  "gasUsed": 306351,
  "gasUsed": 314308,
  "gasUsed": 316090,
  "gasUsed": 310075,
  "gasUsed": 180414,
  "gasUsed": 180189,
  "gasUsed": 197847,
  "gasUsed": 188177,
  "gasUsed": 214422,
  "gasUsed": 128473,
  "gasUsed": 107645,
  "gasUsed": 246066,
  "gasUsed": 245808,
  "gasUsed": 178544,
  "gasUsed": 178544,
  "gasUsed": 107030,
  "gasUsed": 250553,
  "gasUsed": 180512,
  "gasUsed": 124271,
  "gasUsed": 129272,
  "gasUsed": 144977,
  "gasUsed": 107952,
  "gasUsed": 138445,
  "gasUsed": 116041,
  "gasUsed": 277424,
  "gasUsed": 192408,
  "gasUsed": 117315,
  "gasUsed": 421870,
  "gasUsed": 314568,
  "gasUsed": 132715,
  "gasUsed": 134061,
  "gasUsed": 245799,
  "gasUsed": 140449,
exports[`UniversalRouter Gas Tests gas: bytecode size 1`] = `17017`;
  "gasUsed": 301863,
  "gasUsed": 200173,
exports[`Uniswap UX Tests gas: Comparisons Casual Swapper - 3 swaps Permit2 Max Approval Swap 1`] = `1172093`;
exports[`Uniswap UX Tests gas: Comparisons Casual Swapper - 3 swaps Permit2 Sign Per Swap 1`] = `1209450`;
exports[`Uniswap UX Tests gas: Comparisons Frequent Swapper - 10 swaps Permit2 Max Approval Swap 1`] = `3262025`;
exports[`Uniswap UX Tests gas: Comparisons Frequent Swapper - 10 swaps Permit2 Sign Per Swap 1`] = `3428830`;
exports[`Uniswap UX Tests gas: Comparisons Frequent Swapper across 3 swap router versions - 15 swaps across 3 versions Permit2 Max Approval Swap 1`] = `4355287`;
exports[`Uniswap UX Tests gas: Comparisons Frequent Swapper across 3 swap router versions - 15 swaps across 3 versions Permit2 Sign Per Swap 1`] = `4576897`;
exports[`Uniswap UX Tests gas: Comparisons One Time Swapper - Complex Swap Permit2 Max Approval Swap 1`] = `538455`;
exports[`Uniswap UX Tests gas: Comparisons One Time Swapper - Complex Swap Permit2 Sign Per Swap 1`] = `538773`;
exports[`Uniswap UX Tests gas: Comparisons One Time Swapper - Simple Swap Permit2 Max Approval Swap 1`] = `312301`;
exports[`Uniswap UX Tests gas: Comparisons One Time Swapper - Simple Swap Permit2 Sign Per Swap 1`] = `312237`;
  "gasUsed": 212190,
  "gasUsed": 213385,
  mockLooksRareToken?: string
    nftxZap: '0x0fc584529a2AEfA997697FAfAcbA5831faC0c22d',

Fixed code:
import {ReentrancyLock} from './base/ReentrancyLock.sol';
contract UniversalRouter is RouterImmutables, IUniversalRouter, Dispatcher, RewardsCollector, ReentrancyLock {
    function execute(bytes calldata commands, bytes[] calldata inputs) public payable isNotLocked {
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;

contract ReentrancyLock {
    error ContractLocked();

    uint256 private isLocked = 1;

    modifier isNotLocked() {
        if (isLocked != 1) revert ContractLocked();
        isLocked = 2;
        _;
        isLocked = 1;
    }
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.15;

contract ReenteringProtocol {
    error NotAllowedReenter();

    function callAndReenter(address universalRouter, bytes calldata data) public payable {
        (bool success,) = universalRouter.call(data);
        if (!success) revert NotAllowedReenter();
    }
}
import { abi as ROUTER_ABI } from '../../artifacts/contracts/UniversalRouter.sol/UniversalRouter.json'
  ETH_ADDRESS,
const routerInterface = new ethers.utils.Interface(ROUTER_ABI)
    it('reverts if a malicious contract tries to reenter', async () => {
      const reentrantProtocol = await (await ethers.getContractFactory('ReenteringProtocol')).deploy()

      router = (
        await deployUniversalRouter(
          permit2,
          mockLooksRareRewardsDistributor.address,
          mockLooksRareToken.address,
          reentrantProtocol.address
        )
      ).connect(alice) as UniversalRouter

      planner.addCommand(CommandType.SWEEP, [ETH_ADDRESS, alice.address, 0])
      let { commands, inputs } = planner

      const sweepCalldata = routerInterface.encodeFunctionData('execute(bytes,bytes[])', [commands, inputs])
      const reentrantCalldata = reentrantProtocol.interface.encodeFunctionData('callAndReenter', [
        router.address,
        sweepCalldata,
      ])

      planner = new RoutePlanner()
      planner.addCommand(CommandType.NFTX, [0, reentrantCalldata])
      ;({ commands, inputs } = planner)

      const notAllowedReenterSelector = '0xb418cb98'
      await expect(router['execute(bytes,bytes[])'](commands, inputs)).to.be.revertedWith(
        `ExecutionFailed(0, "` + notAllowedReenterSelector + `")`
      )
    })

  "gasUsed": 206020,
  "gasUsed": 341508,
  "gasUsed": 202485,
  "gasUsed": 32927,
  "gasUsed": 109868,
  "gasUsed": 183748,
  "gasUsed": 248781,
  "gasUsed": 268806,
  "gasUsed": 393672,
  "gasUsed": 494924,
  "gasUsed": 39506,
  "gasUsed": 68748,
  "gasUsed": 38480,
  "gasUsed": 34071,
  "gasUsed": 46959,
  "gasUsed": 54315,
  "gasUsed": 57580,
  "gasUsed": 202485,
  "gasUsed": 334432,
  "gasUsed": 192635,
  "gasUsed": 199493,
  "gasUsed": 192047,
  "gasUsed": 308663,
  "gasUsed": 316620,
  "gasUsed": 318402,
  "gasUsed": 312387,
  "gasUsed": 182726,
  "gasUsed": 182501,
  "gasUsed": 200159,
  "gasUsed": 190489,
  "gasUsed": 218512,
  "gasUsed": 130785,
  "gasUsed": 109957,
  "gasUsed": 248378,
  "gasUsed": 248126,
  "gasUsed": 180856,
  "gasUsed": 180856,
  "gasUsed": 109342,
  "gasUsed": 252865,
  "gasUsed": 182824,
  "gasUsed": 126583,
  "gasUsed": 131584,
  "gasUsed": 147289,
  "gasUsed": 110264,
  "gasUsed": 142535,
  "gasUsed": 118353,
  "gasUsed": 279736,
  "gasUsed": 194720,
  "gasUsed": 119627,
  "gasUsed": 424182,
  "gasUsed": 316880,
  "gasUsed": 135027,
  "gasUsed": 136373,
  "gasUsed": 248111,
  "gasUsed": 144539,
exports[`UniversalRouter Gas Tests gas: bytecode size 1`] = `17322`;
  "gasUsed": 304175,
  "gasUsed": 202485,
exports[`Uniswap UX Tests gas: Comparisons Casual Swapper - 3 swaps Permit2 Max Approval Swap 1`] = `1179047`;
exports[`Uniswap UX Tests gas: Comparisons Casual Swapper - 3 swaps Permit2 Sign Per Swap 1`] = `1216404`;
exports[`Uniswap UX Tests gas: Comparisons Frequent Swapper - 10 swaps Permit2 Max Approval Swap 1`] = `3285205`;
exports[`Uniswap UX Tests gas: Comparisons Frequent Swapper - 10 swaps Permit2 Sign Per Swap 1`] = `3452010`;
exports[`Uniswap UX Tests gas: Comparisons Frequent Swapper across 3 swap router versions - 15 swaps across 3 versions Permit2 Max Approval Swap 1`] = `4390057`;
exports[`Uniswap UX Tests gas: Comparisons Frequent Swapper across 3 swap router versions - 15 swaps across 3 versions Permit2 Sign Per Swap 1`] = `4611667`;
exports[`Uniswap UX Tests gas: Comparisons One Time Swapper - Complex Swap Permit2 Max Approval Swap 1`] = `540773`;
exports[`Uniswap UX Tests gas: Comparisons One Time Swapper - Complex Swap Permit2 Sign Per Swap 1`] = `541091`;
exports[`Uniswap UX Tests gas: Comparisons One Time Swapper - Simple Swap Permit2 Max Approval Swap 1`] = `314619`;
exports[`Uniswap UX Tests gas: Comparisons One Time Swapper - Simple Swap Permit2 Sign Per Swap 1`] = `314555`;
  "gasUsed": 214502,
  "gasUsed": 215697,
  mockLooksRareToken?: string,
  mockReentrantProtocol?: string
    nftxZap: mockReentrantProtocol ?? '0x0fc584529a2AEfA997697FAfAcbA5831faC0c22d',