DeFiLayer 1Layer 2
14,723 OP
View results
Submission Details
Severity: high
Invalid

Potential Merkle Proof TOCTOU Vulnerability in Curve Storage Proofs Protocol

Summary

Our analysis of the Curve Storage Proofs protocol implementation has identified a potential Time-of-Check Time-of-Use (TOCTOU) vulnerability in the Merkle proof verification mechanism. If left unaddressed prior to deployment, this issue could potentially allow manipulation of price parameters through the blockhash oracle verification mechanism. This finding stems from the separation between retrieving state roots and verifying proofs, combined with reliance on a single oracle source and absence of recency validation.

Vulnerability Details

The potential vulnerability exists in the Merkle proof validation mechanism implemented in ScrvusdVerifierV1.sol and ScrvusdVerifierV2.sol. These contracts verify cross-chain state proofs from Ethereum by:

  1. Requesting a state root from a blockhash oracle

  2. Using that state root to verify a Merkle proof

  3. Accepting parameter updates based on that verification

Our analysis identified three aspects that contribute to this finding:

1. TOCTOU Pattern in Verification Process

The verification appears to be split across multiple function calls, creating a time gap between checking and using the proof data:

// ScrvusdVerifierV1.sol:61-70
function verifyScrvusdByStateRoot(
uint256 _block_number,
bytes memory _proof_rlp
) external returns (uint256) {
bytes32 state_root = IBlockHashOracle(BLOCK_HASH_ORACLE).get_state_root(_block_number);
uint256[PARAM_CNT] memory params = _extractParametersFromProof(state_root, _proof_rlp);
// Use last_profit_update as the timestamp surrogate
return _updatePrice(params, params[5], _block_number);
}

This separation could potentially create a situation where the system's state might change between retrieval and usage of the state root.

2. Single Source of Truth for State Roots

The implementation appears to rely on a single blockhash oracle without cross-validation:

// ScrvusdVerifierV2.sol:37-48
function verifyPeriodByStateRoot(
uint256 _block_number,
bytes memory _proof_rlp
) external returns (bool) {
bytes32 state_root = IBlockHashOracle(BLOCK_HASH_ORACLE).get_state_root(_block_number);
uint256 period = _extractPeriodFromProof(state_root, _proof_rlp);
return IScrvusdOracleV2(SCRVUSD_ORACLE).update_profit_max_unlock_time(period, _block_number);
}

We couldn't identify additional validation to ensure the state root provided by the oracle is correct, which may create a potential single point of failure.

3. Missing Proof Recency Validation

We observed that both implementations do not appear to check whether provided block numbers are recent:

// Validation that could be considered in both verifier contracts
// require(block.number - _block_number <= MAX_BLOCK_AGE, "Proof too old");

The absence of recency checking could potentially allow replaying of old proofs if the blockhash oracle were to be compromised after deployment.

Potential Impact

If deployed without addressing this finding, there could be potential risk of price manipulation:

Potential Financial Considerations

  • If exploited after deployment, an attacker might be able to manipulate price parameters to create arbitrage opportunities

  • Based on a hypothetical scenario with $10M liquidity in affected pools, the potential impact could be significant

  • In a worst-case scenario, incorrect liquidations in lending protocols using the oracle could have cascading effects

Systemic Considerations

  • Could potentially affect the security guarantees of the Storage Proofs protocol

  • Might create risk for protocols relying on consistent pricing

  • Appears to diverge from best practices for cross-chain oracle security

We believe this finding warrants a HIGH severity classification based on:

  1. It relates to a critical security component (verification of state proofs)

  2. It could potentially be exploitable under certain conditions

  3. The potential impact could be significant if deployed without modifications

Possible Exploitation Path

If deployed without modifications, a potential attacker might:

  1. Monitor the blockhash oracle for vulnerabilities or operational issues

  2. Prepare manipulated price parameters that would be profitable when used

  3. Store historical state proofs from Ethereum blocks for potential replay

