Skip to content

Latest commit

 

History

History
85 lines (59 loc) · 3.88 KB

019.md

File metadata and controls

85 lines (59 loc) · 3.88 KB

Flaky Plum Pig

Medium

Incorrect memory space is allocated in an assembly block

Summary

In the Rollup contract, in the function commitBatch there is an assembly block which is allocating a memory pointer in an incorrect way.

Vulnerability Detail

See the first block where batchDataInput.chunks is type []bytes:

uint256 _chunksLength = batchDataInput.chunks.length;
...
uint256 dataPtr;
assembly {
        dataPtr := mload(0x40)
        mstore(0x40, add(dataPtr, mul(_chunksLength, 32))) 
}

See the second block, where BATCH_HEADER_FIXED_LENGTH = 249 is uint256 constant and batchDataInput.skippedL1MessageBitmap is type bytes.

uint256 _headerLength = BatchHeaderCodecV0.BATCH_HEADER_FIXED_LENGTH +
        batchDataInput.skippedL1MessageBitmap.length;
assembly {
        _batchPtr := mload(0x40)
        mstore(0x40, add(_batchPtr, mul(_headerLength, 32))) 
}

The problem is, that the mul should no be used in the second case, instead a simple addition should be performed add(_batchPtr, _headerLength)).

Why? Because we use array of []bytes in the first case, it means that every single item in the array is a word aka 32 bytes. It means we have to multiply length with 32 to allocate enough memory.

However in the second case _headerLength is the value representing the total length of the batch header which is: constant (number of bytes) + bitmap length (number of bytes) hence the _headerLength is already measured in bytes. As a result we are allocating 32 times more memory.

Impact

What does is mean.. In a most basic case where skippedL1MessageBitmap.length == 0 we will assign a value 249*32=7968 to the free memory pointer (0x40), instead of 249.

In a case where skippedL1MessageBitmap.length > 0, for every single byte in skippedL1MessageBitmap we will allocate 32 times more. Example where skippedL1MessageBitmap.length == 500 we will assign a value (249+500)*32=23968 to the free memory pointer (0x40), instead of 749.

How big problem it is?

The function will most likely do the job, however the gas can become problematic and unimplemented functions may become problematic. With skippedL1MessageBitmap, which is unbounded because it is a dynamic type bytes, the function can become overly expensive. The function is already quite complex and computational heavy. There is also an extreme scenario - based on the docs and documents provided, the team is aware of potential ZK circuit overflow. To avoid overflow to happen, some transactions have to be skipped. That's where skippedL1MessageBitmap come in to play. What if:

  • big amount of transaction that can cause ZK circuit overflow are submitted
  • they have to be skipped, otherwise overflow happen and system will panic
  • the bitmap size increases dramatically and big amount of memory is allocated
  • the price of batch submitting become overly expensive or even cause out-of-gas error

Where does it leave the system? TXs have to be skipped, but batch committing is not possible.. It feels like DoS unless some manual action is performed. However this attack feels rather expensive without real profit to attacker profit.


Final note:

Several functions are not yet implemented. Some of them are even called from the vulnerable function (_getValidSequencerSet, _getBLSMsgHash). More code followed after wrong memory allocation, higher risk of unexpected behavior.

Code Snippet

Assembly block in the Rollup contract:

https://github.com/sherlock-audit/2024-08-morphl2/blob/main/morph/contracts/contracts/l1/rollup/Rollup.sol#L279C2-L282C14

Tool used

Manual Review

Wake

Recommendation

Remove multiplication from the assembly block.

uint256 _headerLength = BatchHeaderCodecV0.BATCH_HEADER_FIXED_LENGTH +
        batchDataInput.skippedL1MessageBitmap.length;
assembly {
        _batchPtr := mload(0x40)
        mstore(0x40, add(_batchPtr, _headerLength)) 
}