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

inconsistent handling of the _gmxLock in the PerpetualVault

Summary

The PerpetualVault contract uses a _gmxLock variable to prevent reentrancy and ensure atomicity during GMX operations (e.g., creating, increasing, or decreasing positions). However, inconsistent handling of this lock can lead to deadlocks, unauthorized actions, or permanent contract locking.

Vulnerability Details

  • The _gmxLock variable is set to true when a GMX operation is initiated and reset to false after the operation is completed (via a callback from GMX).

  • The gmxLock modifier ensures that no other GMX operations can be initiated while _gmxLock is true.

modifier gmxLock() {
if (_gmxLock == true) {
revert Error.GmxLock();
}
_;
}
  • If the afterOrderExecution or afterOrderCancellation callback fails to reset _gmxLock, the contract will remain locked.

  • An attacker could bypass the gmxLock modifier and create a new position while another position is being modified.

  • If _gmxLock is reset prematurely, the contract could allow overlapping GMX operations, leading to race conditions.

Below is a simplified PoC to demonstrate the deadlock vulnerability:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
contract PerpetualVault {
bool private _gmxLock;
modifier gmxLock() {
require(!_gmxLock, "GMX lock active");
_;
_gmxLock = true; // Lock the contract
}
// Simulate a GMX operation
function createOrder() external gmxLock {
// Simulate creating an order on GMX
// Assume this function triggers a callback from GMX
}
// Simulate a GMX callback
function afterOrderExecution() external {
// Simulate a failed callback (e.g., due to an error)
// The lock is not reset, causing a deadlock
// _gmxLock = false; // Commented out to simulate the vulnerability
}
// Check if the contract is locked
function isLocked() external view returns (bool) {
return _gmxLock;
}
}
contract Attack {
PerpetualVault private vault;
constructor(address _vault) {
vault = PerpetualVault(_vault);
}
function attack() external {
// Trigger a GMX operation
vault.createOrder();
// Simulate a failed callback (e.g., due to an error)
// The lock is not reset, causing a deadlock
vault.afterOrderExecution();
// Now the contract is permanently locked
// Any further GMX operations will fail
}
}
Steps to Reproduce
  1. Deploy the PerpetualVault contract.

  2. Deploy the Attack contract, passing the address of the PerpetualVault contract.

  3. Call the attack function on the Attack contract.

    • This triggers a GMX operation and simulates a failed callback.

  4. Call isLocked on the PerpetualVault contract to verify that the contract is permanently locked.

  5. Attempt to call createOrder again. Observe that the transaction reverts with the error "GMX lock active".

Impact

  • The contract becomes permanently locked, preventing any further GMX operations.

  • Users cannot deposit, withdraw, or modify positions, effectively rendering the contract unusable.

Tools Used

Manual Code Review

Recommendations

Always reset _gmxLock to false after a GMX operation, even if the operation fails.

function afterOrderExecution() external {
// Handle order execution
_gmxLock = false; // Reset lock
}

Implement a timeout mechanism to automatically reset _gmxLock if a GMX operation takes too long.

solidity

uint256 public gmxLockTimeout = 1 hours;
uint256 public gmxLockTimestamp;
modifier gmxLock() {
if (_gmxLock) {
require(block.timestamp <= gmxLockTimestamp + gmxLockTimeout, "GMX lock timeout");
}
_;
_gmxLock = true;
gmxLockTimestamp = block.timestamp;
}
function resetGmxLock() external {
require(block.timestamp > gmxLockTimestamp + gmxLockTimeout, "GMX lock not expired");
_gmxLock = false;
}

Ensure that only the GMX proxy contract can call the callback functions.

address public gmxProxy;
function afterOrderExecution() external {
require(msg.sender == gmxProxy, "Invalid caller");
_gmxLock = false; // Reset lock
}
Updates

Lead Judging Commences

n0kto Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
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.