Centrifuge - 0xfuje'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: 30/84

Findings: 2

Award: $296.01

QA:
grade-a

🌟 Selected for report: 0

🚀 Solo Findings: 0

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/main/src/token/ERC20.sol#L48 https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/token/ERC20.sol#L71

Vulnerability details

DOMAIN_SEPARATOR of ERC20 is incorrectly calculated and used

Impact

permit() of ERC20 doesn't work as intented, implementation of EIP-1271 _isValidSignature() will use incorrect digest

Description

_DOMAIN_SEPARATOR of ERC20 is incorrectly calculated. _DOMAIN_SEPARATOR is used as DOMAIN_SEPARATOR in permit() and passed in the _isValidSignature() function.

Since block.chainId will always equal deploymentChainId as we see in the constructor: this means in permit() always the incorrectly calculated _DOMAIN_SEPARATOR will be used since the expression block.chainid == deploymentChainId ? _DOMAIN_SEPARATOR is always true.

src/token/ERC.20.sol - constructor

	constructor(uint8 decimals) {
		...
		deploymentChainId = block.chainId
		_DOMAIN_SERATOR = _calculateDomainSeparator(block.chainid)
	}

src/token/ERC20.sol - _calculateDomainSeparator()

    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)
            )
        );
    }

The problem is that the _calculateDomainSeparator() will use the empty name when it is not yet set in the constructor, only later via file(). Even after the name was set, the incorrectly calculated _DOMAIN_SEPARATOR (set in constructor) remains to be used.

Tools Used

Manual review

Set name in the constructor before calculating _DOMAIN_SEPARATOR via _calculateDomainSeparator(). Consider setting symbol in the constructor as well for convenience.

Assessed type

Timing

#0 - c4-pre-sort

2023-09-15T04:51:54Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2023-09-15T04:52:16Z

raymondfam marked the issue as duplicate of #146

#2 - c4-judge

2023-09-26T18:08:26Z

gzeon-c4 marked the issue as satisfactory

Awards

163.1547 USDC - $163.15

Labels

bug
disagree with severity
downgraded by judge
grade-a
QA (Quality Assurance)
sponsor acknowledged
sufficient quality report
Q-30

External Links

Lines of code

https://github.com/code-423n4/2023-09-centrifuge/blob/main/src/gateway/Gateway.sol#L285-L366

Vulnerability details

Impact

Various impacts depending on the type of the transaction: e.g.: extra tokens can be gained, tranche can be updated to a deprecated price which can be exploited

Description

From Axelar Message Parsing Docs:

Occasionally, transactions can get “stuck” in the pipeline from a source to destination chain (e.g. due to one-off issues that arise with relayers that operate on top of the network).

Transactions have typically gotten “stuck” in the pipeline due to: (A) The transaction failing to relay from the source chain into the Axelar network for processing. (B) The transaction failing to get executed on the destination chain.

For example in the case of B, the transaction can be manually executed on the destination chain by anyone or anyone can increase the gas payment by sending it to the gas receiver of Axelar to replay these transactions.

The problem is while at the original sent time the cross-chain transaction was relevant, it's possible to do damage with it to the system at a later time. Two malicious examples are: updating tranche token price to an old value and transferring tokens that were already sent back to the user on source chain via an admin.

src/gateway/Gateway.sol - handle()

	function handle(bytes calldata message) external onlyIncomingRouter pauseable {
		...
		} else if (Messages.isUpdateTrancheTokenPrice(message)) {
            (uint64 poolId, bytes16 trancheId, uint128 currencyId, uint128 price) =
                Messages.parseUpdateTrancheTokenPrice(message);
            investmentManager.updateTrancheTokenPrice(poolId, trancheId, currencyId, price);
        } else if (Messages.isTransfer(message)) {
            (uint128 currency, address recipient, uint128 amount) = Messages.parseIncomingTransfer(message);
            poolManager.handleTransfer(currency, recipient, amount);
        }
        ...
	}

Deprecated price update

  1. LiquidityPool.sol has a price of 100_000
  2. In the past there were a failed transaction that were intended to change the price to 10_000
  3. Malicious actor pays gas to Axelar relayer to replay the failed transaction
  4. Price gets updated to 10_000
  5. Malicious actor now can exploit the price in various ways (e.g. can deposit into LiquidityPool and get 10x more shares in the next epoch)

Cross-chain transfer

  1. Malicious actor initiated a cross-chain transaction transfer() on PoolManager.sol
  2. currency gets transferred to escrow on source chain
  3. The cross-chain transaction fails, so he requests to get his tokens back from escrow from Centrifuge
  4. Admin who has auth on Root rescues from escrow and sends the tokens back to him
  5. Malicious actor replays the failed transaction by increasing the gas payment
  6. Now he has double amount of tokens, half on source chain, half on destination chain

Tools Used

Manual review, Axelar Docs

Consider monitoring the failed transactions, determining what is an appropriate time when a failed cross-chain transaction can still be replayed and not cause any harm. If they are in the appropriate timeframe: replay the transactions via the planned centrifuge relay gas bot. If they are out of time: call validateContractCall() on Axelar Gateway with the transaction's params to prevent the transaction from any possible replaying.

Assessed type

Other

#0 - c4-pre-sort

2023-09-15T15:50:06Z

raymondfam marked the issue as low quality report

#1 - c4-pre-sort

2023-09-15T15:50:15Z

raymondfam marked the issue as duplicate of #26

#2 - c4-pre-sort

2023-09-17T09:08:57Z

raymondfam marked the issue as sufficient quality report

#3 - c4-pre-sort

2023-09-17T09:09:03Z

raymondfam marked the issue as not a duplicate

#4 - c4-pre-sort

2023-09-17T09:09:08Z

raymondfam marked the issue as primary issue

#5 - c4-pre-sort

2023-09-17T16:55:27Z

raymondfam marked the issue as duplicate of #348

#6 - c4-judge

2023-09-25T16:16:56Z

gzeon-c4 marked the issue as unsatisfactory: Out of scope

#7 - c4-judge

2023-09-26T16:45:50Z

gzeon-c4 marked the issue as not a duplicate

#8 - c4-judge

2023-09-26T16:45:56Z

gzeon-c4 removed the grade

#9 - gzeon-c4

2023-09-26T16:47:04Z

Crosschain message can be executed out-of-order in certain edge case, seems unlikely to be exploited tho

#10 - c4-sponsor

2023-09-26T17:18:19Z

hieronx (sponsor) acknowledged

#11 - c4-sponsor

2023-09-26T17:18:24Z

hieronx marked the issue as disagree with severity

#12 - c4-judge

2023-09-26T17:29:11Z

gzeon-c4 changed the severity to QA (Quality Assurance)

#13 - c4-judge

2023-09-26T17:29:17Z

gzeon-c4 marked the issue as grade-a

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