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
Rank: 79/102
Findings: 1
Award: $56.63
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: brgltd
Also found by: 0x73696d616f, 0xAce, 0xSmartContract, 0xWaitress, 0xkazim, 0xnev, Aymen0909, BGSecurity, Bauchibred, Cayo, ChrisTina, Franfran, IceBear, Infect3d, Kose, Lilyjjo, PNS, RaymondFam, Sathish9098, Team_Rocket, Udsen, YakuzaKiawe, YoungWolves, berlin-101, bin2chen, btk, codeslide, fatherOfBlocks, frazerch, kodyvim, koxuan, lfzkoala, lukris02, matrix_0wl, nadin, naman1778, sashik_eth, tnevler, volodya, wonjun, yjrwkk
56.6347 USDC - $56.63
The variable names used to represent rewardDistributors.length within the Comptroller.sol
contract are inconsistent, decreasing the searchability of code.
These should be changed to the most commonly used name rewardDistributorsCount
to maintain syntax consistency and increase readability.
Comptroller.sol#L93 - "RewardsDistributorsLength"
Comptroller.sol#L940 - "RewardsDistributorsLen"
Comptroller.sol#L1120 - "RewardsDistributorsLength"
Within ErrorReporter.sol
there are 13 instances of unused errors. Unnecessary or obsolete code should be removed or at least explained in the specification or in-line comments. Removal of these instances would improve readability.
ErrorReporter.sol#L7 - TransferComptrollerRejection()
ErrorReporter.sol#L9 - TransferNotEnough()
ErrorReporter.sol#L10 - TransferTooMuch()
ErrorReporter.sol#L12 - MintComptrollerRejection()
ErrorReporter.sol#L15 - RedeemComptrollerRejection()
ErrorReporter.sol#L19 - BorrowComptrollerRejection()
ErrorReporter.sol#L23 - RepayBorrowComptrollerRejection()
ErrorReporter.sol#L29 - LiquidateComptrollerRejection()
ErrorReporter.sol#L32 - LiquidateAccrueBorrowInterestFailed()
ErrorReporter.sol#L37 - LiquidateRepayBorrowFreshFailed()
ErrorReporter.sol#L39 - LiquidateSeizeComptrollerRejection()
ErrorReporter.sol#L42 - SetComptrollerOwnerCheck()
ErrorReporter.sol#L51 - ReduceReservesAdminCheck()
The loop bounding check _ensureMaxLoops()
is insufficiently called within the setActionsPaused()
function of Comptroller.sol
.
The check passes in marketsCount
to bound a loop, but fails to implement proper bound checks on a subsequent nested loop.
Comptroller.sol#L895 - setActionPaused()
This can be resolved by passing marketsCount * actionsCount
into the _ensureMaxLoops()
call.
Incorrectly initializing or updating maxLoopsLimit
through _setMaxLoopsLimit()
could result in an irrecoverably high threshold for loop iteration capacity. This is due to the require
statement specifying that maxLoopsLimit
may never be decreased.
If this threshold is improperly set it would permanently undermine all _ensureMaxLoops()
checks throughout the system, which could present unexpected behavior and denial of service vulnerabilities with no remediation.
It is recommended to introduce some guarded functionality that allows admins to adjust the iteration threshold in both directions, to protect against accidental misconfiguration.
The loop executed inside the liquidateAccount()
function initiates the following control flow, iterating to the length of ordersCount:
Comptroller.liquidateAccount()
--> VToken.forceLiquidateBorrow()
--> VToken._liquidateBorrowFresh()
--> Comptroller.preLiquidateHook()
--> Comptroller._getCurrentLiquiditySnapshot()
--> Comptroller._getHypotheticalLiquiditySnapshot()
(Initiated from loop)
Comptroller.liquidateAccount()
--> VToken.forceLiquidateBorrow()
Comptroller.sol#L669 - liquidateAccount()
VToken.forceLiquidateBorrow()
--> VToken._liquidateBorrowFresh()
VToken.sol#L459 - ForceLiquidateBorrow()
VToken._liquidateBorrowFresh()
--> Comptroller.preLiquidateHook()
VToken.sol#L1026 - _liquidateBorrowFresh()
Comptroller.preLiquidateHook()
--> Comptroller._getCurrentLiquiditySnapshot()
Comptroller.sol#L457 - PreLiquidateHook()
Comptroller._getCurrentLiquiditySnapshot()
--> Comptroller._getHypotheticalLiquiditySnapshot()
Comptroller.sol#L1281 - _getCurrentLiquiditySnapshot()
Nested / Unchecked Loop Comptroller.sol#L1307 - _getHypotheticalLiquiditySnapshot()
Inside the final function _getHypotheticalLiquiditySnapshot()
another loop is executed (unchecked), iterating to the length of assetCount
.
The initial loop inside liquidateAccount()
receives bound checks on ordersCount
from the _ensureMaxLoops()
call, although the nested nature of the second loop (inside _getHypotheticalLiquiditySnapshot()
) renders the current bounding of loop iterations redundant, potentially resulting in denial of service if the resulting combine iterations are sufficiently high.
This is because the nested loop multiples the iterations by a factor of the first max iterations (ordersCount * assetCount
).
To appropriately limit the iterations, liquidateAccount()
should call _ensureMaxLoops()
passing in ordersCount * assetCount
. This would not affect alternate control flows, since calling liquidateAccount()
always results in _getHypotheticalLiquiditySnapshot()
being called.
#0 - c4-judge
2023-05-18T18:26:14Z
0xean marked the issue as grade-b