Skip to content
This repository has been archived by the owner on Mar 3, 2024. It is now read-only.

nobody2018 - When DestinationVault is at loss, LMPVault._withdraw may revert in some cases due to underflow #716

Closed
sherlock-admin opened this issue Aug 30, 2023 · 6 comments
Labels
Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label Escalation Resolved This issue's escalations have been approved/rejected Medium A valid Medium severity issue Reward A payout will be made for this issue

Comments

@sherlock-admin
Copy link
Contributor

sherlock-admin commented Aug 30, 2023

nobody2018

medium

When DestinationVault is at loss, LMPVault._withdraw may revert in some cases due to underflow

Summary

[_calcUserWithdrawSharesToBurn](https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/libs/LMPDebt.sol#L231) is used to figure out how many shares we can burn from the destination as well as what our totalDebt deduction should be (totalDebt being a cached value). If its at a loss, they can only burn an amount proportional to their ownership of LMPVault. totalDebtBurn is the return value of this function, which is calculated based on the cached values. Since DestinationVault is at a loss, totalDebtBurn represents past debts, which may be higher than totalAssetsToPull. This will cause the subtraction underflow in [L475](https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVault.sol#L475) and revert. Users cannot withdraw funds.

Vulnerability Detail

This problem can occur when totalIdle has funds, but not enough to cover the funds the user wants to withdraw. _calcUserWithdrawSharesToBurn returns two values: sharesToBurn and totalDebtBurn. totalDebtBurn represents the past debt, and sharesToBurn represents the share to be withdrawn in DestinationVault. When DestinationVault is at a loss, the assetPulled by destVault.withdrawBaseAsset(sharesToBurn, address(this)) may be less than totalDebtBurn. If totalDebtBurn is greater than info.totalAssetsToPull, then subtraction underflow may occur at L475. In other words, this condition is met: assetPulled < info.totalAssetsToPull < totalDebtBurn. Because when i=1, the info.debtDecrease and info.totalAssetsPulled of L475 are equivalent to totalDebtBurn and assetPulled obtained when i=0 (L488-489).

File: v2-core-audit-2023-07-14\src\vault\LMPVault.sol
465:         if (info.totalAssetsToPull > 0) {
466:             uint256 totalVaultShares = totalSupply();
467: 
468:             // Using pre-set withdrawalQueue for withdrawal order to help minimize user gas
469:             uint256 withdrawalQueueLength = withdrawalQueue.length;
470:             for (uint256 i = 0; i < withdrawalQueueLength; ++i) {
471:                 IDestinationVault destVault = IDestinationVault(withdrawalQueue[i]);
472:->               (uint256 sharesToBurn, uint256 totalDebtBurn) = _calcUserWithdrawSharesToBurn(
473:                     destVault,
474:                     shares,
475:->                   info.totalAssetsToPull - Math.max(info.debtDecrease, info.totalAssetsPulled),//maybe underflow
476:                     totalVaultShares
477:                 );
478:                 if (sharesToBurn == 0) {
479:                     continue;
480:                 }
481: 
482:                 uint256 assetPreBal = _baseAsset.balanceOf(address(this));
483:->               uint256 assetPulled = destVault.withdrawBaseAsset(sharesToBurn, address(this));
484: 
485:                 // Destination Vault rewards will be transferred to us as part of burning out shares
486:                 // Back into what that amount is and make sure it gets into idle
487:                 info.idleIncrease += _baseAsset.balanceOf(address(this)) - assetPreBal - assetPulled;
488:->               info.totalAssetsPulled += assetPulled;
489:->               info.debtDecrease += totalDebtBurn;
.....
493:                 if (info.totalAssetsPulled > info.totalAssetsToPull) {
.....
496:                     break;
497:                 }
.....
502:                 if (info.totalAssetsPulled == info.totalAssetsToPull) {
503:                     break;
504:                 }
505:             }
506:         }

Impact

When DestinationVault is at a loss, users may not be able to withdraw funds due to underflow.

Code Snippet

https://github.com/sherlock-audit/2023-06-tokemak/blob/main/v2-core-audit-2023-07-14/src/vault/LMPVault.sol#L475

Tool used

Manual Review

Recommendation

File: v2-core-audit-2023-07-14\src\vault\LMPVault.sol
470:             for (uint256 i = 0; i < withdrawalQueueLength; ++i) {
471:                 IDestinationVault destVault = IDestinationVault(withdrawalQueue[i]);
472:                 (uint256 sharesToBurn, uint256 totalDebtBurn) = _calcUserWithdrawSharesToBurn(
473:                     destVault,
474:                     shares,
475:-                    info.totalAssetsToPull - Math.max(info.debtDecrease, info.totalAssetsPulled),
475:+                    info.totalAssetsToPull >= Math.max(info.debtDecrease, info.totalAssetsPulled) ? info.totalAssetsToPull - Math.max(info.debtDecrease, info.totalAssetsPulled) : info.totalAssetsToPull - Math.min(info.debtDecrease, info.totalAssetsPulled),
476:                     totalVaultShares
477:                 );
478:                 if (sharesToBurn == 0) {
479:                     continue;
480:                 }

Duplicate of #519

@github-actions github-actions bot added the Excluded Excluded by the judge without consulting the protocol or the senior label Sep 11, 2023
@sherlock-admin2 sherlock-admin2 changed the title Wobbly Sapphire Walrus - When DestinationVault is at loss, LMPVault._withdraw may revert in some cases due to underflow nobody2018 - When DestinationVault is at loss, LMPVault._withdraw may revert in some cases due to underflow Oct 3, 2023
@sherlock-admin2 sherlock-admin2 added the Non-Reward This issue will not receive a payout label Oct 3, 2023
@securitygrid
Copy link

Escalate
This is dup of #519

@sherlock-admin2
Copy link
Contributor

Escalate
This is dup of #519

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.

@sherlock-admin sherlock-admin added the Escalated This issue contains a pending escalation label Oct 3, 2023
@Trumpero
Copy link
Collaborator

Agree with the escalation, this issue is a duplicate of #519. It was my mistake when moving issues into the folders.

@Evert0x
Copy link
Contributor

Evert0x commented Oct 24, 2023

Planning to accept escalation

@Evert0x Evert0x added the Medium A valid Medium severity issue label Oct 26, 2023
@sherlock-admin sherlock-admin added Reward A payout will be made for this issue and removed Non-Reward This issue will not receive a payout labels Oct 26, 2023
@Evert0x
Copy link
Contributor

Evert0x commented Oct 26, 2023

Result:
Medium
Duplicate of #519

@sherlock-admin2 sherlock-admin2 removed the Escalated This issue contains a pending escalation label Oct 26, 2023
@sherlock-admin sherlock-admin added the Escalation Resolved This issue's escalations have been approved/rejected label Oct 26, 2023
@sherlock-admin2
Copy link
Contributor

Escalations have been resolved successfully!

Escalation status:

@sherlock-admin2 sherlock-admin2 added Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label and removed Excluded Excluded by the judge without consulting the protocol or the senior labels Oct 31, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label Escalation Resolved This issue's escalations have been approved/rejected Medium A valid Medium severity issue Reward A payout will be made for this issue
Projects
None yet
Development

No branches or pull requests

5 participants