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
Rank: 9/84
Findings: 2
Award: $1,247.37
π Selected for report: 1
π Solo Findings: 0
π Selected for report: bin2chen
Also found by: castle_chain, maanas, merlin, mert_eren, nobody2018
1114.5087 USDC - $1,114.51
In AxelarRouter.sol
, we need to ensure the legitimacy of the execute()
method execution, mainly through two methods
axelarGateway.validateContractCall ()
to validate if the command
is approved or notonlyCentrifugeChainOrigin()
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()
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" ); _; }
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
π Selected for report: Kow
Also found by: 0xRobsol, 0xfuje, 0xkazim, 0xpiken, Aymen0909, T1MOH, bin2chen, codegpt, gumgumzum, josephdara, lsaudit, nmirchev8, ravikiranweb3, rvierdiiev
132.8565 USDC - $132.86
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
.
token.name
is not initialized in the constructor, it needs to be assigned by file()
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()
.
The user assembles the content of the signature through the EIP-712
standard, but cannot execute permit()
properly.
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); }
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