VYPR
Moderate severityNVD Advisory· Published Jul 2, 2020· Updated Aug 4, 2024

Denial of Service in TenderMint

CVE-2020-15091

Description

TenderMint from version 0.33.0 and before version 0.33.6 allows block proposers to include signatures for the wrong block. This may happen naturally if you start a network, have it run for some time and restart it (without changing chainID). A malicious block proposer (even with a minimal amount of stake) can use this vulnerability to completely halt the network. This issue is fixed in Tendermint 0.33.6 which checks all the signatures are for the block with 2/3+ majority before creating a commit.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/tendermint/tendermintGo
< 0.33.60.33.6

Affected products

1

Patches

1
480b995a3172

consensus: Do not allow signatures for a wrong block in commits

https://github.com/tendermint/tendermintAnton KaliaevJun 30, 2020via ghsa
2 files changed · +108 4
  • consensus/invalid_test.go+97 0 added
    @@ -0,0 +1,97 @@
    +package consensus
    +
    +import (
    +	"testing"
    +
    +	"github.com/tendermint/tendermint/libs/bytes"
    +	"github.com/tendermint/tendermint/libs/log"
    +	tmrand "github.com/tendermint/tendermint/libs/rand"
    +	"github.com/tendermint/tendermint/p2p"
    +	tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
    +	"github.com/tendermint/tendermint/types"
    +)
    +
    +//----------------------------------------------
    +// byzantine failures
    +
    +// one byz val sends a precommit for a random block at each height
    +// Ensure a testnet makes blocks
    +func TestReactorInvalidPrecommit(t *testing.T) {
    +	N := 4
    +	css, cleanup := randConsensusNet(N, "consensus_reactor_test", newMockTickerFunc(true), newCounter)
    +	defer cleanup()
    +
    +	for i := 0; i < 4; i++ {
    +		ticker := NewTimeoutTicker()
    +		ticker.SetLogger(css[i].Logger)
    +		css[i].SetTimeoutTicker(ticker)
    +
    +	}
    +
    +	reactors, blocksSubs, eventBuses := startConsensusNet(t, css, N)
    +
    +	// this val sends a random precommit at each height
    +	byzValIdx := 0
    +	byzVal := css[byzValIdx]
    +	byzR := reactors[byzValIdx]
    +
    +	// update the doPrevote function to just send a valid precommit for a random block
    +	// and otherwise disable the priv validator
    +	byzVal.mtx.Lock()
    +	pv := byzVal.privValidator
    +	byzVal.doPrevote = func(height int64, round int32) {
    +		invalidDoPrevoteFunc(t, height, round, byzVal, byzR.Switch, pv)
    +	}
    +	byzVal.mtx.Unlock()
    +	defer stopConsensusNet(log.TestingLogger(), reactors, eventBuses)
    +
    +	// wait for a bunch of blocks
    +	// TODO: make this tighter by ensuring the halt happens by block 2
    +	for i := 0; i < 10; i++ {
    +		timeoutWaitGroup(t, N, func(j int) {
    +			<-blocksSubs[j].Out()
    +		}, css)
    +	}
    +}
    +
    +func invalidDoPrevoteFunc(t *testing.T, height int64, round int32, cs *State, sw *p2p.Switch, pv types.PrivValidator) {
    +	// routine to:
    +	// - precommit for a random block
    +	// - send precommit to all peers
    +	// - disable privValidator (so we don't do normal precommits)
    +	go func() {
    +		cs.mtx.Lock()
    +		cs.privValidator = pv
    +		pubKey, err := cs.privValidator.GetPubKey()
    +		if err != nil {
    +			panic(err)
    +		}
    +		addr := pubKey.Address()
    +		valIndex, _ := cs.Validators.GetByAddress(addr)
    +
    +		// precommit a random block
    +		blockHash := bytes.HexBytes(tmrand.Bytes(32))
    +		precommit := &types.Vote{
    +			ValidatorAddress: addr,
    +			ValidatorIndex:   valIndex,
    +			Height:           cs.Height,
    +			Round:            cs.Round,
    +			Timestamp:        cs.voteTime(),
    +			Type:             tmproto.PrecommitType,
    +			BlockID: types.BlockID{
    +				Hash:          blockHash,
    +				PartSetHeader: types.PartSetHeader{Total: 1, Hash: tmrand.Bytes(32)}},
    +		}
    +		p := precommit.ToProto()
    +		cs.privValidator.SignVote(cs.state.ChainID, p)
    +		precommit.Signature = p.Signature
    +		cs.privValidator = nil // disable priv val so we don't do normal votes
    +		cs.mtx.Unlock()
    +
    +		peers := sw.Peers().List()
    +		for _, peer := range peers {
    +			cs.Logger.Info("Sending bad vote", "block", blockHash, "peer", peer)
    +			peer.Send(VoteChannel, MustEncode(&VoteMessage{precommit}))
    +		}
    +	}()
    +}
    
  • types/vote_set.go+11 4 modified
    @@ -549,9 +549,11 @@ func (voteSet *VoteSet) sumTotalFrac() (int64, int64, float64) {
     //--------------------------------------------------------------------------------
     // Commit
     
    -// MakeCommit constructs a Commit from the VoteSet.
    -// Panics if the vote type is not PrecommitType or if
    -// there's no +2/3 votes for a single block.
    +// MakeCommit constructs a Commit from the VoteSet. It only includes precommits
    +// for the block, which has 2/3+ majority, and nil.
    +//
    +// Panics if the vote type is not PrecommitType or if there's no +2/3 votes for
    +// a single block.
     func (voteSet *VoteSet) MakeCommit() *Commit {
     	if voteSet.signedMsgType != tmproto.PrecommitType {
     		panic("Cannot MakeCommit() unless VoteSet.Type is PrecommitType")
    @@ -567,7 +569,12 @@ func (voteSet *VoteSet) MakeCommit() *Commit {
     	// For every validator, get the precommit
     	commitSigs := make([]CommitSig, len(voteSet.votes))
     	for i, v := range voteSet.votes {
    -		commitSigs[i] = v.CommitSig()
    +		commitSig := v.CommitSig()
    +		// if block ID exists but doesn't match, exclude sig
    +		if commitSig.ForBlock() && !v.BlockID.Equals(*voteSet.maj23) {
    +			commitSig = NewCommitSigAbsent()
    +		}
    +		commitSigs[i] = commitSig
     	}
     
     	return NewCommit(voteSet.GetHeight(), voteSet.GetRound(), *voteSet.maj23, commitSigs)
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.