xanoutas/solidity-security-findings huggingface dataset:
Addresses can be pre-populated with bad data
Audit: 2024-07-optimism-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Description
Inside `PreimageOracle.sol`, anyone can call the `loadPrecompilePreimagePart()` to load data into the contract.
The problem is that anyone is able to specify the `address _precompile` parameter. This opens up the door for malicious users to pre-populate data and use it to validate wrong data.

## Proof of Concept
`PreimageOracle.readPreimage()` is used to read already validated `preimage_data`, this happens in the `MIPS.sol` implementation and the `mips.go` implementation:

mips.go
```golang
			dat, datLen := m.readPreimage(m.state.PreimageKey, m.state.PreimageOffset)
```

mips.sol
```javascript
                    (bytes32 dat, uint256 datLen) = ORACLE.readPreimage(preimageKey, state.preimageOffset);
```

Looking at `readPreimage`, we can notice this check that happens that ensures the field is set to `true`:
```javascript
    function readPreimage(bytes32 _key, uint256 _offset) external view returns (bytes32 dat_, uint256 datLen_) {
->      require(preimagePartOk[_key][_offset], "pre-image must exist");

        // Calculate the length of the pre-image data
        // Add 8 for the length-prefix part
        datLen_ = 32;
        uint256 length = preimageLengths[_key];
        if (_offset + 32 >= length + 8) {
            datLen_ = length + 8 - _offset;
        }

        // Retrieve the pre-image data
        dat_ = preimageParts[_key][_offset];
    }
```

Now, a malicious user can use the address of the next precompile to be added as a parameter, specify the data he wants to be pass and it well set the flag to `true`, enabling the ability to read the preimage with invalid data.

Put this test in `PreimageOracle.t.sol`:
```javascript
    function test_poc() public {
        bytes memory input = hex"deadbeef1337";
        uint256 offset = 0x00;
        // Newly added precompile
        address precompile = address(bytes20(uint160(0x0b)));
        bytes32 key = precompilePreimageKey(precompile, input);
        oracle.loadPrecompilePreimagePart(offset, precompile, input);

        // try reading, it should succeed
        oracle.readPreimage(key, offset);
    }
```

Running it, this is the output:
```zsh
Ran 1 test for test/cannon/PreimageOracle.t.sol:PreimageOracle_Test
[PASS] test_poc() (gas: 78279)
Traces:
  [78279] PreimageOracle_Test::test_poc()
    ├─ [70193] PreimageOracle::loadPrecompilePreimagePart(0, 0x000000000000000000000000000000000000000b, 0xdeadbeef1337)
    │   ├─ [0] 0x000000000000000000000000000000000000000b::deadbeef(1337) [staticcall]
    │   │   └─ ← [Stop]
    │   └─ ← [Stop]
    ├─ [1420] PreimageOracle::readPreimage(0x06305151be4abfdb70c687fa523f7bcbd835866a36eff720ca0ecbfee10c8320, 0) [staticcall]
    │   └─ ← [Return] 0x0000000000000001010000000000000000000000000000000000000000000000, 9
    └─ ← [Stop]
```

Thi
Dirty bits during loadPrecompiledPreimage breaks data integrity
Audit: 2024-07-optimism-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Description
Inside `PreimageOracle.loadBlobPreimagePart()` function, the following lines of code ensure that partial reads are possible:

```javascript
// Clean the word at `ptr + 0x28` to ensure that data out of bounds of the preimage is zero, if the part
// offset requires a partial read.
mstore(add(ptr, 0x28), 0x00)
```

Data that is `Out Of Bound` should always be cleaned up to facilitate partial reads.

The problem is that the cleaning of dirty bits is not enforced everywhere.

Let's take a look at the `loadPrecompilePreimagePart()` function. `loadPrecompilePreimagePart()` also facilitates the need of partial reads, however, the cleaning of bits is not correctly implemented.

