Skip to content

Latest commit

 

History

History
92 lines (65 loc) · 4.71 KB

097.md

File metadata and controls

92 lines (65 loc) · 4.71 KB

Fast Steel Cormorant

Medium

Unbacked Token Over-Minting via Race Condition in BridgeLogic::executeMintUnbacked

Summary

The executeMintUnbacked function within the BridgeLogic contract of Aave v3.3 is vulnerable to a race condition that permits the minting of unbacked tokens beyond the established unbackedMintCap. This flaw arises from the sequence in which the reserve.unbacked value is incremented before validating against the minting cap, allowing attackers to exploit simultaneous transactions to bypass intended restrictions.

Vulnerability details

The vulnerability is rooted in the improper ordering of state mutation and validation within the executeMintUnbacked function. The critical code segments are as follows:

https://github.com/sherlock-audit/2025-01-aave-v3-3/blob/main/aave-v3-origin/src/contracts/protocol/libraries/logic/BridgeLogic.sol#L57-L108

  1. State Mutation Before Validation: The reserve.unbacked is incremented with the amount prior to checking if this new value exceeds the unbackedMintCap. This allows multiple transactions to pass the cap check based on the initial state before any increments from concurrent transactions are considered.

  2. Lack of Atomicity: Solidity's EVM executes transactions sequentially, but without proper atomic checks or reentrancy guards, rapid succession of transactions can still manipulate state in unintended ways, especially under high network load or in specialized environments.

Impact

  • Consequences:
    • Unbacked Token Inflation: Attackers can mint more unbacked tokens than the protocol allows, diluting the value of tokens and undermining collateral integrity.
    • Liquidity Drain: Excessively minted unbacked tokens, when backed, can lead to disproportionate issuance of aTokens, allowing attackers to withdraw more liquidity than intended.
    • Financial Instability: The protocol may incur significant losses due to the over-issuance of tokens, leading to potential insolvency or the need for emergency interventions.

Attack Logic

  1. Monitoring Network State:

    • The attacker observes the state of reserve.unbacked, particularly as it approaches the unbackedMintCap.
  2. Crafting Multiple Mint Transactions:

    • The attacker prepares several minting transactions, each with an amount that individually does not exceed the cap but cumulatively does.
  3. Simultaneous Submission:

    • Submit all crafted transactions in rapid succession, aiming for them to be mined within the same block or in quick succession where state mutations overlap.
  4. Exploiting the Race Condition:

    • Transaction 1 (Tx1):
      • Increments reserve.unbacked from 950,000 to 980,000.
      • Passes the cap check (980,000 <= 1,000,000).
    • Transaction 2 (Tx2):
      • Independently reads reserve.unbacked as 950,000 (before Tx1's mutation).
      • Increments to 990,000.
      • Passes the cap check (990,000 <= 1,000,000).
    • Transaction 3 (Tx3):
      • Independently reads reserve.unbacked as 950,000.
      • Increments to 970,000.
      • Passes the cap check (970,000 <= 1,000,000).
  5. Resulting State:

    • After all transactions, reserve.unbacked is erroneously set to 1,070,000, surpassing the unbackedMintCap of 1,000,000.
  6. Backing Excess Tokens:

    • The attacker calls executeBackUnbacked to back the inflated unbacked tokens, receiving more aTokens than justified, thereby draining protocol liquidity.

Mitigation Recommendations

  1. Reordering Operations:
    • Validation Before Mutation: Ensure that the cap check occurs before updating reserve.unbacked.
      function executeMintUnbacked(
          // parameters...
      ) external {
          DataTypes.ReserveData storage reserve = reservesData[asset];
          DataTypes.ReserveCache memory reserveCache = reserve.cache();
      
          reserve.updateState(reserveCache);
      
          ValidationLogic.validateSupply(reserveCache, reserve, amount, onBehalfOf);
      
          uint256 unbackedMintCap = reserveCache.reserveConfiguration.getUnbackedMintCap();
          uint256 reserveDecimals = reserveCache.reserveConfiguration.getDecimals();
      
      +     uint256 currentUnbacked = reserve.unbacked;
      +     require(
      +         currentUnbacked + amount <= unbackedMintCap * (10 ** reserveDecimals),
      +         Errors.UNBACKED_MINT_CAP_EXCEEDED
      +     );
      +     reserve.unbacked = currentUnbacked + amount.toUint128();
      
      -     uint256 unbacked = reserve.unbacked += amount.toUint128();
          require(
              unbacked <= unbackedMintCap * (10 ** reserveDecimals),
              Errors.UNBACKED_MINT_CAP_EXCEEDED
          );
      
          // Rest of the function...
      }