xanoutas/solidity-security-findings huggingface dataset:
Fake `ZetaReceived` events cause the outbound cctx to remain pending resulting in a blocked outbound EVM transaction queue
Audit: 2023-11-zetachain-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_client.go#L386
https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_client.go#L423
https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/zetacore_observer.go#L181-L185


# Vulnerability details

## Impact

Events such as `ZetaReceived` or `ZetaReverted`, supposed to be emitted by the connector contract, can be faked by the receiver contract that is called as part of the [`onReceive`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/protocol-contracts/contracts/evm/ZetaConnector.eth.sol#L64-L66) or [`onRevert`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/protocol-contracts/contracts/evm/ZetaConnector.eth.sol#L90-L99) call.

Worst case, a cctx can be purposefully caused to remain stuck in the `PendingOutbound` state, which blocks the outbound EVM transaction queue and prevents further outbound transactions from being sent.

## Proof of Concept

The observer's EVM client confirms sent outbound transaction within the [`IsSendOutTxProcessed`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_client.go#L295) function and sends the confirmation (i.e., `MsgVoteOnObservedOutboundTx` vote message) to ZetaChain to ultimately finalize and settle the cctx.

Specifically, outbound cctx's of type `CoinType_Zeta` are processed by checking the emitted events (logs) of the containing transaction. It is expected that either the `ZetaReceived` or `ZetaReverted` event is **emitted by the connector contract**.

However, in line [`386`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_client.go#L386) (and following), the event's legitimacy is not verified by checking the emitter contract address.

```go
386: 	receivedLog, err := connector.ZetaConnectorNonEthFilterer.ParseZetaReceived(*vLog)
```

Internally, the [`ParseZetaReceived`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/protocol-contracts/pkg/contracts/evm/zetaconnector.non-eth.sol/zetaconnectornoneth.go#L1587-L1594) function only parses the event and makes sure the event's signature matches the expected one.

```go
func (_ZetaConnectorNonEth *ZetaConnectorNonEthFilterer) ParseZetaReceived(log types.Log) (*ZetaConnectorNonEthZetaReceived, error) {
	event := new(ZetaConnectorNonEthZetaReceived)
	if err := _ZetaConnectorNonEth.contract.UnpackLog(event, "ZetaReceived", log); err != nil {
		return nil, err
	}
	event.Raw = log
	return event, nil
}
```

Thereafter, once the first matching `ZetaReceived` event is found and parsed, the confirmation is sent to Ze
A malicious inbound transaction can prevent subsequent events from being processed by observers
Audit: 2023-11-zetachain-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_client.go#L859
https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_client.go#L901


# Vulnerability details

## Impact

An attacker can send an inbound ERC-20 deposit or Zeta transaction with a `message` exceeding the maximum length limit and causing all other subsequent inbound transactions that occur in the same block range (i.e., `startBlock` to `toBlock`) to be ignored by the observers.

## Proof of Concept

> **Please note:** The outlined issue in this submission is different than the medium severity issue reported in "EVM RPC errors may lead to missed inbound transactions" as it can be actively exploited.

ZetaChain observers watch external EVM chains via the `ExternalChainWatcher` function that internally [calls the `observeInTX` function](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_client.go#L725) on each `ob.GetCoreParams().InTxTicker` ticker.

The `observeInTX` function performs multiple tasks:

1. Query for zeta sent (`ZetaSent`) logs
2. Query for ERC-20 deposited logs
3. Query tx's that are sent to the TSS address

The queried blocks are bound by the range of `startBlock` and `toBlock`, which are set in lines `809-810`. The `startBlock` is the previously processed `toBlock` (i.e., retrieved via `ob.GetLastBlockHeightScanned()`), incremented by 1.

At the end of the function, [in line `988`, the `toBlock` is set as the new `lastBlockHeightScanned`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_client.go#L988).

However, if calling `PostSend` in lines [`856`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_client.go#L856) and [`898`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_client.go#L898) errors, the `for` loop is exited early via the subsequent `return` statement.

Consequently, the `observeInTX` function proceeds to store the `toBlock` as the new `lastBlockHeightScanned`, even though the blocks (and their logs) have not been fully processed.

An attacker can exploit this issue with an inbound transaction that has a `message` exceeding the maximum length of [`MaxMessageLength = 10240`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/crosschain/types/message_vote_on_observed_inbound_tx.go#L15). This [upper bound on the message length is enforced in the `MsgVoteOnObservedInboundTx` message's `ValidateBasic` function](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/crosschain/types/message_vote_on_observed_inbound_tx.go#L88-L
Disabling outbound transactions is ineffective and allows for Zeta token theft
Audit: 2023-11-zetachain-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_signer.go#L452
https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_signer.go#L530-L544


# Vulnerability details

## Impact

Outbound EVM transactions can not be disabled and will be wrongly sent out, either stealing Zeta tokens or manifesting in a loss for users who attempt to withdraw gas or ERC-20 tokens.

## Proof of Concept

In lines [`530-544` of the `TryProcessOutTx` function](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_signer.go#L530-L544), called by the observer's EVM signer to send an outbound cctx transaction to the receiver chain, the [`SignOutboundTx`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_signer.go#L140-L168) function is invoked to sign the transaction tx.

This transaction has the [Zeta connector contract as the `to` address and the ABI encoded `onReceive` function call as the calldata](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_signer.go#L157-L162). As a result, the connector contract, e.g., the `ZetaConnectorEth` contract, has the `onReceive` function called and [subsequently transfers Zeta tokens to the `destinationAddress`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/protocol-contracts/contracts/evm/ZetaConnector.eth.sol#L60).

Now let's re-visit a critical part of the `TryProcessOutTx` function, specifically, lines [`439-544`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_signer.go#L439-L544):

```go
439: 	if send.GetCurrentOutTxParam().CoinType == common.CoinType_Cmd { // admin command
... 		// [...]
452: 	} else if send.InboundTxParams.SenderChainId == common.ZetaChain().ChainId && send.CctxStatus.Status == types.CctxStatus_PendingOutbound && flags.IsOutboundEnabled {
453: 		if send.GetCurrentOutTxParam().CoinType == common.CoinType_Gas {
... 			  // [...]
462: 		}
463: 		if send.GetCurrentOutTxParam().CoinType == common.CoinType_ERC20 {
... 			  // [...]
475: 		}
476: 		if send.GetCurrentOutTxParam().CoinType == common.CoinType_Zeta {
... 			  // [...]
490: 		}
491: 	} else if send.CctxStatus.Status == types.CctxStatus_PendingRevert && send.OutboundTxParams[0].ReceiverChainId == common.ZetaChain().ChainId {
... 		// [...]
515: 	} else if send.CctxStatus.Status == types.CctxStatus_PendingRevert {
... 		// [...]
530: 	} else if send.CctxStatus.Status == types.CctxStatus_PendingOutbound {
... 		// [...]
532: 		tx, err = signer.SignOutboundTx(
533: 			ethcommon.HexToAddress(send.InboundTxParams.Sender),
534: 			big.NewInt(send.InboundTxParams.SenderChainId),
535: 			to
zEVM cross-chain messages ignore the user-specified message and prevent calling the destination contract
Audit: 2023-11-zetachain-findings
Severity: HIGH

# 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`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/crosschain/keeper/evm_hooks.go#L42-L44) 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`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/crosschain/keeper/evm_hooks.go#L172-L246) function. This event is emitted by the [`ZetaConnectorZEVM.send`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/protocol-contracts/contracts/zevm/ZetaConnectorZEVM.sol#L92-L108) function to send a cross-chain message to an external chain.

The message input, [`ZetaInterfaces.SendInput`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/protocol-contracts/contracts/zevm/ZetaConnectorZEVM.sol#L8-L21), 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`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/protocol-contracts/contracts/evm/ZetaConnector.eth.sol#L15) or [`ZetaConnectorNonEth`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/protocol-contracts/contracts/evm/ZetaConnector.non-eth.sol#L15)), the [`destinationAddress`'s `onZetaMessage` function is called](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/protocol-contracts/contracts/evm/ZetaConnector.eth.sol#L64-L66) 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](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/crosschain/keeper/evm_hooks.go#L221).

```go
172: func (k Keeper) ProcessZetaSentEvent(ctx sdk.Context, event *c
Outbound transactions that can not be broadcasted to an external EVM chain cause a Denial of Service of all outgoing transactions to this chain
Audit: 2023-11-zetachain-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_signer.go#L579


# Vulnerability details

## Impact

Outgoing transactions to an external EVM chain can be maliciously blocked by crafting a cctx that can not be broadcasted, i.e., causing the RPC to error (with an error that is not handled in the `HandleBroadcastError` function).

For example, causing the intrinsic gas limit to exceed the provided gas limit (minimum `100k`) prevents the transaction from being included in the EVM mempool and blocks the queue of pending outgoing transactions to this external chain.

This is non-recoverable and requires manual intervention and coordination of all validators (observers) to fix the blocking nonce.

## Proof of Concept

Observers retry failed outbound transaction broadcasts for a maximum of 5 retries. Subsequently, in the last retry attempt, the `for` loop is exited in line [`579`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_signer.go#L579) and the function execution is finished.

_Please note that the [`HandleBroadcastError`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/zetacore_observer.go#L335-L350) function in line `570` only handles certain RPC errors and otherwise, simply instructs a retry._

On the next ticker, the `TryProcessOutTx` function attempts to send this cctx again but continues to fail. As nonces on the external chain have to be sequential without gaps, any other transactions are blocked from being sent to this external chain.

```go
562:// retry loop: 1s, 2s, 4s, 8s, 16s in case of RPC error
563:for i := 0; i < 5; i++ {
564:	logger.Info().Msgf("broadcasting tx %s to chain %s: nonce %d, retry %d", outTxHash, toChain, send.GetCurrentOutTxParam().OutboundTxTssNonce, i)
565:	// #nosec G404 randomness is not a security issue here
566:	time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond) // FIXME: use backoff
567:	err := signer.Broadcast(tx)
568:	if err != nil {
569:		log.Warn().Err(err).Msgf("OutTx Broadcast error")
570:		retry, report := HandleBroadcastError(err, strconv.FormatUint(send.GetCurrentOutTxParam().OutboundTxTssNonce, 10), toChain.String(), outTxHash)
571:		if report {
572:			zetaHash, err := zetaBridge.AddTxHashToOutTxTracker(toChain.ChainId, tx.Nonce(), outTxHash, nil, "", -1)
573:			if err != nil {
574:				logger.Err(err).Msgf("Unable to add to tracker on ZetaCore: nonce %d chain %s outTxHash %s", send.GetCurrentOutTxParam().OutboundTxTssNonce, toChain, outTxHash)
575:			}
576:			logger.Info().Msgf("Broadcast to core successful %s", zetaHash)
577:		}
578:		if !retry {
579:			break
580:		}
581:		backOff *= 2
582:		continue
583:	}
584:	logger.Info().Msgf("Broadcast success: nonce %d to chain %s outTxHash %s", send.GetCurrentOutTxParam().OutboundTxTssNonce, toChain, outTxHash)
585:	zetaHas
Tombstoned observer can maliciously add a duplicate observer address resulting in forfeiting voting rewards of targeted observers
Audit: 2023-11-zetachain-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/observer/keeper/msg_server_update_observer.go#L36


# Vulnerability details

## Impact

1. If the `ObserverList` contains duplicates, any [newly created ballots will have duplicates in their `VoterList`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/observer/keeper/keeper_utils.go#L74). As a result, this prevents an observer from receiving voting rewards as the reward for the legitimate vote would be offset by the [penalty received for missing the vote for the duplicate observer address](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/observer/types/ballot.go#L88) ([repeatedly voting for a ballot with the same address does not work](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/observer/types/ballot.go#L11)).
2. Removing an observer (e.g., due to the validator leaving the network or due to slashing) that has duplicates will only remove the first occurrence of the address in the list. See [node/x/observer/keeper/hooks.go#L142](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/observer/keeper/hooks.go#L142). The other occurrences will remain in the list and [allow](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/observer/keeper/keeper_utils.go#L37) the observer to [continue to post new light-client block headers](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/observer/keeper/msg_server_add_block_header.go#L19).

## Proof of Concept

The `MsgUpdateObserver` message, handled in the [`UpdateObserver`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/observer/keeper/msg_server_update_observer.go#L12) function, allows the admin or an ([tombstoned](https://docs.cosmos.network/v0.45/modules/slashing/07_tombstone.html)) observer to update the observer address.

Internally, in line [`36`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/observer/keeper/msg_server_update_observer.go#L36), the [`UpdateObserverAddress`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/observer/keeper/msg_server_update_observer.go#L86-L100) function is called to update the observer address in the `ObserverMapper` for each chain.

```go
086: func (k Keeper) UpdateObserverAddress(ctx sdk.Context, oldObserverAddress, newObserverAddress string) {
087: 	observerMappers := k.GetAllObserverMappers(ctx)
088: 	for _, om := range observerMappers {
089: 		UpdateObserverList(om.ObserverList, oldObserverAddress, newObserverAddress)
090: 		k.SetObse
Using unconfirmed UTXOs as inputs for transactions is vulnerable to griefing attacks
Audit: 2023-11-zetachain-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/bitcoin_client.go#L737


# Vulnerability details

## Impact

BTC transactions originating from the TSS address can be griefed by an attacker, preventing the TSS address from sending BTC transactions.

## Proof of Concept

The Bitcoin client retrieves all UTXO's for the TSS address with the [`FetchUTXOS`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/bitcoin_client.go#L711-L759) function. Subsequently, the UTXO's are [used as inputs for outgoing cctx's](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/btc_signer.go#L90) to cover the expenses of the transaction.

Concretely, the UTXOs are queried from the RPC by calling the `ListUnspentMinMaxAddresses` function in line [`737`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/bitcoin_client.go#L737). However, the first argument, the minimum number of confirmations, is set to 0.

```go
File: bitcoin_client.go
711: func (ob *BitcoinChainClient) FetchUTXOS() error {
...  	// [...]
720:
721: 	// get the current block height.
722: 	bh, err := ob.rpcClient.GetBlockCount()
723: 	if err != nil {
724: 		return fmt.Errorf("btc: error getting block height : %v", err)
725: 	}
726: 	maxConfirmations := int(bh)
727:
728: 	// List unspent.
729: 	tssAddr := ob.Tss.BTCAddress()
730: 	address, err := btcutil.DecodeAddress(tssAddr, config.BitconNetParams)
731: 	if err != nil {
732: 		return fmt.Errorf("btc: error decoding wallet address (%s) : %s", tssAddr, err.Error())
733: 	}
734: 	addresses := []btcutil.Address{address}
735:
736: 	// fetching all TSS utxos takes 160ms
737: ❌	utxos, err := ob.rpcClient.ListUnspentMinMaxAddresses(0, maxConfirmations, addresses)
...  	// [...]
759: }
```

As a result, the UTXOs returned by the RPC call include unconfirmed UTXOs, i.e., transaction outputs that are not yet confirmed by subsequent blocks.

Consequently, an attacker can craft a BTC transaction to the TSS address, with a low fee, and broadcast it to the network (the transaction outputs must match the range of UTXOs that will be used by the TSS address for the next transaction). While the transaction and its outputs are sitting unconfirmed in the mempool, the observer Bitcoin clients will retrieve these unconfirmed outputs as UTXOs.

The unconfirmed UTXOs are then used as inputs for outgoing TSS BTC transactions, which will also remain unconfirmed as long as the original transaction (from the attacker) remains unconfirmed. This can cause a halt in the outgoing BTC transactions from the TSS address.

Moreover, the attacker can replace the original transaction via the [Replace-by-fee (RBF) policy](https://bitcoinops.org/en/topics/replace-by-fee/), and change the recipient address to an address o