Axelar Network - Viktor_Cortess's results

Decentralized interoperability network.

General Information

Platform: Code4rena

Start Date: 12/07/2023

Pot Size: $80,000 USDC

Total HM: 11

Participants: 47

Period: 9 days

Judge: berndartmueller

Total Solo HM: 1

Id: 260

League: ETH

Axelar Network

Findings Distribution

Researcher Performance

Rank: 26/47

Findings: 1

Award: $94.77

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

Labels

bug
2 (Med Risk)
satisfactory
duplicate-319

Awards

94.7708 USDC - $94.77

External Links

Lines of code

https://github.com/code-423n4/2023-07-axelar/blob/2f9b234bb8222d5fbe934beafede56bfb4522641/contracts/gmp-sdk/executable/AxelarExecutable.sol#L17-L28

Vulnerability details

Impact

The external function execute() from AxelarExecutable contracts calls the internal function _execute from InterchainProposalExecutor contract that executes the proposal. But these functions are not payable and can't receive native tokens during the call making it impossible to send native tokens to the target contracts during proposal execution.

Proof of Concept

The execute() external function calls the crucial internal function _execute:

function execute( bytes32 commandId, string calldata sourceChain, string calldata sourceAddress, bytes calldata payload ) external { bytes32 payloadHash = keccak256(payload); if (!gateway.validateContractCall(commandId, sourceChain, sourceAddress, payloadHash)) revert NotApprovedByGateway(); _execute(sourceChain, sourceAddress, payload); }

The _execute internal function is responsible for executing the proposal. However, it lacks the ability to receive native tokens, rendering the execution of calls with call.value > 0 impossible.

function _execute( string calldata sourceChain, string calldata sourceAddress, bytes calldata payload ) internal override { _beforeProposalExecuted(sourceChain, sourceAddress, payload); // Check that the source address is whitelisted if (!whitelistedSenders[sourceChain][StringToAddress.toAddress(sourceAddress)]) { revert NotWhitelistedSourceAddress(); } // Decode the payload (address interchainProposalCaller, InterchainCalls.Call[] memory calls) = abi.decode(payload, (address, InterchainCalls.Call[])); // Check that the caller is whitelisted if (!whitelistedCallers[sourceChain][interchainProposalCaller]) { revert NotWhitelistedCaller(); } // Execute the proposal with the given arguments _executeProposal(calls); _onProposalExecuted(sourceChain, sourceAddress, interchainProposalCaller, payload); emit ProposalExecuted(keccak256(abi.encode(sourceChain, sourceAddress, interchainProposalCaller, payload))); }

The last internal function, _executeProposal(), is supposed to send some native tokens with the call, but due to the non-payable nature of the contract and its functions, it is unable to receive native tokens making sending impossible.

function _executeProposal(InterchainCalls.Call[] memory calls) internal { for (uint256 i = 0; i < calls.length; i++) { InterchainCalls.Call memory call = calls[i]; (bool success, bytes memory result) = call.target.call{ value: call.value }(call.callData); if (!success) { _onTargetExecutionFailed(call, result); } else { _onTargetExecuted(call, result); } } }

Test from InterchainProposalExecutor reverts with an error if we try to send some msg.value with the call:

describe('_execute', function () { it.only('should be able to call target contract', async function () { // whitelist caller and sender await executor.setWhitelistedProposalCaller('ethereum', signerAddress, true); await executor.setWhitelistedProposalSender('ethereum', signerAddress, true); const callData = dummy.interface.encodeFunctionData('setState', ['Hello World']); const calls = [ { target: dummy.address, value: 0, callData, }, ]; const payload = ethers.utils.defaultAbiCoder.encode( ['address', 'tuple(address target, uint256 value, bytes callData)[]'], [signerAddress, calls], ); const broadcast = () => executor.forceExecute('ethereum', signerAddress, payload, { value: '100' });

Error:

1) Interchain Proposal Executor _execute should be able to call target contract: Error: non-payable method cannot override value (operation="overrides.value", value="100", code=UNSUPPORTED_OPERATION, version=contracts/5.7.0)

Tools Used

vs

To address this issue, I recommend adding either the payable keyword to the external execute() function or implementing a receive() fallback function in the contract. This way, the contract will be able to receive native tokens, allowing successful execution of calls with call.value > 0.

Assessed type

Payable

#0 - c4-pre-sort

2023-07-29T00:04:13Z

0xSorryNotSorry marked the issue as duplicate of #319

#1 - c4-judge

2023-09-08T10:59:18Z

berndartmueller 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