Run the following PoC inside `PreimageOracle.t.sol`, it mocks a 5 byte partial read(the 2's):
```javascript
    function test_poc() public {
        // This is the payload, requires a partial read for the second word. Total length is 37.
        // 1111111111111111111111111111111111111111111111111111111111111111
        // 2222222222
        bytes memory input = hex"11111111111111111111111111111111111111111111111111111111111111112222222222";
        // We want to read the 2222222222
        uint256 offset = input.length + 4;
        address precompile = address(bytes20(uint160(0x04)));
        vm.breakpoint('o');
        bytes32 key = precompilePreimageKey(precompile, input);
        oracle.loadPrecompilePreimagePart(offset, precompile, input);

        bytes32 part = oracle.preimageParts(key, offset);
    }
``` 

The test output:
```zsh
[PASS] test_poc() (gas: 78104)
Traces:
  [78104] PreimageOracle_Test::test_poc()
    ├─ [0] VM::breakpoint("o")
    │   └─ ← [Return]
    ├─ [67735] PreimageOracle::loadPrecompilePreimagePart(41, Identity: [0x0000000000000000000000000000000000000004], 0x11111111111111111111111111111111111111111111111111111111111111112222222222)
    │   ├─ [21] PRECOMPILES::identity(0x11111111111111111111111111111111111111111111111111111111111111112222222222) [staticcall]
    │   │   └─ ← [Return] 0x11111111111111111111111111111111111111111111111111111111111111112222222222
    │   └─ ← [Stop]
    ├─ [601] PreimageOracle::preimageParts(0x06c4a2fd35b5429fcec535932119ba9f452275d11a1a96998ae904aeaafbae2f, 41) [staticcall]
    │   └─ ← [Return] 0x2222222222111111111111222222222200000000000000000000000000000000
    └─ ← [Stop]

Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 745.58µs (96.33µs CPU time)

Ran 1 test suite in 126.33ms (745.58µs CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
```

As we can see, the `part` data stored is filled with dirty bits:
- `0x2222222222111111111111222222222200000000000000000000000000000000`

By running `forge debug test/cannon/PreimageOracle.t.sol --tc PreimageOracle_Test --sig "test_poc()"` w
Unvalidated Memory Access in `readMem` and `writeMem` Functions
Audit: 2024-07-optimism-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

### Impact
The [readMem()](https://github.com/code-423n4/2024-07-optimism/blob/main/packages/contracts-bedrock/src/cannon/MIPS.sol#L538C4-L584C6) and [writeMem()](https://github.com/code-423n4/2024-07-optimism/blob/main/packages/contracts-bedrock/src/cannon/MIPS.sol#L592C3-L632C6) functions do not properly validate the memory address being accessed. This could lead to unauthorized access to memory outside the intended ranges, which could then allow a black hat to read or modify sensitive data, in creative ways. As the contract is using a `32-bit` address space `uint32` for `addresses` without implementing proper bounds checking, possibly then allowing access to the entire 4GB address space.

Specific attack vectors: 
- Overwriting the `preimageKey` and/or `preimageOffset` to gain unauthorized access to preimage data.
- Manipulating the `pc` or `nextPC` values to alter program flows, even possible paths to bypass security checks.
- Adjusting with intent the heap value to cause memory allocation issues.
- Altering the `exitCode` or `exited` flags to prematurely terminate or continue execution inappropriately.

### Proof of Concept
The bug leading to the attack vectors mentioned, is present in both [readMem()](https://github.com/code-423n4/2024-07-optimism/blob/main/packages/contracts-bedrock/src/cannon/MIPS.sol#L538C4-L584C6) and [writeMem()](https://github.com/code-423n4/2024-07-optimism/blob/main/packages/contracts-bedrock/src/cannon/MIPS.sol#L592C3-L632C6) functions but here is the relevant snippet of [readMem()](https://github.com/code-423n4/2024-07-optimism/blob/main/packages/contracts-bedrock/src/cannon/MIPS.sol#L538C4-L584C6):

```solidity
function readMem(uint32 _addr, uint8 _proofIndex) internal pure returns (uint32 out_) {
    unchecked {
        uint256 offset = proofOffset(_proofIndex);

        assembly {
            // Validate the address alignment.
            if and(_addr, 3) { revert(0, 0) }

            // Load the leaf value.
            let leaf := calldataload(offset)
            offset := add(offset, 32)

            // ... (merkle proof verification)

            // Bits to shift = (32 - 4 - (addr % 32)) * 8
            let shamt := shl(3, sub(sub(32, 4), and(_addr, 31)))
            out_ := and(shr(shamt, leaf), 0xFFffFFff)
        }
    }
}
```

The function only checks for `4-byte` alignment but does not validate if the address is within the allowed memory range.

Add the below foundry test function to [MIPS.t.sol](https://github.com/code-423n4/2024-07-optimism/blob/main/packages/contracts-bedrock/test/cannon/MIPS.t.sol) to show the bug, will need to fix setup when running if bugs occur: 

```solidity
    function testUnauthorizedMemoryAccess() public {
        // Setup initial state
        bytes32 initialMemRoot = bytes32(uint256(1));
        uint32 initialPC = 0x1000;
Inability to challenge the first LLP due to a wrong check
Audit: 2024-07-optimism-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details


## Proof of Concept

Take a look at https://github.com/code-423n4/2024-07-optimism/blob/2e11e15e7a9a86f90de090ebf9e3516279e30897/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L609-L637

```solidity
    function challengeFirstLPP(
        address _claimant,
        uint256 _uuid,
        Leaf calldata _postState,
        bytes32[] calldata _postStateProof
    )
        external
    {
        // Verify that the leaf is present in the merkle tree.
        bytes32 root = getTreeRootLPP(_claimant, _uuid);
        if (!_verify(_postStateProof, root, _postState.index, _hashLeaf(_postState))) revert InvalidProof();

        // The poststate index must be 0 in order to challenge it with this function.
        if (_postState.index != 0) revert StatesNotContiguous();

        // Absorb and permute the input bytes into a fresh state matrix.
        LibKeccak.StateMatrix memory stateMatrix;
        LibKeccak.absorb(stateMatrix, _postState.input);
        LibKeccak.permutation(stateMatrix);

        // Verify that the post state hash doesn't match the expected hash.
        if (keccak256(abi.encode(stateMatrix)) == _postState.stateCommitment) revert PostStateMatches();

        // Mark the keccak claim as countered.
        proposalMetadata[_claimant][_uuid] = proposalMetadata[_claimant][_uuid].setCountered(true);

        // Pay out the bond to the challenger.
        _payoutBond(_claimant, _uuid, msg.sender);
    }
```

This function is used to challenge the first keccak256 block that was absorbed, issue howeverr is that it includes a wrong check _logically_, that would always cause for the attempt to use this function to revert, this is because the check `   if (_postState.index != 0) revert StatesNotContiguous();`  assumes the postState index would be `0`, but that wouldn't be the case considering this is the first LPP being challenged, hint on how this correctly works can be seen [here](https://github.com/code-423n4/2024-07-optimism/blob/2e11e15e7a9a86f90de090ebf9e3516279e30897/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L592), so in the case of `PreimageOracle#challengeFirstLPP()` we should instead expect the postImage index to be `1`.

## Impact
`PreimageOracle#challengeFirstLPP()` would always revert due to the current post image check applied which is attempting to ensure the index is `0` instead of `1`.
## Recommended Mitigation Steps
Consider applying these changes:
```diff
    function challengeFirstLPP(
        address _claimant,
        uint256 _uuid,
        Leaf calldata _postState,
        bytes32[] calldata _postStateProof
    )
        external
    {
        // Verify that the leaf is present in the merkle tree.
        bytes32 root = getTreeRootLPP(_claimant, _uuid);
        if (!_verify(_postStateProof, root, _post
challengeRootL2Block can be called after the clock has expired
Audit: 2024-07-optimism-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

The game state can be modified after the game clock expired.

In `FaultDisputeGame`, `challengeRootL2Block()` does not check whether the game clock has expired. Hence, a successful challenge can still be made even after the game is closed, i.e. during the bond payment period.

This breaks an important invariant about the game clock and could result in a modified game state after the game expiration. 

Consider the following scenario:

- Alice is a successful challenger to the root claim of the output root game. At the game expiration time, she’s entitled to the bond payment for this claim.
- Bob successfully calls `challengeRootL2Block()` after the game expired.
- Due to how `resolveClaim()` always prioritizes bond payment to L2 block challenger, the bond will be paid back to Bob instead of Alice.
- However, notice that if Alice was able to call `resolveClaim()` before Bob called `challengeRootL2Block()`, the bond would have been paid to Alice. Bob’s challenge call will still succeed, even though he will not be able to claim the bond.

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

POC test:
https://gist.github.com/taishi8117/caf3ac011f237067af877e8ab4c79135

## Tools Used
Manual Review

## Recommended Mitigation Steps
In `challengeRootL2Block()`, add a require statement to ensure that the game clock has not expired


## Assessed type

Access Control
reset `initLPP`  may get funds stuck in contract
Audit: 2024-07-optimism-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-optimism/blob/0f8027921e621a9ac75d8eb20cbda873965e3b8a/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L417


# Vulnerability details

## Impact

if try to call `initLPP` twice , previous funds may get stuck

## Proof of Concept

in function `initLPP` ,there seems don't have any measures to control user init LPP twice with same params. 
Function don't check this  caller's  uuid has been set  or not. 
Let's say  if user try to call  `initLPP` twice due to double click in front end or backend send duplicate msgs, 
,the result is array `proposals`  may exist  duplicate value, and  the previous msg.value may be overided and this 
part of funds may gets stuck in contract.
  


```solidity
  function initLPP(uint256 _uuid, uint32 _partOffset, uint32 _claimedSize) external payable {
        // The bond provided must be at least `MIN_BOND_SIZE`.
        if (msg.value < MIN_BOND_SIZE) revert InsufficientBond();

        // The caller of `addLeavesLPP` must be an EOA, so that the call inputs are always available in block bodies.
        if (msg.sender != tx.origin) revert NotEOA();

        // The part offset must be within the bounds of the claimed size + 8.
        if (_partOffset >= _claimedSize + 8) revert PartOffsetOOB();

        // The claimed size must be at least `MIN_LPP_SIZE_BYTES`.
        if (_claimedSize < MIN_LPP_SIZE_BYTES) revert InvalidInputSize();

        // Initialize the proposal metadata.
  @>      LPPMetaData metaData = proposalMetadata[msg.sender][_uuid];
  @>       proposalMetadata[msg.sender][_uuid] = metaData.setPartOffset(_partOffset).setClaimedSize(_claimedSize);
  @>      proposals.push(LargePreimageProposalKeys(msg.sender, _uuid));

        // Assign the bond to the proposal.
  @>      proposalBonds[msg.sender][_uuid] = msg.value;
    }



```

## Tools Used
manual 

## Recommended Mitigation Steps

check sender's Lpp has been inited  or not



## Assessed type

Invalid Validation
`challengeLPP`  don't have need the proposal is being finalized
Audit: 2024-07-optimism-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-optimism/blob/0f8027921e621a9ac75d8eb20cbda873965e3b8a/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L541-L543


# Vulnerability details

## Impact

invalid validation,change Lpp don't have to wait proposal is being finalized. 

## Proof of Concept 
 In function `addLeavesLPP` annotation, we have a decscribe that means challenge period has to wait until finalized is set.

```solidity
 function addLeavesLPP(
  ...
    // If the proposal is being finalized, set the timestamp to the current block timestamp. This begins the
    // challenge period, which must be waited out before the proposal can be finalized.
    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();
    }
```
The issue is in function `challengeLPP` , we don't have related limit, user can challenge at any time. 

## Tools Used
manual 

## Recommended Mitigation Steps

add limit in `challengeLPP` and `challengeFirstLPP` to make sure proposal is finalized when challenge
 



## Assessed type

Invalid Validation
There is no fixed deadline. The Fault Dispute Game can essentially go on because of the unlimited extension
Audit: 2024-07-optimism-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol#L371-L382


# Vulnerability details

## Summary

When there is a Dispute Game, both the challenger and the defender has a timer. If the game is played within `MAX_CLOCK_DURATION` and `CLOCK_EXTENSION`, the clock will not time out until the game reaches its `MAX_DEPTH` and be resolved from there.

## Impact

The deadline will not be as effective as intended.

## Proof of Concept

When the game is played, both parties have a clock that counts until `MAX_CLOCK_DURATION`. If their time is before `MAX_CLOCK_DURATION` but after `MAX_CLOCK_DURATION - CLOCK_EXTENSION`, then their time will be set to `MAX_CLOCK_DURATION - CLOCK_EXTENSION`.

```
        // If the remaining clock time has less than `CLOCK_EXTENSION` seconds remaining, grant the potential
        // grandchild's clock `CLOCK_EXTENSION` seconds. This is to ensure that, even if a player has to inherit another
        // team's clock to counter a freeloader claim, they will always have enough time to to respond. This extension
        // is bounded by the depth of the tree. If the potential grandchild is an execution trace bisection root, the
        // clock extension is doubled. This is to allow for extra time for the off-chain challenge agent to generate
        // the initial instruction trace on the native FPVM.
        if (nextDuration.raw() > MAX_CLOCK_DURATION.raw() - CLOCK_EXTENSION.raw()) {
            // If the potential grandchild is an execution trace bisection root, double the clock extension.
            uint64 extensionPeriod =
                nextPositionDepth == SPLIT_DEPTH - 1 ? CLOCK_EXTENSION.raw() * 2 : CLOCK_EXTENSION.raw();
>           nextDuration = Duration.wrap(MAX_CLOCK_DURATION.raw() - extensionPeriod);
        }
```

Assume Alice is the defender and Bob is the challenger. The `MAX_CLOCK_DURATION` is one day and `CLOCK_EXTENSION` is 1 hours.

Alice creates the Fault Dispute Game with an output root at time T1, and Bob's timer starts from 0. 

At time T2 (one hour later), Bob challenges the claim with an attack. Alice timer starts counting from 0 upwards. 

At time T3 (23.5 hours later), Alice disputes the attack with another attack. Since Alice timer is within 1 hour of `MAX_CLOCK_DURATION`, Alice timer is set back to 23 hours. Bob timer starts counting from 1 hour.

At time T4 (22.5 hours later), Bob disputes Alice claim. Since Bob timer is now 23.5 hours, it is within the hour of `MAX_CLOCK_DURATION` and the timer resets to 23 hours. Alice timer starts counting from 23 hour.

At time T5...T6.. until the game reaches `MAX_DEPTH`, as long as Bob and Alice plays within the `CLOCK_EXTENSION` period, the clock is extended indefinitely and Alice and Bob will always have `CLOCK_EXTENSION` to counter the claim.

This is not the intended case. What should happen is if Bob fails to defend his claim with a m
If disputes are uncontested at the same depth, then the bond recipient would be the dispute creator and not the game winner
Audit: 2024-07-optimism-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol#L580-L590


# Vulnerability details

## Summary 

If a game does not reach `MAX_DEPTH`, it means that either the challenger or the defender has timed out. If there are uncontested claims in the same depth (from both challenger and defender), then the dispute creator will be able to get back their bond instead of the game winner sweeping all the winnings.

## Impact

The winner will not win all the bonds.

## Proof of Concept

Let's say there are 3 moves, and 4 `claimData` in total. Alice is the defendant and Bob is the challenger.

Alice creates the game. `claimData[0]` is her claim.

Bob attacks. `claimData[1]` is his claim. subgames[0] is populated with 1 (the first game). The depth is now 2.

Alice attacks. `claimData[2]` is Alice claim. subgames[1] is populated with 2 (the second game). The depth is now 3.

Bob defends at depth 2. `claimData[3]` is Bob's claim. subgames[1] is populated with 3 (the third game). The depth is now 3.

Now, there are 2 games at depth 3. subgames[2] is not populated since Bob went back to depth 1 and defend.

Alice decides not to play anymore because she realizes that her output root was wrong. She times out and Bob wins. By right, Bob should win both of Alice's bond, but he only wins the first one.

Since subgames[2] and subgames[3] have no value inside, but `claimData[2]` and `claimData[3]` is Alice and Bob's second dispute respectively, when `resolveClaim()` happens, `challengeIndicesLen == 0 && _claimIndex != 0` will pass.

```
 uint256[] storage challengeIndices = subgames[_claimIndex];
        uint256 challengeIndicesLen = challengeIndices.length;

        // Uncontested claims are resolved implicitly unless they are the root claim. Pay out the bond to the claimant
        // and return early.
>       if (challengeIndicesLen == 0 && _claimIndex != 0) {
            // In the event that the parent claim is at the max depth, there will always be 0 subgames. If the
            // `counteredBy` field is set and there are no subgames, this implies that the parent claim was successfully
            // stepped against. In this case, we pay out the bond to the party that stepped against the parent claim.
            // Otherwise, the parent claim is uncontested, and the bond is returned to the claimant.
            address counteredBy = subgameRootClaim.counteredBy;
>           address recipient = counteredBy == address(0) ? subgameRootClaim.claimant : counteredBy;
            _distributeBond(recipient, subgameRootClaim);
            resolvedSubgames[_claimIndex] = true;
            return;
        }
```

Two issue arises. Alice can claim before Bob, although it should be in order. Secondly, Alice can claim back her second bond even though she lost.

Alice calls `resolveClaim()` with `_claimIndex` as 2. `claimData[2]` is Alice claim. `counteredBy` is