Synthetix Logic Error Bugfix Review

Summary
On May 2, 2022, whitehat thunderdeep14 submitted a critical bug in Synthetix via Immunefi, which the Synthetix team then shortly patched.
For his bug find, thunderdeep14 was paid $100,000, which is the max critical reward for Synthetix’s bug bounty program on Immunefi. Additionally, since Synthetix is eligible for matching payouts under Nexus Mutual’s Matching Program, the whitehat will be earning an additional $50,000, making his total payout $150,000.
Immunefi is pleased to have facilitated this responsible disclosure with our platform. Our goal is to make Web3 safer by incentivizing hackers to responsibly disclose bugs and receive clean money and reputation in exchange.
Now, on to an explanation about the vulnerability itself.
Vulnerability Analysis
Synthetix’s SIP 236 illustrates how fee reclamation and rebate features work in Synthetix.
One of the important features of the SIP is the following section:
The calculation of owing or owed is as follows:Amount * (1 — feeRate) * (srcRate/destRate — newSrcRate/newDestRate)If the result is negative, the amount is owed as a rebate.
This means that if the price of the destination token falls, the synth would rebate the destination token.
The vulnerability happens because Synthetix used the wrong variable, i.e., if the destination price decreases, then synth would rebate the difference in the destination token.
If a user were to call exchange
before reclaiming their rebate, the exchange sourceAmount
would be inflated. When an exchange occurs, the amount to be received by the user, amountReceived
, is being calculated inside the internal function call to _exchange
. Within _exchange
, amountReceived
is being assigned on [L486]:
amountReceived = _deductFeesFromAmount(entry.destinationAmount, entry.exchangeFeeRate);
If we look at how entry.destinationAmount
is being calculated [L442], we see effectiveValueAndRatesAtRound
is being called with the parameters sourceCurrencyKey
, sourceAmount
, destinationCurrencyKey
, entry.roundIdForSrc
, entry.roundIdForDest
.
The vulnerability stems from passing the sourceAmount
here instead of sourceAmountAfterSettlement
. This means the full source amount (before the debts are applied) is being used to calculate the destination amount. When the call to _convert
occurs, only the sourceAmountAfterSettlement
is burned, but the amountReceived
calculated from the full sourceAmount
is being issued in the function.
To summarize, the function _exchange
utilized the sourceAmount
variable to calculate the amount that has to be rebated to the destination but did not do a post-reclaim check on it to see whether the amount is accurate relative to the dest token value.
To give an example of this vulnerability in action, let’s assume we are exchanging sUSD to sBTC, and the price of sBTC dropped in the cooldown period. Synth would rebate the difference amount in sBTC but the issue here is that the difference would be rebated in the destination token and synth failed to calculate the rebate value of sBTC in sUSD.
Vulnerability Fix
Synthetix fixed the vulnerability at the following commit.
Acknowledgements
We would like to thank thunderdeep14 for doing an amazing job and responsibly disclosing such an important bug. Big props also to the Synthetix team who responded quickly to the report and patched the bug.
If you’d like to start bug hunting, we got you. Check out the Web3 Security Library, and start earning rewards on Immunefi — the leading bug bounty platform for web3 with the world’s biggest payouts.
And if you’re feeling good about your skillset and want to see if you will find bugs in the code, check out the bug bounty program from Synthetix.