xanoutas/solidity-security-findings huggingface dataset:
DOS through claim of unsafe blocks
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/DisputeGameFactory.sol#L84-L132


# Vulnerability details

## Impact

The `create` function in the DisputeGameFactory contract requires a unique UUID for each game. This UUID is derived from the game type, root claim, and extra data.

```solidity
        // Compute the unique identifier for the dispute game.
        Hash uuid = getGameUUID(_gameType, _rootClaim, _extraData);

        // If a dispute game with the same UUID already exists, revert.
>>>        if (GameId.unwrap(_disputeGames[uuid]) != bytes32(0)) revert GameAlreadyExists(uuid);

        // Pack the game ID.
        GameId id = LibGameId.pack(_gameType, Timestamp.wrap(uint64(block.timestamp)), address(proxy_));
```

When initiating a dispute game, the output root typically corresponds to the latest safe L2 block.

However, as previously noted, the `create` function necessitates a unique UUID. This means if that the claim has already been made for this block while its still unsafe, a claim can no longer be made.

Nevertheless, the requirement for a unique UUID introduces a potential vulnerability.

It can be weaponized by an attacker that keeps creating games for not yet safe L2 blocks, so a legit proposal cannot be created for the root when its safe.

If the L1 hash to walk back from is saved during creation, then that game will permanently fail, so they could predict it just a few seconds before based on gossiped data

## Tools Used

Eyes

## Recommended Mitigation Steps

Include parentHash in the uuid calculation.
```diff
+Hash uuid = getGameUUID(_gameType, _rootClaim, _extraData, parentHash);
```





## Assessed type

Other
No checks whether `LPP` is currently in `CHALLENGE_PERIOD` when it gets challenged
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#L568-L606
https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L609-L637


# Vulnerability details

## Description
When proposing a `LPP` in the `PreimageOracle` before it gets added to the oracle, it has to go through a `CHALLENGE_PERIOD` during which any user can contest it if it is invalid.  
The problem is, that there is no check whether a `LPP` is in the `CHALLENGE_PERIOD`. This means that it can be challenged in two invalid cases:
* Before it has been `finalized` meaning before all context has been added to it
* After the `CHALLENGE_PERIOD` is over meaning it has already been included in the oracle

`challengeLPP`:
```Solidity
function challengeLPP(
    address _claimant,
    uint256 _uuid,
    LibKeccak.StateMatrix memory _stateMatrix,
    Leaf calldata _preState,
    bytes32[] calldata _preStateProof,
    Leaf calldata _postState,
    bytes32[] calldata _postStateProof
)
    external
{
    // Verify that both leaves are present in the merkle tree.
    bytes32 root = getTreeRootLPP(_claimant, _uuid);
    if (
        !(
            _verify(_preStateProof, root, _preState.index, _hashLeaf(_preState))
                && _verify(_postStateProof, root, _postState.index, _hashLeaf(_postState))
        )
    ) revert InvalidProof();

    // Verify that the prestate passed matches the intermediate state claimed in the leaf.
    if (keccak256(abi.encode(_stateMatrix)) != _preState.stateCommitment) revert InvalidPreimage();

    // Verify that the pre/post state are contiguous.
    if (_preState.index + 1 != _postState.index) revert StatesNotContiguous();

    // Absorb and permute the input bytes.
    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);
}
```
We see that there is no check concerning the `CHALLENGE_PERIOD` here, meaning we can challenge a proposal at any time even though it should only be possible during this period.

## Impact
This breaks part of the purpose of the `CHALLENGE_PERIOD` and allows an attacker to challenge a proposal that is not yet finished, possible leading to them being able to contest a not-yet but soon-to-be valid proposal.


## Proof of Concept
The following PoC shows, that `challengeLPP` will succeed on a proposal that has not yet been `finalized`.
In order to run it, please add it to the tests in `PreimageOracle.t.s
Clock Duration Check Bypass in move Function
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#L369


# Vulnerability details

## Description

In the `FaultDisputeGame` contract, there's an issue with check. The comment suggests that a move should be prevented if the clock has exceeded the maximum duration, but the code only checks for exact equality.

## Current Behavior

The current implementation in the `move` function is:

```solidity
// INVARIANT: A move can never be made once its clock has exceeded `MAX_CLOCK_DURATION`
//            seconds of time.
if (nextDuration.raw() == MAX_CLOCK_DURATION.raw()) revert ClockTimeExceeded();
```

This check only prevents moves when the clock duration is exactly equal to `MAX_CLOCK_DURATION`, not when it exceeds it.

## Expected Behavior

The check should prevent moves when the clock duration is greater than or equal to `MAX_CLOCK_DURATION`, to align with the comment and ensure that no moves can be made once the maximum duration has been reached or exceeded.

## Mitigation

Update the condition to use greater than or equal to:

```solidity
if (nextDuration.raw() >= MAX_CLOCK_DURATION.raw()) revert ClockTimeExceeded();
```

## Impact

The current implementation still prevents moves at the exact `MAX_CLOCK_DURATION`, but it could potentially allow moves if the duration somehow exceeded this value.

## Steps to Reproduce

1. Deploy the `FaultDisputeGame` contract
2. Initiate a game and make moves until a claim's clock reaches exactly `MAX_CLOCK_DURATION`
3. Attempt to make a move against this claim (this will currently revert as expected)
4. If possible, manipulate the clock to exceed `MAX_CLOCK_DURATION` (this may require modifying the contract for testing purposes)
5. Attempt to make a move against the claim with an exceeded clock (this would currently succeed, which is not the intended behavior)



## Assessed type

Invalid Validation
Array Lengths Not Checked
Audit: 2024-07-optimism-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-optimism/blob/e685f194269e3477c7e8ac25c27e3b5956c05360/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L568-L606
https://github.com/code-423n4/2024-07-optimism/blob/e685f194269e3477c7e8ac25c27e3b5956c05360/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L640-L693


# Vulnerability details

## Impact
If the array lengths are not verified to ensure equality of lengths, operations performed by the user may not be executed completely due to a mismatch in the number of items iterated over compared to the number of items provided in the second array. This can lead to inconsistencies in the execution of functions, which bad actors can exploit to disrupt or manipulate processes within the contract.

## Proof of Concept
1. Deploy contract *'PreimageOracle.sol'*.
2. Call the *'challengeLPP'* function with *'_preStateProof'* and *'_postStateProof'* arrays of different lengths.
3. Observe that functions may fail or not behave as expected due to unverified array length mismatches.
```
// Example of PoC
function testChallengeLPPWithDifferentArrayLengths() public {
    PreimageOracle oracle = new PreimageOracle();
    // Dummy data
    address claimant = address(0x123);
    uint256 uuid = 1;
    LibKeccak.StateMatrix memory stateMatrix;
    Leaf memory preState;
    bytes32[] memory preStateProof = new bytes32[](2); // Length 2
    Leaf memory postState;
    bytes32[] memory postStateProof = new bytes32[](3); // Length 3
    
    try oracle.challengeLPP(claimant, uuid, stateMatrix, preState, preStateProof, postState, postStateProof) {
        // If no revert occurs, there is a problem
        assert(false);
    } catch {
        // If a revert occurs, the expected behavior
    }
}
```

## Tools Used
Manual review

## Recommended Mitigation Steps
1. Verify that the lengths of the *'_preStateProof'* and *'_postStateProof'* arrays are the same before proceeding with further verification in the *'challengeLPP'* and *'squeezeLPP'* functions.
2. Add a check at the start of the function to ensure the array length is appropriate.
```
function challengeLPP(
    address _claimant,
    uint256 _uuid,
    LibKeccak.StateMatrix memory _stateMatrix,
    Leaf calldata _preState,
    bytes32[] calldata _preStateProof,
    Leaf calldata _postState,
    bytes32[] calldata _postStateProof
)
    external
{
    require(_preStateProof.length == _postStateProof.length, "Array lengths must match");

    // The rest of the code...
}

