sherlock-audit / 2024-09-predict-fun-judging

5 stars 4 forks source link

eeyore - Borrower will overcollateralize the loan when the last lender fulfills a partially fulfilled borrow request, in a case where the borrow request was initially matched and partially fulfilled with a loan offer. #254

Open sherlock-admin4 opened 1 month ago

sherlock-admin4 commented 1 month ago

eeyore

Medium

Borrower will overcollateralize the loan when the last lender fulfills a partially fulfilled borrow request, in a case where the borrow request was initially matched and partially fulfilled with a loan offer.

Summary

When a borrower creates a borrow request, they specify the collateralAmount they are willing to provide in exchange for the loanAmount. These values together act as the collateralization ratio. For example, collateralAmount == 150 and loanAmount == 100 results in a 150% collateral ratio.

Once the borrow request is created, any lender can accept the request via the acceptBorrowRequest() function. After execution, the appropriate collateralization ratio is applied, and the loan is created. There is an edge case when using acceptBorrowRequest() where the last lender will have slightly more collateralization due to rounding errors, but this will result in only a few wei at most.

However, if a borrow request is matched with a loan offer via the matchProposals() function, the collateralization ratio used for both proposals is based on the loan offer proposal. This collateral ratio cannot exceed the borrow request collateral ratio. In short, the borrower will need to provide an equal or lesser amount of collateral than specified in the request but must meet the collateral required by the loan offer.

This functionality benefits the borrower and matches the loan offer creator expectations, so everything seems correct.

But there is an edge case where the borrower ends up providing more collateral than expected, breaking their anticipated collateralization ratio. This scenario could also be exploited by malicious actors to farm overcollateralized loans for profit.

Root Cause

The root cause is using the collateralAmountRequired calculated with loan offer data: https://github.com/sherlock-audit/2024-09-predict-fun/blob/main/predict-dot-loan/contracts/PredictDotLoan.sol#L395-L399

When updating the fulfillment data of the borrow request, this leads to a situation where the collateral amount is undervalued compared to the loan amount in the borrower's fulfillment data: https://github.com/sherlock-audit/2024-09-predict-fun/blob/main/predict-dot-loan/contracts/PredictDotLoan.sol#L401-L406

This results in overcollateralization for the last lender of the borrow request: https://github.com/sherlock-audit/2024-09-predict-fun/blob/main/predict-dot-loan/contracts/PredictDotLoan.sol#L983 https://github.com/sherlock-audit/2024-09-predict-fun/blob/main/predict-dot-loan/contracts/PredictDotLoan.sol#L1166-L1167

Internal pre-conditions

  1. A borrow request with a high collateralization ratio is matched with a loan offer with a low collateralization ratio.

External pre-conditions

None.

Attack Path

Preconditions:

  1. There is a borrow request with collateralAmount == 200 and loanAmount == 100.
  2. There is a loan offer with collateralAmount == 75 and loanAmount == 50.

Attack:

  1. Malicious actors match the borrow request with the loan offer.
  2. Malicious actors fulfill the borrower request for the remaining (50).

Impact

The borrower’s collateralization ratio is significantly higher than expected for the last lender. Malicious actors can exploit this by crafting overcollateralized loans, by matching proposals and taking more favorable hedge positions.

PoC

Using the data from the Attack Path, the collateralization ratio will be:

  1. Match offer:
collateralAmountRequired = 75 * 50 / 50 = 75
borrowRequestFulfillment.collateralAmount += 75
borrowRequestFulfillment.loanAmount += 50

Collateralization ratio used is 150%, which is acceptable as it is below the 200% borrower request collateralization ratio.

  1. Malicious actors fulfill the borrower request (fulfillment.loanAmount + fulfillAmount == proposal.loanAmount):
collateralAmountRequired = 200 - 75 = 125
borrowRequestFulfillment.collateralAmount += 125
borrowRequestFulfillment.loanAmount += 50

Collateralization ratio used is 250%, which is unacceptable as it exceeds the 200% borrower request collateralization ratio. The difference is more than the acceptable few wei described in the README.

Mitigation

When updating the fulfillment data of the borrow request, update collateralAmount as if the borrower request collateralization ratio was used for loan creation:

            _updateFulfillment( 
                borrowRequestFulfillment,
-               collateralAmountRequired,
+               _calculateCollateralAmountRequired(
+                   borrowRequest,
+                   borrowRequestFulfillment,
+                   fulfillAmount
+               ),
                fulfillAmount,
                borrowRequestProposalId
            );