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

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

Closed
sherlock-admin2 opened this issue Aug 24, 2024 · 24 comments
Labels
Escalation Resolved This issue's escalations have been approved/rejected Non-Reward This issue will not receive a payout Sponsor Disputed The sponsor disputed this issue's validity Will Fix The sponsor confirmed this issue will be fixed

Comments

@sherlock-admin2
Copy link
Contributor

sherlock-admin2 commented Aug 24, 2024

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);
    }
@github-actions github-actions bot added the Medium A Medium severity issue. label Sep 5, 2024
@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 Sep 9, 2024
@sherlock-admin4 sherlock-admin4 changed the title Faithful Teal Cuckoo - Lack of oracle validation in acceptLtvUpdate can result in a DoS for the Pool-Asset pair Yashar - Lack of oracle validation in acceptLtvUpdate can result in a DoS for the Pool-Asset pair Sep 15, 2024
@sherlock-admin4 sherlock-admin4 added the Reward A payout will be made for this issue label Sep 15, 2024
@S3v3ru5
Copy link

S3v3ru5 commented Sep 16, 2024

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
Copy link

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
Copy link

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.

@sherlock-admin4 sherlock-admin4 added the Escalated This issue contains a pending escalation label Sep 16, 2024
@sherlock-admin3 sherlock-admin3 added Sponsor Disputed The sponsor disputed this issue's validity and removed Sponsor Confirmed The sponsor acknowledged this issue is valid labels Sep 18, 2024
@yashar0x
Copy link

yashar0x commented Sep 19, 2024

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:

  • poolId = 10
  • asset = USDC
  • Since ltvFor[10][USDC] == 0, the update happens immediately.

Later on, they change the LTV again:

  • poolId = 10
  • asset = DAI
  • Same thing here: ltvFor[10][DAI] == 0, so it updates right away.

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

  • poolId = 10
  • asset = USDC
  • This time, ltvFor[10][USDC] != 0, so they need to wait 72 hours.
  • After 48 hours, the protocol admin removes the oracle for USDC.
  • 24 hours later, the pool owner calls acceptLtvUpdate, and since there’s no oracle for USDC, the pool breaks. The pool, which was fine with DAI, is now down while this could be completely mitigated by checking the oracle status.
  • Now If the pool owner switches the LTV back to DAI, the pool will be DoSed for 72 hours.

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
Copy link
Collaborator

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
Copy link

yashar0x commented Sep 25, 2024

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
Copy link
Collaborator

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
Copy link

yashar0x commented Sep 26, 2024

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
Copy link

ruvaag commented Sep 29, 2024

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
Copy link

yashar0x commented Sep 29, 2024

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
Copy link
Collaborator

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
Copy link

yashar0x commented Oct 4, 2024

@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
Copy link
Collaborator

@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
Copy link

yashar0x commented Oct 5, 2024

@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 WangSecurity removed the Medium A Medium severity issue. label Oct 7, 2024
@WangSecurity
Copy link

Result:
Invalid
Unique

@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 Escalated This issue contains a pending escalation labels Oct 7, 2024
@sherlock-admin3 sherlock-admin3 added the Escalation Resolved This issue's escalations have been approved/rejected label Oct 7, 2024
@sherlock-admin4
Copy link
Contributor

Escalations have been resolved successfully!

Escalation status:

@yashar0x
Copy link

yashar0x commented Oct 7, 2024

@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
Copy link
Collaborator

@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
Copy link

yashar0x commented Oct 7, 2024

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
Copy link

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
Copy link

yashar0x commented Oct 8, 2024

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
Copy link

@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-admin3 sherlock-admin3 removed Sponsor Disputed The sponsor disputed this issue's validity Will Fix The sponsor confirmed this issue will be fixed labels Oct 20, 2024
@sherlock-admin2
Copy link
Contributor Author

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

@sherlock-admin3 sherlock-admin3 added Sponsor Disputed The sponsor disputed this issue's validity Will Fix The sponsor confirmed this issue will be fixed labels Oct 20, 2024
@yashar0x
Copy link

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

it hurts :\

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 Disputed The sponsor disputed this issue's validity Will Fix The sponsor confirmed this issue will be fixed
Projects
None yet
Development

No branches or pull requests

9 participants