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

Unvalidated profit_max_unlock_time

Summary

The oracle blindly trust the verifier inputs without sanitization. While the verifier is trusted to provide accurate data, the oracle should still enforce safety constraints (profit_max_unlock_time ≥ 1 hour).

https://github.com/CodeHawks-Contests/2025-03-curve/blob/198820f0c30d5080f75073243677ff716429dbfd/contracts/scrvusd/oracles/ScrvusdOracleV2.vy#L334-L348

Vulnerability detail

This vulnerability occur when the profit_max_unlock_time parameter is updated without validationallowing values that could destabilize the oracle price calculations. The profit_max_unlock_time determine how quickly profits are distributed to scrvUSD holders so unvalidated malicious or incorrect values can lead to division by zero leading to revert via overflow protection or even skewed price simulations.

In ScrvusdVerifierV2.sol, the verifier read the profit_max_unlock_time from the scrvUSD vault storage slot through state proofs:

uint256 internal PERIOD\_SLOT = 37; // profit\_max\_unlock\_time
function \_extractPeriodFromProof(...) internal view returns (uint256) {
Verifier.SlotValue memory slot = Verifier.extractSlotValueFromProof(
keccak256(abi.encode(PERIOD\_SLOT)), // Slot 37
account.storageRoot,
proofs\[1].toList()
);
return slot.value; // No validation
}

The verifier call the update_profit_max_unlock_time in the oracle contract with the extracted value:

@external
def update_profit_max_unlock_time(_profit_max_unlock_time: uint256, _block_number: uint256) -> bool:
access_control._check_role(UNLOCK_TIME_VERIFIER, msg.sender)
assert self.last_block_number <= _block_number, "Outdated"
self.profit_max_unlock_time = _profit_max_unlock_time
return prev_value != _profit_max_unlock_time

No validation is performed on _profit_max_unlock_time here.

The profit_max_unlock_time is critical in the _obtain_price_params function for simulating scrvUSD price:

def _obtain_price_params(parameters_ts: uint256) -> PriceParams:
period: uint256 = self.profit_max_unlock_time
number_of_periods: uint256 = (parameters_ts - params.last_profit_update) // period # Division by period!

Impact

- Division by Zero: If profit_max_unlock_time = 0 then number_of_periods calculation will fail making oracle price update stop breaking dependent protocols.

- Overly Large Values: If profit_max_unlock_time is set to an extremely large value (`1e18`), number_of_periods become `0`, freezing profit distribution simulations and oracle return stale prices leading to pool arbitrage losses.

Proof of Concept

1. A verifier submit profit_max_unlock_time = 0 through verifyPeriodByBlockHash.

2. profit_max_unlock_time is set to 0 in the oracle.

3. The next call to _obtain_price_params trigger division by zero reverting all price updates.

4. Stableswap-ng pools relying on the oracle cannot update prices leading to liquidity provider loss.

Recommendations

1. Ensure profit_max_unlock_time is within reasonable bounds during extraction:

function _extractPeriodFromProof() internal view returns (uint256) {
require(slot.value >= 3600 && slot.value <= 31536000, "Invalid period"); // 1 hour ≤ period ≤ 1 year
return slot.value;
}

2. Add validation in the oracle

@external
def update_profit_max_unlock_time(_profit_max_unlock is_time: uint256, _block_number: uint256) -> bool:
assert _profit_max_unlock_time >= 3600, "Period too low"
assert _profit_max_unlock_time <= 31536000, "Period too high"
# rest of the code
Updates

Lead Judging Commences

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

[invalid] finding-missing-proof-content-validation

- See [here]([https://github.com/CodeHawks-Contests/2025-03-curve?tab=readme-ov-file#blockhash-oracle)](https://github.com/CodeHawks-Contests/2025-03-curve?tab=readme-ov-file#blockhash-oracle) on how it is used to verify storage variable - All state roots and proofs must be verified by the OOS `StateProofVerifier` inherited as `Verifier` (where the price values and params are extracted), so there is no proof that manipulating timestamp/inputs can affect a price update - It is assumed that the OOS prover will provide accurate data and the OOS verifier will verify the prices/max unlock time to be within an appropriate bound/values - There is a account existance check in L96 of `ScrvusdVerifierV1.sol`, in which the params for price updates are extracted from

Support

FAQs

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