Skip to content

Latest commit

 

History

History
70 lines (45 loc) · 4.13 KB

018.md

File metadata and controls

70 lines (45 loc) · 4.13 KB

Old Obsidian Nuthatch

High

Malicious lender can delete all lend offers.

Summary

A malicious lender can exploit the the logical error in the lend offer deletion process to delete all active lend offers.

Root Cause

Internal pre-conditions

No response

External pre-conditions

No response

Attack Path

  1. A malicious lender creates a lend offer with perpetual = false.
  2. The lend offer is fully matched with a borrow offer and is deleted from the list of active lend offers. The isActive flag of the lend offer is set to false and availableAmount decreases to zero.
  3. Assume that there are multiple active lend offers in the list.
  4. The lender adds funds to the deleted lend offer by calling DLOImplementation.addFunds() to increase the availableAmount of the lend offer again to a non-zero value.
  5. The lender fully matches the deleted lend offer to a new borrow offer by calling the DebitaV3Aggregator.matchOffersV3() function.
  6. The matchOffersV3() function calls DLOImplementation.acceptLendingOffer(), which then calls DebitaLendOfferFactory.deleteOrder().
  7. Due to the logic error in the deleteOrder() function, the first active lend offer in the list is deleted.
  8. The malicious lender repeats step 4 through 7 multiple times, deleting all active lend offers from the protocol.

Impact

A malicious lender can delete all active lend offers, effectively disrupting the entire lending system and rendering the protocol useless.

PoC

  1. Step 3 of the attack path is possible because DLOImplementation.addFunds() doesn't check isActive flag.
  2. Step 5 and is possible because matchOffsetV3() doesn't check the isActive flag but only verify the isLendOrderLegit flag.
  3. Step 6 is possible because acceptLendingOffer() doesn't check the isActive flag but only verify the availableAmount value.
  4. In step 7, the DebitaLendOfferFactory.deleteOrder() function contains the following logic:
    function deleteOrder(address _lendOrder) external onlyLendOrder {
208:    uint index = LendOrderIndex[_lendOrder];
        LendOrderIndex[_lendOrder] = 0;

        // switch index of the last borrow order to the deleted borrow order
212:    allActiveLendOrders[index] = allActiveLendOrders[activeOrdersCount - 1];
213:    LendOrderIndex[allActiveLendOrders[activeOrdersCount - 1]] = index;

        // take out last borrow order

217:    allActiveLendOrders[activeOrdersCount - 1] = address(0);

219:    activeOrdersCount--;
    }

Since the _lendOrder has already been deleted, index will be 0 in L208. As a result, in L212-L213, the first lend offer (at index 0) will be overwritten by the last lend offer (at index activeOrdersCount - 1). Finally, in L217-L219, the activeOrdersCount decreases by 1, effectively deleting the first lend offer from the list, regardless of the state of _lendOrder.

Mitigation

Add the check for the isActive flag in both the DLOImplementation.addFunds() and DLOImplementation.acceptLendingOffer() functions to prevent inactive lend offers from being manipulated or matched.