function squeezeLPP(
    address _claimant,
    uint256 _uuid,
    LibKeccak.StateMatrix memory _stateMatrix,
    Leaf calldata _preState,
    bytes32[] calldata _preStateProof,
    Leaf calldata _postState,
    bytes32[] calldata _postStateProof
)
    external
{
    require(_preStateProof.length == _postStateProof.length, "Array lengths must match");

    // The rest of the code...
}
```


## Assessed type

Invalid Validation
Incorrect Memory Alignment and Proof Verification in 'readMem' and 'writeMem' Functions
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/MIPS.sol#L538-L584
https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/MIPS.sol#L592-L632


# Vulnerability details

## Impact
- Incorrect Memory Operations: Misaligned memory addresses can lead to incorrect data being read or written, potentially causing data corruption.
- Invalid Proof Verification: Failure to correctly verify memory proofs can result in accepting invalid memory states, compromising the integrity of the MIPS emulator.

## Proof of Concept
- If an unaligned address is passed to *'readMem'* or *'writeMem'*, the functions will not correctly handle the memory operation, leading to potential data corruption.
```
uint32 unalignedAddress = 0x00000003; // Example of an unaligned address
uint32 value = readMem(unalignedAddress, 0); // This should revert with an alignment error
```
- If the proof verification logic is incorrect, an invalid memory proof could be accepted, leading to an incorrect memory root being used.
```
// Example of an incorrect proof that should fail verification
bytes memory incorrectProof = hex"0000000000000000000000000000000000000000000000000000000000000000";
uint32 address = 0x00000000;
uint32 value = readMem(address, 0); // This should revert with a proof verification error
```

## Tools Used
Manual review

## Recommended Mitigation Steps
- Ensure the alignment check is correctly implemented with a meaningful error message.
```
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("Memory address not aligned") }

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

            // Convenience function to hash two nodes together in scratch space.
            function hashPair(a, b) -> h {
                mstore(0, a)
                mstore(32, b)
                h := keccak256(0, 64)
            }

            // Start with the leaf node.
            // Work back up by combining with siblings, to reconstruct the root.
            let path := shr(5, _addr)
            let node := leaf
            for { let i := 0 } lt(i, 27) { i := add(i, 1) } {
                let sibling := calldataload(offset)
                offset := add(offset, 32)
                switch and(shr(i, path), 1)
                case 0 { node := hashPair(node, sibling) }
                case 1 { node := hashPair(sibling, node) }
            }

            // Load the memory root from the first field of state.
               let memRoot := mload(0x80)

               // Verify the root matches.
               if iszero(eq(node, memRoot)) {
Insufficient Input Validation in 'PreimageKeyLib' Functions
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/PreimageKeyLib.sol#L12-L20


# Vulnerability details

## Impact
this may cause unexpected behavior due to insufficient input validation in the *'localizeIdent'*, *'localize'*, and *'keccak256PreimageKey'* functions. This can result in the creation of invalid keys, which can cause failures in key localization and potentially compromise the integrity of systems that rely on those keys.

## Proof of Concept
- Invalid Local Context: Pass an empty local context to the *'localize'* function.
```
bytes32 invalidContext = bytes32(0);
bytes32 key = someKey; // Assume someKey is a valid key

// This should fail because the local context is invalid (empty).
bytes32 localizedKey = PreimageKeyLib.localize(key, invalidContext);
```
- Invalid Identifier: Pass an identifier that is out of the expected range to the *'localizeIdent'* function.
```
uint256 invalidIdent = 2**248; // Out of the valid range
bytes32 localContext = someLocalContext; // Assume someLocalContext is a valid local context

// This should fail because the identifier is out of the expected range.
bytes32 localizedKey = PreimageKeyLib.localizeIdent(invalidIdent, localContext);
```
- Empty Preimage: Pass an empty preimage to the *'keccak256PreimageKey'* function.
```
bytes memory emptyPreimage = "";

bytes32 preimageKey = PreimageKeyLib.keccak256PreimageKey(emptyPreimage);
// This should fail because the preimage is empty.
```

## Tools Used
Manual review

## Recommended Mitigation Steps
- Add Input Validation in *'localizeIdent'*:
```
function localizeIdent(uint256 _ident, bytes32 _localContext) internal view returns (bytes32 key_) {
    require(_ident < 2**248, "Identifier out of range");
    require(_localContext != bytes32(0), "Invalid local context");

    assembly {
        key_ := or(shl(248, 1), and(_ident, not(shl(248, 0xFF))))
    }
    key_ = localize(key_, _localContext);
}
```
- Add Input Validation in *'localize'*:
```
function localize(bytes32 _key, bytes32 _localContext) internal view returns (bytes32 localizedKey_) {
    require(_localContext != bytes32(0), "Invalid local context");

    assembly {
        let ptr := mload(0x40)
        mstore(0, _key)
        mstore(0x20, caller())
        mstore(0x40, _localContext)
        localizedKey_ := or(and(keccak256(0, 0x60), not(shl(248, 0xFF))), shl(248, 1))
        mstore(0x40, ptr)
    }
}
```
- Add Input Validation in *'keccak256PreimageKey'*:
```
function keccak256PreimageKey(bytes memory _preimage) internal pure returns (bytes32 key_) {
    require(_preimage.length > 0, "Preimage cannot be empty");

    assembly {
        let size := mload(_preimage)
        let h := keccak256(add(_preimage, 0x20), size)
        key_ := or(and(h, not(shl(248, 0xFF))), shl(248, 2))
    }
}
```


## Assessed type

Library
Multiplication Overflow Leading to Memory Corruption and Incorrect Register Write-Back
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#L967
https://github.com/code-423n4/2024-07-optimism/blob/main/packages/contracts-bedrock/src/cannon/MIPS.sol#L763


# Vulnerability details

## Impact
The unchecked multiplication of int32 values (rs and rt) and type conversion within the execute function in MIPS contract can result in an overflow, leading to the assignment of an incorrect value to the val variable in the step(...) function. This incorrect value is subsequently written to memory and the destination register in the step function. This can cause data corruption in contract and break of Proper MIPS contract functionality and storage
## Proof of Concept
```solidity
 function step(bytes calldata _stateData, bytes calldata _proof, bytes32 _localContext) public returns (bytes32) {
        unchecked {
...
 uint32 val = execute(insn, rs, rt, mem) & 0xffFFffFF; // swr outputs more than 4 bytes without the mask
...
     // write memory
            if (storeAddr != 0xFF_FF_FF_FF) {
                writeMem(storeAddr, 1, val);
            }

            // write back the value to destination register
            return handleRd(rdReg, val, true);
        }
}
```
The function provided above shows how step(...) function is implemented in the MIPS.sol contract, it can be noted from the code fragments provided in the function that the value of val is set by making a call to the execute(...) function and this value was used to write memory later in the function to the destination register, it can be noted that this was done within the unchecked region.
```solidity
function execute(uint32 insn, uint32 rs, uint32 rt, uint32 mem) internal pure returns (uint32 out) {
        unchecked {
...
if (opcode == 0 || (opcode >= 8 && opcode < 0xF)) {
...
} else {
                // SPECIAL2
                if (opcode == 0x1C) {
                    uint32 func = insn & 0x3f; // 6-bits
                    // mul
                    if (func == 0x2) {
>>>                        return uint32(int32(rs) * int32(rt)); ❌
                    }
                    // clz, clo
                    else if (func == 0x20 || func == 0x21) {
                    ... } 
                 ...}
    ...
    }
...
}
```
The code above shows how the execute(...) function was implemented, as noted in the pointer above the unchecked multiplication of int32 values (rs and rt) & type conversion to uint32 within the execute function above would lead to an overflow, which would result in incorrect calculation. This is because multiplication of certain values within int32 can go above the max int32 value, the problem with this issue is that the operation was carried out inside an unchecked environment and it was converted to type uint32 even though the value might have gone above it, which means the overflow which would have normally revert would go unnoticed thereby corrupting write memory and sending wrong value data t
Implicit underflows can revert it 
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#L738


# Vulnerability details

* There are a few underflows that are converted via a typecast afterwards to the expected value. If solidity 0.8.x would be used, then the code would revert.


* `int256(a-b)` where a and b are uint: For example, if `a=1` and `b=2`, then the intermediate result would be `uint(-1) == 2**256-1`
`int256(-x)` where x is a uint. For example, `if x=1`, then the intermediate result would be `uint(-1) == 2**256-1`


From FaultDisputeGame.sol `L738:`

```
function getRequiredBond(Position _position) public view returns (uint256 requiredBond_) {

uint256 lnA = uint256(FixedPointMathLib.lnWad(int256(a * FixedPointMathLib.WAD))); // underflow in intermediate result

...

int256 rawGas = FixedPointMathLib.powWad(base, int256(depth * FixedPointMathLib.WAD)); // underflow in intermediate result
...
}
```


## Tools Used

Vscode

## Recommended Mitigation Steps

Recommend replacing `int256(a-b)` with `int256(a)-int256(b)`, and replacing `int256(-x)` with `-int256(x)`


## Assessed type

Under/Overflow
The function `loadLocalData` does not properly check the `_partOffset`.
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/PreimageOracle.sol#L134-L150


# Vulnerability details

## Impact
The function loadLocalData will access memory out of bounds, causing the variable `part` to have unexpected content.

## Details
Function loadLocalData is used to load local data from Preimage and store the corresponding part at _partOffset. The important check here is that _partOffset can't be out of the bound.
```solidity
// Revert if the given part offset is not within bounds.
if (_partOffset > _size + 8 || _size > 32) { 
    revert PartOffsetOOB();
}
```
However, this check is incorrect. When the _size = 32 and _partOffset = 40. The content of part will be the memory from the 40 to 72 (40+32), which exceeds the limit of 64.

## Tools Used
Vscode

## Recommended Mitigation Steps
```solidity
// Revert if the given part offset is not within bounds.
if (_partOffset > _size + 8 || _partOffset > 32 || _size > 32) { 
    revert PartOffsetOOB();
}
```


## Assessed type

Invalid Validation
Missing address check for instructions LH and LHU
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#L990-L993
https://github.com/code-423n4/2024-07-optimism/blob/main/packages/contracts-bedrock/src/cannon/MIPS.sol#L1008-L1011


# Vulnerability details

## Impact
Instruction processing cannot detect problems in time.

## Details
According to the link https://www.cs.cmu.edu/afs/cs/academic/class/15740-f97/public/doc/mips-isa.pdf, page 99, Section Restrictions, the address must be naturally aligned. If the least-significant bit of the address is non-zero, an Address Error exception occurs. However, in the contract, the address is not checked.

## Tools Used
Vscode

## Recommended Mitigation Steps
Check the address and when the least-significant bit of the address is non-zero, revert.


## Assessed type

Other
For instructions such as DIV and mthi, store register will be set to 0, which is unexpectedly.
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#L383-L445
https://github.com/code-423n4/2024-07-optimism/blob/main/packages/contracts-bedrock/src/cannon/MIPS.sol#L804


# Vulnerability details

## Impact
Store register will be mistakenly set to 0.

## Details
In the function `handleHiLo`, when _storeReg is not 0, the store reg will be set to val. From the perspective of MISP instruction design, the store register needs to be set only when _func is mfhi and mflo. However, in the implementation, for the instructions mthi, mtlo, mult, div, etc., store register may also be overwritten. The same problem occurs at the end of the function step.

## Tools Used
Vscode

## Recommended Mitigation Steps
Place store register assignments in each instruction branch rather than at the end of a function. For exapmle:
```solidity
// mfhi: Move the contents of the HI register into the destination
if (_func == 0x10 && _storeReg != 0) {
    state.registers[_storeReg] = state.hi;
}
```


## Assessed type

Other