-
Notifications
You must be signed in to change notification settings - Fork 6
0x52 - FundRateArbitrage is vulnerable to inflation attacks #54
Comments
1 comment(s) were left on this issue during the judging contest. takarez commented:
|
Escalate I am not completely sure about the judgement here and am therefore escalating to get @Czar102 's opinion on how this should be judged. I believe that #56 and #21 should be treated as different issues than this one. I am not even sure if this issue and other duplicates are valid, as they rely on the front-running on Arbitrum assumption, which has not been explicitly confirmed to be valid or invalid on Sherlock. Please take a look at the thread on #56 to better understand the differences. But the TLDR is:
|
You've created a valid escalation! To remove the escalation from consideration: Delete your comment. You may delete or edit your escalation comment anytime before the 48-hour escalation window closes. After that, the escalation becomes final. |
Fixed PR: JOJOexchange/smart-contract-EVM@b3cf3d6 |
This issue:
Your issue:
|
Disagree that the request/permit design prevents this. As described in code comments for It is better to implement native contract defence a classic ERC4626 attack. Should be kept as a high finding. |
@giraffe0x Again you are speculating on off-chain mechanisms. While it is a valid concern, the focus should be on contract level code logic, and sherlocks assumption is that admin will make the right decisions when permitting withdrawal requests. Also, here is the most recent example of where first depositor inflation is rated as medium: |
"I think both this issue and your issue implies that the user needs to be the first depositor, especially the scenario highlighted. This does not explicitly requires a front-run, given a meticulous user can make their own EV calculations." How would you run this attack without front-running? Share inflation attacks explicitly require front-running |
@detectiveking123 I agree that the only possible reason for this issue to be valid is if
If both of the above scenario does not apply, all of the issues and its duplicates should be low severity. |
Fix looks good. Adds a virtual offset which prevents this issue. |
Front-running isn't necessary as the attacker can deposit 1 wei + donate and just wait for someone to make a deposit under 100,000e6. But this way the attack is still risky to execute as the attacker will lose the donated USDC in case he isn't the first depositor. However, this can be mitigated if the attacker created a contract the does the 1 wei deposit + donate action in a single transaction BUT revert in case it isn't the first deposit in the protocol. Planning to reject escalation and keep issue state as is. |
@Evert0x not sure that makes sense, if you just wait for someone then they should just not deposit (it's a user mistake to deposit, they should be informed that they'll retrieve no shares back in the UI). |
After a discussion with @Evert0x, planning to make it a Medium severity issue – frontend can display information for users not to fall victim to this exploit by displaying a number of output shares. |
@IAm0x52 So you agree the post audit code you have audited and agreed solves the share inflation issue is vulnerable to the rounding attack? |
No it's not vulnerable to the rounding attack due to withdrawal minimums and deposit fees. |
@IAm0x52 Okay, my previous exploit assumed that withdrawal minimum and deposit fees were zero. Let's do a concrete example involving them. We will assume that Note: This is on the post-audit, fixed codebase.
Unless someone has challenges regarding the validity of this example, I will rest my case here and leave it to the head of judging. The fact that the issue exists and drains the contract in a version of the codebase that the LW has agreed fixes share inflation proves that this issue is distinct and valid. |
This is again mistaken. Using the changed code our initial index would be:
This would mean that the following is minted:
After deposit our index is now:
Now lets finish the example:
|
Funny, that made me laugh. But, apologies; I forgot the order of operations was the other way around. You should send into the contract first.
Also happy to just provide a PoC on the post-audit code if it resolves this discussion. |
The problem you run into is that now when another person deposits, they will mint with an index of:
Which will cause the attacker to immediately lose 1000 USDC because supply has the offset enabled. This is why we use the virtual offset because it will prevent all gains by counteracting the inflation. The more people that deposit the more it depresses the exchange rate (and the more the attacker loses) so the users who deposited right away will be able to withdraw and profit from the attacker and now he has lost all his money. This is the exact purpose of the virtual offset and the reason it is implemented to break inflation attacks. The more they inflate the more they lose to others' deposits, causing a vicious cycle that causes massive loss to the attacker. |
@IAm0x52 check your math there You said: But, despite the math being wrong, I do get your point. The attacker can mitigate this pretty easily though. Just send in $1000 initially and deposit a larger amount ($5000 let's say, so you yourself acquire most of the "cheap" shares). The attacker will then put around $200 of capital at risk for the future ability to drain all deposits. Though, even if the attacker had to put up the full $1000 at risk to drain all future deposits, it would be worth and a valid attack. |
Hey could you provide a PoC on the posit-audit, thanks. |
Ah I see. The offset being used in the production code is not high enough. In fact it actually doesn't fix the inflation issue at all (either #54 or #57). I will recommend a higher offset. 1e3 can be broken with a donation of 1e9 which is an attainable number. Instead an offset of 1e9 would be more fitting and will break any chance of inflation. It was my error to approve such an offset. 1e3 does not remediate the possibility of inflation. |
@IAm0x52 It did fix the inflation issue, at least 99.9% of it, but it fixes 0% of the rounding issue. There are degrees to fixing things. Consider that this was a completely reasonable fix to #54, but if you had not known about #57, you would have never proposed a different fix. @Czar102 will leave the decision up to you now. |
Could you provide a PoC with the latest codebase? Thank you so much. |
@JoscelynFarr Sure, give me 1-2 days of time since it's a weekday. It should look pretty similar to the PoC in #57 but I'll have to modify it a bit. |
Yeah I'd like to second that request. I cannot get the repeated deposit and withdrawal working on the new code (or on the old code either). In order to get 2 shares of earnUSDC you have to deposit 2 USDC to get 2 EarnUSDC. Then when I withdraw 1 JUSD + 1 wei it pays 2 USDC but now the attackers EarnUSDC is also 0. So I am unable to get any additional USDC out of the vault. I see how your POC works as the primary deposit but I cannot get the repeated deposits and withdrawals to work. Additionally that only seems to work a single time per account, as further withdrawals revert with the message "lockedEarnUSDCAmount is bigger than earnUSDCBalance" |
@IAm0x52 you're not supposed to get additional USDC out of the vault. The profit is in your JOJODealer credit (reread the code a bit if you want to understand why). See this line of the PoC:
I will look at this further first thing after work tomorrow. |
Also @IAm0x52 yes optimally you should use a new EOA to do each deposit/withdraw iteration |
I succeeded with creating the PoC. For your convenience, I just forked the production Github repo and added my PoC there. Please run Link: https://github.com/detectiveking123/smart-contract-EVM I will also post the PoC script here for other people's convenience, though it will not work without some of the additional setup:
|
@Czar102 doesn't the admin having to call permitWithdrawRequests() change the severity of 57? |
@IllIllI000 Looking at the code, it is not so clear cut as "admin has to call permitWithdrawRequests". I believe this is a high -- there are a few points I'd like to make here:
|
The rules for Medium state |
@IllIllI000 This depends on which version of the code you're talking about. In the original version of the code, there can be much more lost than 0.1% lost per withdraw request. But in the updated, post-audit version of the code, it is around 0.1% per withdraw request. Also consider that these withdraws can be done as many times as one wants. This is a fundamental issue that breaks the withdraw functionality though; the calculations are just incorrect, which doesn't affect just the exploiter, but also regular users. As I stated above, even a well-acting admin is forced into a decision between one type of user fund loss or another. |
The only thing that matters for these bugs is the original version of the code during the time of the audit. Can you outline what the maximum percentage lost is (just a ballpark estimate of the order of magnitude), so the Sherlock team can answer the question of how the admin having to approve it affects the severity? |
@IllIllI000 Up to But again, the withdraw functionality is broken to the point where there can be a loss of user funds regardless of what the admin does. |
Thanks detectiveking123. @Czar102 can you answer how the admin constraint affects severity (the reasoning based on the rules, not just the final severity answer), so we can properly submit and judge cases like this in the future? |
I think the point made by @detectiveking123 is an accurate view – an admin doesn't make mistakes when it comes to their interactions, but when it comes to accepting values determined by an in-scope code, I think it's reasonable that a relatively small rounding error may go unnoticed. It seems this "signing off" of an admin on withdrawals is only an external sanity check, and we obviously can't assume it will catch any small discrepancy. I'd like to note that we have some README questions in works that will allow Watsons to answer the question "What checks are made when calling admin function xyz?" to be able to define the scope of issues with admin interactions more precisely. |
Have already fix in here: |
This argument doesn't make much sense to me. As pointed out by @IllIllI000 with permitWithdrawRequests() there is a delay on every withdrawal. Inflation is ridiculously easy to see when it happens and both attacks are only is effective with inflation. Lots of eyes are on the vault at launch and anyone could escalate to admin before any funds leave. Admin can also remove all funds from the contract so you are incorrect in saying that it always causes loss of funds, swap to remove USDC or refundJUSD to remove JUSD, then redistribute funds back to appropriate parties. IMO seems very unlikely either would lead to loss even before fixes. Seems judgment is firm but looks like both #54 and #57 should be low, due to this obvious constraint we've overlooked. |
0x52
high
FundRateArbitrage is vulnerable to inflation attacks
Summary
When index is calculated, it is figured by dividing the net value of the contract (including USDC held) by the current supply of earnUSDC. Through deposit and donation this ratio can be inflated. Then when others deposit, their deposit can be taken almost completely via rounding.
Vulnerability Detail
FundingRateArbitrage.sol#L98-L104
Index is calculated is by dividing the net value of the contract (including USDC held) by the current supply of totalEarnUSDCBalance. This can be inflated via donation. Assume the user deposits 1 share then donates 100,000e6 USDC. The exchange ratio is now 100,000e18 which causes issues during deposits.
FundingRateArbitrage.sol#L258-L275
Notice earnUSDCAmount is amount / index. With the inflated index that would mean that any deposit under 100,000e6 will get zero shares, making it exactly like the standard ERC4626 inflation attack.
Impact
Subsequent user deposits can be stolen
Code Snippet
FundingRateArbitrage.sol#L258-L275
Tool used
Manual Review
Recommendation
Use a virtual offset as suggested by OZ for their ERC4626 contracts
The text was updated successfully, but these errors were encountered: