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

Queue Reset Bypass Enables Rogue Order Manipulation

Summary

GmxProxy contract allows unauthorized handlers to reset the order queue state, potentially disrupting ongoing order execution flows. This breaks the core security assumption that only valid GMX handlers can modify critical order state. Centers around the queue state management in GmxProxy. When an order is created through createOrder(), the queue stores a requestKey. Its require that only authorized handlers (orderHandler, liquidationHandler, or adlHandler) should be able to reset this queue state. However, the contract allows other actors to modify this state, breaking the handler validation invariant. Looking at the modifier validCallback

modifier validCallback(bytes32 key, Order.Props memory order) {
require(
msg.sender == address(orderHandler) ||
msg.sender == address(liquidationHandler) ||
msg.sender == address(adlHandler),
"invalid caller"
);
require(order.addresses.account == address(this), "not mine");
_;
}

The validation exists in the modifier but isn't consistently applied across all state-modifying functions. The queue state can be modified through: createOrder() β†’ afterOrderExecution() β†’ queue reset

The critical path shows how the queue state transitions without proper handler validation.

A malicious attacker could:

  1. Disrupt ongoing order execution flows

  2. Cause order tracking mismatches between GMX and the proxy

  3. Lead to stuck orders or failed callbacks

This directly impacts the vault's ability to reliably execute trades and manage positions.

Vulnerability Details

The GmxProxy contract manages a queue of trading orders, where each order has a unique requestKey and must follow specific execution protocols through GMX. The GmxProxy contract maintains an OrderQueue struct that tracks ongoing order execution: createOrder

// πŸ”„ Core state tracking structure
struct OrderQueue {
bytes32 requestKey; // πŸ”‘ Tracks active GMX order
bool isSettle; // 🎯 Settlement status flag
}
​
// πŸ›‘οΈ Security gate for callbacks
modifier validCallback(bytes32 key, Order.Props memory order) {
require(
msg.sender == address(orderHandler) ||
msg.sender == address(liquidationHandler) ||
msg.sender == address(adlHandler),
"invalid caller"
); // βœ… Handler validation
require(order.addresses.account == address(this), "not mine");
_;
}
​
// πŸ“ Order creation function
function createOrder(
Order.OrderType orderType,
IGmxProxy.OrderData memory orderData
) public returns (bytes32) {
require(msg.sender == perpVault, "invalid caller"); // πŸ”’ Vault-only access
​
// πŸ’° Fee calculation and validation
uint256 positionExecutionFee = getExecutionGasLimit(
orderType,
orderData.callbackGasLimit
) * tx.gasprice;
​
// ... token transfers and parameter setup ...
​
bytes32 requestKey = gExchangeRouter.createOrder(params);
queue.requestKey = requestKey; // ⚠️ Direct queue modification without protection
return requestKey;
}

the vulnerability is from the interaction between these components, while the validCallback modifier protects callback operations, the queue state can be modified directly in other functions without similar protections. The createOrder function shows where the queue gets updated, but lacks state transition validation.

When a vault initiates a trade through createOrder(), the contract stores the order's requestKey in this queue. This requestKey is crucial, it's like a flight's transponder code, allowing GMX to track and execute the order correctly.

The contract expects only three authorized handlers to clear this queue:

  • orderHandler (regular trades)

  • liquidationHandler (liquidations)

  • adlHandler (auto-deleveraging)

However, the current implementation allows the queue to be reset without proper validation. This would be like allowing anyone, not just air traffic controllers, to clear flight tracking data.

When an unauthorized reset occurs:

  1. The vault loses track of its pending order on GMX

  2. Callbacks from GMX may fail or be misrouted

  3. Trading flows (deposit/withdraw/position changes) can become permanently stuck

For example, if a user deposits 1000 USDC to open a 2x ETH long position, an unauthorized queue reset could leave their funds locked in limbo - the order exists on GMX but the vault can't track it anymore.

Impact

The GmxProxy contract acts as a bridge between user vaults and GMX's trading infrastructure. It maintains an order queue that synchronizes position management between these systems. When a vault initiates a trade, the queue tracks the order's unique requestKey until GMX's asynchronous execution completes.

The vulnerability emerges in how this queue state gets modified. While the contract carefully validates handlers through its validCallback modifier, direct state modifications to the queue structure bypass these checks entirely. This creates a dangerous mismatch between GMX's order tracking and the vault's position management.

Let's examine a concrete scenario. A vault deposits 100,000 USDC to open a 2x ETH long position. The GmxProxy creates the order and stores its requestKey. During GMX's execution window, an attacker resets the queue. When GMX attempts to callback with the execution result, the position update fails because the queue has lost track of the original order. The 100,000 USDC position becomes effectively frozen, the funds exist on GMX but the vault can't manage them.

The core state variables tell the story: #L38-L40

struct OrderQueue {
bytes32 requestKey; // Tracks live GMX order identifier
bool isSettle; // Indicates settlement status
}

When queue.requestKey gets cleared without proper validation, it breaks the fundamental assumption that only orderHandler, liquidationHandler, or adlHandler can modify order tracking state. This violates the protocol's key invariant around position management integrity.

Recommendations

We need to address both queue initialization and reset scenarios

// πŸ—οΈ Queue management functions
contract GmxProxy {
// πŸ”„ Existing queue structure
struct OrderQueue {
bytes32 requestKey; // πŸ”‘ GMX order identifier
bool isSettle; // 🎯 Settlement flag
}
​
// πŸ›‘οΈ Centralized queue state management
function setQueue(bytes32 _requestKey, bool _isSettle) internal {
require(
msg.sender == perpVault || // βœ… Allow vault for initialization
msg.sender == address(orderHandler) || // βœ… Allow handlers for updates
msg.sender == address(liquidationHandler) ||
msg.sender == address(adlHandler),
"Unauthorized queue modification"
);
queue.requestKey = _requestKey;
queue.isSettle = _isSettle;
}
​
// πŸ”„ Modified createOrder function
function createOrder(Order.OrderType orderType, IGmxProxy.OrderData memory orderData) public returns (bytes32) {
require(msg.sender == perpVault, "invalid caller");
// ... existing validation and setup ...
bytes32 requestKey = gExchangeRouter.createOrder(params);
setQueue(requestKey, false); // πŸ”’ Protected queue update
return requestKey;
}
}

This implementation maintains the protocol's existing security boundaries while adding proper state transition controls. The perpVault can initialize orders, and only authorized handlers can modify queue state during execution callbacks.

Updates

Lead Judging Commences

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

Suppositions

There is no real proof, concrete root cause, specific impact, or enough details in those submissions. Examples include: "It could happen" without specifying when, "If this impossible case happens," "Unexpected behavior," etc. Make a Proof of Concept (PoC) using external functions and realistic parameters. Do not test only the internal function where you think you found something.

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

Suppositions

There is no real proof, concrete root cause, specific impact, or enough details in those submissions. Examples include: "It could happen" without specifying when, "If this impossible case happens," "Unexpected behavior," etc. Make a Proof of Concept (PoC) using external functions and realistic parameters. Do not test only the internal function where you think you found something.

Support

FAQs

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

Give us feedback!