Petite Fleece Cheetah
High
The finding shows how the users can cause a spamming attack on the chain by calling such methods as WrappedDelegate
, WrappedUndelegate
and others.
Anybody can call the methods WrappedDelegate()
, WrappedUndelegate()
, WrappedBeginRedelegate()
, WrappedCancelUnbondingDelegation()
in the epoching
module's msg server that are later added to the queue. The problem is that they are then processed in the EndBlocker
which means that a huge amount of such messages will halt the chain according to the Cosmos specification:
https://docs.cosmos.network/v0.50/learn/beginner/app-anatomy#beginblocker-and-endblocker
it is important to remember that application-specific blockchains are deterministic. Developers must be careful not to introduce non-determinism in BeginBlocker or EndBlocker, and must also be careful not to make them too computationally expensive, as [gas](https://docs.cosmos.network/v0.50/learn/beginner/gas-fees) does not constrain the cost of BeginBlocker and EndBlocker execution.
ABCI methods (like EndBlocker) [are not constrained by gas](https://docs.cosmos.network/v0.45/basics/app-anatomy.html#beginblocker-and-endblocker). Therefore, it is essential to ensure that they always will finish in a reasonable time. Otherwise, the chain will halt.
No response
No response
An attacker can spam the queue by submitting a lot of messages.
The chain will halt due to the EndBlocker
ABCI method.
Let's take a look at the following functionality:
unc (ms msgServer) WrappedDelegate(goCtx context.Context, msg *types.MsgWrappedDelegate) (*types.MsgWrappedDelegateResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
if msg.Msg == nil {
return nil, types.ErrNoWrappedMsg
}
// verification rules ported from staking module
valAddr, valErr := sdk.ValAddressFromBech32(msg.Msg.ValidatorAddress)
if valErr != nil {
return nil, valErr
}
if _, err := ms.stk.GetValidator(ctx, valAddr); err != nil {
return nil, err
}
if _, err := sdk.AccAddressFromBech32(msg.Msg.DelegatorAddress); err != nil {
return nil, err
}
bondDenom, err := ms.stk.BondDenom(ctx)
if err != nil {
return nil, err
}
if msg.Msg.Amount.Denom != bondDenom {
return nil, errorsmod.Wrapf(
sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Msg.Amount.Denom, bondDenom,
)
}
blockHeight := uint64(ctx.HeaderInfo().Height)
if blockHeight == 0 {
return nil, types.ErrZeroEpochMsg
}
blockTime := ctx.HeaderInfo().Time
txid := tmhash.Sum(ctx.TxBytes())
queuedMsg, err := types.NewQueuedMessage(blockHeight, blockTime, txid, msg)
if err != nil {
return nil, err
}
ms.EnqueueMsg(ctx, queuedMsg)
err = ctx.EventManager().EmitTypedEvents(
&types.EventWrappedDelegate{
DelegatorAddress: msg.Msg.DelegatorAddress,
ValidatorAddress: msg.Msg.ValidatorAddress,
Amount: msg.Msg.Amount.Amount.Uint64(),
Denom: msg.Msg.Amount.GetDenom(),
EpochBoundary: ms.GetEpoch(ctx).GetLastBlockHeight(),
},
)
if err != nil {
return nil, err
}
return &types.MsgWrappedDelegateResponse{}, nil
}
As you can see here, the message is added to the queue:
queuedMsg, err := types.NewQueuedMessage(blockHeight, blockTime, txid, msg)
The queue is then processed in the EndBlocker
:
https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/epoching/abci.go#L88-113
// get all msgs in the msg queue
queuedMsgs := k.GetCurrentEpochMsgs(ctx)
// forward each msg in the msg queue to the right keeper
for _, msg := range queuedMsgs {
res, err := k.HandleQueuedMsg(ctx, msg)
// skip this failed msg and emit and event signalling it
// we do not panic here as some users may wrap an invalid message
// (e.g., self-delegate coins more than its balance, wrong coding of addresses, ...)
// honest validators will have consistent execution results on the queued messages
if err != nil {
// emit an event signalling the failed execution
err := sdkCtx.EventManager().EmitTypedEvent(
&types.EventHandleQueuedMsg{
EpochNumber: epoch.EpochNumber,
Height: msg.BlockHeight,
TxId: msg.TxId,
MsgId: msg.MsgId,
Error: err.Error(),
},
)
if err != nil {
return nil, err
}
// skip this failed msg
continue
}
Consider adding a restriction on how many messages can be submitted.