Venus Protocol Isolated Pools - 0x73696d616f's results

Earn, Borrow & Lend on the #1 Decentralized Money Market on the BNB Chain

General Information

Platform: Code4rena

Start Date: 08/05/2023

Pot Size: $90,500 USDC

Total HM: 17

Participants: 102

Period: 7 days

Judge: 0xean

Total Solo HM: 4

Id: 236

League: ETH

Venus Protocol

Findings Distribution

Researcher Performance

Rank: 39/102

Findings: 2

Award: $306.37

QA:
grade-b

🌟 Selected for report: 1

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: 0x73696d616f

Also found by: 0xadrii, 0xbepresent, 0xkazim, J4de, peanuts, pontifex, rvierdiiev, volodya

Labels

bug
2 (Med Risk)
downgraded by judge
primary issue
satisfactory
selected for report
M-02

Awards

249.7365 USDC - $249.74

External Links

Lines of code

https://github.com/code-423n4/2023-05-venus/blob/main/contracts/Comptroller.sol#L199 https://github.com/code-423n4/2023-05-venus/blob/main/contracts/Comptroller.sol#L299 https://github.com/code-423n4/2023-05-venus/blob/main/contracts/Comptroller.sol#L324 https://github.com/code-423n4/2023-05-venus/blob/main/contracts/Comptroller.sol#L553 https://github.com/code-423n4/2023-05-venus/blob/main/contracts/Comptroller.sol#L1240 https://github.com/code-423n4/2023-05-venus/blob/main/contracts/Comptroller.sol#L1255 https://github.com/code-423n4/2023-05-venus/blob/main/contracts/Comptroller.sol#L578

Vulnerability details

Impact

Incorrect borrowBalance and token collateral values. Could lead to many different exploits, such has:

  • Users with a collateral token that fell in price substancially can borrow another underlying whose price has not been updated and earn profit.
  • Users can borrow/redeem/transfer more if the interest/price was not updated.

Proof of Concept

In the Comptroller, the total collateral balance and borrow balance is calculated at _getHypotheticalLiquiditySnapshot(...). This function calculates these balances in the following loop:

for (uint256 i; i < assetsCount; ++i) { VToken asset = assets[i]; // Read the balances and exchange rate from the vToken (uint256 vTokenBalance, uint256 borrowBalance, uint256 exchangeRateMantissa) = _safeGetAccountSnapshot( asset, account ); // Get the normalized price of the asset Exp memory oraclePrice = Exp({ mantissa: _safeGetUnderlyingPrice(asset) }); // Pre-compute conversion factors from vTokens -> usd Exp memory vTokenPrice = mul_(Exp({ mantissa: exchangeRateMantissa }), oraclePrice); Exp memory weightedVTokenPrice = mul_(weight(asset), vTokenPrice); // weightedCollateral += weightedVTokenPrice * vTokenBalance snapshot.weightedCollateral = mul_ScalarTruncateAddUInt( weightedVTokenPrice, vTokenBalance, snapshot.weightedCollateral ); // totalCollateral += vTokenPrice * vTokenBalance snapshot.totalCollateral = mul_ScalarTruncateAddUInt(vTokenPrice, vTokenBalance, snapshot.totalCollateral); // borrows += oraclePrice * borrowBalance snapshot.borrows = mul_ScalarTruncateAddUInt(oraclePrice, borrowBalance, snapshot.borrows); // Calculate effects of interacting with vTokenModify if (asset == vTokenModify) { // redeem effect // effects += tokensToDenom * redeemTokens snapshot.effects = mul_ScalarTruncateAddUInt(weightedVTokenPrice, redeemTokens, snapshot.effects); // borrow effect // effects += oraclePrice * borrowAmount snapshot.effects = mul_ScalarTruncateAddUInt(oraclePrice, borrowAmount, snapshot.effects); } }

As can be seen, the oracle price is not updated via calling updatePrice(...), nor the borrow interest is updated by calling AccrueInterest(...). Only the corresponding VToken that called the borrow(...), transfer(...)orredeem(...)` has an updated price and interest, which could lead to critical inaccuracies for accounts with several VTokens.

Tools Used

Vscode, Hardhat

Update the price and interest of every collateral except the VToken that triggered the hook, which has already been updated. Similarly to what is being done on healAccount(...).

for (uint256 i; i < userAssetsCount; ++i) { userAssets[i].accrueInterest(); oracle.updatePrice(address(userAssets[i])); }

Assessed type

Oracle

#0 - c4-judge

2023-05-17T21:47:43Z

0xean marked the issue as duplicate of #88

#1 - c4-judge

2023-06-05T14:09:17Z

0xean changed the severity to 2 (Med Risk)

#2 - c4-judge

2023-06-05T14:09:45Z

0xean marked the issue as satisfactory

#3 - c4-judge

2023-06-05T16:32:01Z

0xean marked the issue as selected for report

#4 - chechu

2023-06-08T10:28:24Z

To share more info related to this topic:

  • As we said in #88, we assume the prices in the rest of the markets will be updated frequently because they are updated every time other users interact directly with these other markets
  • Moreover, "update" in our context only affects the TWAP oracle. As you can see here, our oracles system uses Chainlink, Binance Oracle, Pyth, and TWAP sources.
    • Chainlink and Binance prices are not updated by Venus anyway. Chainlink and Binance update these price feeds following the classical rules of heartbeat and deviation (example here). In the Oracle system, we check the last time these prices were updated and discard them (reverting the TX) if they are staled.
    • TWAP needs a proactive update, executed by Venus when the mentioned oracle.updatePrice is invoked. If no one invokes this update function for an asset later used indirectly by another user, and the difference between the price offered by the TWAP oracle and the rest of the oracles (i.e. Chainlink) is greater than a threshold configure in our Oracle system, the TX will be also reverted. Could this generate a DoS? I suppose that potentially it can do it, but as soon as a user interact with the "staled" asset in Venus the block will disappear

#5 - 0xean

2023-06-08T12:45:59Z

@chechu - I believe I should mark #104 as a dupe of this as well. Since this issue talks about both prices updated and accruing interests.

we assume the prices in the rest of the markets will be updated frequently because they are updated every time other users interact directly with these other markets

This assumption I think has risks in which the wardens are calling out. Its hard to say how real these risks are apriori without making assumptions about usage of all the markets.

I think these issues should be batched together over concerns around accrueInterest and price updates into a single M issue.

#6 - chechu

2023-06-08T14:26:17Z

This assumption I think has risks in which the wardens are calling out. Its hard to say how real these risks are apriori without making assumptions about usage of all the markets.

@0xean IMHO I think the risk is low, but I could understand the concern and the lack of a guarantee in the code.

Regarding grouping this and #104, #104 only mentions the outdated interests and this mentions the outdated interest and the outdated prices. So, up to you, you are the judge :)

#7 - 0xean

2023-06-08T14:39:31Z

Sorry, I meant duping #104 against this. thanks for the response, will judge accordingly!

#8 - c4-judge

2023-06-08T14:39:54Z

0xean marked the issue as duplicate of #486

#9 - 0xean

2023-06-08T14:40:40Z

Issues mentioning both will receive 100% credit while issues mentioning one will receive 50%

#10 - c4-judge

2023-06-08T17:12:14Z

0xean marked the issue as not a duplicate

#11 - c4-judge

2023-06-08T17:12:33Z

0xean marked the issue as primary issue

L-01 VToken, transfer(...), missing 0x0 address check

transfer(...)

L-02 VToken, transferFrom(...) allows the owner to be the spender

transferFrom(...) This is different than what is stated on the EIP20, where a user would have to explicitly increase their balance. Thus, the function should revert if the owner is the spender and they haven't increased their balance.

L-03 Comptroller, VToken, It's possible to enter any number of markets by minting and then borrowing instead of calling enterMarkets(...)

enterMarkets(...)

L-04 VToken, _initialize(...) underlying no contract size check.

_initialize(...) EOAs won't revert on IERC20Upgradeable(underlying).totalSupply();

L-05 PoolRegistry, AddMarket(...), if the input.rateModel is wrong by mistake, the wrong parameters are set.

AddMarket(...)

Consider creating 2 separate functions with different arguments.

N-01 VToken, getAccountSnapshot(...), return variable error confuses linter and marks it blue

error at getAccountSnapshot(...)

N-02 BaseJumpRateMovelV2, getBorrowRate(...) calculate normal rate before the conditions

getBorrowRate(...) The normalRate could be calculated before the conditions and reused.

N-03 RewardsDistributor, _updateRewardTokenSupplyIndex(...), supplyState.block = blockNumber out of if/else

_updateRewardTokenSupplyIndex Avoids duplicate code.

#0 - c4-judge

2023-05-18T18:28:32Z

0xean marked the issue as grade-b

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter