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

3 stars 2 forks source link

Yashar - Lack of oracle validation in `acceptLtvUpdate` can result in a DoS for the Pool-Asset pair #548

Closed sherlock-admin2 closed 1 month ago

sherlock-admin2 commented 2 months ago

Yashar

Medium

Lack of oracle validation in acceptLtvUpdate can result in a DoS for the Pool-Asset pair

Summary

The RiskEngine.sol allows pool owners to request LTV updates with a 72-hour timelock. However, while the requestLtvUpdate function checks for a valid oracle, the acceptLtvUpdate function does not. This could lead to a situation where an LTV update is accepted after the oracle has been removed or invalidated, resulting in DoS for the Pool-Asset pair.

Vulnerability Detail

Pool owners can update LTV parameters using the requestLtvUpdate function, which employs a 72-hour timelock before the LTV change takes effect. During the request phase, the function ensures a valid oracle is set for the asset:

        // set oracle before ltv so risk modules don't have to explicitly check if an oracle exists
        if (oracleFor[asset] == address(0)) revert RiskEngine_NoOracleFound(asset);

After the timelock, the pool owner can accept this request via the acceptLtvUpdate function. However, given the 72-hour delay, there is a possibility that the protocol's admin could remove or change the oracle for the asset. The acceptLtvUpdate function does not re-check the oracle's validity before updating the LTV:

    function acceptLtvUpdate(uint256 poolId, address asset) external {
        if (msg.sender != pool.ownerOf(poolId)) revert RiskEngine_OnlyPoolOwner(poolId, msg.sender);

        LtvUpdate memory ltvUpdate = ltvUpdateFor[poolId][asset];

        // revert if there is no pending update
        if (ltvUpdate.validAfter == 0) revert RiskEngine_NoLtvUpdate(poolId, asset);

        // revert if called before timelock delay has passed
        if (ltvUpdate.validAfter > block.timestamp) revert RiskEngine_LtvUpdateTimelocked(poolId, asset);

        // revert if timelock deadline has passed
        if (block.timestamp > ltvUpdate.validAfter + TIMELOCK_DEADLINE) {
            revert RiskEngine_LtvUpdateExpired(poolId, asset);
        }

        // apply changes
        ltvFor[poolId][asset] = ltvUpdate.ltv;
        delete ltvUpdateFor[poolId][asset];
        emit LtvUpdateAccepted(poolId, asset, ltvUpdate.ltv);
    }

If the LTV is updated for an asset without an oracle, the getAssetValue function, which fetches the asset's price from the oracle, will always revert, resulting in a DoS for the given Pool-Asset pair.

Impact

If the LTV is updated for an asset without an oracle, it will cause a DoS for the affected Pool-Asset pair, as any attempts to fetch the asset's value will revert.

Code Snippet

https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/main/protocol-v2/src/RiskEngine.sol#L167-L187 https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/main/protocol-v2/src/RiskEngine.sol#L190-L210 https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/main/protocol-v2/src/RiskModule.sol#L183-L187

Tool used

Manual Review

Recommendation

Re-check the validity of the oracle for the asset upon accepting the ltv update:

    function acceptLtvUpdate(uint256 poolId, address asset) external {
        if (msg.sender != pool.ownerOf(poolId)) revert RiskEngine_OnlyPoolOwner(poolId, msg.sender);

+       if (oracleFor[asset] == address(0)) revert RiskEngine_NoOracleFound(asset);
+
        LtvUpdate memory ltvUpdate = ltvUpdateFor[poolId][asset];

        // revert if there is no pending update
        if (ltvUpdate.validAfter == 0) revert RiskEngine_NoLtvUpdate(poolId, asset);

        // revert if called before timelock delay has passed
        if (ltvUpdate.validAfter > block.timestamp) revert RiskEngine_LtvUpdateTimelocked(poolId, asset);

        // revert if timelock deadline has passed
        if (block.timestamp > ltvUpdate.validAfter + TIMELOCK_DEADLINE) {
            revert RiskEngine_LtvUpdateExpired(poolId, asset);
        }

        // apply changes
        ltvFor[poolId][asset] = ltvUpdate.ltv;
        delete ltvUpdateFor[poolId][asset];
        emit LtvUpdateAccepted(poolId, asset, ltvUpdate.ltv);
    }
S3v3ru5 commented 1 month ago

How is this issue different from Protocol owner removing a oracle for a pool asset after pool has been initialized?

Two things:

The RiskEngine.requestLtvUpdate function sets the validAfter to block.timestamp if this is first time LTV is being set for the pool.

        if (ltvFor[poolId][asset] == 0) ltvUpdate = LtvUpdate({ ltv: ltv, validAfter: block.timestamp });

So there is no delay in this case. When the LTV is first being set, i.e pool has never been used before this, then acceptLTV can be called immediately. And if LTV is not set then pool cannot be used. There's no impact until LTV is set atleast once.

Now the other case:

