Open code423n4 opened 1 year ago
The issue is well demonstrated, properly formatted, contains a coded POC. Marking as HQ.
0xSorryNotSorry marked the issue as high quality report
0xSorryNotSorry marked the issue as primary issue
deanamiel marked the issue as disagree with severity
Corrected Severity: QA We might account for this if a better approach is found than rounding off decimals. For pre-existing tokens with different decimals a wrapper can be made by the deployer that does the decimal handling, which is the current intended use for such tokens.
This is a very well-written submission!
However, I agree with the sponsor that wrapper tokens are supposed to be used in case of different decimals across chains. For instance, see the wrapper ERC-20 token for USDC on BNB chain, Axelar Wrapped USDC (axlUSDC)
, https://bscscan.com/address/0x4268B8F0B87b6Eae5d897996E6b845ddbD99Adf3#readContract.
Thus, I'm downgrading to QA (Low).
berndartmueller changed the severity to QA (Quality Assurance)
berndartmueller marked the issue as grade-c
@berndartmueller Please be aware that the sponor's comment and severity suggestion is only about the first part of the issue (pre-existing tokens) and therefore not conclusive for the whole report.
StandardizedToken
(not mentioned by the sponsor):StandardizedToken
with the same TokenId
but different decimals on different chains, see first part of PoC about (remote) deployment and InterchainTokenService.getCustomTokenId(...).
This should not be possible in the first place as long as the transfer amount does not get scaled according to decimals automatically, i.e. it's a bug which is even more severe than the above issue also leading to loss of funds.TokenId
but different decimals already registered on another chain. I am looking forward to more fact-based opinions about this and fair judgement considering the proven impacts, see also second part of PoC that demonstrates loss of funds (wrong transfer of funds by factor 100).
Have a nice day, everyone!
Hey @MarioPoneder!
According to the Interchain token service docs, there are two types of bridges:
Canonical bridges are used to enable bridging pre-existing ERC-20 tokens across chains. Such bridges are deployed via InterchainTokenService.registerCanonicalToken
on the source chain and InterchainTokenService.deployRemoteCanonicalToken
for the remote (destination) chains. Here, the trust assumption is that anyone can create canonical bridges - the resulting StandardizedToken
on the remote chains will have matching decimals with the pre-existing ERC-20 token (see InterchainTokenService.sol#L334. Thus, there is no such issue with non-matching decimals for canonical bridges.
In regards to custom bridges, the trust assumption is different (docs):
Users using Custom Bridges need to trust the deployers as they could easily confiscate the funds of users if they wanted to, same as any ERC20 distributor could confiscate the funds of users.
In the submission's PoC, the described issue is demonstrated by deploying a StandardizedToken
with 18 decimals on the source chain and deploying corresponding StandardizedToken
tokens with 16 decimals on remote chains (via deployAndRegisterRemoteStandardizedToken
. In this case, it truly shows a mismatch of token decimals between the source and remote chain, leading to a loss of bridged funds.
Even though the docs mention that the deployer of such a custom bridge has to be trusted, implementing the warden's second mitigation recommendation (normalizing token transfer amounts to, e.g., 18 decimals) certainly helps mitigate this issue and reduces the surface for potential errors (malicious or non-malicious) when deploying custom bridges. For example, the Wormhole bridge does this as well by normalizing the token transfer amounts to 8 decimals.
Ultimately, this leads me to acknowledge the outlined issue and medium severity chosen by the warden.
Kindly invite the sponsor's feedback before changing the severity back to medium. @deanamiel
This previously downgraded issue has been upgraded by berndartmueller
berndartmueller marked the issue as satisfactory
berndartmueller marked the issue as selected for report
The standardized token scenario would still not lead to loss of funds. One could still bridge tokens back and get the same amount, the amounts seen would be mismatched (when divided by 10^ decimals) but this is only a cosmetic issue. We still believe this is a QA level issue.
The standardized token scenario would still not lead to loss of funds. One could still bridge tokens back and get the same amount, the amounts seen would be mismatched (when divided by 10^ decimals) but this is only a cosmetic issue. We still believe this is a QA level issue.
Loss of funds may not be permanent, indeed. Still, it presents an issue for users bridging StandardizedToken
tokens and the token pools ( itself. Such tokens with mismatching decimals on the source- and destination chains can also be purposefully bridged to steal funds.
We consider this QA or Low severity. As mentioned before, there is no actual loss of funds since you can always bridge the same amount back. Furthermore, Standardized tokens still require trusting the deployer, unlike Canonical tokens. Tokens deployed and whitelisted via the UI will make sure the same decimals are used for deployments. Users are not recommended to interact with arbitrary Standardized tokens given that the deployer can still be malicious in various ways.
The ERC20 spec does not require the presence of name
, symbol
, decimals
. While commonly supported, these are still optional fields, so we left it flexible in the main protocol.
You can also build deployer contracts on top of this that do enforce various checks such as using the on-chain name, symbol, decimals before deploying.
Lines of code
https://github.com/code-423n4/2023-07-axelar/blob/2f9b234bb8222d5fbe934beafede56bfb4522641/contracts/its/token-manager/TokenManager.sol#L77-L100 https://github.com/code-423n4/2023-07-axelar/blob/2f9b234bb8222d5fbe934beafede56bfb4522641/contracts/its/interchain-token-service/InterchainTokenService.sol#L493-L523 https://github.com/code-423n4/2023-07-axelar/blob/2f9b234bb8222d5fbe934beafede56bfb4522641/contracts/cgp/AxelarGateway.sol#L455-L467
Vulnerability details
Impact
Introduction
According to the docs, the Axelar network supports cross-chain bridging of external
ERC20
tokens as well as their ownStandardizedToken
(using lock/unlock, mint/burn or liquidity pools).StandardizedToken
with the sameTokenId
but different decimals on different chains, see PoC.Bug description
A cross-chain bridging can be performed using the TokenManager.sendToken(...) method which correctly collects the source tokens from the sender and subsequently calls the InterchainTokenService.transmitSendToken(...) method that generates the payload for the remote
ContractCall
and also emits theTokenSent
event. However, this payload as well as the susequently emittedContractCall
andTokenSent
events, see InterchainTokenService:L512-L514 contain the unscaled source amount with respect to the source token's decimals.Next, this exact payload (actually its
keccak256
hash) gets relayed on the remote chain as it is via AxelarGateway.approveContractCall(...) and theContractCall
is now approved to be executed with the source amount irrespective of the remote token's decimals.Therefore, the bridged amounts are off by a factor of
10^abs(sourceDecimals - remoteDecimals)
.Note that there are also other ways/entry-points to reproduce this issue with the current codebase.
Consequences
This leads to loss of funds for user/protocol/pool when source token decimals are lower/higher than remote token decimals, because the token amount is just passed through instead of being scaled accordingly.
Proof of Concept
The first part of the PoC demonstrates the following:
StandardizedToken
with the sameTokenId
but different decimals on different chains. In this example, 18 decimals on source chain and 16 decimals on remote chains.sendToken
method creates the aforementioned payload (to be relayed) and the respectiveContractCall
/TokenSent
events with the unscaled source amount.Just apply the diff below and run the test with
npm run test test/its/tokenServiceFullFlow.js
:The second part of the PoC demonstrates that the aforementioned payload is relayed/approved as it is to the remote chain and the source token amount is received on the remote chain irrespective of the remote token's decimals.
Just apply the diff below and run the test with
npm run test test/its/tokenService.js
:Tools Used
VS Code, Hardhat
Recommended Mitigation Steps
This issue cannot be solved easily since the remote chain doesn't know about the token decimals on the source chain and vice versa. I suggest the following options:
ContractCall
/TokenSent
events at all instances. De-normalize token amounts on the remote chain accordingly.Assessed type
Decimal