Skip to content

Latest commit

 

History

History
115 lines (90 loc) · 6.31 KB

071.md

File metadata and controls

115 lines (90 loc) · 6.31 KB

Powerful Yellow Bear

High

Attacker manipulates precision loss to overcharge borrower on APR

Summary

The matchOffersV3 function in the loan contract calculates the weightedAverageAPR using integer division, leading to cumulative precision loss. This results in a discrepancy between the advertised maxApr and the actual APR borrowers pay (maxAPR + 28). Borrowers unknowingly pay higher interest, violating the APR constraint defined in the borrow order. This vulnerability occurs not only for "APR" but also for "Ratio".

Root Cause

  1. Integer division precision loss:

  2. Small lend amounts:

    • Small values in lendAmountPerOrder (e.g., 1) exacerbate precision loss as their contribution is disproportionately affected by rounding.
  3. Discrepancy between enforcement and charged APR:

    • Borrowers expect weightedAverageAPR to adhere to maxApr, but the actual APR charged is derived from the lenders' APR (lendInfo.apr), which remains unaffected by rounding.

Attack Path

Scenario

  • Borrower's maxApr: 3,000.
  • Attack Plan: Exploit the precision loss in weightedAverageAPR calculation by creating multiple LendOffers with highly skewed lendAmount values and a consistent lendInfo.apr of 3028. The goal is to manipulate the calculation so that the weightedAverageAPR appears to be 3000, while the borrower effectively pays 3028 APR.

Steps

  1. Borrower Prepares to Take Loan:

    • The borrower creates a borrow order with a collateral that allows an availableAmount of 10,028e12 and specifies a maxApr of 3000.(10,028e12 and 3000 are selected for simple calculation and any values are ok.)
  2. Attacker Creates Lend Offers:

    • The attacker creates 29 LendOffers, distributing the lendAmount as follows:
      • LendOffer 1: lendAmount = 10000e12, lendInfo.apr = 3028.
      • LendOffers 2 to 29: lendAmount = 1e12, lendInfo.apr = 3028.

    This vulnerability occurs not only for "APR" but also for "Ratio".

Impact

  1. Borrower Overpayment:
    • Borrowers pay more interest than anticipated, leading to hidden costs.

What is more serious is when the borrower calculates the exact amount of token to pay the debt and then proceeds payDebt, it will be reverted with unexpected reason. So the severity is HIGH.

The larger the amount of token borrowed, the more severe the consequences.

Please refer to PoC for detailed explanation.

  1. Protocol Reputation Damage:

    • This discrepancy undermines trust, as the protocol does not transparently enforce the advertised maxApr.
  2. Legal and Regulatory Risks:

    • Failure to enforce accurate APR disclosures could expose the protocol to legal challenges.

PoC

Scenario:

  • lendAmountPerOrder: [10000e18, 1e18, 1e18, ..., 1e18] (29 values - lendOrders.length < 30).
  • lendInfo.apr: 3028 for all lenders.
  • Borrow order maxApr: 3000.

Observed Behavior:

  1. Calculated weightedAverageAPR:
    weightedAverageAPR = (3028 * 10000e18) / 10000e18= 3027. weightedAverageAPR = (3027 * 10000e18) / 10001e18 + (3028 * 1e18) / 10001e18 = 3026. weightedAverageAPR = (3026 * 10001e18) / 10002e18 + (3028 * 1e18) / 10002e18 = 3025. weightedAverageAPR = (3025 * 10002e18) / 10003e18 + (3028 * 1e18) / 10003e18 = 3024. ... After 100 iterations, weightedAverageAPR is truncated to 3000 due to integer division.

  2. Actual APR Charged:
    Borrower is charged an effective APR of 3028, derived directly from the lenders' lendInfo.apr.

Length of borrow = 80day! The borrow calculates the max interest with apr=3000: interest = 10028e18 * 3000 / 10000 * 80days / 31536000 = 659e18 total = 10028e18 + 659e18 = 10,687e18 So the borrow prepares the amount of 10687e18 and tries to payDebt on the last day of borrow. If it's 12h before the borrow finish, the borrower thinks that amount is sufficient since there are 12 hours left.. But the real interest(79.day, 3000 apr) and the attacked interest(79.5day, 3028 apr) are: real interest = 10028e18 * 3000 / 10000 * 79.5days / 31536000 = 654e18 attacked interest = 10028e18 * 3028 / 10000 * 79.5days / 31536000 = 661e18 loss = 7e18

This loss cannot be viewed as a simple precision loss, and the longer the loan period and loan amount, the more significant the loss.

And also PayDebt reverts even when the borrower believes he has sufficient funds.

Mitigation

  1. Accumulate Weighted Contributions and Divide by Total Principle Amount:

    • For each principle, calculate the weighted contribution of each lender’s APR (lendInfo.apr) based on their lent amount (lendAmountPerOrder[i]).:
      weightedAverageRatio[principleIndex] += lendInfo.apr * lendAmountPerOrder[i];
      amountPerPrinciple[principleIndex] += lendAmountPerOrder[i];
    • After processing all lend orders, calculate the weighted average by dividing the accumulated value by the total amount lent for the given principle (amountPerPrinciple[principleIndex]).
         weightedAverageRatio[principleIndex] /= amountPerPrinciple[principleIndex];
  2. Use fixed-point arithmetic:

    • Perform calculations with higher precision (e.g., 18 decimals) using fixed-point libraries like OpenZeppelin's SafeMath or PRBMath:
      uint updatedLastApr = (weightedAverageAPR[principleIndex] * amountPerPrinciple[principleIndex] * 10**PRECISION) /
          ((amountPerPrinciple[principleIndex] + lendAmountPerOrder[i]) * 10**PRECISION);
  3. Cap Small Contributions:

    • Set a minimum threshold for lendAmountPerOrder to ensure meaningful contributions:
      require(lendAmountPerOrder[i] >= MINIMUM_AMOUNT, "Lend amount too small");