Skip to content

Latest commit

 

History

History
98 lines (70 loc) · 3.1 KB

037.md

File metadata and controls

98 lines (70 loc) · 3.1 KB

Sneaky Mercurial Python

High

Votes cannot be extended and verified in the FirstBlockHeight of an epoch

Summary

ExtendVote() will return early without extending any vote when called in the FirstBlockHeight of a new epoch

The same issue affects the VerifyVoteExtension() function too.

https://github.com/babylonlabs-io/babylon/blob/b5646552a1606e38fcdfa97ed2606549b9a233f7/x/checkpointing/vote_ext.go#L49-L52

Root Cause

When vote_ext::ExtendVote() is called, it checks to ensure that the request is from the last or most recent processed height of the current epoch

File: ...babylon/x/checkpointing/vote_ext.go
42: func (h *VoteExtensionHandler) ExtendVote() sdk.ExtendVoteHandler {
43: 	return func(ctx sdk.Context, req *abci.RequestExtendVote) (*abci.ResponseExtendVote, error) {
44: 		k := h.ckptKeeper
45: 	@>	// the returned response MUST not be nil
46: 		emptyRes := &abci.ResponseExtendVote{VoteExtension: []byte{}}
47: 
48: 		epoch := k.GetEpoch(ctx)
49: 		// BLS vote extension is only applied at the last block of the current epoch
50: 	@>	if !epoch.IsLastBlockByHeight(req.Height) {
51: 			return emptyRes, nil
52: 		}

This is to ensure votes are not extended from far previous blocks However, there is a problem with this, if we look into the epoch.IsLastBlockByHeight() function, notice that it wrongly assumes an epoch will always have a last block (this is not always the case)

File: ...babylon/x/epoching/types/epoching.go
52: func (e Epoch) IsLastBlockByHeight(height int64) bool {
53: @>	return e.GetLastBlockHeight() == uint64(height)
54: }

File: .../babylon/x/epoching/types/epoching.go
30: func (e Epoch) GetLastBlockHeight() uint64 {
31: 	if e.EpochNumber == 0 {
32: 		return 0
33: 	}
34: @>	return e.FirstBlockHeight + e.CurrentEpochInterval - 1
35: }

Notice that the GetLastBlockHeight() function does not take into consideration when the epoch is in its FirstBlockHeight

Internal Pre-conditions

vote is being extended when the epoch is in its FirstBlockHeight

External Pre-conditions

NIL

Attack Path

NIL

Impact

  • Vote Extension will be blocked and no vote is extended (i.e empty vote extension is sent)
  • proposal preparation is also blocked because votes are not extended and this can lead to a chain halt
  • Votes cannot be verified too

PoC

  • current epoch = 5
  • CurrentEpochInterval = 20
  • FirstBlockHeight = 101
  • current block height = FirstBlockHeight = 101
  • req.Height = 100
  • ExtendVote() is called and e.GetLastBlockHeight() is evaluated as
34: 	return e.FirstBlockHeight + e.CurrentEpochInterval - 1
34: 	101 + 20 -1 = 120
  • the condition below evaluates to false and ExtendVote() returns an empty byte slice for emptyRes and no vote is extended
53: @>	return e.GetLastBlockHeight() == uint64(height) // 120 != 100

Mitigation

  • Consider modifying the ExtendVote() function to allow extension from the last block of the previous epoch when the a new epoch is in its FirstBlockHeight.
  • When the epoch is in its FirstBlockHeight, GetLastBlockHeight() should return
FirstBlockHeight - 1