LTV is set and pool is being used. Multiple users have borrowed from the pool. All operations are working correctly.

Consider the following two exploit scenarios: First:

  1. Protocol owner decided to not support certain asset and owner has removed oracle for the asset.

Impact: All pools that have this asset have stopped working.

Second scenario (mentioned in the issue):

  1. Pool owner wants to change LTV to a different one and called requestLtvUpdate .
  2. Protocol owner decided to not support pool asset and owner has removed oracle for the asset.
  3. Pool owner calls acceptLTV and LTV has been updated.

Impact: All pools that have this asset have stopped working. The impact happens immediately after the 2nd step.

As the issue mentions the root cause to be in acceptLtv and acceptLtv does not be considered for the impact.

The issue described is not cause for the impact shown. Impact comes from Protocol owner removing oracle for an asset. It does not matter if this was done in the middle of Ltv update.

Also Protocol owner decided to not support certain asset and owner has removed oracle for the asset. is not a valid issue.

NicolaMirchev commented 1 month ago

Escalate.

The main argument on why the following issue should be invalid in the above comment. As a summary issue relay on admin performing action, which would anyways brick the protocol usage. Such vulnerabilities are considered invalid regarding Sherlock docs.

sherlock-admin3 commented 1 month ago

Escalate.

The main argument on why the following issue should be invalid in the above comment. As a summary issue relay on admin performing action, which would anyways brick the protocol usage. Such vulnerabilities are considered invalid regarding 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.

yashar0x commented 1 month ago

Protocol owner decided to not support certain asset and owner has removed oracle for the asset is not a valid issue.

The problem isn't about the admin deciding to remove an asset or not. The issue is how the LTV update should work in this case.

Now, to explain further regarding both of your exploit scenarios:

Let’s say the pool owner sets the LTV for the first time:

Later on, they change the LTV again:

But now, the pool owner wants to go back to USDC:

As you can see, it's not the Admin causing the DoS on the pool. What actually causes the pool to be inaccessible for 72 hours is the failure to check the oracle status when updating the LTV.

Your statement about this being an admin action is correct only when the admin removes an asset, which results in an immediate DoS for the pools actively using that asset. However, this report points out that the protocol isn't preventing LTV updates to an asset that has been removed.

cvetanovv commented 1 month ago

I agree with the escalation.

This issue fits Sherlock's call validation rule, which is that admin actions can create an issue:

An admin action can break certain assumptions about the functioning of the code.

The root of the issue is that the admin can remove an Oracle for an asset after an LTV update request has been made but before it has been accepted, which fits the above rule.

Planning to accept the escalation and invalidate the issue.

yashar0x commented 1 month ago

hey @cvetanovv, thanks for the feedback!

i'd like to kindly ask you to reconsider the issue. as i pointed out, there are two distinct scenarios at play here:

  1. Admin removes the oracle for an asset, which would indeed result in the pools utilizing that asset experiencing a DoS. this scenario could fall under the rule of admin action affecting the protocol’s behavior.

  2. After the oracle is removed, logically, pool owners should not be allowed to update the LTV for the asset without an oracle. this is where the issue arises. allowing pool owners to proceed with the LTV update for an asset that no longer has a valid oracle is not simply an admin action. it's a lack of validation within the contract logic itself, which leads to the vulnerability.

i believe the second scenario falls outside the scope of admin action and highlights a missing validation step that should be addressed to prevent unintended DoS.

cvetanovv commented 1 month ago

The second scenario won't happen if the first one isn't happening.

The admin removes Oracle, which causes issues in the code, and enters the Sherlock admin rule:

An admin action can break certain assumptions about the functioning of the code.

If the first action is not present, we have no issue.

My decision to accept the escalation remains.

yashar0x commented 1 month ago

hey @cvetanovv

using the logic that "the second scenario won’t happen if the first one doesn’t," we could invalidate a lot of vulnerabilities. almost every operation in a protocol stems from an admin action. by that reasoning, any vulnerability could be dismissed since the protocol’s deployment is also an admin action.

the real issue here is that not checking the oracle status when updating the LTV is a logic flaw, not an admin action. it’s this flaw that allows the DoS to happen, not the admin’s behavior. this distinction is important.

ruvaag commented 1 month ago

The escalation seems valid to me. It should be a low/info.

While this could be added as a redundant check, there is no strong impact-likelihood basis for this to be a medium. It should be a low due to low likelihood + dependence on admin errors.

yashar0x commented 1 month ago

hey @ruvaag thanks for sharing your feedback

regarding your first statement about the "redundant check": obviously, it's not a redundant check, but even if it were, the first one would be redundant because with both checks there, the absence of the first one won’t DoS the pool, the second one will.

i disagree with the "no-strong-impact" comment. i mean, if a Pool getting DoSed isn't a big deal, what is?

cvetanovv commented 1 month ago

This issue is invalid because it depends on some actions of the admin.

Once the pool is initialized, the admin does not need to update the oracle. Even if he does and some functionality is broken, this is not valid according to Sherlock's rules:

