Skip to content
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

xiaoming90 - Value of vault shares can be manipulated #67

Closed
sherlock-admin3 opened this issue Jul 3, 2024 · 10 comments
Closed

xiaoming90 - Value of vault shares can be manipulated #67

sherlock-admin3 opened this issue Jul 3, 2024 · 10 comments
Labels
Escalation Resolved This issue's escalations have been approved/rejected Non-Reward This issue will not receive a payout Sponsor Confirmed The sponsor acknowledged this issue is valid Will Fix The sponsor confirmed this issue will be fixed

Comments

@sherlock-admin3
Copy link

sherlock-admin3 commented Jul 3, 2024

xiaoming90

Medium

Value of vault shares can be manipulated

Summary

The value of vault shares can be manipulated. Inflating the value of vault shares is often the precursor of more complex attacks. Internal (Notional-side) or external protocols that integrate with the vault shares might be susceptible to potential attacks in the future that exploit this issue.

Vulnerability Detail

It was found that the value of the vault shares can be manipulated.

Instance 1 - Kelp

To increase the value of vault share, malicious can directly transfer a large number of stETH to their KelpCooldownHolder contract. In Line 78, the holder contract will determine the number of stETH to be withdrawn from LIDO via IERC20(stETH).balanceOf(address(this)). This means that all the stETH tokens residing on the holder contract, including the ones that are maliciously transferred in, will be withdrawn from LIDO.

https://github.com/sherlock-audit/2024-06-leveraged-vaults/blob/main/leveraged-vaults-private/contracts/vaults/staking/protocols/Kelp.sol#L78

File: Kelp.sol
69:     /// @notice this method need to be called once withdraw on Kelp is finalized
70:     /// to start withdraw process from Lido so we can unwrap stETH to ETH
71:     /// since we are not able to withdraw ETH directly from Kelp
72:     function triggerExtraStep() external {
73:         require(!triggered);
74:         (/* */, /* */, /* */, uint256 userWithdrawalRequestNonce) = WithdrawManager.getUserWithdrawalRequest(stETH, address(this), 0);
75:         require(userWithdrawalRequestNonce < WithdrawManager.nextLockedNonce(stETH));
76: 
77:         WithdrawManager.completeWithdrawal(stETH);
78:         uint256 tokensClaimed = IERC20(stETH).balanceOf(address(this));
79: 
80:         uint256[] memory amounts = new uint256[](1);
81:         amounts[0] = tokensClaimed;
82:         IERC20(stETH).approve(address(LidoWithdraw), amounts[0]);
83:         LidoWithdraw.requestWithdrawals(amounts, address(this));
84: 
85:         triggered = true;
86:     }

When determining the value of vault share of a user, the convertStrategyToUnderlying function will be called, which internally calls _getValueOfWithdrawRequest function.

The withdrawsStatus[0].amountOfStETH at Line 126 will be inflated as the amount will include the stETH attackers maliciously transferred earlier. As a result, the vault share will be inflated.

https://github.com/sherlock-audit/2024-06-leveraged-vaults/blob/main/leveraged-vaults-private/contracts/vaults/staking/protocols/Kelp.sol#L126

File: Kelp.sol
114:     function _getValueOfWithdrawRequest(
115:         WithdrawRequest memory w,
116:         address borrowToken,
117:         uint256 borrowPrecision
118:     ) internal view returns (uint256) {
119:         address holder = address(uint160(w.requestId));
120: 
121:         uint256 expectedStETHAmount;
122:         if (KelpCooldownHolder(payable(holder)).triggered()) {
123:             uint256[] memory requestIds = LidoWithdraw.getWithdrawalRequests(holder);
124:             ILidoWithdraw.WithdrawalRequestStatus[] memory withdrawsStatus = LidoWithdraw.getWithdrawalStatus(requestIds);
125: 
126:             expectedStETHAmount = withdrawsStatus[0].amountOfStETH;
127:         } else {
128:             (/* */, expectedStETHAmount, /* */, /* */) = WithdrawManager.getUserWithdrawalRequest(stETH, holder, 0);
129: 
130:         }
131: 
132:         (int256 stETHToBorrowRate, /* */) = Deployments.TRADING_MODULE.getOraclePrice(
133:             address(stETH), borrowToken
134:         );
135: 
136:         return (expectedStETHAmount * stETHToBorrowRate.toUint() * borrowPrecision) /
137:             (Constants.EXCHANGE_RATE_PRECISION * stETH_PRECISION);
138:     }

Instance 2 - Ethena

Etherna vault is vulnerable to similar issue due to the due of .balanceOf at Line 37 below.

