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

Replay attack

Summary

The functions that update the period (via update_profit_max_unlock_time) do not implement any explicit replay protection.

Vulnerability Details

An attacker might resubmit an old valid proof to force repeated updates, potentially disrupting the intended update cadence.

Impact

High

Tools Used

Manual

Recommendations

You can introduce a private mapping (processedBlockNumbers) that records the block number for which a proof has already been accepted. Both external functions (verifyPeriodByBlockHash and verifyPeriodByStateRoot in ScrvusdVerifierV2 contract) check that the block number hasn’t been processed before proceeding and mark it once processed. Here is the code

// Replay protection: mapping to track block numbers that have already been processed.

mapping(uint256 => bool) private processedBlockNumbers;

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"
);
// Replay protection: ensure this block number has not been processed yet.
require(!processedBlockNumbers[block_header.number], "Proof already processed");
processedBlockNumbers[block_header.number] = true;
uint256 period = _extractPeriodFromProof(block_header.stateRootHash, _proof_rlp);
return IScrvusdOracleV2(SCRVUSD_ORACLE).update_profit_max_unlock_time(period, block_header.number);
}
function verifyPeriodByStateRoot(
uint256 _block_number,
bytes memory _proof_rlp
) external returns (bool) {
// Replay protection: ensure this block number has not been processed.
require(!processedBlockNumbers[_block_number], "Proof already processed");
processedBlockNumbers[_block_number] = true;
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);
}
Updates

Lead Judging Commences

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

[invalid] finding-replay-proof-lack-nonce

- All proof generated within `_proof_rlp` is generated via the off-chain prover, so there is no concrete proof that this proofs are non-unique. - All state roots and proofs must be verified by the OOS `StateProofVerifier` inherited as `Verifier`, so there is no proof that manipulating proofs can successfully pass a price update

Support

FAQs

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