Closed howlbot-integration[bot] closed 3 months ago
Uniswap V3 data uses abi.encodePacked
which does not align with the 32 bytes memory packing of solidity. That is why the offset needs to be that specific value. The PoC does not show if the proposed solution gives the right decoding
Looks like a valid issue. However, I'm asking for a PoC (coded) to show that accessing out of bound memory will cause issues in this case. QA for now unless there is a demonstrated impact.
koolexcrypto marked the issue as primary issue
koolexcrypto changed the severity to QA (Quality Assurance)
koolexcrypto marked the issue as grade-c
@koolexcrypto thank you for asking the PoC
Here is the coded PoC:
Add this test file(PrelaunchPointsPoCTest.t.sol
) to ./test
folder.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/PrelaunchPoints.sol";
import "../src/interfaces/ILpETH.sol";
import "../src/interfaces/IWETH.sol";
import "../src/mock/AttackContract.sol";
import "../src/mock/MockLpETH.sol";
import "../src/mock/MockLpETHVault.sol";
import {ERC20Token} from "../src/mock/MockERC20.sol";
import {LRToken} from "../src/mock/MockLRT.sol";
import "forge-std/console.sol";
contract PrelaunchPointsPoCTest is Test {
PrelaunchPoints public prelaunchPoints;
ILpETH public lpETH;
ILpETHVault public lpETHVault;
IWETH public weth;
ERC20Token public mockToken;
uint256 public constant INITIAL_SUPPLY = 1000 ether;
bytes32 referral = bytes32(uint256(1));
address constant EXCHANGE_PROXY = 0xDef1C0ded9bec7F1a1670819833240f027b25EfF;
address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address[] public allowedTokens;
bytes public invalidData;
function setUp() public {
weth = IWETH(WETH);
lpETH = new MockLpETH();
lpETHVault = new MockLpETHVault();
mockToken = new ERC20Token();
address[] memory allowedTokens_ = new address[](1);
allowedTokens_[0] = WETH;
prelaunchPoints = new PrelaunchPoints(EXCHANGE_PROXY, WETH, allowedTokens_);
vm.deal(address(this), 10 ether); // fund the test contract with 10 ether
// Construct invalid data where encodedPathLength is not 32-byte aligned
invalidData = abi.encodePacked(
bytes4(0x803ba26d), // UniswapV3 selector
uint256(1 ether), // inputTokenAmount
address(this), // recipient
uint256(50), // encodedPathLength (not aligned with 32-byte boundary)
WETH, // inputToken
address(0x1234567890AbcdEF1234567890aBcdef12345678) // arbitrary address to simulate misalignment
);
}
function testDecodeUniswapV3DataOutOfBounds() public {
// Lock some WETH
weth.deposit{value: 1 ether}();
IERC20(address(weth)).approve(address(prelaunchPoints), 1 ether);
prelaunchPoints.lock(address(weth), 1 ether, referral);
// Activate claiming period
prelaunchPoints.setLoopAddresses(address(lpETH), address(lpETHVault));
vm.warp(block.timestamp + 120 days + 1);
prelaunchPoints.convertAllETH();
// Attempt to claim with invalid data
prelaunchPoints.claim(address(weth), 100, PrelaunchPoints.Exchange.UniswapV3, invalidData);
}
}
Then run this command
forge test --match-path ./test/PrelaunchPointsPoCTest.t.sol
Output log:
Ran 1 test for test/PrelaunchPointsPoCTest.t.sol:PrelaunchPointsPoCTest
[FAIL. Reason: EvmError: Revert] testDecodeUniswapV3DataOutOfBounds() (gas: 5053)
Traces:
[3252308] PrelaunchPointsPoCTest::setUp()
├─ [452586] → new MockLpETH@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
│ └─ ← [Return] 2035 bytes of code
├─ [438773] → new MockLpETHVault@0x2e234DAe75C793f67A35089C9d99245E1C58470b
│ └─ ← [Return] 1966 bytes of code
├─ [430367] → new ERC20Token@0xF62849F9A0B5Bf2913b396098F7c7019b51A820a
│ └─ ← [Return] 1924 bytes of code
├─ [1573824] → new PrelaunchPoints@0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9
│ └─ ← [Return] 7521 bytes of code
├─ [0] VM::deal(PrelaunchPointsPoCTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 10000000000000000000 [1e19])
│ └─ ← [Return]
└─ ← [Stop]
[5053] PrelaunchPointsPoCTest::testDecodeUniswapV3DataOutOfBounds()
└─ ← [Revert] EvmError: Revert
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 1.41ms (86.72µs CPU time)
Ran 1 test suite in 12.06s (1.41ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
Failing tests:
Encountered 1 failing test in test/PrelaunchPointsPoCTest.t.sol:PrelaunchPointsPoCTest
[FAIL. Reason: EvmError: Revert] testDecodeUniswapV3DataOutOfBounds() (gas: 5053)
The test testDecodeUniswapV3DataOutOfBounds
confirms the existence of an out-of-bounds memory access issue in the _decodeUniswapV3Data
function due to the misalignment of encodedPathLength
. The PoC shows that when the encodedPathLength
is not aligned with the 32-byte boundary, the function attempts to read incorrect memory, causing a revert.
Hi @mePopye
Thank you for the PoC.
If the demonstrated impact is only revert, the issue is deemed to be a QA. Since it is caused by a user input and the impact is on the user itself
@koolexcrypto it's a valid concern ser. Maybe I am not able to demonstrate the real impact of this issue due to mine lack of experience/knowledge. But I think it should be considered
I understand this. but since this is depending on user input, many issues can be computed based on user input , ultimately those are QA according to severity categorisation
Lines of code
https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L454-L463
Vulnerability details
Description
The
_decodeUniswapV3Data
function within thePrelaunchPoints
contract is responsible for parsing data received from the 0x API when Uniswap V3 is used for token swaps. However, there is a issue in the way the function calculates the offset to access theoutputToken
address from the encoded data.The vulnerability arises from the following lines of code:
This issue occurs due to the assumption that
encodedPathLength
aligns with 32-byte memory boundaries, which is a standard in EVM memory operations. WhenencodedPathLength
results in an offset that is not aligned on a 32-byte boundary, the subsequent memory access to retrieve theoutputToken
will be incorrect.This misalignment can happen when the
encodedPathLength
is not a multiple of 32 bytes. For example, ifencodedPathLength
is 50 bytes, the resulting offset for accessing theoutputToken
would bep + 158
bytes, which is not aligned with the 32-byte boundary.Impact
The out-of-bounds access issue in the
_decodeUniswapV3Data
function can have several consequences:If the
encodedPathLength
is not aligned with the 32-byte boundary, the function may read incorrect data from memory when accessing theoutputToken
. This can lead to erroneous execution logic and unexpected behavior in the contract.If the function reads data from unintended memory locations, it may access sensitive information or execute unintended code paths, compromising the security of the contract and its users.
The function may return incorrect values or cause the contract to revert unexpectedly, leading to inconsistent or unreliable execution.
Proof of Concept
Here is the affected code:
Consider the following scenario:
_data
parameter passed to the_decodeUniswapV3Data
function contains anencodedPathLength
of 50 bytes.outputToken
by adding theencodedPathLength
to the base pointerp
and an offset of 108 bytes:add(p, add(encodedPathLength, 108))
.outputToken
would bep + 158
bytes (108 + 50).p + 158
bytes may lead to reading incorrect data or accessing memory beyond the bounds of the input data.This scenario illustrates how an improperly aligned
encodedPathLength
can result in incorrect memory access and potential out-of-bounds access, leading to erroneous behavior or security vulnerabilities in the contract.Tools Used
Manual Review
Recommended Mitigation Steps
To mitigate this vulnerability, I can recommend to modify the offset calculation to ensure proper memory alignment. The
encodedPathLength
should be rounded up to the nearest multiple of 32 bytes before using it to calculate the memory offset for accessing theoutputToken
.Here's the recommended code change:
The modified code introduces a new variable
alignedPathLength
, which is calculated by rounding upencodedPathLength
to the nearest multiple of 32 using bitwise operations. This ensures that the resulting offset for accessing theoutputToken
is always aligned with the 32-byte boundary.Here's a diff representation of the changes:
Assessed type
Context