Centrifuge - bin2chen's results

The institutional ecosystem for on-chain credit.

General Information

Platform: Code4rena

Start Date: 08/09/2023

Pot Size: $70,000 USDC

Total HM: 8

Participants: 84

Period: 6 days

Judge: gzeon

Total Solo HM: 2

Id: 285

League: ETH

Centrifuge

Findings Distribution

Researcher Performance

Rank: 9/84

Findings: 2

Award: $1,247.37

🌟 Selected for report: 1

πŸš€ Solo Findings: 0

Findings Information

🌟 Selected for report: bin2chen

Also found by: castle_chain, maanas, merlin, mert_eren, nobody2018

Labels

bug
2 (Med Risk)
downgraded by judge
high quality report
primary issue
satisfactory
selected for report
sponsor confirmed
M-02

Awards

1114.5087 USDC - $1,114.51

External Links

Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/gateway/routers/axelar/Router.sol#L44

Vulnerability details

Vulnerability details

In AxelarRouter.sol, we need to ensure the legitimacy of the execute() method execution, mainly through two methods

  1. axelarGateway.validateContractCall () to validate if the command is approved or not
  2. onlyCentrifugeChainOrigin() is used to validate that sourceChain sourceAddress is legal.

Let's look at the implementation of onlyCentrifugeChainOrigin()

    modifier onlyCentrifugeChainOrigin(string calldata sourceChain, string calldata sourceAddress) {        
@>      require(msg.sender == address(axelarGateway), "AxelarRouter/invalid-origin");
        require(
            keccak256(bytes(axelarCentrifugeChainId)) == keccak256(bytes(sourceChain)),
            "AxelarRouter/invalid-source-chain"
        );
        require(
            keccak256(bytes(axelarCentrifugeChainAddress)) == keccak256(bytes(sourceAddress)),
            "AxelarRouter/invalid-source-address"
        );
        _;
    }

The problem is that this restriction msg.sender == address(axelarGateway)

When we look at the official axelarGateway.sol contract, it doesn't provide any call external contract 'sexecute() method

so msg.sender cannot be axelarGateway, and the official example does not restrict msg.sender

the security of the command can be guaranteed by axelarGateway.validateContractCall(), sourceChain, sourceAddress.

there is no need to restrict msg.sender

axelarGateway code address https://github.com/axelarnetwork/axelar-cgp-solidity/blob/main/contracts/AxelarGateway.sol

can't find anything that calls router.execute()

Impact

router.execute() cannot be executed properly, resulting in commands from other chains not being executed, protocol not working properly

remove msg.sender restriction

    modifier onlyCentrifugeChainOrigin(string calldata sourceChain, string calldata sourceAddress) {        
-       require(msg.sender == address(axelarGateway), "AxelarRouter/invalid-origin");
        require(
            keccak256(bytes(axelarCentrifugeChainId)) == keccak256(bytes(sourceChain)),
            "AxelarRouter/invalid-source-chain"
        );
        require(
            keccak256(bytes(axelarCentrifugeChainAddress)) == keccak256(bytes(sourceAddress)),
            "AxelarRouter/invalid-source-address"
        );
        _;
    }

Assessed type

Context

#0 - c4-pre-sort

2023-09-15T21:49:32Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2023-09-15T21:49:40Z

raymondfam marked the issue as primary issue

#2 - c4-pre-sort

2023-09-17T17:00:48Z

raymondfam marked the issue as high quality report

#3 - c4-sponsor

2023-09-18T13:53:08Z

hieronx (sponsor) confirmed

#4 - gzeon-c4

2023-09-25T14:58:34Z

This seems High risk to me since the Axelar bridge is a centerpiece of this protocol, and when deployed in a certain way where the AxelarRouter is the only ward, it might causes user deposits to be stuck forever.

#5 - c4-judge

2023-09-25T14:58:45Z

gzeon-c4 changed the severity to 3 (High Risk)

#6 - c4-judge

2023-09-25T14:59:37Z

gzeon-c4 marked the issue as satisfactory

#7 - gzeon-c4

2023-09-26T16:05:54Z

Reconsidering severity to Med here since the expected setup would have DelayedAdmin able to unstuck the system

#8 - c4-judge

2023-09-26T16:06:05Z

gzeon-c4 changed the severity to 2 (Med Risk)

#9 - c4-judge

2023-09-26T16:54:11Z

gzeon-c4 marked the issue as selected for report

#10 - merteren1234

2023-09-28T07:40:12Z

Hi @gzeon-c4 i think this issue should be high. Because if this issue will not fix than protocol will suffer %100 percent after launch and without deploy new contract this issue will not fix. Let assume this contract deploy without fix this issue: Than in short time it will be understood that execute function cannot be used and critical functions of protocol cannot be used and this happens without any malicious act or edge case or protocol teams mistake. After that protocol team needs to pause everything should give permission to unstake tokens from protocol (which lead to centralization risk) and deploy new gateawat router contract and this situation will effect protocol's repetuation and relability for users.I think this issue has high impact due to critical severity of posibility and not impact just some cases of users but effect all protocol's core functions.

#11 - gzeon-c4

2023-09-28T10:00:16Z

The protocol would not be able to whitelist user by calling updateMember if this is deployed, so the issue is likely to be discovered very early and can be redeployed without chance of user fund getting stuck.

#12 - merteren1234

2023-09-28T10:20:32Z

@gzeon-c4 Thanks for answer. yes this issue would discovered before users use this protocol. But my main point while giving 'what if' scenario is show that how big impact of the this vulnerability .Because this vulnerability makes whole system unusable. Also this vulnerability happens not rely to possibility. %100 system will suffer from this.Because of that i think this issue should high instead of medium.

#13 - gzeon-c4

2023-09-28T10:27:26Z

https://docs.code4rena.com/awarding/judging-criteria/severity-categorization

2 β€” Med: Assets not at direct risk, but the function of the protocol or its availability could be impacted, or leak value with a hypothetical attack path with stated assumptions, but external requirements. 3 β€” High: Assets can be stolen/lost/compromised directly (or indirectly if there is a valid attack path that does not have hand-wavy hypotheticals).

Since asset is not at risk and the mistake can be easily discovered early with a easy fix to reconfigure the gateway using the delayed admin, I think Med is appropriate as it only impact the availability of the protocol temporarily.

#14 - merteren1234

2023-09-28T10:49:54Z

hmm i get it. Thanks for clarification.

#15 - hieronx

2023-10-03T15:45:32Z

Findings Information

Awards

132.8565 USDC - $132.86

Labels

bug
2 (Med Risk)
satisfactory
sufficient quality report
duplicate-146

External Links

Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/512e7a71ebd9ae76384f837204216f26380c9f91/src/token/ERC20.sol#L228

Vulnerability details

Vulnerability details

In the constructor method of ERC20.SOL, _DOMAIN_SEPARATOR will be generated by _calculateDomainSeparator() and this variable will be used as part of the signature in permit().

The main method is as follows.

contract ERC20 is Context {
...
    constructor(uint8 decimals_) {
        decimals = decimals_;
        wards[_msgSender()] = 1;
        emit Rely(_msgSender());

        deploymentChainId = block.chainid;
@>      _DOMAIN_SEPARATOR = _calculateDomainSeparator(block.chainid);
    }

    function _calculateDomainSeparator(uint256 chainId) private view returns (bytes32) {
        return keccak256(
            abi.encode(
                keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
@>?             keccak256(bytes(name)),
                keccak256(bytes(version)),
                chainId,
                address(this)
            )
        );
    }

    function permit(address owner, address spender, uint256 value, uint256 deadline, bytes memory signature) public {
        require(block.timestamp <= deadline, "ERC20/permit-expired");
        require(owner != address(0), "ERC20/invalid-owner");

        uint256 nonce;
        unchecked {
            nonce = nonces[owner]++;
        }
        bytes32 digest = keccak256(
            abi.encodePacked(
                "\x19\x01",
@>              block.chainid == deploymentChainId ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(block.chainid),
                keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonce, deadline))
            )
        );

        require(_isValidSignature(owner, digest, signature), "ERC20/invalid-permit");

        allowance[owner][spender] = value;
        emit Approval(owner, spender, value);
    }

