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

GMX Handler Spoofing Enables Unauthorized Position Control

Summary

GmxProxy contract allows unauthorized addresses to execute callbacks by bypassing the handler validation checks. This breaks the security assumption that only authorized GMX handlers can trigger position updates. this occurs when a malicious actor directly calls callback functions while spoofing as an authorized handler address. The GmxProxy contract validates the caller against orderHandler, liquidationHandler, and adlHandler addresses, but the validation can be circumvented due to insufficient checks in the modifier implementation.

We expects that only authorized handlers can execute callbacks. However, the implementation in GmxProxy.sol shows: 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");
_;
}

While the modifier checks msg.sender, it doesn't verify the authenticity of the handler addresses themselves.

See this trace: Unauthorized caller β†’ afterOrderExecution() β†’ position state changes β†’ potential fund loss

Vulnerability Details

When users want to adjust their positions whether opening, closing, or modifying leverage, these requests flow through GmxProxy. The contract is designed to only accept position management callbacks from three authorized sources, the order handler, liquidation handler, and ADL (Auto-Deleveraging) handler.

However, the current implementation has a critical flaw in its validation logic. While the contract checks that the caller matches one of these handlers, it doesn't properly secure the handler addresses themselves. This creates a scenario where an attacker could manipulate position states by impersonating a valid handler.

The implementation in GmxProxy.sol uses mutable storage variables for handler addresses rather than immutable ones. When combined with the validation modifier. The system becomes vulnerable because handler addresses could potentially be manipulated after deployment.

// πŸ”“ Vulnerable: Mutable storage for critical security addresses
address public orderHandler;
address public liquidationHandler;
address public adlHandler;
​
// πŸŒ‰ Core infrastructure connections
IExchangeRouter public gExchangeRouter;
address public gmxRouter;
IDataStore public dataStore;
address public orderVault;
IGmxReader public gmxReader;
​
// πŸ›‘οΈ Security checkpoint that can be bypassed
modifier validCallback(bytes32 key, Order.Props memory order) {
require(
msg.sender == address(orderHandler) || // 🚨 Checks against mutable addresses
msg.sender == address(liquidationHandler) || // 🚨 Can be changed by attackers
msg.sender == address(adlHandler), // 🚨 Can be changed by attackers
"invalid caller"
);
require(order.addresses.account == address(this), "not mine");
_;
}
​
// 🎯 Main attack surface
function afterOrderExecution(
bytes32 requestKey,
Order.Props memory order,
EventLogData memory eventData
) external override validCallback(requestKey, order) { // πŸ”‘ Entry point for exploitation
// ... position management logic that could be manipulated ...
// πŸ’° Critical fund movement operations
if (eventData.uintItems.items[0].value > 0) {
IERC20(eventData.addressItems.items[0].value).safeTransfer(perpVault, eventData.uintItems.items[0].value);
}
// πŸ“Š Position state updates
IGmxProxy.OrderResultData memory orderResultData = IGmxProxy.OrderResultData(
order.numbers.orderType,
order.flags.isLong,
order.numbers.sizeDeltaUsd,
outputToken,
outputAmount,
queue.isSettle
);
}

This code represents the core security boundary between GMX's position management system and the vault's funds. The vulnerability stems from the mutable handler addresses combined with the critical operations in afterOrderExecution().

In a vault managing leveraged GMX positions, unauthorized callbacks could lead to:

  • Forced position closures at unfavorable prices

  • Manipulation of position sizes and leverage

  • Unauthorized claiming of funding fees

  • Disruption of the vault's risk management strategy

Impact

When a vault wants to adjust leverage or close positions, these requests must flow through authorized GMX handlers, the order handler for normal trades, liquidation handler for underwater positions, and ADL handler for deleveraging events. The protocol assumes these handlers provide a secure boundary between user actions and GMX's core functionality.

However, the current implementation stores handler addresses in mutable storage variables rather than making them immutable at deployment. This minor design choice creates a serious vulnerability because an attacker can update these addresses and then call position management functions directly, bypassing all intended security checks.

