Skip to content

Latest commit

 

History

History
152 lines (114 loc) · 4.8 KB

008.md

File metadata and controls

152 lines (114 loc) · 4.8 KB

Petite Fleece Cheetah

High

Users can cause the chain halt by spamming attack

Summary

The finding shows how the users can cause a spamming attack on the chain by calling such methods as WrappedDelegate, WrappedUndelegate and others.

Root Cause

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.

https://github.com/crytic/building-secure-contracts/tree/master/not-so-smart-contracts/cosmos/abci_fast#slow-abci-methods

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.

Internal Pre-conditions

No response

External Pre-conditions

No response

Attack Path

An attacker can spam the queue by submitting a lot of messages.

Impact

The chain will halt due to the EndBlocker ABCI method.

PoC

Let's take a look at the following functionality:

https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/epoching/keeper/msg_server.go#L28-83

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
			}

Mitigation

Consider adding a restriction on how many messages can be submitted.