Atharv - Dilution of Donations in Tranche
sherlock-admin2 opened this issue · comments
Atharv
medium
Dilution of Donations in Tranche
Summary
In this attack, the attacker takes advantage of the non-atomic nature of the donation and the share valuation process. By strategically placing deposit and withdrawal transactions around the donation transaction, the attacker can temporarily inflate their share of the pool to capture a large portion of the donated funds, which they then quickly exit with, leaving the pool with their original investment plus extra value extracted from the donation.
Vulnerability Detail
Though there is no reasonable flow where users will just 'donate' assets to others, Risk Manager may needs to call donateToTranche
to compensate the jrTranche after an auction didn't get sold and was manually liquidated after cutoff time or in case of bad debt.
donateToTranche
function of a lending pool smart contract, allows for a sandwich attack that can be exploited by a malicious actor to dilute the impact of donations made to a specific tranche. This attack involves front-running a detected donation transaction with a large deposit and following it up with an immediate withdrawal after the donation is processed.
The lending pool contract in question allows liquidity providers (LPs) to deposit funds into tranches, which represent slices of the pool's capital with varying risk profiles. The donateToTranche
function permits external parties to donate assets to a tranche, thereby increasing the value of the tranche's shares and benefiting all LPs proportionally. Transactions can be observed by one of the LP's before they are mined. An attacker can exploit this by identifying a pending donation transaction and executing a sandwich attack. This attack results in the dilution of the donation's intended effect, as the attacker's actions siphon off a portion of the donated funds.
Impact
Dilution of Donation: The intended impact of the donation on the original LPs is diluted as the attacker siphons off a portion of the donated funds.
Steps to Reproduce Issue
-
Front-Running: The attacker deposits a significant amount of assets into the target tranche before the donation transaction is confirmed, temporarily increasing their share of the tranche.
-
Donation Processing: The original donation transaction is processed, increasing the value of the tranche's shares, including those recently acquired by the attacker.
-
Back-Running: The attacker immediately withdraws their total balance from the tranche, which now includes a portion of the donated assets, effectively extracting value from the donation meant for the original LPs.
Code Snippet
Coded PoC
https://github.com/Atharv181/Arcadia-POC
- git clone https://github.com/Atharv181/Arcadia-POC
- cd Arcadia-POC
- forge install
- forge test --mt test_PoC -vvv
Tool used
Manual Review, Foundry
Recommendation
- Snapshot Mechanism: Take snapshots of share ownership at random intervals and distribute donations based on the snapshot to prevent exploitation.
- Timelocks: Implement a timelock mechanism that requires funds to be locked for a certain period before they can be withdrawn.
1 comment(s) were left on this issue during the judging contest.
takarez commented:
invalid
Low severity, A combination of Front and Back running not possible on Base, Optimism and Arbitrum due to private sequencers. Other chains were not explicitly mentioned, or at least all the issues and the duplicates do not present a possible chain where a sandwich attack is possible.
Escalate
I'm escalating this on behalf of @pa-sh0k because I believe his claims should be considered via a proper escalation. This is what he states:
The loss of funds can be caused not only if the attacker executes an atomic sandwich attack, but also if they do the following steps:
- backrun the liquidation that ended up in transferring collateral to the admin, since this always leads to the donateToTranche call, and then deposit to the tranche
- backrun the donation itself and redeem their shares, obtaining profit
Hence, the reason for invalidation is incorrect.
Also, the sponsor has acknowledged the issue in discord channel.
By Sherlock’s rules, this issue is a medium, it suits the following criteria: “Causes a loss of funds but requires certain external conditions or specific states, or a loss is highly constrained. The losses must exceed small, finite amount of funds, and any amount relevant based on the precision or significance of the loss”.
Escalate
I'm escalating this on behalf of @pa-sh0k because I believe his claims should be considered via a proper escalation. This is what he states:
The loss of funds can be caused not only if the attacker executes an atomic sandwich attack, but also if they do the following steps:
- backrun the liquidation that ended up in transferring collateral to the admin, since this always leads to the donateToTranche call, and then deposit to the tranche
- backrun the donation itself and redeem their shares, obtaining profit
Hence, the reason for invalidation is incorrect.
Also, the sponsor has acknowledged the issue in discord channel.
By Sherlock’s rules, this issue is a medium, it suits the following criteria: “Causes a loss of funds but requires certain external conditions or specific states, or a loss is highly constrained. The losses must exceed small, finite amount of funds, and any amount relevant based on the precision or significance of the loss”.
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.
If such a backrun would happen, the admin would just not donate via donateToTranche
.
The auction proceeds would in this case donated via different means (it is already the unhappy flow of an unhappy flow where things have to be handled manually).
A bit annoying, but no loss of user funds.
So the attacker has the risk that they have to provide liquidty in the Tranche for un unknown amount of time, without any certainty of profits at all.
Detecting the backrun and getting the funds back to the depositors via different means is indeed a solution to the problem.
However, it does not make the problem itself invalid or non-existent: there may be different solutions and all of them, of course, should lead to no loss of user funds.
The issue still exists and it can be prevented using the described method only if it was known beforehand, but it was not stated in the known issues or anywhere else.
I have said this in the discord channel, but since the conversation was moved here, I will add a quote so anyone can understand the context:
private sequencer doesn't have anything to do with this issue, this can be done in separate blocks and with backruns,
check my comment on this issue: #128
So, the attack has the following scenario:
- a liquidation results in unhappy flow with unsold collateral
- collateral is sent to the admin
- in the next block (or a bit later), once the attacker sees this onchain, attacker deposits funds to the junior tranche
- during period of length T admin manually sells the collateral
- admin donates the funds to the junior tranche
- in next block (or a bit later), once the attacker sees this onchain, attacker redeems their shares
This results in gains for the attacker and losses for the original depositors, since they got less funds that they should have after the liquidation was manually resolved
Also, wanted to state that my issue, #128 , is a duplicate of this one and everything said in this discussion is applicable to it and vice versa.
I want to state again that donateToTranche
is never enforced by the protocol in any means.
It is a function that can but not must be used in the case a manual liquidation is triggered.
Normally if the protocol functions as expected, auctions terminate automatically.
We however foresaw a flow that if for some reason an auction would not end (this already means things did not work out as expected), a trusted user (set by the Creditor) can manually liquidate the assets.
After the assets are liquidated he can choose if and how to distribute the assets to potentially impacted users.
The donateToTranche
is a function that might help in this process but the protocol obliges nobody to use it.
It is not part of the core functionality. It is a function to help in an already manual unhappy flow of a unhappy flow.
There are no guaranteed losses for LPs, and attackers have no guaranteed way to make profits, but have to put significant amounts of capital at stake.
Note on the recommendations:
Snapshot Mechanism: Take snapshots of share ownership at random intervals and distribute donations based on the snapshot to prevent exploitation.
Snapshots are not mutually exclusive from donateToTranche
. We are in a manual trusted flow.
If the manual liquidator could liquidate remaining assets before new people deposit in the Tranche he can use donateToTranche
. If not they can use a snapshot and distribute funds.
Timelocks: Implement a timelock mechanism that requires funds to be locked for a certain period before they can be withdrawn.
This creates new issues and risks since the the locking mechanisms of Tranches are already complex.
Before this issue was submitted, escalated and this discussion was started, it was not stated anywhere, that backrunning activity will be monitored and if something malicious is noticed, other ways of liquidation settling will be used.
If you have known about such attack vector and already had other ways of handling it, it should have been stated in the known issues.
It is a trusted manual flow executed by a permissioned role!
And that is clearly stated in the code
The fact that it is executed by a permissioned role is already assumed in the issue. The issue is that when trusted manual flow is executed by a permissioned role using donateToTranche
, users' funds can be stolen.
What is stated in the code (LendingPool.sol#L346-L347) about donateToTranche
is the following:
It is supposed to serve as a way to compensate the jrTranche after an auction didn't get sold and was manually liquidated after cutoffTime.
From this comment it cannot be concluded that backrunning will be monitored and if something malicious is noticed, other ways of liquidation settling will be used.
The fact that this flow is executed by a permissioned role does not make it invincible.
What was stated previously:
I do acknowledge it exists, but we consider it a low, since pay-out for attackers is uncertain and as you said, it can be prevented if an attacker really tries to pull it of.
Obviously, this issue can't be prevented it is not known about by the admin. Since it was submitted, which is my job as a Watson, now the admins know about it and can prevent the loss of funds.
Addressing the Sherlock's criteria:
“Causes a loss of funds but requires certain external conditions or specific states, or a loss is highly constrained. The losses must exceed small, finite amount of funds, and any amount relevant based on the precision or significance of the loss”.
This issue requires certain external conditions and causes loss of funds if these conditions are met.
Regarding the following words of the sponsor:
attackers have no guaranteed way to make profits, but have to put significant amounts of capital at stake
Attacker is guaranteed to make profits if the conditions are met. Also, probability of depositing funds into the protocol as a liquidity provider for a short period of time, which the attacker would have to do, has near-to-zero risk of losing money. They would probably even make money by providing liquidity.
From this comment it cannot be concluded that backrunning will be monitored and if something malicious is noticed, other ways of liquidation settling will be used.
As we say in the comment:
It is supposed to serve as a way to compensate the jrTranche after an auction didn't get sold and was manually liquidated after cutoffTime.
not
It is supposed to serve as the way to compensate the jrTranche after an auction didn't get sold and was manually liquidated after cutoffTime.
Addressing the Sherlock's criteria:
We even state that this flow is not enforced by the smart contracts:
https://github.com/arcadia-finance/lending-v2/blob/dcc682742949d56928e7e8e281839d2229bd9737/src/Liquidator.sol#L431
"Not enforced by the smart contracts" is equal to "it is manual", which is already assumed in the issue.
The issue is invalid if donateToTranche
is never used for refunding the users. The comments imply that at some point it will be used for it, so, once it is used, the mentioned conditions are met and loss of funds is caused.
The argument that if admin wants to use donateToTranche
but sees that an attacker made a large deposit trying to frontrun the call and then will decide to use other way of refunding is not applicable here, as before this issue was submitted, the need for monitoring and preventing such attack was not known.
If other way of refunding is chosen for some other reasons, the attacker can simply withdraw their funds at no loss.
Talking about the severity the identified vulnerability clearly meets the criteria for a medium severity issue. It results in a loss of funds, as highlighted by the Dilution of Donations, which directly impacts the intended recipients of those funds. While the exploit may require certain external conditions or specific states to occur, the potential for loss is significant and cannot be dismissed lightly.
The fact that the vulnerability exists and can lead to tangible financial harm underscores its severity. Even though the losses may not be immediate or guaranteed, they exceed a small, finite amount of funds, especially when considering the significance of the loss to affected lenders.
Let's not use chatGPT when discussing the findings.
I would like to add that even if there is no attacker, this will harm other users' profits. As long as a new user deposits assets into jrTranche after _settleAuction()
is called, when donateToTranche()
is called later to compensate the jrTranche, the profit of the original user will be diluted.
Obviously, the operation of a new user depositing assets into tranche will happen at any time, and as long as the protocol is not checked, you have no way to avoid it.
The protocol will later "donate" these proceeds back to the impacted Tranches
It seems that the donation was to be to Tranches, not to Tranches' holders at the time of the auction/auction failure. This itself is a logical issue that should be recognized as the core issue here.
I think most doubts regarding the validity of this issue come from the fact that modifying the planned usage of the functionality solves this issue. I don't think this means that an issue is invalid.
If I am mistaken and there is other clear evidence that the donation was to be done to holders at the time the auctioned assets were subtracted from a tranche's balance, I will agree with invalidation. Right now, the sponsor's arguments seem to be that this the comments were suggesting a route for next steps, and these steps could be different. Without mentioning the mint monitoring checks, this seems to be equivalent to an approach that "in a manual action, we would notice this issue", while assuming that the issue will be found anyway doesn't invalidate its existence.
@nevillehuang @Thomas-Smets do my points make sense?
@Czar102 This issue is definitely possible, however, donateToTranche()
is not a core functionality of the protocol, as it is solely used for manual liquidations to allow the manual liquidator to possibly serve as a way to compensate junior tranches. I suggest relooking at the sponsor comments here and here
@nevillehuang why "not a core functionality" is used as an argument for issue's invalidity? It is definitely in scope and is a part of the protocol
@nevillehuang why "not a core functionality" is used as an argument for issue's invalidity? It is definitely in scope and is a part of the protocol
The function is never called from a function within the protocol.
It is always a someone from outside the protocol that must call the function.
An attacker is never certain the function will be called, they cannot enforce it in any way possible.
If you read my comments above I am not denying it is an issue, I stated that since there is no guarantee on profit and it can even be avoided there is ever profit for an attacker, I consider it a low issue.
Is there an expectation of a donation of nonzero proceeds in the unhappy flow? Or are positive proceeds not expected? @Thomas-Smets
I would like to add that even if there is no attacker, this will harm other users' profits. As long as a new user deposits assets into jrTranche after _settleAuction() is called, when donateToTranche() is called later to compensate the jrTranche, the profit of the original user will be diluted.
Also dilutes the donations for users.
It is clearly mentioned here that it is used to compensate the tranche after an auction didn't get sold (unhappy flow)
My point of view: it is a low issue
I feel discussing it more is not that useful since we are just in a yes-no discussion.
We are talking about an emergency situation where the protocol already didn't function as expected and that has to be resolved 100% manually. And in no way is donateToTranche()
enforced to be called, if it can be called it makes things easier, if not it can still be resolved 100% without losses to users.
- It should never occur in the first place (low probability)
- If an attacker wants to make significant profits with this, they have to put a lot of funds in the Tranche (order of total liquidity in the Tranche). This also can't be done atomic or via flash loans, so it have to actual attacker funds locked in Tranche. Even if it happens the dilution is a share of a single badDebt amount shared over all LPs.
- The attacker can not trigger or be sure
donateToTranche()
is ever triggered. The recipient of the Account is a permissioned role, and not forced to usedonateToTranche()
in this specific flow.
@Czar102 Is there an expectation of a donation of nonzero proceeds in the unhappy flow? Or are positive proceeds not expected?
Not sure I fully understand the question, auctionBoughtIn
is only called if an auction failed, that can be due to market conditions (-> no profit) or due to technical problems (reverting getValue or sometghing like that), in the latter case it can be that the proceeds of the manually liquidated Account were bigger than the initial debt.
@Atharv181, It is clearly mentioned here that it is used to compensate the tranche after an auction didn't get sold (unhappy flow)
We say very clear: It is A way. We do not say it is the way.
It is not enforced by the logic of the protocol!
The situation of unhappy flow hasn't been taken out of scope (despite it being perceived as extremely improbable), and it has been explicitly mentioned that the donateToTranche()
is to be used in scenarios where funds from the liquidation are to be returned. An action not being enforced by the smart contract logic doesn't make the intended use of a function out of scope.
Since this approach is exploitable, I'm planning to consider it a valid Medium severity issue.
Result:
Medium
Has duplicates