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

Unverified BlockHashOracle Input Enables Profit Unlock Period Manipulation

Summary

A security asymmetry exists between the two verification paths for updating the profit unlock period in the Curve Storage Proofs protocol. The verifyPeriodByBlockHash function properly validates the entire block header, while verifyPeriodByStateRoot relies solely on the BlockHashOracle for state root verification without additional cross-validation, creating a potential avenue for parameter manipulation.

The ScrvusdVerifierV2 contract offers two methods to verify and update the profit_max_unlock_time parameter:

function verifyPeriodByBlockHash(
bytes memory _block_header_rlp,
bytes memory _proof_rlp
) external returns (bool) {
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"
);
// ...
}
function verifyPeriodByStateRoot(
uint256 _block_number,
bytes memory _proof_rlp
) external returns (bool) {
bytes32 state_root = IBlockHashOracle(BLOCK_HASH_ORACLE).get_state_root(_block_number);
// ...
}

The first method validates the block hash, which implicitly verifies the entire block header including the state root. The second method trusts the state root directly from the BlockHashOracle without independent verification.

Impact

The security asymmetry between verification paths creates a significant vulnerability in the protocol's cross-chain parameter relay system. By trusting the BlockHashOracle directly for state roots, the verifyPeriodByStateRoot function enables potential manipulation of the profit_max_unlock_time parameter if the oracle is compromised. This critical parameter governs the velocity of profit unlocking in the scrvUSD vault, directly influencing yield recognition rates and token price trajectory.

This architectural flaw bypasses the cryptographic validation present in the block header approach, effectively creating a privileged path that circumvents security guarantees. When exploited, an attacker could alter yield distribution mechanics across the entire scrvUSD ecosystem, affecting all token holders' returns and potentially creating inconsistent pricing across different chains.

The vulnerability strikes at the core security assumption of the oracle system - that state data from Ethereum is verified with equivalent cryptographic assurance regardless of the verification method used. When this assumption fails, the entire cross-chain oracle model is compromised, undermining the protocol's ability to maintain consistent, manipulation-resistant pricing for scrvUSD across blockchain networks.

Recommendations

function verifyPeriodByStateRoot(
uint256 _block_number,
bytes memory _block_header_rlp,
bytes memory _proof_rlp
) external returns (bool) {
// Parse and verify the block header
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"
);
require(block_header.number == _block_number, "Block number mismatch");
// Use the verified state root from the block header
bytes32 state_root = block_header.stateRootHash;
// Continue with existing implementation using the verified state root
uint256 period = _extractPeriodFromProof(state_root, _proof_rlp);
return IScrvusdOracleV2(SCRVUSD_ORACLE).update_profit_max_unlock_time(period, block_header.number);
}

This fix aligns with the protocol's apparent security model of using verified block headers as the foundation for trustworthy state proofs.

Updates

Lead Judging Commences

0xnevi Lead Judge about 2 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.