Malicious attacker could force-close profitable positions at manipulated prices, increase leverage up to the protocol maximum of 3x regardless of vault settings, and siphon funding fees meant for legitimate depositors. With $100M+ in total value locked across GMX vaults, even a single compromised position could trigger cascading liquidations.

Recommendations

Consider making the handler addresses immutable and validate them at deployment

// πŸ”’ GmxProxy.sol - Secure Implementation
address public immutable orderHandler; // πŸ›‘οΈ Cannot be changed after deployment
address public immutable liquidationHandler; // πŸ›‘οΈ Cannot be changed after deployment
address public immutable adlHandler; // πŸ›‘οΈ Cannot be changed after deployment
​
// βœ… Validated initialization
constructor(
address _orderHandler,
address _liquidationHandler,
address _adlHandler
) {
// πŸ” Strict validation of critical addresses
require(_orderHandler != address(0), "Invalid order handler");
require(_liquidationHandler != address(0), "Invalid liquidation handler");
require(_adlHandler != address(0), "Invalid ADL handler");
// πŸ” One-time immutable assignment
orderHandler = _orderHandler;
liquidationHandler = _liquidationHandler;
adlHandler = _adlHandler;
}
​
// πŸ›‘οΈ Security checkpoint now uses immutable addresses
modifier validCallback(bytes32 key, Order.Props memory order) {
require(
msg.sender == orderHandler || // βœ… Checks against immutable address
msg.sender == liquidationHandler || // βœ… Cannot be manipulated
msg.sender == adlHandler, // βœ… Cannot be manipulated
"invalid caller"
);
require(order.addresses.account == address(this), "not mine");
_;
}

This implementation creates an unbreakable security boundary between:

  • PerpetualVault.sol (manages user positions)

  • GmxProxy.sol (handles GMX interactions)

  • IGmxReader.sol (provides market data)

  • IGmxProxy.sol (defines interface standards)

With this immutable pattern, we ensures handler addresses remain constant throughout the contract lifecycle, preventing any potential manipulation of the position management system.

Updates

Lead Judging Commences

n0kto Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Admin is trusted / Malicious keepers

Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelihood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point. Keepers are added by the admin, there is no "malicious keeper" and if there is a problem in those keepers, that's out of scope. ReadMe and known issues states: " * System relies heavily on keeper for executing trades * Single keeper point of failure if not properly distributed * Malicious keeper could potentially front-run or delay transactions * Assume that Keeper will always have enough gas to execute transactions. There is a pay execution fee function, but the assumption should be that there's more than enough gas to cover transaction failures, retries, etc * There are two spot swap functionalies: (1) using GMX swap and (2) using Paraswap. We can assume that any swap failure will be retried until success. " " * Heavy dependency on GMX protocol functioning correctly * Owner can update GMX-related addresses * Changes in GMX protocol could impact system operations * We can assume that the GMX keeper won't misbehave, delay, or go offline. " "Issues related to GMX Keepers being DOS'd or losing functionality would be considered invalid."

n0kto Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Admin is trusted / Malicious keepers

Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelihood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point. Keepers are added by the admin, there is no "malicious keeper" and if there is a problem in those keepers, that's out of scope. ReadMe and known issues states: " * System relies heavily on keeper for executing trades * Single keeper point of failure if not properly distributed * Malicious keeper could potentially front-run or delay transactions * Assume that Keeper will always have enough gas to execute transactions. There is a pay execution fee function, but the assumption should be that there's more than enough gas to cover transaction failures, retries, etc * There are two spot swap functionalies: (1) using GMX swap and (2) using Paraswap. We can assume that any swap failure will be retried until success. " " * Heavy dependency on GMX protocol functioning correctly * Owner can update GMX-related addresses * Changes in GMX protocol could impact system operations * We can assume that the GMX keeper won't misbehave, delay, or go offline. " "Issues related to GMX Keepers being DOS'd or losing functionality would be considered invalid."

Support

FAQs

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

Give us feedback!