Admin Input/call validation:

  • An admin action can break certain assumptions about the functioning of the code. Example: Pausing a collateral causes some users to be unfairly liquidated or any other action causing loss of funds. This is not considered a valid issue.

My decision to accept the escalation and invalidate the issue remains.

yashar0x commented 1 month ago

@cvetanovv

Even if he does and some functionality is broken, this is not valid

sir, if the admin removes an asset (oracle), pools using that asset will break, and yeah, that's totally an admin action. but that’s not what this report is about. the issue here is the code allowing pool owners to update the LTV to an asset that shouldn’t be supported. so how can we call this an admin action?

it depends on some actions of the admin

by that logic, this finding would be invalid too because it happens due to an admin removing an asset while positions can still use it. but it's considered valid because the issue isn't the admin's action, it’s that the positions can still use an asset that shouldn’t be supported, even though the issue stems from an admin action.

cvetanovv commented 1 month ago

@yashar0x

Even if we don't consider the admin rule, there is a getOracleFor() function for anyone to check if an asset has an oracle.

https://github.com/sherlock-audit/2024-08-sentiment-v2/blob/main/protocol-v2/src/RiskEngine.sol#L122-L127

    function getOracleFor(address asset) public view returns (address) {
        address oracle = oracleFor[asset];
        if (oracle == address(0)) revert RiskEngine_NoOracleFound(asset);
        return oracle;
    }

The pool owner can call the function before calling acceptLtvUpdat() to check the Oracle. If he doesn't, it is his mistake.

My decision to accept the escalation remains.

yashar0x commented 1 month ago

@cvetanovv

there is a getOracleFor() function for anyone to check if an asset has an oracle. The pool owner can call the function before calling acceptLtvUpdat() to check the Oracle. If he doesn't, it is his mistake.

sir, we can’t justify skipping the check in the main function just because there’s a view function to check the oracle status, especially since it’s not called within acceptLtvUpdate. this seems more like a front-end check.

if the issue could be mitigated in this way and it was intended to be, it should have been included in the docs or in the known issues.

WangSecurity commented 1 month ago

Result: Invalid Unique

sherlock-admin4 commented 1 month ago

Escalations have been resolved successfully!

Escalation status:

yashar0x commented 1 month ago

@WangSecurity @Evert0x @cvetanovv

is it really okay to close the issue when we’re still discussing it? the issue is valid, and none of @cvetanovv’s points were accurate. my last comment hasn’t even been answered, but the issue got closed :| how?

cvetanovv commented 1 month ago

@yashar0x I have written four times about how the escalation will end. There were three weeks for discussion. Since you think the escalation is incorrect, what I wrote is also invalid, and the comment of the sponsor is invalid, I'll give Evert or Wang to review the issue.

yashar0x commented 1 month ago

hey @cvetanovv

you think the escalation is incorrect

yep, i'm sure the escalation is incorrect, and i’ve addressed all the points raised by the escalator here

what I wrote is also invalid

i’ve responded to all your comments about the validity of the finding, but each time you bring up a new challenge, which is fine. i believe your main argument is that it's an admin action, but you haven’t clarified why this finding isn’t considered an admin action, but this issue is. i explained my view here.

and the comment of the sponsor is invalid

the sponsor originally confirmed the finding as medium, then after the escalation, he changed his mind. even then, he didn’t deny the existence of the bug, he just said it’s low, and i replied to that here.

i believe i’ve provided all the necessary info for your concerns but still haven’t gotten a justified answer.

it would be great if @WangSecurity and @Evert0x could take a look at the report and the whole discussion.

WangSecurity commented 1 month ago

The escalation was resolved because @cvetanovv has explained his decision several times without changing it from the beginning. Hence, the discussion has become unnecessarily long with repeating similar arguments. @cvetanovv is correct that the Admin action rule applies here, and the issue should be invalid based on it. If you need, I could make a more detailed answer, but it would be repeating the same point as above, so there's no point in it.

yashar0x commented 1 month ago

hey @WangSecurity,

thanks a lot for the feedback! i think i answered all the doubts, but the issue still got closed. pretty sure it was unfair since i gave all the evidence showing it wasn’t an admin action. feels like @cvetanovv would’ve invalidated it anyway, so i guess there wasn’t much point in dragging it out. just wish it had happened sooner so we didn’t waste each other’s time. no need for more detail, just a bit disappointed. thanks again for checking it out, G!

WangSecurity commented 1 month ago

@cvetanovv was indeed correct here, and it is my honest opinion that this rule has to apply here. The escalation was resolved because the discussion started to repeat the same arguments without changing the decision.

sherlock-admin2 commented 3 weeks ago

The protocol team fixed this issue in the following PRs/commits: https://github.com/sentimentxyz/protocol-v2/pull/311

yashar0x commented 3 weeks ago

The protocol team fixed this issue in the following PRs/commits: sentimentxyz/protocol-v2#311

it hurts :\