Summary
A write-after-write vulnerability can occur when multiple write operations target the same memory location or storage variable without proper synchronization. Additionally, incorrect calculation of offsets and lengths in assembly code may result in improper parsing of input data, causing the function to return incorrect or malformed data.
Vulnerability Details
A write-after-write vulnerability potentially occurs in this function
function parseMultiTypeInitData(bytes calldata initData)
internal
pure
returns (
uint256[] calldata types,
bytes[] calldata initDatas
)
{
assembly ("memory-safe") {
let offset := initData.offset
let baseOffset := offset
let dataPointer := add(baseOffset, calldataload(offset))
types.offset := add(dataPointer, 32)
types.length := calldataload(dataPointer)
offset := add(offset, 32)
dataPointer := add(baseOffset, calldataload(offset))
initDatas.offset := add(dataPointer, 32)
initDatas.length := calldataload(dataPointer)
}
}
Test for parseMultiTypeInitData
pragma solidity 0.8.26;
import "forge-std/Test.sol";
import "./LocalCallDataParserLib.sol";
contract LocalCallDataParserLibTest is Test {
using LocalCallDataParserLib for bytes;
function testParseMultiTypeInitData() public {
uint256[] memory types = new uint256[](2);
types[0] = 1;
types[1] = 2;
bytes[] memory initDatas = new bytes[](2);
initDatas[0] = abi.encode("data1");
initDatas[1] = abi.encode("data2");
bytes memory encodedData = abi.encode(types, initDatas);
(uint256[] memory parsedTypes, bytes[] memory parsedInitDatas) = encodedData.parseMultiTypeInitData();
assertEq(parsedTypes.length, 2, "Parsed types length mismatch");
assertEq(parsedTypes[0], 1, "Parsed type 0 mismatch");
assertEq(parsedTypes[1], 2, "Parsed type 1 mismatch");
assertEq(parsedInitDatas.length, 2, "Parsed initDatas length mismatch");
assertEq(keccak256(parsedInitDatas[0]), keccak256(initDatas[0]), "Parsed initData 0 mismatch");
assertEq(keccak256(parsedInitDatas[1]), keccak256(initDatas[1]), "Parsed initData 1 mismatch");
}
}
Impact
Although the function employs the "memory-safe"
modifier, errors in pointer arithmetic or memory access could still lead to unexpected behavior or data corruption.
Tools Used
Manual Review
Recommendations
function parseMultiTypeInitData(bytes calldata initData)
internal
pure
returns (
uint256[] calldata types,
bytes[] calldata initDatas
)
{
uint256 typesOffset;
uint256 typesLength;
uint256 initDatasOffset;
uint256 initDatasLength;
assembly ("memory-safe") {
let baseOffset := initData.offset
typesOffset := add(baseOffset, calldataload(baseOffset))
typesLength := calldataload(typesOffset)
let initDatasOffsetPos := add(baseOffset, 0x20)
initDatasOffset := add(baseOffset, calldataload(initDatasOffsetPos))
initDatasLength := calldataload(initDatasOffset)
}
types = initData[typesOffset + 0x20 : typesOffset + 0x20 + typesLength * 0x20];
initDatas = initData[initDatasOffset + 0x20 : initDatasOffset + 0x20 + initDatasLength * 0x20];
}
Temporary variables (typesOffset
, typesLength
, initDatasOffset
, and initDatasLength
) are utilized to store the offsets and lengths of the respective data fields read from calldata, preventing premature overwriting of memory locations.
The process of reading and writing from calldata is segmented. Initially, the offsets and lengths of types
and initDatas
are read and stored in temporary variables. These values are then assigned outside the assembly block to ensure accurate handling.
Assembly constructs are correctly employed to manage calldata and memory operations safely. Offsets are calculated accurately, and lengths are assigned outside the assembly block to prevent potential issues.