A hypothetical attack path might involve:

  1. Waiting for (or causing) oracle maintenance, upgrades, or malfunctions

  2. Submitting carefully crafted proofs that validate against incorrect state roots

  3. Executing trades using any resulting price discrepancies

  4. Potentially extracting value before the issue is detected

Suggested Remediation

We respectfully suggest considering the following improvements before deployment:

Immediate Considerations

  1. Block Recency Validation - A straightforward enhancement with significant security benefits

    function verifyScrvusdByStateRoot(
    uint256 _block_number,
    bytes memory _proof_rlp
    ) external returns (uint256) {
    // Require blocks to be within acceptable range (e.g., last 7 days)
    require(
    block.number - _block_number < 7200 * 7, // Approximately 7 days of blocks
    "Block too old for verification"
    );
    bytes32 state_root = IBlockHashOracle(BLOCK_HASH_ORACLE).get_state_root(_block_number);
    // Rest of function remains unchanged
    }

High Priority Considerations

  1. Multi-Oracle Consensus - Consider implementing state root validation across multiple sources

    function verifyScrvusdByStateRoot(
    uint256 _block_number,
    bytes memory _proof_rlp
    ) external returns (uint256) {
    // Get state root from primary oracle
    bytes32 primary_state_root = IBlockHashOracle(PRIMARY_ORACLE).get_state_root(_block_number);
    // Get state root from secondary oracle for validation
    bytes32 secondary_state_root = IBlockHashOracle(SECONDARY_ORACLE).get_state_root(_block_number);
    // Validate state roots match
    require(
    primary_state_root == secondary_state_root,
    "State root validation failed"
    );
    uint256[PARAM_CNT] memory params = _extractParametersFromProof(primary_state_root, _proof_rlp);
    return _updatePrice(params, params[5], _block_number);
    }
  2. Parameter Change Circuit Breakers - Consider adding thresholds for maximum parameter changes

    function _updatePrice(
    uint256[PARAM_CNT] memory params,
    uint256 ts,
    uint256 number
    ) internal returns (uint256) {
    // Calculate expected price impact
    uint256 oldPrice = getCurrentPrice();
    uint256 newPrice = calculatePriceFromParams(params);
    uint256 priceChange = calculatePriceChangePercentage(oldPrice, newPrice);
    // For significant changes, require additional verification
    if (priceChange > MAX_NORMAL_PRICE_CHANGE) {
    require(isMultiOracleConfirmed(number), "Large price change requires multi-oracle consensus");
    }
    return IScrvusdOracle(SCRVUSD_ORACLE).update_price(params, ts, number);
    }

Additional Security Enhancements to Consider

  1. Rate Limiting on Parameter Changes

    • Time-based rate limiting for consecutive updates

    • Maximum change thresholds for critical parameters

    • Additional verification for changes exceeding thresholds

  2. Time-Bounded Proof Validation

    • Maximum age for proofs (e.g., 24 hours)

    • Stricter recency requirements for high-value parameters

    • Time-weighted verification approaches

  3. Monitoring System

    • Blockhash oracle performance monitoring

    • Alerts for unusual parameter changes

    • Health checks for cross-chain verification system

We believe implementing these suggestions would significantly enhance the security posture of the Curve Storage Proofs protocol before deployment and align with industry best practices for secure cross-chain state verification.

Updates

Lead Judging Commences

0xnevi Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope
Assigned finding tags:

[invalid] finding-missing-proof-content-validation

- See [here]([https://github.com/CodeHawks-Contests/2025-03-curve?tab=readme-ov-file#blockhash-oracle)](https://github.com/CodeHawks-Contests/2025-03-curve?tab=readme-ov-file#blockhash-oracle) on how it is used to verify storage variable - All state roots and proofs must be verified by the OOS `StateProofVerifier` inherited as `Verifier` (where the price values and params are extracted), so there is no proof that manipulating timestamp/inputs can affect a price update - It is assumed that the OOS prover will provide accurate data and the OOS verifier will verify the prices/max unlock time to be within an appropriate bound/values - There is a account existance check in L96 of `ScrvusdVerifierV1.sol`, in which the params for price updates are extracted from

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.