In the ScrvusdVerifierV1
contract related to unverified storage slot existence in the _extractParametersFromProof
function. The function extracts values from storage slots without verifying whether these slots exist in the contract's storage. Despite an apparent awareness of this issue (as indicated by the in-code comment), the implementation proceeds without proper verification. This creates a potential security risk that could compromise the integrity of the price data provided by the oracle.
The vulnerability is located in the _extractParametersFromProof
function:
The issue stems from the lack of verification after extracting slot values:
The extractSlotValueFromProof
function is called to extract values from storage slots.
The comment "Slots might not exist, but typically we just read them" indicates awareness of the issue.
The code assigns slot.value
to the parameters array without verifying if the slot truly exists.
Unlike the account existence check (which has a require
statement), there's no equivalent verification for slot existence.
The impact of this vulnerability is moderately severe, considering the context:
Data Integrity Concerns: The oracle could use non-existent or default values for price calculations, potentially leading to inaccurate price reporting.
Partial Oracle Manipulation: If an attacker can craft proofs for non-existent slots, they might be able to influence some of the parameters used in price calculations.
Silent Failures: The system might operate on incorrect data without any indication of failure, making the issue difficult to detect during normal operation.
Contract Evolution Risk: If the SCRVUSD contract changes its storage layout, the verifier might read from incorrect or non-existent slots without failing.
Inconsistent State: Some parameters might be real while others are default or zero values, creating an inconsistent state for price calculations.
The likelihood of exploitation is assessed as MEDIUM-LOW based on:
Developer Awareness: The code comment "Slots might not exist, but typically we just read them" suggests this is a known design decision rather than an oversight. This indicates some level of risk acceptance in the design.
Technical Barriers: Exploiting this vulnerability would require:
Deep understanding of Ethereum storage proofs
Ability to craft valid proofs for non-existent slots
Knowledge of how the oracle uses these parameters for price calculations
Risk Mitigation Factors:
The account existence check provides some protection (the overall contract must exist)
The defined slot numbers (PARAM_SLOTS) should correspond to actual slots in a properly deployed SCRVUSD contract
External systems may have additional validation on price updates
Attack Surface: An attacker would need to interact with the verification functions directly and provide carefully crafted proofs.
Manual code review
Static analysis
Semantic understanding of Ethereum storage proofs
Context analysis of the codebase
Implement Slot Existence Verification: Modify the extractSlotValueFromProof
function to return a boolean indicating whether the slot exists, and then check this value:
Fallback Values: If accepting non-existent slots is a design decision, implement explicit fallback values and document this behavior:
Storage Layout Validation: Implement a function to validate that the expected storage layout matches the actual layout of the SCRVUSD contract.
Formal Verification: Consider using formal verification techniques to prove the correctness of the slot extraction process.
Enhanced Logging: Add events that log when non-existent slots are encountered to facilitate monitoring and debugging.
Documentation: If this is indeed an accepted design decision, thoroughly document the behavior and potential risks associated with reading non-existent slots.
By implementing these recommendations, particularly the first one, the contract can ensure that it only processes valid data from existing storage slots, thereby maintaining the integrity of the oracle's price feed.
- Looking at the OOS `StateProofVerifier` and `MerklePatriciaProofVerifier` contract that extracts the slot, the `exists` flag will be flagged as true as long as a non-zero length value is returned as seen [here](https://github.com/curvefi/curve-xdao/blob/3ff77bd2ccc9c88d50ee42d2a746fc7648c7ff2c/contracts/libs/StateProofVerifier.sol#L133C13-L136). From the `MerklePatriciaProofVerifier.extractProofValue`, the minimum length returned will be 1 as represenetd by `bytes(0)`. So this seems to be purely a sanity check that might not even be required. - A slot with zero values is only allowed when the proof provided by the prover correctly proofs that such values are included within the Merkle-Patricia-Tree. The values fetched from mainnet from the V3Vault stored in the merkle trie is likely checked before hand and aggregated into the MerkleTree.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.