xanoutas/solidity-security-findings huggingface dataset:
After lender is unsanctioned, he will not be able to withdraw and deposit again
Audit: 2023-10-wildcat-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketConfig.sol#L88-L102
https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketBase.sol#L197-L213


# Vulnerability details

## Impact
- After lender is unsanctioned, he will not be able to withdraw and deposit again.
- Stuck funds in the Market.

## Explanation
If a lender's address is flagged as sanctioned by the Chainanalysis oracle, there are two possible scenarios:

> 1. **Using the `nukeFromOrbit()` Function**: In this case, anyone can invoke the [`WildcatMarketConfig.sol#nukeFromOrbit()`](https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketConfig.sol#L74-L81) function with the sanctioned user's `accountAddress`. This function checks if the lender is indeed sanctioned by verifying their status with the Chainanalysis oracle. If confirmed as sanctioned, the lender's account is blocked within the market, and their state is updated accordingly.
```jsx
  function nukeFromOrbit(address accountAddress) external nonReentrant {
    if (!IWildcatSanctionsSentinel(sentinel).isSanctioned(borrower, accountAddress)) {
      revert BadLaunchCode();
    }
    MarketState memory state = _getUpdatedState();
    _blockAccount(state, accountAddress);
    _writeState(state);
  }
```

> 2. **Executing `executeWithdrawal()` as a Sanctioned Lender**: In this scenario, the sanctioned lender, while still flagged as sanctioned by the Chainalysis oracle, initiates a withdrawal by calling [`WildcatMarketWithdrawals.sol#executeWithdrawal()`](https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketWithdrawals.sol#L137-L188). If the withdrawal is executed while the lender is sanctioned, an escrow contract is created between the lender and the borrower within the market. The lender's market tokens (scaledBalance) are transferred to this escrow contract instead of directly to the lender, and the lender's account is marked as `Blocked` within the market.
```jsx
  function executeWithdrawal(
    address accountAddress,
    uint32 expiry
  ) external nonReentrant returns (uint256) {
    if (expiry > block.timestamp) {
      revert WithdrawalBatchNotExpired();
    }
    MarketState memory state = _getUpdatedState();

    WithdrawalBatch memory batch = _withdrawalData.batches[expiry];
    AccountWithdrawalStatus storage status = _withdrawalData.accountStatuses[expiry][
      accountAddress
    ];

    // ...code...
    
    if (IWildcatSanctionsSentinel(sentinel).isSanctioned(borrower, accountAddress)) {
      _blockAccount(state, accountAddress);
      address escrow = IWildcatSanctionsSentinel(sentinel).createEscrow(
        accountAddress,
        borrower,
        address(asset)
      );
      asset.safeTransfer(escrow, normalizedAmountWithdrawn);
      emit Sanction
False Claims Can Be Settled On Due To Incorrect Time Calculation
Audit: 2024-07-optimism-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

Background

In the dispute game, participants make claims for and against challenges, and the distribution of bonds is tied to the outcomes of these games. A fundamental invariant in this system is that no moves can be made once the game's maximum duration is exceeded. However, this invariant is currently broken due to an improper implementation in the move function.

Vulnerability Details

The core issue lies in the following code snippet within the move function:

```
        // 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 reverts if nextDuration is exactly equal to `MAX_CLOCK_DURATION`. It does not account for cases where `nextDuration` exceeds `MAX_CLOCK_DURATION`  (i.e., `nextDuration.raw() > MAX_CLOCK_DURATION.raw()`). As a result, moves can still be made even if the clock has surpassed the maximum duration, breaking the invariant.

To fix this, the check should use >= instead of ==:

Breaking this invariant exposes the dispute game to significant vulnerabilities. Any logic dependent on the integrity of the maximum clock duration becomes unreliable. This can lead to scenarios where invalid claims are accepted and valid claims are rejected.

Exploit Scenario

Consider the following exploit scenario:

A game is played out, and the final successful move is made. Participants are waiting for the challenge clock to exceed the maximum duration so the game can be resolved and bonds can be distributed.

As soon as the maximum clock duration is reached, an attacker makes a move with a false claim. This move passes because the invariant in the move function is broken:

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

Since the move is made after the maximum clock duration, the attacker can then immediately settle the false claim by calling resolveClaim, which has the following check:

```
if (challengeClockDuration.raw() < MAX_CLOCK_DURATION.raw()) revert ClockNotExpired();

```

The illegal move is made after the maximum clock duration, and it is successfully resolved because the check in resolveClaim passes. This allows the attacker to exploit the system, causing an invalid claim to be accepted.

Summary 

The Max Clock invariant ensures that a subgame can only be resolved once the clock of its would-be counter has expired. If the maximum clock duration is exceeded, the game can be resolved, and bonds can be paid out.

However, due to the broken invariant in the move function, an attacker can make an illegal move after the maximum clock duration, and the subsequent call to resolveClaim will pass the check, resulting
Spoofing of PreimageOracle via re-`initLPP` & re-`squeezeLPP`
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#L416-L437
https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L639-L693


# Vulnerability details

## Short Summary & Impact

From [README: Attack ideas](https://github.com/code-423n4/2024-07-optimism?tab=readme-ov-file#attack-ideas-where-to-focus-for-bugs)

> The Preimage Oracle is trusted to only provide accurate data. Is there a way to get invalid data added and use it to prove an invalid execution trace?

This finding demonstrates how arbitrary `Keccak256`-encoded data returned from `PreimageOracle` contract can be spoofed via re-initializing and re-squeezing Large Preimage Proposals (LPPs). The root cause of the vulnerability stems from insufficient input validation for methods `initLPP` and `squeezeLPP`: they accept already finalized LPPs, thus allowing an attacker to mutate key LPP parameters. We further demonstrate how this unauthorized LPP mutation can be employed to spoof data returned by the method `readPreimage` for `Keccak256`-encoded preimage parts.

Spoofing such crucial data can lead to arbitrary impacts, including (but not limited to) withdrawing all tokens contained in Optimism.


## Detailed Description

For `Keccak256`-encoded data which is too large to be submitted in a single transaction, `PreimageOracle` contract provides the following route via submitting Large Preimage Proposals (LPPs):

- [initLPP](https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L417) creates a new LPP with the specified `_uuid`, `_partOffset`, and `_claimedSize` parameters.
- [addLeavesLPP](https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L440) allows to iteratively supply preimage parts of the LPP.
- [challengeLPP](https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L568) / [challengeFirstLPP](https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L609) allow to permissionlessly  challenge the LPP if inconsistencies in the supplied preimage data are detected.
- [squeezeLPP](https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L640) finalizes the LPP in case challenged period has passed and the LPP has not been successfully challenged.

The root cause of the vulnerability is in the fact that both `initLPP` and `squeezeLPP` do not perform validation whether the LPP has been already initialized resp. finalized.
Spoofing of PreimageOracle by squeezing unfinalized LPPs
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#L656-L657


# Vulnerability details

## Short Summary & Impact

From [README: Attack ideas](https://github.com/code-423n4/2024-07-optimism?tab=readme-ov-file#attack-ideas-where-to-focus-for-bugs)

> The Preimage Oracle is trusted to only provide accurate data. Is there a way to get invalid data added and use it to prove an invalid execution trace?

We demonstrate that arbitrary `Keccak256`-encoded data  can be added to authorized preimage parts by squeezing unfinalized  Large Preimage Proposals (LPPs), thus completely bypassing the challenge period for LPPs. The vulnerability is in the wrong timestamp check of the `squeezeLPP` function, which allows to squeeze an LPP at any time point before it has been finalized.

Spoofing PreimageOracle data can lead to arbitrary impacts, including (but not limited to) withdrawing all tokens contained in Optimism.


## Detailed Description

Function [squeezeLPP](https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L640) contains the following [timestamp check](https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L656-L657):

```solidity
// Check if the challenge period has passed since the proposal was finalized.
if (block.timestamp - metaData.timestamp() <= CHALLENGE_PERIOD) revert ActiveProposal();
```

The problem with the above check is that after calling [initLPP](https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L417), we have `metaData.timestamp() == 0`, thus for realistic timestamps (e.g. `1722112271`) the check above is immediately enabled (doesn't revert). This allows to execute `squeezeLPP` at any time point before the LPP in question is finalized, the first time when `metaData.timestamp()` is assigned a non-zero value. As a result, completely fake data can be easily introduced into the authorized preimage mapping, leading to arbitrary impacts.

## Proof of Concept

From [README: A Note on POCs](https://github.com/code-423n4/2024-07-optimism?tab=readme-ov-file#a-note-on-pocs)
> Forge tests are acceptable for any POCs that only need to demonstrate small or isolated properties, but the smart contract test suite is not configured with the full honest challenger behavior nor does it use the actual fault proof VM as the step function.

As the present finding is local only to `PreimageOracle` contract, as well as is independent of the honest challenger behavior or the actual fault proof VM, we employ Forge tests for the PoC. In the PoC we:

1. Warp to a realistic timestamp, thus creating the conditions for the exploit.
2. Initialize an LPP (`initLPP`)for
Valid _moves_ would be rejected due to a wrong check
Audit: 2024-07-optimism-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-optimism/blob/2e11e15e7a9a86f90de090ebf9e3516279e30897/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol#L319-L416


# Vulnerability details


## Proof of Concept

Take a look at https://github.com/code-423n4/2024-07-optimism/blob/2e11e15e7a9a86f90de090ebf9e3516279e30897/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol#L319-L416

```solidity
    function move(Claim _disputed, uint256 _challengeIndex, Claim _claim, bool _isAttack) public payable virtual {

        // snip
        // INVARIANT: The `msg.value` must exactly equal the required bond.
        if (getRequiredBond(nextPosition) != msg.value) revert IncorrectBondAmount();

        // Compute the duration of the next clock. This is done by adding the duration of the
        // grandparent claim to the difference between the current block timestamp and the
        // parent's clock timestamp.
        Duration nextDuration = getChallengerDuration(_challengeIndex);

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

        // 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);
        }

        // Construct the next clock with the new duration and the current block timestamp.
        Clock nextClock = LibClock.wrap(nextDuration, Timestamp.wrap(uint64(block.timestamp)));

        // snip
        emit Move(_challengeIndex, _claim, msg.sender);
    }
```

This function is used to place both `attack` and `defend` moves, it includes multiple checks to ensure various invariants hold some of which have been attached to the snippet above.

Now whereas this function would execute normally, there is an issue when the move's next duration clock is `= MAX_CLOCK_DURATION` seconds of time.

Would be key to note that the invalid state the protocol doesn't wa
Attacker can modify previously verified entry in `PreimageOracle`
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#L686-L689


# Vulnerability details

## Description
Large preimage proposals can be added to the `PreimageOracle` after they went through a `CHALLENGE_PERIOD` during which anyone can challenge it if they find that it is invalid.
After the challenge period is over and the proposal has not been contested, it will be added to the oracle. To separate proposals from accepted preimages, there are two mappings `proposal*` and `preimage*`.
Entries in `proposal*` mappings are, as the name suggests, proposals and entries in `preimage*` are accepted preimages. Now the problem is, that entries are not removed from the `proposal*` mappings, making it possible to modify a `preimage*` mapping after the respective entry has already been finalized.

In order to propose a `LPP`, a user has to first call `initLPP`, then `addLeavesLPP` and once the challenge period has passed, `squeezeLPP` which then moves the proposal to the confirmed preimages.  
First looking at `initLPP`:

```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;
}
```
Here we can see, that when initializing an `LPP`, the user can specify a `_uuid`, `_partOffset` and `_claimedSize`.

Looking at `squeezeLPP`:

```Solidity
function squeezeLPP(
    address _claimant,
    uint256 _uuid,
    LibKeccak.StateMatrix memory _stateMatrix,
    Leaf calldata _preState,
    bytes32[] calldata _preStateProof,
    Leaf calldata _postState,
    bytes32[] calldata _postStateProof
)
    external
{
    LPPMetaData metaData = proposalMetadata[_claimant][_uuid];

    // Check if the proposal was countered.
    if (metaData.countered()) revert BadProposal();

    // Check if the challenge period has passed since the proposal was finalized.
    if (block.timestamp - metaData.timestamp() <= CHALLENGE_PERIOD) revert ActiveProposal();

    // Verify that both leaves are present in the merkle tree.
squeezeLPP can be called on unfinalized proposals to write arbitrary data
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

# Vulnerability details

## Impact

When calling `squeezeLPP()`, it verifies that the proposal has been finalized for a duration of `CHALLENGE_PERIOD` seconds:

    ```solidity
    if (block.timestamp - metaData.timestamp() <= CHALLENGE_PERIOD) revert ActiveProposal();
    ```

A vulnerability arises if the proposal was never finalized finalized because in such cases, `metaInfo.timestamp()` returns 0 and `block.timestamo - metaDAta.timestamp()` check will pass.

This allows for arbitrary encoded data to be added to previously initialized preimage when calling `squeezeLPP`, because it will update the storage with malicious data.

## Tools Used

Eyes

## Recommended Mitigation Steps

```diff
         if (metaData.countered()) revert BadProposal();
+        if (metaData.timestamp() == 0) revert ActiveProposal();
``





## Assessed type

Other
Break in logic implementation of `move`
Audit: 2024-07-optimism-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details


## Summary

The `move` implementation logic allows for a bypass due to break in logic leading to `move` still being challenged even when its clock has exceeded the `MAX_CLOCK_DURATION`

## Description

According to the natspec:
-  INVARIANT: A move can never be made once its clock has exceeded `MAX_CLOCK_DURATION` seconds of time.

However, this invariant does not hold due to the fact that, the check which is meant to explicitly handle this is not being properly implemented.

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

The above explicitly checks that if the `nextDuration` equals the `MAX_CLOCK_DURATION`, it should revert.
But in certain situations, the `nextDuration` is going to be greater than the `MAX_CLOCK_DURATION` in which according to the INVARIANT is also meant to revert but wont do so in this case.

It is known that the main reason for the above implementation is to allow for `CLOCK_EXTENSION` which is used to grant the potential grandchild's clock extensions seconds. This checks if the `(nextDuration.raw() > MAX_CLOCK_DURATION.raw() - CLOCK_EXTENSION.raw())`. However, if this is'nt the case i.e there is no `CLOCK_EXTENSION`, a move can still be made when it's clock exceeds `MAX_CLOCK_DURATION` seconds of time


## Impact

- Break in logic
- Claim can still be made after maximum clock duration has elapsed.
- Possibility of a dishonest actor proving a false claim.
- Removes the safeguards meant to be in place

## Proof of Code

```solidity
    function move(Claim _disputed, uint256 _challengeIndex, Claim _claim, bool _isAttack) public payable virtual {
        // INVARIANT: Moves cannot be made unless the game is currently in progress.
        if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress();

        ...

        Duration nextDuration = getChallengerDuration(_challengeIndex);

        // 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(); //@audit should be >=, otherwise a move can still be challenged if its clock has exceeded the MAX_CLOCK_DURATION

        // 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