Platform: Code4rena
Start Date: 29/07/2022
Pot Size: $50,000 USDC
Total HM: 6
Participants: 75
Period: 5 days
Judge: GalloDaSballo
Total Solo HM: 3
Id: 149
League: ETH
Rank: 4/75
Findings: 5
Award: $7,710.47
π Selected for report: 3
π Solo Findings: 0
2078.8043 USDC - $2,078.80
An owner can call removeWrapping
, even if there are still circulating wrapped tokens. This will cause the unwrapping of those tokens to fail, as unwrapped[wrappedToken]
will be addres(0)
.
Track how many wrapped tokens are in circulation, only allow the removal of a wrapped tokens when there are 0 to ensure for users that they will always be able to unwrap.
#0 - re1ro
2022-08-05T01:12:46Z
Valid observation. We will consider a different approach.
removeWrapping
method was removed
https://github.com/axelarnetwork/axelar-xc20-wrapper/pull/4
#1 - GalloDaSballo
2022-08-28T23:11:38Z
The warden has shown how, the Admin can remove the mapping that allows to redeem bridged tokens, because this will cause the inability to unwrap, and can be operated by the admin, I agree with Medium Severity.
The sponsor has confirmed and they have mitigated by removing the function
3464.6739 USDC - $3,464.67
After EIP-4758, the SELFDESTRUCT
op code will no longer be available. According to the EIP, "The only use that breaks is where a contract is re-created at the same address using CREATE2 (after a SELFDESTRUCT)". Axelar is exactly such an application, the current deposit system will no longer work.
To avoid that Axelar simply stops working one day, the architecture should be changed. Instead of generating addresses for every user, the user could directly interact with the deposit service and the deposit service would need to keep track of funds and provide refunds directly.
#0 - re1ro
2022-08-05T01:00:15Z
Very good spot. We will address this
#1 - GalloDaSballo
2022-09-04T19:20:44Z
The warden has shown a plausible upgrade path for Ethereum that will remove the SELFDESTRUCT
opcode, bricking the DepositReceiver
functionality.
If the fork was in place today, the code would be broken, and the finding should be of high severity.
Because the fork is not in place, and no clear timeline is defined for "The Purge", I think Medium Severity to be correct
2078.8043 USDC - $2,078.80
https://github.com/code-423n4/2022-07-axelar/blob/3373c48a71c07cfce856b53afc02ef4fc2357f8c/contracts/AxelarGateway.sol#L268 https://github.com/code-423n4/2022-07-axelar/blob/3373c48a71c07cfce856b53afc02ef4fc2357f8c/contracts/AxelarGateway.sol#L311
According to the specifications, only the current operators should be able to transfer operatorship. However, there is one way to circumvent this. Because currentOperators is not updated in the loop, when multiple transferOperatorship
commands are submitted in the same execute
call, all will succeed. After the first one, the operators that signed these commands are no longer the current operators, but the call will still succeed.
This also means that one set of operators could submit so many transferOperatorship
commands in one execute
call that OLD_KEY_RETENTION
is reached for all other ones, meaning they would control complete set of currently valid operators.
Set currentOperators
to false
when the operators were changed.
#0 - re1ro
2022-08-05T00:58:46Z
This case would never occur in practice because our command batches are produced and signed by the Axelar nerwork.
So there would be never 2 transferOperatorship
commands in the same batch.
In general if the recent operators turn malicious they can overtake the gateway disregarding this finding.
We still have added the sanity check to set currentOperators
to false
.
https://github.com/axelarnetwork/axelar-cgp-solidity/pull/138
#1 - milapsheth
2022-08-24T17:51:09Z
For more context, the current operators could just easily transfer the operatorship in multiple txs instead. We heavily rely on the assumption the majority of the operators by weight are not malicious.
#2 - GalloDaSballo
2022-09-04T19:18:26Z
Very interesting find.
Per the warden submission: the operators can sign a transferOperatorship
and then still act as if they are the current operator while their calls are being executed.
This can be further extended to perform more than OLD_KEY_RETENTION
to invalidate all old keys, which may be desirable or a malicious attack depending on context.
The sponsor disagrees with severity, citing that the main assumption of the code is that operators by weight are non malicious
Personally I think the finding:
transferOperatorship
)OLD_KEY_RETENTION
txs#3 - GalloDaSballo
2022-09-04T23:55:06Z
With the information that I have, considering that:
Because this is contingent on a malicious majority, and considering that a malicious majority can perform even worse attacks (DOS, TX Censoring, Shutting down the chain)
I believe that Medium Severity is correct
π Selected for report: oyc_109
Also found by: 0x1f8b, 0x52, 0xNazgul, 0xSmartContract, 0xf15ers, 8olidity, Aymen0909, Bnke0x0, CertoraInc, Chom, CodingNameKiki, Deivitto, Dravee, ElKu, IllIllI, JC, Lambda, Noah3o6, NoamYakov, RedOneN, Respx, ReyAdmirado, Rohan16, Rolezn, Ruhum, Sm4rty, TomJ, Twpony, Waze, Yiko, __141345__, ajtra, apostle0x01, ashiq0x01, asutorufos, bardamu, benbaessler, berndartmueller, bharg4v, bulej93, c3phas, cccz, ch13fd357r0y3r, codexploder, cryptonue, cryptphi, defsec, djxploit, durianSausage, fatherOfBlocks, gogo, hansfriese, horsefacts, ignacio, kyteg, lucacez, mics, rbserver, robee, sashik_eth, simon135, sseefried, tofunmi, xiaoming90
56.3243 USDC - $56.32
https://github.com/code-423n4/2022-07-axelar/blob/a46fa61e73dd0f3469c0263bc6818e682d62fb5f/contracts/deposit-service/ReceiverImplementation.sol#L23 https://github.com/code-423n4/2022-07-axelar/blob/a46fa61e73dd0f3469c0263bc6818e682d62fb5f/contracts/deposit-service/ReceiverImplementation.sol#L51 https://github.com/code-423n4/2022-07-axelar/blob/a46fa61e73dd0f3469c0263bc6818e682d62fb5f/contracts/deposit-service/ReceiverImplementation.sol#L71 https://github.com/code-423n4/2022-07-axelar/blob/a46fa61e73dd0f3469c0263bc6818e682d62fb5f/contracts/deposit-service/ReceiverImplementation.sol#L86 https://github.com/code-423n4/2022-07-axelar/blob/3373c48a71c07cfce856b53afc02ef4fc2357f8c/contracts/gas-service/AxelarGasService.sol#L128 https://github.com/code-423n4/2022-07-axelar/blob/3373c48a71c07cfce856b53afc02ef4fc2357f8c/contracts/gas-service/AxelarGasService.sol#L144 https://github.com/code-423n4/2022-07-axelar/blob/a1205d2ba78e0db583d136f8563e8097860a110f/xc20/contracts/XC20Wrapper.sol#L63
The system uses transfer
which only has a 2300 gas stipend in various places for transferring ETH. Depending on the logic of the retriever (e.g., a smart contract that performs some storage read and writes on receive, for instance a multi-sig), this may not be sufficient and the transfer can revert. See also https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/ for a discussion of this issue.
Use call
instead, e.g.:
(bool success, ) = payable(payAddress).call{amount}(""); require(success, "Transfer failed.");
#0 - re1ro
2022-08-05T01:00:46Z
Duplicate of #4
π Selected for report: IllIllI
Also found by: 0x1f8b, 0xNazgul, 0xsam, 8olidity, Aymen0909, Bnke0x0, Chom, CodingNameKiki, Deivitto, Dravee, ElKu, Fitraldys, JC, Lambda, MiloTruck, Noah3o6, NoamYakov, RedOneN, Respx, ReyAdmirado, Rohan16, Rolezn, Ruhum, Sm4rty, TomJ, Tomio, Waze, __141345__, a12jmx, ajtra, ak1, apostle0x01, asutorufos, benbaessler, bharg4v, bulej93, c3phas, defsec, djxploit, durianSausage, erictee, fatherOfBlocks, gerdusx, gogo, kyteg, lucacez, medikko, mics, owenthurm, oyc_109, rbserver, robee, sashik_eth, simon135, tofunmi
31.8812 USDC - $31.88
refundTokenDeposit
within AxelarDepositService
, the address of the gatewayToken
is retrieved in every loop iteration (https://github.com/code-423n4/2022-07-axelar/blob/a46fa61e73dd0f3469c0263bc6818e682d62fb5f/contracts/deposit-service/AxelarDepositService.sol#L115). However, as this address does not change, the retrieval can be moved outside of the loop to save a lot of gas when the number of tokens is large.unchecked
because an overflow is not possible (as the iterator is bounded):./auth/AxelarAuthWeighted.sol: for (uint256 i; i < recentOperators.length; ++i) { ./auth/AxelarAuthWeighted.sol: for (uint256 i = 0; i < weightsLength; ++i) { ./auth/AxelarAuthWeighted.sol: for (uint256 i = 0; i < signatures.length; ++i) { ./auth/AxelarAuthWeighted.sol: for (; operatorIndex < operatorsLength && signer != operators[operatorIndex]; ++operatorIndex) {} ./auth/AxelarAuthWeighted.sol: for (uint256 i; i < accounts.length - 1; ++i) { ./gas-service/AxelarGasService.sol: for (uint256 i; i < tokens.length; i++) { ./deposit-service/AxelarDepositService.sol: for (uint256 i; i < refundTokens.length; i++) { ./deposit-service/AxelarDepositService.sol: for (uint256 i; i < refundTokens.length; i++) { ./deposit-service/AxelarDepositService.sol: for (uint256 i; i < refundTokens.length; i++) {
#0 - re1ro
2022-08-05T00:57:40Z
Good spot
Dup #2
#1 - GalloDaSballo
2022-08-23T00:51:49Z
Will save 100 gas per instance
20 per instance
280 gas saved in total, rounding up to 300
#2 - re1ro
2022-08-23T01:15:16Z