Old Obsidian Nuthatch
High
A malicious lender can exploit the the logical error in the lend offer deletion process to delete all active lend offers.
- The DebitaLendOfferFactory.deleteOrder() function doesn't delete the
isLendOrderLegit
flag. - The DLOImplementation.addFunds() function doesn't verify the
isActive
flag before allowing additional funds to be added to a lend offer. - The DebitaV3Aggregator.matchOffersV3() function doesn't check
isActive
flag but only verify theisLendOrderLegit
flag when processing lend offers. - The DLOImplementation.acceptLendingOffer() function doesn't check
isActive
flag but only verify theavailableAmount
value.
No response
No response
- A malicious lender creates a lend offer with
perpetual = false
. - 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 tofalse
andavailableAmount
decreases to zero. - Assume that there are multiple active lend offers in the list.
- The lender adds funds to the deleted lend offer by calling
DLOImplementation.addFunds()
to increase theavailableAmount
of the lend offer again to a non-zero value. - The lender fully matches the deleted lend offer to a new borrow offer by calling the
DebitaV3Aggregator.matchOffersV3()
function. - The
matchOffersV3()
function callsDLOImplementation.acceptLendingOffer()
, which then callsDebitaLendOfferFactory.deleteOrder()
. - Due to the logic error in the
deleteOrder()
function, the first active lend offer in the list is deleted. - The malicious lender repeats step 4 through 7 multiple times, deleting all active lend offers from the protocol.
A malicious lender can delete all active lend offers, effectively disrupting the entire lending system and rendering the protocol useless.
- Step 3 of the attack path is possible because
DLOImplementation.addFunds()
doesn't checkisActive
flag. - Step 5 and is possible because
matchOffsetV3()
doesn't check theisActive
flag but only verify theisLendOrderLegit
flag. - Step 6 is possible because
acceptLendingOffer()
doesn't check theisActive
flag but only verify theavailableAmount
value. - 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
.
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.