code-423n4 / 2024-06-size-validation

1 stars 0 forks source link

A lender can Bypassing the validateUserIsNotBelowOpeningLimitBorrowCR Check in BuyCreditMarket there breaking a main invariant #553

Closed c4-bot-10 closed 2 months ago

c4-bot-10 commented 2 months ago

Lines of code

https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/Size.sol#L181-L183 https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/Size.sol#L192

Vulnerability details

Impact

The issue lies in the protocol's logic for checking the Credit Ratio (CR) during the purchase of a credit position via the BuyCreditMarket function.

Specifically:

Here's the vulnerability that can be exploited:

Proof of Concept

Exploitation Scenario:

  1. Setup: Assume the protocol has a CR opening limit set to 1.5(Based on the test setup in test).
  2. User Action: A user purchases an existing credit position with a CR close to the liquidation threshold (1.3).
  3. Outcome: Since the creditPositionId is not RESERVED_ID, the check validateUserIsNotBelowOpeningLimitBorrowCR is bypassed, allowing the user to purchase a position with a dangerously low CR.

    Potential Consequences

Code Reference:

Test Case:

  1. Setup: Set up a scenario where the opening CR limit is 1.5 and liquidation Ration 1.3.
  2. User Action: Create a credit position with a CR of 1.3.
  3. Purchase: Attempt to purchase this position using the buyCreditMarket function.
  4. Expected Result: The purchase should succeed despite the CR being below the opening limit.

Tool Used

Manual Code Analysis

Recommended Mitigation Steps

Implement Comprehensive CR Check:

Assessed type

Error

Tomiwasa0 commented 2 months ago

Thank you for judging @hansfriese,

In Sell Credit Market a Lender Can sell Credit in 2 ways,

  1. With Reserve Id - Checks BorrowCR
  2. with Credit Id - Uses Future credit to get funds like in compensate (CR doesn't matter)

Note in Buy credit there is no If and else statement nested so we don't need to check both as already stated above.

 if (params.creditPositionId == RESERVED_ID) {
            // slither-disable-next-line unused-return
            state.createDebtAndCreditPositions({
                lender: msg.sender,
                borrower: msg.sender,
                futureValue: creditAmountIn,
                dueDate: block.timestamp + tenor
            });
        }

        state.createCreditPosition({
            exitCreditPositionId: params.creditPositionId == RESERVED_ID
                ? state.data.nextCreditPositionId - 1
                : params.creditPositionId,
            lender: params.lender,
            credit: creditAmountIn
        });

In Buy Credit Market a Lender Can buy Credit in 2 ways,

  1. With Reserve Id - Checks BorrowCR
  2. with Credit Id - Bypass Check (eventhough we are buying, we can buy below Opening Borrow CR )

    if (params.creditPositionId == RESERVED_ID) {
    // slither-disable-next-line unused-return
    state.createDebtAndCreditPositions({
        lender: msg.sender,
        borrower: borrower,
        futureValue: creditAmountOut,
        dueDate: block.timestamp + tenor
    });
    } else {
    state.createCreditPosition({
        exitCreditPositionId: params.creditPositionId,
        lender: msg.sender,
        credit: creditAmountOut
    });
    }

    We should check the OpeningBorrowCr always in BUYcreditMarket regardless of what was used (Credit/Reserve Id). The below allows for a bypass when credit is used

    if (params.creditPositionId == RESERVED_ID) {
          state.validateUserIsNotBelowOpeningLimitBorrowCR(params.borrower);
        }