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

The governance will fail to add an ecosystem token if someone creates a hToken that uses that ecosystem token #881

Open
c4-submissions opened this issue Oct 6, 2023 · 17 comments
Labels
2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working disagree with severity Sponsor confirms validity, but disagrees with warden’s risk assessment (sponsor explain in comments) M-01 primary issue Highest quality submission among a set of duplicates satisfactory satisfies C4 submission criteria; eligible for awards selected for report This submission will be included/highlighted in the audit report sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity") sufficient quality report This report is of sufficient quality

Comments

@c4-submissions
Copy link
Contributor

Lines of code

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootPort.sol#L493

Vulnerability details

Ecosystem tokens are tokens that dont have an underlying token address in any branch and only the global representation exists. The governance adds them by calling addEcosystemToken() where the _ecoTokenGlobalAddress will be the Maia or Hermes token as the sponsor mentioned or any other tokens that could be added in the future.

The problem is that anyone can create a hToken where the underlying asset is the ecosystem token and then this mapping will get updated in setAddresses():

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootPort.sol#L252

252:  getLocalTokenFromUnderlying[_underlyingAddress][_srcChainId] = _localAddress;

The _underlyingAddress equals to the _ecoTokenGlobalAddress and when the admin calls addEcosystemToken() then it will revert because of this check that is incorrect:

https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/RootPort.sol#L493

493:   if (getLocalTokenFromUnderlying[_ecoTokenGlobalAddress][localChainId] != address(0)) {
494:       revert AlreadyAddedEcosystemToken();
495:   }

getLocalTokenFromUnderlying[_ecoTokenGlobalAddress] wont be a zero address because it was set when the attacker added the hToken.

This check here is redundant and even if someone creates a hToken where the underlying asset will be the ecosystem token then it will not be tied to the ecosystem token in any way because it has a different global address and a different local address.

Impact

The governance will fail to add the ecosystem tokens and they will not work with this part of Ulysses because an attacker can easily create a hToken before the ecosystem token is set.

Proof of Concept

Add this to RootTest.t.sol

function testAddEcosystemTokenAttack() public {
        //arbitrumMockToken will be the Maia or Hermes token
        hevm.deal(address(this), 1 ether);

        //Attacker adds the Maia or Hermes token
        arbitrumCoreRouter.addLocalToken{value: 0.0005 ether}(
            address(arbitrumMockToken), GasParams(0.5 ether, 0.5 ether)
        );

        newArbitrumAssetGlobalAddress =
            RootPort(rootPort).getLocalTokenFromUnderlying(address(arbitrumMockToken), rootChainId);

        require(
            RootPort(rootPort).getGlobalTokenFromLocal(address(newArbitrumAssetGlobalAddress), rootChainId)
                == address(newArbitrumAssetGlobalAddress),
            "Token should be added"
        );
        require(
            RootPort(rootPort).getLocalTokenFromGlobal(newArbitrumAssetGlobalAddress, rootChainId)
                == address(newArbitrumAssetGlobalAddress),
            "Token should be added"
        );
        require(
            RootPort(rootPort).getUnderlyingTokenFromLocal(address(newArbitrumAssetGlobalAddress), rootChainId)
                == address(arbitrumMockToken),
            "Token should be added"
        );

        //The admin will then fail to add an ecosystem token because the tx reverts
        rootPort.addEcosystemToken(address(arbitrumMockToken));

    }

As you can see here the tx reverts because of that check and the admin will fail to add the ecosystem token

Tools Used

Foundry

Recommended Mitigation Steps

Remove this check as its redundant, setting the ecosystem tokens when initializing is not the best solution because other tokens can be added in the future.

if (getLocalTokenFromUnderlying[_ecoTokenGlobalAddress][localChainId] != address(0)) {
     revert AlreadyAddedEcosystemToken();
}

Assessed type

DoS

@c4-submissions c4-submissions added 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working labels Oct 6, 2023
c4-submissions added a commit that referenced this issue Oct 6, 2023
@c4-pre-sort
Copy link

0xA5DF marked the issue as primary issue

@c4-pre-sort c4-pre-sort added primary issue Highest quality submission among a set of duplicates sufficient quality report This report is of sufficient quality labels Oct 10, 2023
@c4-pre-sort
Copy link

0xA5DF marked the issue as sufficient quality report

@0xBugsy
Copy link

0xBugsy commented Oct 17, 2023

If the token has not yet been added to the system a new one can simply be deployed. In addition this can be done in a multicall paired with the token deployment itself for added security. Removing this check would lead to added governance power / responsibility.

@c4-sponsor c4-sponsor added the sponsor disputed Sponsor cannot duplicate the issue, or otherwise disagrees this is an issue label Oct 17, 2023
@c4-sponsor
Copy link

0xBugsy (sponsor) disputed

@alcueca
Copy link

alcueca commented Oct 25, 2023

Regardless of the mitigation, which might not be great, the issue is that if an ecosystem token is not set, an attacker can add it as an underlying of some other token and then it will not be possible to set it anymore as a global address (because the ecosystem token already is an underlying token).

@0xLightt
Copy link

0xLightt commented Oct 25, 2023

The attacker would have to act between token deployment and adding it as an ecosystem token, only way for that to happen would need to come from a governance/setup mistake.

