Ajna Protocol - 0xWaitress's results

A peer to peer, oracleless, permissionless lending protocol with no governance, accepting both fungible and non fungible tokens as collateral.

General Information

Platform: Code4rena

Start Date: 03/05/2023

Pot Size: $60,500 USDC

Total HM: 25

Participants: 114

Period: 8 days

Judge: Picodes

Total Solo HM: 6

Id: 234

League: ETH

Ajna Protocol

Findings Distribution

Researcher Performance

Rank: 38/114

Findings: 1

Award: $285.37

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: vakzz

Also found by: 0xStalin, 0xWaitress, SpicyMeatball

Labels

bug
3 (High Risk)
partial-50
sponsor confirmed
duplicate-8

Awards

285.3731 USDC - $285.37

External Links

Lines of code

https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-core/src/RewardsManager.sol#L310-L318

Vulnerability details

Impact

updateBucketExchangeRatesAndClaim has no check on the address of the pool.

Proof of Concept

malicious caller can pass in an arbitrary address of the pool, leading to a call to pass and updating a non-existing pool for _updateBucketExchangeRates and _updateBucketExchangeRate, subsequently inflating the updateRewardsClaimed of the curBurnEpoch.

    function updateBucketExchangeRatesAndClaim(
        address pool_,
        uint256[] calldata indexes_
    ) external override returns (uint256 updateReward) {
        updateReward = _updateBucketExchangeRates(pool_, indexes_);

        // transfer rewards to sender
        _transferAjnaRewards(updateReward);
    }

https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-core/src/RewardsManager.sol#L310-L318

  1. In _updateBucketExchangeRates, first an external view to the currentBurnEpoch() is called, which the malicious caller can return anything.

    function _updateBucketExchangeRates(
        address pool_,
        uint256[] memory indexes_
    ) internal returns (uint256 updatedRewards_) {
        // get the current burn epoch from the given pool
        uint256 curBurnEpoch = IPool(pool_).currentBurnEpoch();

https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-core/src/RewardsManager.sol#L671-L735

  1. Assuming the caller want to manipulate the value of updateRewardsClaimed, he would return a non-zero value for curBurnEpoch. Then _getPoolAccumulators and _updateBucketExchangeRateAndCalculateRewards are called.
else {
            // retrieve accumulator values used to calculate rewards accrued
            (
                uint256 curBurnTime,
                uint256 totalBurned,
                uint256 totalInterestEarned
            ) = _getPoolAccumulators(pool_, curBurnEpoch, curBurnEpoch - 1);

            if (block.timestamp <= curBurnTime + UPDATE_PERIOD) {

                // update exchange rates and calculate rewards if tokens were burned and within allowed time period
                for (uint256 i = 0; i < indexes_.length; ) {

                    // calculate rewards earned for updating bucket exchange rate
                    updatedRewards_ += _updateBucketExchangeRateAndCalculateRewards(
                        pool_,
                        indexes_[i],
                        curBurnEpoch,
                        totalBurned,
                        totalInterestEarned
                    );

                    // iterations are bounded by array length (which is itself bounded), preventing overflow / underflow
                    unchecked { ++i; }
                }

                uint256 rewardsCap            = Maths.wmul(UPDATE_CAP, totalBurned);
                uint256 rewardsClaimedInEpoch = updateRewardsClaimed[curBurnEpoch];

                // update total tokens claimed for updating bucket exchange rates tracker
                if (rewardsClaimedInEpoch + updatedRewards_ >= rewardsCap) {
                    // if update reward is greater than cap, set to remaining difference
                    updatedRewards_ = rewardsCap - rewardsClaimedInEpoch;
                }

                // accumulate the full amount of additional rewards
                updateRewardsClaimed[curBurnEpoch] += updatedRewards_;
            }

_getPoolAccumulators has no check on _pool as an valid input

https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-core/src/RewardsManager.sol#L636-L661

    function _getPoolAccumulators(
        address pool_,
        uint256 currentBurnEventEpoch_,
        uint256 lastBurnEventEpoch_
    ) internal view returns (uint256, uint256, uint256) {
        (
            uint256 currentBurnTime,
            uint256 totalInterestLatest,
            uint256 totalBurnedLatest
        ) = IPool(pool_).burnInfo(currentBurnEventEpoch_);

        (
            ,
            uint256 totalInterestAtBlock,
            uint256 totalBurnedAtBlock
        ) = IPool(pool_).burnInfo(lastBurnEventEpoch_);

        uint256 totalBurned   = totalBurnedLatest   != 0 ? totalBurnedLatest   - totalBurnedAtBlock   : totalBurnedAtBlock;
        uint256 totalInterest = totalInterestLatest != 0 ? totalInterestLatest - totalInterestAtBlock : totalInterestAtBlock;

        return (
            currentBurnTime,
            totalBurned,
            totalInterest
        );
    }

_updateBucketExchangeRateAndCalculateRewards has no check on _pool as an valid input

https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-core/src/RewardsManager.sol#L768-L804

    function _updateBucketExchangeRateAndCalculateRewards(
        address pool_,
        uint256 bucketIndex_,
        uint256 burnEpoch_,
        uint256 totalBurned_,
        uint256 interestEarned_
    ) internal returns (uint256 rewards_) {
        uint256 burnExchangeRate = bucketExchangeRates[pool_][bucketIndex_][burnEpoch_];
...

Tools Used

validate pool address is an valid input.

Assessed type

Rug-Pull

#0 - c4-judge

2023-05-18T10:26:41Z

Picodes marked the issue as primary issue

#1 - c4-sponsor

2023-05-19T19:03:44Z

MikeHathaway marked the issue as sponsor confirmed

#2 - c4-judge

2023-05-30T19:15:52Z

Picodes marked the issue as partial-50

#3 - Picodes

2023-05-30T19:16:19Z

Insufficient description of the impact and justification for the severity level

#4 - c4-judge

2023-05-30T19:17:31Z

Picodes marked issue #8 as primary and marked this issue as a duplicate of 8

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