code-423n4 / 2023-01-ondo-findings

0 stars 0 forks source link

Unbounded Chainlink oracle time delay vulnerability #352

Closed code423n4 closed 1 year ago

code423n4 commented 1 year ago

Lines of code

https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/lending/OndoPriceOracleV2.sol#L92

Vulnerability details

Summary

The contract OndoPriceOracleV2 allows for the owner to set an association between an fToken and a Chainlink oracle for price retrieval. The contract also allows the owner to set a maxmum amount of time delay that it will tolerate from all Chainlink oracles. However, there is no check in place to ensure that the time delay returned by a Chainlink oracle does not exceed this maximum.

Impact

An attacker could manipulate the time delay returned by a Chainlink oracle to be greater than the maximum amount of time delay set in the contract. This could cause the contract to use an outdated or malicious price for an fToken.

Proof of Concept

https://github.com/code-423n4/2023-01-ondo/blob/main/contracts/lending/OndoPriceOracleV2.sol

Tools Used

Manual.

Recommended Mitigation Steps

To fix this vulnerability, the contract should include a check to ensure that the time delay returned by a Chainlink oracle does not exceed the maximum amount of time delay set in the contract. If it does, the contract should not use the oracle's price for the fToken.

example :

function getUnderlyingPrice(address fToken) external view override returns 
(uint256) {
    uint256 price;

    // Get price of fToken depending on OracleType
    OracleType oracleType = fTokenToOracleType[fToken];
    if (oracleType == OracleType.MANUAL) {
      // Get price stored in contract storage
      price = fTokenToUnderlyingPrice[fToken];
    } else if (oracleType == OracleType.COMPOUND) {
      // Get associated cToken and call Compound oracle
      address cTokenAddress = fTokenToCToken[fToken];
      price = cTokenOracle.getUnderlyingPrice(cTokenAddress);
    } else if (oracleType == OracleType.CHAINLINK) {
      ChainlinkOracleInfo oracleInfo = fTokenToChainlinkOracle[fToken];
      ruby uint256 timeDelay = oracleInfo.oracle.getLatestRoundData().timestamp - block.timestamp;
      require(timeDelay <= maxChainlinkOracleTimeDelay, "Chainlink oracle time delay exceeded maximum allowed");
      price = oracleInfo.oracle.getLatestRoundData().result.mul(oracleInfo.scaleFactor).div(1e18);
    }
    // Check if a price cap has been set
    if (fTokenToUnderlyingPriceCap[fToken] != 0) {
      price = min(price, fTokenToUnderlyingPriceCap[fToken]);
    }
    return price;
  }

This example code checks if the time delay returned by the Chainlink oracle is less than or equal to the maximum amount of time delay set in the contract. If it is not, the contract will not use the oracle's price for the fToken.

c4-judge commented 1 year ago

trust1995 marked the issue as unsatisfactory: Out of scope