Skip to content

Latest commit

 

History

History
101 lines (79 loc) · 3.6 KB

063.md

File metadata and controls

101 lines (79 loc) · 3.6 KB

Tiny Licorice Loris

Medium

Malicious actors can always DOS WrappedTokenGatewayV3.withdrawETHWithPermit() by frontrunning and calling permit()

Summary

aWETH.permit() call within WrappedTokenGatewayV3.withdrawETHWithPermit() will DOS the tx if it is frontrun with direct permit(). https://github.com/sherlock-audit/2025-01-aave-v3-3/blob/main/aave-v3-origin/src/contracts/helpers/WrappedTokenGatewayV3.sol#L140

 function withdrawETHWithPermit(
    address,
    uint256 amount,
    address to,
    uint256 deadline,
    uint8 permitV,
    bytes32 permitR,
    bytes32 permitS
  ) external override {
    IAToken aWETH = IAToken(POOL.getReserveAToken(address(WETH)));
    uint256 userBalance = aWETH.balanceOf(msg.sender);
    uint256 amountToWithdraw = amount;

    // if amount is equal to type(uint256).max, the user wants to redeem everything
    if (amount == type(uint256).max) {
      amountToWithdraw = userBalance;
    }
    // permit `amount` rather than `amountToWithdraw` to make it easier for front-ends and integrators
    aWETH.permit(msg.sender, address(this), amount, deadline, permitV, permitR, permitS);//@audit-issue DOS by frontrunning with direct permit() https://www.trust-security.xyz/post/permission-denied
    aWETH.transferFrom(msg.sender, address(this), amountToWithdraw);
    POOL.withdraw(address(WETH), amountToWithdraw, address(this));
    WETH.withdraw(amountToWithdraw);
    _safeTransferETH(to, amountToWithdraw);
  }

Root Cause

aWETH.permit() call is supposed to be used via try-catch so that even if WrappedTokenGatewayV3.withdrawETHWithPermit() is frontrun by an attacker by directly calling permit() with the ERC712 permit sig there won't be a revert but it is not.

The root cause of this DOS is that the aWETH.permit() isn't used in a try-catch. So attackers can always cause reverts by calling permit() directly. using up the nonce

 function permit(
    address owner,
    address spender,
    uint256 value,
    uint256 deadline,
    uint8 v,
    bytes32 r,
    bytes32 s
  ) external override {
    require(owner != address(0), Errors.ZERO_ADDRESS_NOT_VALID);
    //solium-disable-next-line
    require(block.timestamp <= deadline, Errors.INVALID_EXPIRATION);
    uint256 currentValidNonce = _nonces[owner];
    bytes32 digest = keccak256(
      abi.encodePacked(
        '\x19\x01',
        DOMAIN_SEPARATOR(),
        keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline))
      )
    );
    require(owner == ecrecover(digest, v, r, s), Errors.INVALID_SIGNATURE);
    _nonces[owner] = currentValidNonce + 1;
    _approve(owner, spender, value);
  }
 

Extra context: https://www.trust-security.xyz/post/permission-denied

Internal Pre-conditions

No response

External Pre-conditions

Tx needs to happen in a chain where frontrunning is possible. e.g ETH, Avalanche e.t.c

Attack Path

  1. User calls WrappedTokenGatewayV3.withdrawETHWithPermit()
  2. Attacker front-runs the tx, calling permit() directly with the ERC712 permit sig of the user. This will use up user's nonce.
nonces[owner] = currentValidNonce + 1;
  1. User's nonce is used up, causing reverts in his transaction.

Impact

User's can always be prevented from withdrawing their ETH via WrappedTokenGatewayV3.withdrawETHWithPermit() by simply frontrunning their tx and calling permit() directly with their ERC712 permit sig.

This can be done all the time indefinitely at no cost...All it cost is gas fees

PoC

No response

Mitigation

Wrap the aWETH.permit() call within `WrappedTokenGatewayV3.withdrawETHWithPermit() in a try-catch