-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
danzero - Small amount of borrow can drain pool #291
Comments
1 comment(s) were left on this issue during the judging contest. Honour commented:
|
I think this should be a unique issue on its own Possible duplicates: While these issues had some similarity with this report, there's no mention how user can "get away" by withdrawing the initial supplied amount. I believe it became a whole other issue when user can get away by withdrawing their initial supplied amount.When the pool implementation is upgraded which includes the health check fix by the admin, they could possibly be liquidated if the user didn't withdraw. |
Also why is this not a high vulnerability ? This is a direct theft of user funds and protocol which could spread like a wildfire if many people discovered this vulnerability knowing the simplicity to execute the attack which eventually could trigger a bank-run. |
Escalate This is a good catch, but the impact does not meet the criteria for medium severity. Here is the code where the precision loss occurs: uint256 userTotalDebt = balance.debtShares;
if (userTotalDebt != 0) userTotalDebt = userTotalDebt.rayMul(reserve.getNormalizedDebt());
userTotalDebt = assetPrice * userTotalDebt;
unchecked {
return userTotalDebt / assetUnit;
} For precision loss, we require This means we require This means that Most chainlink feeds return values in 8 decimals. Take for example In this case, assetUnit = Hence in order for precision loss, we require So each iteration, we can borrow less than 1e10 assets This means it takes 100 million iterations of supply->borrow->withdraw in order to profit $1. (Since With 4000 iterations per transaction, 25000 transactions are required. While the above transactions will use extreme amounts of gas we'll optimistically assume they take the average gas cost of an L2 which is $0.02 The cost would be $0.02 * 25k = $500, to earn $1. Side note: Also, this won't work at all for lower decimal assets like USDT/USDC since |
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. |
Thanks for the escalation and the breakdown, I agree that this should not be a medium severity. This should be a high severity, however for the record, this report also meets the medium vulnerability: From https://docs.sherlock.xyz/audits/real-time-judging/judging#v.-how-to-identify-a-medium-issue:
This attack can be replayed indefinitely on almost every token out there given 18 decimal format token is the default standard for ERC20 token. Some ways this attack can be stopped is if the admin froze the asset (which could be temporary) or all lenders decided to withdraw the asset. And for why this deserves a high severity, from https://docs.sherlock.xyz/audits/real-time-judging/judging#iv.-how-to-identify-a-high-issue :
Take an example, a lender that lends $100 in a $100_000 pool, it is guaranteed that this attack will eventually cause a $100 loss given it can be replayed indefinitely, maybe not in 1 day. But as more users discover this vulnerability, the loss could scale up pretty quick. |
I completely agree with the escalation: #291 (comment) The attack exceeds the gain many times over. This is no more than Low severity. Planning to accept the escalation and invalidate the issue. |
The gain does not matter, when evaulating the severity of a vulnerability, Sherlock rules don't consider the gain of the attacker. |
@Almur100 I don't understand what you mean by that comment. You want all duplicates of this issue to be reviewed separately from this issue?
You mean someone would pay $500 to get $1, and it's an economically viable attack to be a valid Medium? |
No , I am saying #137,#148,#390 are explaining the same problem which is different from the issue #291. Similar issue is accepted in sentiment v2, see the sentiment v2 issue , the link is sherlock-audit/2024-08-sentiment-v2-judging#572 min borrow amount should be set in lending and borrowing protocol, but in zerolend there is no check for borrowing minimum amounts, Attackers can borrow multiple small amounts of positions which may not be profitable during liquidation. This is the problem which is explained in issue #137, issue #148, issue #390. |
|
Following your example, executing the script will cost each person $20 ,to borrow $0.04. No follower would do this. Currently AAVE doesn't implement a min. borrow as well, see here , the influencer doesn't have to wait for zerolend, if this was realistic |
The person sharing this attack will not share how much profit will be made, just how it is possible to steal someone's token. People that listen to this type of tips are usually not so savvy, they just execute the script and think later before they realized the results. Some would even try to execute the script more than once.
AAVE has different health check logic, you can try it yourself it will not work. |
I don't think this logic can be used to validate any attack especially on sherlock. Savvy or not, i believe such people just want to make a profit rather than "steal someone's tokens" and loose money in return. Also the attack cannot be replayed indefinitely as claimed due to the heavy cost on the attacker |
We are talking about users here, not admin, so we can't expect them to be rational.
Using that logic, even a 1 penny transaction cost could stop an indefinite replay attack if the attacker only had $1 in their wallet. It can be replayed indefinitely, because anyone can exploit this bug at anytime. |
The implementation is the same as in AAVE, and there is no issue. In the Sentiment contest, a similar issue is valid because it is stated in the readme that the minimum borrow/debt amount can be 0 or a very low value. Here, the situation is different; it can still be low value, but the user has no initiative to take small borrowings. Furthermore, the readme states that the protocol will use "liquidation bots that run off-chain to execute liquidations". All the duplicates indicate that the protocol will get bad debt. But that won't happen because the bots will take care of this. My decision to accept the escalation and invalidate the issue remains. |
I don't think you have fully understood the issue, there are 2 different root causes mentioned in this issue : The non-zero borrow amount requirement and the As long as the attacker borrow amount is under 1e10 for a 18 token decimal, their debt will be deemed as 0 from the |
The duplicates of this issue only stops at borrowing and hence the impact is limited to bad debts. Here, the token has been withdrawn, the owner of the token is the user, can't be liquidated, the fund has been lost, it will not return to the pool nor the protocol, it is not a case of bad debt anymore, it is theft of funds which could lead to insolvency. |
@git-denial Nobody would borrow 1e10 for an 18-decimal token. I don't think you understand how 1e10 is smaller than 1e18. This is 0.00000001 of the token's value. The attack is economically unsustainable. |
@cvetanovv You don't understand how 1e10 can become 1000e18. I have provided a scenario where this attack can be done by many people to make it more 'economically sustainable'. Downplaying a vulnerability like this is why hackers prefer to hack protocols themselves. |
@cvetanovv There's 0 mention of attacker's profit, economic sustainability, etc. And if you think it implies likelihood, there's also 0 mention of it. |
@cvetanovv
And regarding likelihood here: #437
|
When the attack costs 500 times the gain, it is not an adequate grief attack, and no one will do it. Juan has shown a very good example: The example that the attacker can share the attack cannot be taken into consideration because it is not written in the issue itself. But even if we take it into account, the cost of the attack remains the same. Someone to earn $500 would have to pay $250,000 in gas taxes. This issue is no more than Low/Information severity. My decision is to accept the escalation and invalidate the issue. |
Result: |
Escalations have been resolved successfully! Escalation status:
|
danzero
High
Small amount of borrow can drain pool
Summary
A user can supply some amount of a token and borrow a small amount of a token from the pool and withdraw the initial amount they supplied without repaying the borrowed amount which could cause insolvency for the pool.
Root Cause
https://github.com/sherlock-audit/2024-06-new-scope/blob/main/zerolend-one/contracts/core/pool/logic/ValidationLogic.sol#L124
The
validateBorrow
function InValidationLogic.sol
is used to validate borrow request from the user, currently it only requires that the borrowed amount is not 0, this allows user to borrow a minuscule amount of token.https://github.com/sherlock-audit/2024-06-new-scope/blob/main/zerolend-one/contracts/core/pool/logic/ValidationLogic.sol#L239
The
validateHealthFactor
function inValidationLogic.sol
validate the requested amount of withdrawal from the user corresponding to the health factor. ThehealthFactor
variable returned from theGenericLogic.calculateUserAccountData
function is compared with theHEALTH_FACTOR_LIQUIDATION_THRESHOLD
to be bigger or equal or else it reverts with an error.https://github.com/sherlock-audit/2024-06-new-scope/blob/main/zerolend-one/contracts/core/pool/logic/GenericLogic.sol#L69
Inside the
calculateUserAccountData
function thevars.totalDebtInBaseCurrency
variable determines thehealthFactor
variable. Thevars.totalDebtInBaseCurrency
variable is increased by the_getUserDebtInBaseCurrency
function.https://github.com/sherlock-audit/2024-06-new-scope/blob/main/zerolend-one/contracts/core/pool/logic/GenericLogic.sol#L184
Inside this function the
usersTotalDebt
variable is divided by theassetUnit
variable which is fetched from the reserve configuration of the asset. This is a problem if theassetUnit
is bigger than theusersTotalDebt
as it will amount to 0 in the end which would set thehealthFactor
variable totype(uint256).max
which would allow the user to withdraw the initial amount that is supplied while still keeping the borrowed amount.Internal pre-conditions
External pre-conditions
Attack Path
Impact
PoC
Mitigation
Fix the require statement in
validateBorrow
function InValidationLogic.sol
such that it the minimum borrowed amount depends on the decimals of the asset borrowed. An attacker can get away borrowing 1e9 of an asset with 1e18 decimal but fails when it tries to borrow 1e10, hence the require statement could be something like "minimum borrow amount needs to be 8 decimal difference to the asset decimal". Could be something like this:The text was updated successfully, but these errors were encountered: