29068 - [SC - Medium] AaveOracle contract does not verify price stale...
Submitted on Mar 6th 2024 at 14:02:31 UTC by @Paludo0x for Boost | ZeroLend
Report ID: #29068
Report type: Smart Contract
Report severity: Medium
Target: https://explorer.zksync.io/address/0x785765De3E9ac3D8eEb42B4724A7FEA8990142B8
Impacts:
Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield
Description
Brief/Intro
AaveOracle contract relies on Pyth oracles to retrieve assets price feeds and the actual implementation doesn't check whether prices are fresh or staled. Assets prices are retrieved by means of function AaveOracle::getAssetPrice()
. For instance this function is called by LiquidationLogic::_calculateAvailableCollateralToLiquidate
which "calculates how much of a specific collateral can be liquidated, given a certain amount of debt asset". Therefore a wrong calculation can lead to liquidate q wrong amount of base assets.
Vulnerability Details
This is the code snippet of function AaveOracle::getAssetPrice()
.
The price oracle address is stored in mapping assetsSources[asset]
. The price is retrieved by means of int256 price = source.latestAnswer();
and only if price = 0
the _fallbackOracle is called. Therefore even if price is far from the actual market price it's deemed acceptable.
Let's take as an example https://explorer.zksync.io/address/0xf531672C92Ad4658c54B4fBE855029Df43c57390#contract which is the oracle for asset 0x4B9eb6c0b6ea15176BBF62841C6B2A8a398cb656 (Dai Stablecoin).
You can see that PythAggregatorV3::latestAnswer()
calls pyth.getPriceUnsafe(priceId);
pyth
address correspond to 0xf087c864AEccFb6A2Bf1Af6A0382B0d0f6c5D834 which is a proxy contract. While the implementation can be found here: https://explorer.zksync.io/address/0xb1b239054fa2e37da736e43102d175f21c5f7450#contract
You can see that getPriceUnsafe
just query the price feed without checking if it is up to date
Impact Details
This are examples of where AaveOracle::getAssetPrice()
is used:
LiquidationLogic::_calculateAvailableCollateralToLiquidate
which is called byLiquidationLogic::executeLiquidationCall
which i turns is called by Pool::liquidationCall`GenericLogic::calculateUserAccountData
retrieves assets price to verify account health factor, and it's called in turn byValidationLogic::validateHealthFactor
orValidationLogic.validateBorrow
That means that if price is staled, especially in a turbolent market condition, can lead to incorrect liquidations or worse, lack of liquidation, and health factor wrong calculation
Fix suggestion
The advice is to implement a verification if the feed last update is within a thresold by means of PythAggregatorV3::latestTimestamp()
Proof of concept
The requirement of price staleness verification is suggested by Pyth protocol in NATSPEC of function pyth.getPriceUnsafe(priceId);
Next the following sentence can be found at function docs: https://docs.pyth.network/price-feeds/api-reference/evm/get-price-unsafe
"This function may return a price from arbitrarily far in the past. It is the caller's responsibility to check the returned publishTime to ensure that the update is recent enough for their use case."
Last updated