Before starting the cooldown, malicious user can directly transfer in a large number of sUSDe to the EthenaCooldownHolder holder contract.

https://github.com/sherlock-audit/2024-06-leveraged-vaults/blob/main/leveraged-vaults-private/contracts/vaults/staking/protocols/Ethena.sol#L37

File: Ethena.sol
35:     function _startCooldown() internal override {
36:         uint24 duration = sUSDe.cooldownDuration();
37:         uint256 balance = sUSDe.balanceOf(address(this));
38:         if (duration == 0) {
39:             // If the cooldown duration is set to zero, can redeem immediately
40:             sUSDe.redeem(balance, address(this), address(this));
41:         } else {
42:             // If we execute a second cooldown while one exists, the cooldown end
43:             // will be pushed further out. This holder should only ever have one
44:             // cooldown ever.
45:             require(sUSDe.cooldowns(address(this)).cooldownEnd == 0);
46:             sUSDe.cooldownShares(balance);
47:         }
48:     }

Thus, when code in Lines 87 and 99 are executed, the userCooldown.underlyingAmount returns will be large, which inflates the value of the vault shares.

https://github.com/sherlock-audit/2024-06-leveraged-vaults/blob/main/leveraged-vaults-private/contracts/vaults/staking/protocols/Ethena.sol#L77

File: Ethena.sol
077:     function _getValueOfWithdrawRequest(
078:         WithdrawRequest memory w,
079:         address borrowToken,
080:         uint256 borrowPrecision
081:     ) internal view returns (uint256) {
082:         address holder = address(uint160(w.requestId));
083:         // This valuation is the amount of USDe the account will receive at cooldown, once
084:         // a cooldown is initiated the account is no longer receiving sUSDe yield. This balance
085:         // of USDe is transferred to a Silo contract and guaranteed to be available once the
086:         // cooldown has passed.
087:         IsUSDe.UserCooldown memory userCooldown = sUSDe.cooldowns(holder);
088: 
089:         int256 usdeToBorrowRate;
090:         if (borrowToken == address(USDe)) {
091:             usdeToBorrowRate = int256(Constants.EXCHANGE_RATE_PRECISION);
092:         } else {
093:             // If not borrowing USDe, convert to the borrowed token
094:             (usdeToBorrowRate, /* */) = Deployments.TRADING_MODULE.getOraclePrice(
095:                 address(USDe), borrowToken
096:             );
097:         }
098: 
099:         return (userCooldown.underlyingAmount * usdeToBorrowRate.toUint() * borrowPrecision) /
100:             (Constants.EXCHANGE_RATE_PRECISION * USDE_PRECISION);
101:     }

Impact

Inflating the value of vault shares is often the precursor of more complex attacks. Internal (Notional-side) or external protocols that integrate with the vault shares might be susceptible to potential attacks in the future that exploit this issue.

Code Snippet

https://github.com/sherlock-audit/2024-06-leveraged-vaults/blob/main/leveraged-vaults-private/contracts/vaults/staking/protocols/Kelp.sol#L78

https://github.com/sherlock-audit/2024-06-leveraged-vaults/blob/main/leveraged-vaults-private/contracts/vaults/staking/protocols/Kelp.sol#L126

https://github.com/sherlock-audit/2024-06-leveraged-vaults/blob/main/leveraged-vaults-private/contracts/vaults/staking/protocols/Ethena.sol#L37

https://github.com/sherlock-audit/2024-06-leveraged-vaults/blob/main/leveraged-vaults-private/contracts/vaults/staking/protocols/Ethena.sol#L77

Tool used

Manual Review

Recommendation

Instance 1 - Kelp

Consider using the before and after balances to determine the actual number of stETH obtained after the execution of WithdrawManager.completeWithdrawal function to guard against potential donation attacks.

function triggerExtraStep() external {
    require(!triggered);
    (/* */, /* */, /* */, uint256 userWithdrawalRequestNonce) = WithdrawManager.getUserWithdrawalRequest(stETH, address(this), 0);
    require(userWithdrawalRequestNonce < WithdrawManager.nextLockedNonce(stETH));
+		uint256 tokenBefore = IERC20(stETH).balanceOf(address(this));
    WithdrawManager.completeWithdrawal(stETH);
+		uint256 tokenAfter = IERC20(stETH).balanceOf(address(this));

-    uint256 tokensClaimed = IERC20(stETH).balanceOf(address(this));
+    uint256 tokensClaimed = tokenAfter - tokenBefore

    uint256[] memory amounts = new uint256[](1);
    amounts[0] = tokensClaimed;
    IERC20(stETH).approve(address(LidoWithdraw), amounts[0]);
    LidoWithdraw.requestWithdrawals(amounts, address(this));

    triggered = true;
}

