Skip to content

Latest commit

 

History

History
148 lines (117 loc) · 5.02 KB

065.md

File metadata and controls

148 lines (117 loc) · 5.02 KB

Slow Misty Iguana

High

The attacker creates a large number of fps to conduct DoS attacks.

Summary

The attacker creates a large number of fps to conduct DoS attacks.

Root Cause

  1. The creation of fp only verifies the proof of possession. After the creation is completed, the user can create a new fp again after transferring the assets.
  2. There is no limit on the number of fp creations.
  3. BeginBlock -> UpdatePowerDist will traverse all activated fps including those with a stake amount of 0.

Internal Pre-conditions

External Pre-conditions

Attack Path

  1. The amount of BTC held by the attacker reaches the possession requirement.
  2. The attacker creates fp.
  3. The attacker transfers BTC to another address.
  4. The attacker creates fp again.
  5. The attacker stake the minimum number of tokens to fp, and fp is activated, and then unstakes.
  6. The attacker repeats the above steps to create any number of fp.
  7. Due to too many fps, the chain becomes slow.
  8. The attacker only spends the gas fee, the invalid fp will be permanently stored in the fp list, and each block will be traversed.

Impact

Slow ABCI methods

PoC

The creation of fp only verifies proof of possession:

func (ms msgServer) CreateFinalityProvider(goCtx context.Context, req *types.MsgCreateFinalityProvider) (*types.MsgCreateFinalityProviderResponse, error) {
    ......
->	// verify proof of possession
->	if err := req.Pop.Verify(fpAddr, req.BtcPk, ms.btcNet); err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid proof of possession: %v", err)
	}

	if err := ms.AddFinalityProvider(ctx, req); err != nil {
		return nil, err
	}
	return &types.MsgCreateFinalityProviderResponse{}, nil
}

https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/btcstaking/keeper/msg_server.go#L82

Therefore, the attacker can create any number of FPs after transferring BTC.

After fp is staked, it will be activated and added to VotingPowerDistCache:

func (k Keeper) ProcessAllPowerDistUpdateEvents(
	ctx context.Context,
	dc *ftypes.VotingPowerDistCache,
	events []*types.EventPowerDistUpdate,
) *ftypes.VotingPowerDistCache {
	
	sdkCtx := sdk.UnwrapSDKContext(ctx)
	for _, event := range events {
		switch typedEvent := event.Ev.(type) {
		case *types.EventPowerDistUpdate_BtcDelStateUpdate:
			delEvent := typedEvent.BtcDelStateUpdate
			delStkTxHash := delEvent.StakingTxHash

			btcDel, err := k.BTCStakingKeeper.GetBTCDelegation(ctx, delStkTxHash)
			if err != nil {
				panic(err) // only programming error
			}

			switch delEvent.NewState {
->			case types.BTCDelegationStatus_ACTIVE:
				// newly active BTC delegation
				// add the BTC delegation to each restaked finality provider
				for _, fpBTCPK := range btcDel.FpBtcPkList {
					fpBTCPKHex := fpBTCPK.MarshalHex()
->					activedSatsByFpBtcPk[fpBTCPKHex] = append(activedSatsByFpBtcPk[fpBTCPKHex], btcDel.TotalSat)
				}
        ......
    }

Even if the stake is cancelled, fp is still saved in the VotingPowerDistCache. UpdatePowerDist only sorts the dc and does not remove invalid fp.

BeginBlock -> UpdatePowerDist -> ProcessAllPowerDistUpdateEvents will traverse all fp:

func (k Keeper) UpdatePowerDist(ctx context.Context) {
	height := uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height)
	// GetBTCHeightAtBabylonHeight(HeaderInfo().Height)
	btcTipHeight := k.BTCStakingKeeper.GetCurrentBTCHeight(ctx)

	// get the power dist cache in the last height
->	dc := k.GetVotingPowerDistCache(ctx, height-1)
	if dc == nil {
		// no BTC staker at the prior height
		dc = ftypes.NewVotingPowerDistCache()
	}

	// get all power distribution update events during the previous tip and the current tip
	lastBTCTipHeight := k.BTCStakingKeeper.GetBTCHeightAtBabylonHeight(ctx, height-1)
	events := k.BTCStakingKeeper.GetAllPowerDistUpdateEvents(ctx, lastBTCTipHeight, btcTipHeight)

	// clear all events that have been consumed in this function
	defer func() {
		for i := lastBTCTipHeight; i <= btcTipHeight; i++ {
			k.BTCStakingKeeper.ClearPowerDistUpdateEvents(ctx, i)
		}
	}()

	// reconcile old voting power distribution cache and new events to construct the new distribution
->	newDc := k.ProcessAllPowerDistUpdateEvents(ctx, dc, events)

	// record voting power and cache for this height
	k.recordVotingPowerAndCache(ctx, newDc)
	// emit events for finality providers with state updates
	k.handleFPStateUpdates(ctx, dc, newDc)
	// record metrics
	k.recordMetrics(newDc)
}

func (k Keeper) ProcessAllPowerDistUpdateEvents(
	ctx context.Context,
	dc *ftypes.VotingPowerDistCache,
	events []*types.EventPowerDistUpdate,
) *ftypes.VotingPowerDistCache {
    ......
	// iterate over all finality providers and apply all events
->	for i := range dc.FinalityProviders {
		// create a copy of the finality provider
		fp := *dc.FinalityProviders[i]
		fpBTCPKHex := fp.BtcPk.MarshalHex()
        ......
    }
}

https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/finality/keeper/power_dist_change.go#L24

Mitigation

  1. Limit the number of fp creations
  2. Remove invalid fp from fp list.