DeFiFoundry
50,000 USDC
View results
Submission Details
Severity: high
Invalid

Denial-of-Service via RefundExecutionFee Transfer Exhaustion in _cancelFlow

Summary

The Gamma protocol aims to provide automated position management on GMX, with refund mechanisms for execution fees when operations are cancelled. However, the current implementation of the refund mechanism uses unsafe ETH transfer methods that could allow malicious actors to block critical protocol operations.

In the _cancelFlow function and in the _mint function used in the perpetualVault.sol contract, we attempt to refund execution fees by calling:

try IGmxProxy(gmxProxy).refundExecutionFee(depositInfo[counter].owner, depositInfo[counter].executionFee) {} catch { ... }

Which initiates transfer in the refundExecutionFee in the GmxProxy.sol, with and address for the receipient being the owner of the deposit:

function refundExecutionFee(address receipient, uint256 amount) external
{require(msg.sender == perpVault, "invalid caller");
=> payable(receipient).transfer(amount);}

Vulnerability Details

Because we are transfering ETH and not some of the ERC20 tokens used in the contract a malicious user can DoS the system easily by:

1. Exploting the refund mechanism by first making a deposit with a legitimate Ethereum address they control. This deposit requires sending some ETH as an execution fee. The key to the attack is that this address will be used later to receive the ETH refund when cancellation occurs.

The attacker then implements a fallback function in their receiving address contract that either performs complex operations requiring more than 2300 gas or simply reverts. This is possible because the transfer() function in GmxProxyonly forwards 2300 gas - a limitation hard-coded into Ethereum's transfer() method.

When the protocol attempts to cancel the deposit and refund the execution fee, GmxProxy will try to transfer the ETH back to the attacker's address. Due to the gas limitations of transfer(), this operation will fail. However, because GmxProxydoesn't handle this failure and the error doesn't propagate to PerpetualVault's try-catch block, the cancellation process gets stuck.

Impact

This causes a denial of service because the cancellation flow never completes. Critical state updates in _cancelFlow()that should occur after the refund attempt - such as setting the flow to LIQUIDATION and updating the next action selector - never execute. The protocol becomes stuck in this incomplete cancellation state.

The attack is particularly effective because it:

  1. Requires minimal upfront cost (just the deposit and execution fee)

  2. Can be repeated to continuously block protocol operations

  3. Affects core protocol functionality around position management

  4. Cannot be easily mitigated without a contract upgrade

Tools Used

Manual review

Recommendations

  1. Replace transfer with a checked call:

function refundExecutionFee(address recipient, uint256 amount) external {
require(msg.sender == perpVault, "invalid caller");
=> (bool success, ) = payable(recipient).call{value: amount}("");
=> require(success, "ETH transfer failed");
}
Updates

Lead Judging Commences

n0kto Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
n0kto Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!