A critical vulnerability in the _handleReturn function of PerpetualVault.sol allows an attacker to steal execution fees meant to be refunded to other users by exploiting an incorrect variable reference. The same vulnerability is also present in the _mint and _cancelFlow functions, creating a systemic issue throughout the contract.
The vulnerability results in direct theft of funds (execution fees) with minimal effort and can be repeatedly exploited against multiple victims.
The vulnerability is in the _handleReturn function of PerpetualVault.sol, specifically in the code that refunds unused execution fees to users. The function incorrectly refunds fees to depositInfo[counter].owner instead of depositInfo[depositId].owner:
The issue is that counter is a global variable representing the last deposit ID, while depositId is the ID of the specific deposit being withdrawn. This means that execution fee refunds are always sent to the most recent depositor rather than the user who initiated the withdrawal.
The same issue exists in other critical functions:
In the _mint function (around line 787), where execution fees are refunded incorrectly:
In the _cancelFlow function (around lines 1227-1228 and 1237-1238), similar incorrect refunds occur:
Below is a Proof of Concept demonstrating this vulnerability in the _handleReturn function:
The PoC demonstrates that when Alice (who deposited first) withdraws her funds, the refund of unused execution fees is sent to Bob (the latest depositor) instead of Alice.
This vulnerability has a severe financial impact:
Direct theft of users' execution fees (which can be substantial in high gas environments)
The vulnerability is easily and repeatedly exploitable by attackers who monitor the mempool for withdraw transactions
Any user who makes a deposit becomes the recipient of all subsequent execution fee refunds until another deposit is made
Users who paid high execution fees for priority processing lose a significant portion of those fees to unrelated parties
Numerical proof: In our PoC, Alice loses 0.008 ETH (worth potentially tens or hundreds of dollars depending on ETH price) to Bob, who simply made a deposit after her.
Foundry for writing and executing the proof of concept test
Manual code analysis of PerpetualVault.sol
To fix this vulnerability, the code should be corrected to refund execution fees to the appropriate owner:
This is a straightforward fix that ensures execution fee refunds are sent to the correct user who initiated the withdrawal.
Likelihood: Low, contract has to call cancelFlow after a withdraw, and the settle action is already executed by GMX. Impact: High, the fees will be distributed to the last depositor and not the withdrawer.
Likelihood: Medium/High, when withdraw on a 1x vault. Impact: High, the fees will be distributed to the last depositor and not the withdrawer.
Likelihood: Low, contract has to call cancelFlow after a withdraw, and the settle action is already executed by GMX. Impact: High, the fees will be distributed to the last depositor and not the withdrawer.
Likelihood: Medium/High, when withdraw on a 1x vault. Impact: High, the fees will be distributed to the last depositor and not the withdrawer.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.