Any ZetaSent
events are processed regardless of what contract emits them
Description
The main method through which funds are intended to be bridged over from the zEVM to another chain is by calling the send()
function in the zEVM's ZetaConnectorZEVM contract. This function emits a ZetaSent
event, which is intended to be processed by the crosschain
module's PostTxProcessing()
hook.
It is crucial that this function checks to ensure that any ZetaSent
event it picks up originated from the ZetaConnectorZEVM contract. Otherwise, any malicious attacker can deploy their own contract on the zEVM and emit arbitrary ZetaSent
events to send arbitrary amounts of ZETA without actually holding any ZETA.
Impact
Inside the PostTxProcessing()
hook, we see the following:
func (k Keeper) PostTxProcessing(
ctx sdk.Context,
msg core.Message,
receipt *ethtypes.Receipt,
) error {
target := receipt.ContractAddress
if msg.To() != nil {
target = *msg.To()
}
for _, log := range receipt.Logs {
eZRC20, err := ParseZRC20WithdrawalEvent(*log)
if err == nil {
if err := k.ProcessZRC20WithdrawalEvent(ctx, eZRC20, target, ""); err != nil {
return err
}
}
eZeta, err := ParseZetaSentEvent(*log)
if err == nil {
if err := k.ProcessZetaSentEvent(ctx, eZeta, target, ""); err != nil {
return err
}
}
}
return nil
}
The receipt
parameter of this function contains information about transactions that occur on the zEVM. This function iterates through all logs (i.e., emitted events) in each receipt and attempts to parse the events as Withdrawal
or ZetaSent
events.
However, there is no check to ensure that these events originate from the ZetaConnectorZEVM contract. This allows a malicious attacker to deploy their own contract on the zEVM, which would allow them to emit arbitrary ZetaSent
events, and thus gain access to ZETA tokens that they otherwise should not have access to.
The prototype of the ZetaSent
event is as follows:
event ZetaSent(
address sourceTxOriginAddress,
address indexed zetaTxSenderAddress,
uint256 indexed destinationChainId,
bytes destinationAddress,
uint256 zetaValueAndGas,
uint256 destinationGasLimit,
bytes message,
bytes zetaParams
);
It is important to note that Withdrawal
events are not affected by this bug. The ProcessZRC20WithdrawalEvent()
function checks and ensures that the event was emitted from a whitelisted ZRC20 token contract address.
Recommendations
Add a check to ensure that ZetaSent
events are only processed if they are emitted from the ZetaConnectorZEVM contract.
Remediation
This issue has been acknowledged by ZetaChain, and a fix was implemented in commit 8a988ae9↗.