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

Unverified Slot Existence in ScrvusdVerifierV1

Summary

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.

Vulnerability Details

The vulnerability is located in the _extractParametersFromProof function:

function _extractParametersFromProof(
bytes32 stateRoot,
bytes memory proofRlp
) internal view returns (uint256[PARAM_CNT] memory) {
RLPReader.RLPItem[] memory proofs = proofRlp.toRlpItem().toList();
require(proofs.length == PROOF_CNT, "Invalid number of proofs");
// Extract account proof
Verifier.Account memory account = Verifier.extractAccountFromProof(
SCRVUSD_HASH,
stateRoot,
proofs[0].toList()
);
require(account.exists, "scrvUSD account does not exist");
// Extract slot values
uint256[PARAM_CNT] memory params;
for (uint256 i = 1; i < PROOF_CNT; i++) {
Verifier.SlotValue memory slot = Verifier.extractSlotValueFromProof(
keccak256(abi.encode(PARAM_SLOTS[i])),
account.storageRoot,
proofs[i].toList()
);
// Slots might not exist, but typically we just read them.
params[i - 1] = slot.value; // @audit-info Unverified Slot Existence
}
return params;
}

The issue stems from the lack of verification after extracting slot values:

  1. The extractSlotValueFromProof function is called to extract values from storage slots.

  2. The comment "Slots might not exist, but typically we just read them" indicates awareness of the issue.

  3. The code assigns slot.value to the parameters array without verifying if the slot truly exists.

  4. Unlike the account existence check (which has a require statement), there's no equivalent verification for slot existence.

Impact

The impact of this vulnerability is moderately severe, considering the context:

  1. Data Integrity Concerns: The oracle could use non-existent or default values for price calculations, potentially leading to inaccurate price reporting.

  2. 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.

  3. Silent Failures: The system might operate on incorrect data without any indication of failure, making the issue difficult to detect during normal operation.

  4. Contract Evolution Risk: If the SCRVUSD contract changes its storage layout, the verifier might read from incorrect or non-existent slots without failing.

  5. Inconsistent State: Some parameters might be real while others are default or zero values, creating an inconsistent state for price calculations.

Likelihood Assessment

The likelihood of exploitation is assessed as MEDIUM-LOW based on:

  1. 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.

  2. 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

  3. 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

  4. Attack Surface: An attacker would need to interact with the verification functions directly and provide carefully crafted proofs.

Tools Used

  • Manual code review

  • Static analysis

  • Semantic understanding of Ethereum storage proofs

  • Context analysis of the codebase

Recommendations

  1. Implement Slot Existence Verification: Modify the extractSlotValueFromProof function to return a boolean indicating whether the slot exists, and then check this value:

(bool exists, uint256 value) = Verifier.extractSlotValueFromProof(
keccak256(abi.encode(PARAM_SLOTS[i])),
account.storageRoot,
proofs[i].toList()
);
require(exists, "Storage slot does not exist");
params[i - 1] = value;
  1. Fallback Values: If accepting non-existent slots is a design decision, implement explicit fallback values and document this behavior:

(bool exists, uint256 value) = Verifier.extractSlotValueFromProof(...);
params[i - 1] = exists ? value : FALLBACK_VALUES[i - 1];
  1. Storage Layout Validation: Implement a function to validate that the expected storage layout matches the actual layout of the SCRVUSD contract.

  2. Formal Verification: Consider using formal verification techniques to prove the correctness of the slot extraction process.

  3. Enhanced Logging: Add events that log when non-existent slots are encountered to facilitate monitoring and debugging.

  4. 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.

Updates

Lead Judging Commences

0xnevi Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

[invalid] finding-slot-not-check-verifierv1-v2

- 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.

Support

FAQs

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