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

Stale Blockhash Oracle Enforces Incorrect Historical Data

Summary

The verifier relies on an external blockhash oracle to validate proofs for state updates. If the blockhash oracle returns stale data—due, for example, to latency—attackers may submit valid proofs corresponding to older blocks.

This can result in the oracle adopting historical vault parameters (such as manipulated values of total_supply, total_idle, or balance_of_self), thereby freezing price updates or causing incorrect pricing.

Vulnerability Details

The verifyScrvusdByBlockHash function validates a state proof by comparing the blockhash from a provided block header with the blockhash retrieved from an external oracle. The assumption is that the blockhash oracle delivers fresh data that reflects the current chain state.

https://github.com/CodeHawks-Contests/2025-03-curve/blob/main/contracts/scrvusd/verifiers/ScrvusdVerifierV1.sol#L52-L67

/// @param _block_header_rlp The RLP-encoded block header
/// @param _proof_rlp The state proof of the parameters
function verifyScrvusdByBlockHash(
bytes memory _block_header_rlp,
bytes memory _proof_rlp
) external returns (uint256) {
Verifier.BlockHeader memory block_header = Verifier.parseBlockHeader(_block_header_rlp);
require(block_header.hash != bytes32(0), "Invalid blockhash");
require(
block_header.hash == IBlockHashOracle(BLOCK_HASH_ORACLE).get_block_hash(block_header.number),
"Blockhash mismatch"
);
uint256[PARAM_CNT] memory params = _extractParametersFromProof(block_header.stateRootHash, _proof_rlp);
return _updatePrice(params, block_header.timestamp, block_header.number);
}

The verifier trusts that the blockhash oracle always returns recent blockhashes, within a maximum acceptable age (e.g., no older than 30 minutes as per project specs). However, if the oracle suffers delays or latency, it may return blockhashes from older blocks.

An attacker can exploit this by submitting valid proofs that reference these older blocks. If, during those historical blocks, vault parameters (like total_supply or balance_of_self) were manipulated or abnormal, the oracle will update its state based on stale data rather than the current state.

The use of outdated parameters leads to incorrect calculation of the raw_price. Because the smoothing mechanism only moderates per-update changes, basing an update on historical data can cause abrupt price deviations, undermining the integrity of the price feed.

Impact

The oracle may report prices that do not reflect current market conditions, creating opportunities for arbitrage.

Although the vulnerability hinges on the update frequency and freshness of the blockhash oracle, any delay that allows stale data to be used for state updates can lead to significant economic risks.

Tools Used

Manual Review

Recommendations

Modify the verifier to reject proofs associated with block numbers that exceed a predefined age limit (e.g., 30 minutes or a specific number of blocks).

Incorporate checks that compare the block timestamp from the block header with the current time, ensuring that proofs based on outdated timestamps are rejected.

Updates

Lead Judging Commences

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

[invalid] finding-block-number-no-input-check

- Anything related to the output by the `BLOCK_HASH_ORACLE` is OOS per \[docs here]\(<https://github.com/CodeHawks-Contests/2025-03-curve?tab=readme-ov-file#blockhash-oracle>). - The PoC utilizes a mock `BLOCK_HASH_ORACLE`which is not representative of the one used by the protocol - Even when block hash returned is incorrect, the assumption is already explicitly made known in the docs, and the contract allows a subsequent update within the same block to update and correct prices - All state roots and proofs must be verified by the OOS `StateProofVerifier` inherited as `Verifier`, so there is no proof that manipulating block timestamp/block number/inputs can affect a price update - There seems to be a lot of confusion on the block hash check. The block hash check is a unique identifier of a block and has nothing to do with the state root. All value verifications is performed by the OOS Verifier contract as mentioned above

Support

FAQs

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