the content of _DOMAIN_SEPARATOR contains the token's name

The problem is that there are two issues with this token.name.

  1. token.name is not initialized in the constructor, it needs to be assigned by file()

  2. poolManager.updateTrancheTokenMetadata() can modify token.name later.

so _DOMAIN_SEPARATOR will contain a blank name , it's wrong.

Because name can change, the immutable nature of _DOMAIN_SEPARATOR is infeasible and it cannot be used as part of a signature.

It is recommended not to use _DOMAIN_SEPARATOR, but to calculate it in real-time with _calculateDomainSeparator().

Impact

The user assembles the content of the signature through the EIP-712 standard, but cannot execute permit() properly.

Proof of Concept

The following code demonstrates this, assembling the signature content as standard, but not the same as token.DOMAIN_SEPARATOR()

add to Tranche.t.sol

    function test_NoEqualDomainSeparator() public {
        bytes32 tokenDomainSeparator = token.DOMAIN_SEPARATOR();
        bytes32 calculateDomainSeparator = keccak256(
            abi.encode(
                keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                keccak256(bytes(token.name())),
                keccak256(bytes(token.version())),
                block.chainid,
                address(token)
            )
        );
      
        console.log("equal:",tokenDomainSeparator == calculateDomainSeparator);
    }
 $ forge test -vvv --match-test test_NoEqualDomainSeparator

Running 1 test for test/token/Tranche.t.sol:TrancheTokenTest
[PASS] test_NoEqualDomainSeparator() (gas: 14966)
Logs:
  equal: false

remove _DOMAIN_SEPARATOR , calculate it in real-time with _calculateDomainSeparator().

contract ERC20 is Context {
...

-   uint256 public immutable deploymentChainId;
-   bytes32 private immutable _DOMAIN_SEPARATOR;

    constructor(uint8 decimals_) {
        decimals = decimals_;
        wards[_msgSender()] = 1;
        emit Rely(_msgSender());

-       deploymentChainId = block.chainid;
-       _DOMAIN_SEPARATOR = _calculateDomainSeparator(block.chainid);
    }


    function DOMAIN_SEPARATOR() external view returns (bytes32) {
-       return block.chainid == deploymentChainId ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(block.chainid);
+       return _calculateDomainSeparator(block.chainid);
    }

    function permit(address owner, address spender, uint256 value, uint256 deadline, bytes memory signature) public {
        require(block.timestamp <= deadline, "ERC20/permit-expired");
        require(owner != address(0), "ERC20/invalid-owner");

        uint256 nonce;
        unchecked {
            nonce = nonces[owner]++;
        }
        bytes32 digest = keccak256(
            abi.encodePacked(
                "\x19\x01",
-               block.chainid == deploymentChainId ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(block.chainid),
+               _calculateDomainSeparator(block.chainid), 
                keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonce, deadline))
            )
        );

        require(_isValidSignature(owner, digest, signature), "ERC20/invalid-permit");

        allowance[owner][spender] = value;
        emit Approval(owner, spender, value);
    }

Assessed type

Context

#0 - c4-pre-sort

2023-09-15T21:47:50Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2023-09-15T21:48:02Z

raymondfam marked the issue as duplicate of #146

#2 - c4-judge

2023-09-26T18:07:32Z

gzeon-c4 marked the issue as satisfactory

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax Β© 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter