code-423n4 / 2023-11-zetachain-findings

0 stars 0 forks source link

zEVM cross-chain messages ignore the user-specified message and prevent calling the destination contract #413

Open c4-bot-9 opened 11 months ago

c4-bot-9 commented 11 months ago

Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/crosschain/keeper/evm_hooks.go#L221

Vulnerability details

Impact

Cross-chain Zeta messages originating from the zEVM have an empty message field, preventing the destinationAddress contract from being called.

This renders the cross-chain messaging functionality useless as the message is never used and potentially causes a loss of funds (if assets have been burned on the zEVM) or locked funds (if unable to unlock on the receiver end).

Proof of Concept

zEVM transactions are post-processed in the PostTxProcessing function of the x/crosschain module. Specifically, the goal is to parse and process ZetaSent and ZRC-20 Withdrawal events and send them to the corresponding, external receiver chains.

Any emitted ZetaSent events are parsed and processed in the ProcessZetaSentEvent function. This event is emitted by the ZetaConnectorZEVM.send function to send a cross-chain message to an external chain.

The message input, ZetaInterfaces.SendInput, allows the sender to specify a message that is forwarded to the receiver contract (destinationAddress) on the destination chain.

Specifically, once the cross-chain message is received by the onReceive function of the ZetaConnector contract on the receiver chain (e.g., ZetaConnectorEth or ZetaConnectorNonEth), the destinationAddress's onZetaMessage function is called and the message is provided as a parameter.

However, the user-specified message is not used, instead, it is overwritten by an empty string in line 221.

172: func (k Keeper) ProcessZetaSentEvent(ctx sdk.Context, event *connectorzevm.ZetaConnectorZEVMZetaSent, emittingContract ethcommon.Address, txOrigin string) error {
...     // [...]
212:
213:    // Bump gasLimit by event index (which is very unlikely to be larger than 1000) to always have different ZetaSent events msgs.
214:    msg := types.NewMsgVoteOnObservedInboundTx(
215:        "",
216:        emittingContract.Hex(),
217:        senderChain.ChainId,
218:        txOrigin, toAddr,
219:        receiverChain.ChainId, /
220:        amount,
221: ❌      "",
222:        event.Raw.TxHash.String(),
223:        event.Raw.BlockNumber,
224:        90000,
225:        common.CoinType_Zeta,
226:        "",
227:        event.Raw.Index,
228:    )
229:    sendHash := msg.Digest()
230:
231:    // Create the CCTX
232:    cctx := k.CreateNewCCTX(ctx, msg, sendHash, tss.TssPubkey, types.CctxStatus_PendingOutbound, &senderChain, receiverChain)
...     // [...]
246: }

As a result, the destination contract is never called as the message is empty.

Tools Used

Manual review

Recommended mitigation steps

Consider using the user-specified message instead of overwriting it with an empty string.

Assessed type

Other

DadeKuma commented 11 months ago

Empty string instead of using ZetaConnectorZEVMZetaSent.Message

c4-pre-sort commented 11 months ago

DadeKuma marked the issue as sufficient quality report

c4-pre-sort commented 11 months ago

DadeKuma marked the issue as primary issue

c4-sponsor commented 10 months ago

lumtis (sponsor) confirmed

c4-judge commented 10 months ago

0xean marked the issue as satisfactory

c4-judge commented 10 months ago

0xean marked the issue as selected for report