The check cannot be overridden if there is any deposits of that underlying or there would be funds lost. Mitigation could be improved by this, allow an ecosystem token to override a global token if it's total supply is zero. This way, any mistake can be circumvented without any redeployment (if the ecosystem token is not distributed yet) and our setup can be more flexible.

@alcueca
Copy link

alcueca commented Oct 25, 2023

Adding an ecosystem token immediately after creation is not obvious, or in the documentation. This is then a valid DoS attack, which you can mitigate with careful governance.

@c4-judge
Copy link
Contributor

alcueca marked the issue as satisfactory

@c4-judge c4-judge added the satisfactory satisfies C4 submission criteria; eligible for awards label Oct 25, 2023
@c4-judge
Copy link
Contributor

alcueca marked the issue as selected for report

@c4-judge c4-judge added the selected for report This submission will be included/highlighted in the audit report label Oct 27, 2023
@stalinMacias
Copy link

Hey @alcueca , I think this issue is more of a QA, based on the sponsor's comment "only way for that to happen would need to come from a governance/setup mistake. All of the functions that are able to update the state of ecosystem & global tokens have an access control that can only be called by governance, how can an attacker simply go ahead and set any token they wish as an ecosystemToken?

From the PoC, I can see the code is not running a prank to simulate the execution of the addLocalToken() to be made from an attacker, instead, the PoC executes that call as the owner of the contract, that's why it doesn't fail, but in production, attacker can't just simply gain access to the owner account and do that types of calls.

Could you revisit this issue again?

Also, @0xLightt , could you correct me if what I've just said is incorrect, thanks a lot for your time

@QiuhaoLi
Copy link

Hi @stalinMacias , the attacker is not calling rootPort's onlyOwner setters, but from the arbitrumCoreRouter (a branch chain). By doing that he can make _srcChainId of rootPort:setAddresses same as the localChainId of the root chain (Arbitrum).

As for the PoC, you can add the prank to simulate a better scenario, but it will still work:

function testAddEcosystemTokenAttack() public {

        hevm.startPrank(address(123));
        hevm.deal(address(123), 1 ether);
        //Attacker adds the Maia or Hermes token
        arbitrumCoreRouter.addLocalToken{value: 0.0005 ether}(
            address(arbitrumMockToken), GasParams(0.5 ether, 0.5 ether)
        );
        hevm.stopPrank();

        hevm.startPrank(rootPort.owner());
        //The admin will then fail to add an ecosystem token because the tx reverts
        rootPort.addEcosystemToken(address(arbitrumMockToken));
    }

Seems like a good catch. CC: @alcueca

@peakbolt
Copy link

I have to agree with the sponsor that this issue is a governance error and is a good QA. The issue is more of a lack of proper documentation on setup and adding ecosystem token.

Otherwise, governance error issues like #345 would be valid if we go by the argument that the correct use is not obvious and not in the doc. Furthermore, #345 also explained that the error exists within the original test case, which means that the test case itself is demonstrating the error as a correct usage.

@c4-sponsor c4-sponsor added sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity") and removed sponsor disputed Sponsor cannot duplicate the issue, or otherwise disagrees this is an issue labels Nov 1, 2023
@c4-sponsor
Copy link

0xLightt (sponsor) confirmed

@c4-sponsor c4-sponsor added the disagree with severity Sponsor confirms validity, but disagrees with warden’s risk assessment (sponsor explain in comments) label Nov 1, 2023
@c4-sponsor
Copy link

0xLightt marked the issue as disagree with severity

@0xLightt
Copy link

0xLightt commented Nov 1, 2023

Updated review and feedback to reflect our opinion on this issue.

@alcueca
Copy link

alcueca commented Nov 3, 2023

Thanks for your feedback, but this stays as Medium.

Someone correct me if I'm wrong, but as an "ecosystem token" I understand something like MAIA. A token that is not necessarily used only for Ulysses. As such, I would expect that the creation of an ecosystem token be independent from the Ulysses deployment and management, unless explicitly set in the documentation and enforced somehow.

The attacker would have to act between token deployment and adding it as an ecosystem token, only way for that to happen would need to come from a governance/setup mistake.

My take here is that unless specifically stated in the docs, I see this as a very easy mistake to make. In other words, if this vulnerability wouldn't have been reported, why would MaiaDAO add ecosystem tokens to Ulysses in the same transaction that they are deployed?

The comparison to #345 is not reasonable. In #345 governance is required to enter non-sensical parameters into a call in order for the protocol to suffer. In this action, governance doesn't need to do anything obviously wrong.

@C4-Staff C4-Staff added the M-01 label Nov 8, 2023
@0xBugsy
Copy link

0xBugsy commented Nov 12, 2023

Addressed at Maia-DAO/2023-09-maia-remediations@e715c21

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working disagree with severity Sponsor confirms validity, but disagrees with warden’s risk assessment (sponsor explain in comments) M-01 primary issue Highest quality submission among a set of duplicates satisfactory satisfies C4 submission criteria; eligible for awards selected for report This submission will be included/highlighted in the audit report sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity") sufficient quality report This report is of sufficient quality
Projects
None yet
Development

No branches or pull requests