Ethermint Ante handler bypass
Description
In commit 3362b13
↗ a fix was added in order to prevent the Ethermint Ante handler from being bypassed (see https://jumpcrypto.com/writing/bypassing-ethermint-ante-handlers/↗).
The patch was based on the original fix↗ implemented in Evmos, but the issue is that ZetaChain has the x/group module enabled, which allows for a new way of bypassing the Ante handler.
The group.MsgSubmitProposal
allows for arbitrary messages to be run when the proposal is passed:
// MsgSubmitProposal is the Msg/SubmitProposal request type.
type MsgSubmitProposal struct {
// group_policy_address is the account address of group policy.
GroupPolicyAddress string `protobuf:"bytes,1,opt,name=group_policy_address,json=groupPolicyAddre
ss,proto3" json:"group_policy_address,omitempty"`
// proposers are the account addresses of the proposers.
// Proposers signatures will be counted as yes votes.
Proposers []string `protobuf:"bytes,2,rep,name=proposers,proto3" json:"proposers,omitempty"`
// metadata is any arbitrary metadata to attached to the proposal.
Metadata string `protobuf:"bytes,3,opt,name=metadata,proto3" json:"metadata,omitempty"`
// messages is a list of `sdk.Msg`s that will be executed if the proposal passes.
Messages []*types.Any `protobuf:"bytes,4,rep,name=messages,proto3" json:"messages,omitempty"`
// exec defines the mode of execution of the proposal,
// whether it should be executed immediately on creation or not.
// If so, proposers signatures are considered as Yes votes.
Exec Exec `protobuf:"varint,5,opt,name=exec,proto3,enum=cosmos.group.v1.Exec" json:"exec,omitempty"`
}
Since anyone can create a group with themselves as the only member, they can then submit a proposal with a message that will be executed immediately using the Exec
option of Try
. The checkDisabledMsgs
function is only checking for authz
messages, and so the group proposal will not be filtered:
func (ald AuthzLimiterDecorator) checkDisabledMsgs(msgs []sdk.Msg, isAuthzInnerMsg bool, nestedLvl int) error {
if nestedLvl >= maxNestedMsgs {
return fmt.Errorf("found more nested msgs than permited. Limit is : %d", maxNestedMsgs)
}
for _, msg := range msgs {
switch msg := msg.(type) {
case *authz.MsgExec:
innerMsgs, err := msg.GetMessages()
if err != nil {
return err
}
nestedLvl++
if err := ald.checkDisabledMsgs(innerMsgs, true, nestedLvl); err != nil {
return err
}
case *authz.MsgGrant:
authorization, err := msg.GetAuthorization()
if err != nil {
return err
}
url := authorization.MsgTypeURL()
if ald.isDisabledMsg(url) {
return fmt.Errorf("found disabled msg type: %s", url)
}
default:
url := sdk.MsgTypeURL(msg)
if isAuthzInnerMsg && ald.isDisabledMsg(url) {
return fmt.Errorf("found disabled msg type: %s", url)
}
}
}
return nil
}
Impact
Similar to the original finding in section 3.11 of the April 21st, 2023 report, this can be used to steal the transaction fees for the current block, and also to trigger an infinite loop, halting the entire chain.
Recommendations
A new case should be added to the checkDisabledMsgs
method to check the group.MsgSubmitProposal
message in the same way as the existing messages:
case *group.MsgSubmitProposal:
innerMsgs, err := msg.GetMsgs()
if err != nil {
return err
}
nestedLvl++
if err := ald.checkDisabledMsgs(innerMsgs, true, nestedLvl); err != nil {
return err
}
Remediation
This issue has been acknowledged by ZetaChain, and a fix was implemented in commit cd279b80↗.