Shambolic Banana Barbel
Medium
Calling getSkippedBitmap()
on a batch header using an out of bounds index doesn't perform any validations on the index, and allows us to read into arbitrary memory.
In BatchHeaderCodecV0.sol
, the getSkippedBitmap() function is used to access a bitmap word at a specific index.
function getSkippedBitmap(uint256 batchPtr, uint256 index) internal pure returns (uint256 _bitmap) {
assembly {
batchPtr := add(batchPtr, BATCH_HEADER_FIXED_LENGTH)
_bitmap := mload(add(batchPtr, mul(index, 32)))
}
}
The function does not check if the index is within the bounds of the bitmap, which can lead to out-of-bounds memory access.
getSkippedBitmap()
must be able to be called with an index that is out of bounds.
None
N/A
getSkippedBitmap()
is able to return an invalid bitmap if it is called with an index that is out of bounds. For example, if we reached outside of the memory that has been used so far, we will get the 0
bitmap, which will imply that no messages were skipped.
The following test can be added to BatchHeaderCodecV0.t.sol
to demonstrate the issue:
function testPapa_getSkippedBitmap() public {
bytes memory batchHeader0 = new bytes(249 + 32);
assembly {
mstore(add(batchHeader0, 0x20), shl(248, 1)) // version
mstore(add(batchHeader0, add(0x20, 1)), shl(192, 1)) // batchIndex = 1
mstore(add(batchHeader0, add(0x20, 9)), shl(192, 1)) // l1MessagePopped = 1
mstore(add(batchHeader0, add(0x20, 17)), shl(192, 1)) // totalL1MessagePopped = 1
mstore(add(batchHeader0, add(0x20, 25)), ZERO_VERSIONED_HASH) // l1dataHash
mstore(add(batchHeader0, add(0x20, 57)), ZERO_VERSIONED_HASH) // l2 tx blob versioned hash
mstore(add(batchHeader0, add(0x20, 89)), ZERO_VERSIONED_HASH) // prevStateHash
mstore(add(batchHeader0, add(0x20, 121)), ZERO_VERSIONED_HASH) // postStateHash
mstore(add(batchHeader0, add(0x20, 153)), ZERO_VERSIONED_HASH) // withdrawRootHash
mstore(add(batchHeader0, add(0x20, 185)), ZERO_VERSIONED_HASH) // sequencerSetVerifyHash
mstore(add(batchHeader0, add(0x20, 217)), ZERO_VERSIONED_HASH) // parentBatchHash
mstore(add(batchHeader0, add(0x20, 249)), ZERO_VERSIONED_HASH) // bitmap0
}
// we can now stick extra values later in memory
bytes memory x = abi.encodePacked(uint(1056));
// access the pointer to the batch header
uint batchPtr;
assembly { batchPtr := add(batchHeader0, 0x20) }
// calling getSkippedBitmap with index = 0 gets the correct bitmap
assertEq(BatchHeaderCodecV0.getSkippedBitmap(batchPtr, 0), uint(ZERO_VERSIONED_HASH));
// calling with an index greater than 1 reaches out of bounds
// 1056 = 0x420
assertEq(
bytes32(BatchHeaderCodecV0.getSkippedBitmap(batchPtr, 3)),
0x0000000000042000000000000000000000000000000000000000000000000000
);
}
Since we've already validated the length of the skipped bitmap against getL1MessagePopped()
, we can call this function with the batchPtr
to access the bitmap length, and then check the index against it.