Instance 2 - Ethena

Pass in the actual amount of sUSDe that needs to be withdrawn instead of using the balanceOf.

- function _startCooldown() internal override {
+ function _startCooldown(uint256 balance) internal override {
    uint24 duration = sUSDe.cooldownDuration();
-    uint256 balance = sUSDe.balanceOf(address(this));
    if (duration == 0) {
        // If the cooldown duration is set to zero, can redeem immediately
        sUSDe.redeem(balance, address(this), address(this));
    } else {
        // If we execute a second cooldown while one exists, the cooldown end
        // will be pushed further out. This holder should only ever have one
        // cooldown ever.
        require(sUSDe.cooldowns(address(this)).cooldownEnd == 0);
        sUSDe.cooldownShares(balance);
    }
}
@github-actions github-actions bot closed this as completed Jul 5, 2024
@github-actions github-actions bot added Medium A valid Medium severity issue Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label labels Jul 5, 2024
@mystery0x mystery0x removed the Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label label Jul 11, 2024
@mystery0x mystery0x reopened this Jul 11, 2024
@sherlock-admin4 sherlock-admin4 changed the title Tart Aegean Nightingale - Value of vault shares can be manipulated xiaoming90 - Value of vault shares can be manipulated Jul 11, 2024
@sherlock-admin4 sherlock-admin4 added the Reward A payout will be made for this issue label Jul 11, 2024
@jeffywu
Copy link

jeffywu commented Jul 13, 2024

While this is valid and we will fix it, it's also not clear that this can be used as an attack vector. Vault shares cannot be shorted so manipulating the price up does not create an obvious way for an attacker to profit.

@sherlock-admin3 sherlock-admin3 added Sponsor Confirmed The sponsor acknowledged this issue is valid Will Fix The sponsor confirmed this issue will be fixed labels Jul 13, 2024
@novaman33
Copy link

Escalate,
It is an informational finding that does not show an a clear attack. It falls into the future issues category of sherlock docs.

@sherlock-admin3
Copy link
Author

Escalate,
It is an informational finding that does not show an a clear attack. It falls into the future issues category of sherlock docs.

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-admin4 sherlock-admin4 added the Escalated This issue contains a pending escalation label Jul 13, 2024
@WangSecurity
Copy link

WangSecurity commented Jul 15, 2024

The report itself says that it opens up an attack vector for the future. Moreover, I don't see how this issue causes the loss of funds or qualifies for Med severity.

Planning to accept the escalation and invalidate the issue.

@0502lian
Copy link

The report itself says that it opens up an attack vector for the future. Moreover, I don't see how this issue causes the loss of funds or qualifies for Med severity.

Planning to accept the escalation and leave the issue as it is.

Maybe my understanding is incorrect. If I am wrong, please correct me.
Planning to accept the escalation——means not leaving the issue as it is, means the issue is a low/info issue.

@WangSecurity
Copy link

@0502lian thank you, you’re correct

@WangSecurity WangSecurity removed the Medium A valid Medium severity issue label Jul 16, 2024
@sherlock-admin2 sherlock-admin2 added Non-Reward This issue will not receive a payout and removed Reward A payout will be made for this issue labels Jul 16, 2024
@WangSecurity
Copy link

Result:
Invalid
Unique

@sherlock-admin2 sherlock-admin2 removed the Escalated This issue contains a pending escalation label Jul 16, 2024
@sherlock-admin3 sherlock-admin3 added the Escalation Resolved This issue's escalations have been approved/rejected label Jul 16, 2024
@sherlock-admin4
Copy link
Contributor

Escalations have been resolved successfully!

Escalation status:

@sherlock-admin3 sherlock-admin3 added Won't Fix The sponsor confirmed this issue will not be fixed Will Fix The sponsor confirmed this issue will be fixed and removed Will Fix The sponsor confirmed this issue will be fixed Won't Fix The sponsor confirmed this issue will not be fixed labels Aug 13, 2024
jeffywu added a commit to notional-finance/leveraged-vaults that referenced this issue Aug 13, 2024
jeffywu added a commit to notional-finance/leveraged-vaults that referenced this issue Aug 13, 2024
@sherlock-admin2
Copy link
Contributor

The protocol team fixed this issue in the following PRs/commits:
notional-finance/leveraged-vaults#105

@sherlock-admin2
Copy link
Contributor

The Lead Senior Watson signed off on the fix.

jeffywu added a commit to notional-finance/leveraged-vaults that referenced this issue Sep 9, 2024
* fix: adding post mint and redeem hooks

* test: changes to base tests

* config: changes to config

* feat: changes to global

* feat: changes to trading

* feat: changes to utils

* feat: changes to single sided lp

* feat: vault storage

* fix: misc fixes

* fix: staking vaults

* fix: solidity versions

* fix: test build

* fix: adding staking harness

* fix: adding initialization

* fix: initial test bugs

* fix: weETH valuation

* fix: deleverage collateral check

* fix: initial harness compiling

* fix: initial test running

* fix: acceptance tests passing

* test: migrated some tests

* fix: withdraw tests

* test: adding deleverage test

* fix: adding liquidation tests

* test: withdraw request

* test: finalize withdraws manual

* test: tests passing

* fix: single sided lp tests with vault rewarder

* fix: putting rewarder tests in

* fix: reward tests running

* fix: vault rewarder address

* fix: initial staking harness

* fix: adding staking harness

* fix: initial PT vault build

* fix: moving ethena vault code

* fix: moving etherfi code

* feat: adding pendle implementations

* fix: staking harness to use USDC

* fix: curve v2 adapter for trading

* test: basic tests passing

* fix: adding secondary trading on withdraw

* fix tests

* fix: trading on redemption

* fix: ethena vault config

* fix: switch ethena vault to sell sDAI

* fix warnings

* fix: more liquidation tests passing

* fix: ethan liquidation tests

* pendle harness build

* fix: initial tests passing

* fix: adding pendle oracle

* fix: test deal token error

* fix: changing pendle liquidation discount

* fix: all tests passing

* fix: etherfi borrow currency

* fix: adding more documentation

* change mainnet fork block

* properly update data seed files

* fix arbitrum tests

* fix test SingleSidedLP:Convex:crvUSD/[USDT]

* fix: can finalize withdraws

* fix: refactor withdraw valuation

* fix: pendle expiration tests

* fix: pendle pt valuation

* remove flag

* fix: remove redundant code path

* fix: initial commit

* fix: vault changes

* fix: vault changes

* fix: some tests passing

* fix: fixing more tests

* fix: updated remaining tests

* fix: split withdraw bug

* fix: new test

* fix: remaining tests

* fix: split withdraw reqest bug

* feat: add PendlePTKelp vault

* update oracle address, fix tests

* Address CR comments

* add test_canTriggerExtraStep

* fix tests

* fix: run tests

* feat: adding generic vault

* feat: update generate tests

* fix: changes from merge

* fix: adding has withdraw requests

* fix: update oracle address for network

* fix: merge kelp harness

* fix: base tests passing

* fix: move generation config

* fix: initial pendle test generation

* fix: mainnet tests passing

* fix: vault rewarder

* fix: more pendle tests

* fix: pendle dex test

* fix: adding camelot dex

* fix: update usde pt

* fix: adding camelot adapter

* fix: support configurable dex

* fix: adding more PT vaults

* fix: approval bug

* fix: update dex information

* fix: mainnet tests passing

* fix: update arbitrum pendle tests

* fix: update deployment addresses

* test: add balancer v2 batch trade

* fix: add given out batch trade

* fix: remove trade amount filling

* fix: add some comments

* fix: audit issue #60

* fix: switch to using getDecimals

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#73

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#72

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#70

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#66

* test: adding pendle oracle test

* fix:  sherlock-audit/2024-06-leveraged-vaults-judging#69

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#64

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#43

* fix: audit issue #18

* fix: move slippage check

* fix: add comment back

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#56

* test: adding test that catches math underflow

* fix: adding test for vault shares

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#44

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#6

* test: adds test to check split withdraw request value

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#78

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#80

* fix: updating valuations for tests

* fix: update run tests

* fix: remove stETH withdraws from Kelp in favor of ETH withdraws

* fix: update tests for pendle rs eth

* fix: resolve compile issues

* fix: rsETH oracle price

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#87

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#67

* fix: sherlock-audit/2024-06-leveraged-vaults-judging#6

* test: update tests for invalid splits

* fix: sherlock fix review comments

* merge: merged master into branch

* fix: empty reward tokens

* fix: claim rewards tests

* fix: liquidation tests

* fixing more tests

* fix: allowing unused reward pools

* test: migrating reward pools

* fix: rewarder test

* fix: claim rewards before withdrawing

* fix: deployed vault rewarder lib on arbitrum

* fix: deployed new tbtc vault

* docs: adding deployment documentation

* fix: update config

---------

Co-authored-by: sbuljac <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Escalation Resolved This issue's escalations have been approved/rejected Non-Reward This issue will not receive a payout Sponsor Confirmed The sponsor acknowledged this issue is valid Will Fix The sponsor confirmed this issue will be fixed
Projects
None yet
Development

No branches or pull requests

8 participants