fix: defend ddos voting attack with other mini fix according to audit (#1741)

This commit is contained in:
NathanBSC 2023-07-11 10:13:08 +08:00 committed by GitHub
parent 288804ced0
commit 3fd5b0c149
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 41 additions and 6 deletions

@ -67,7 +67,7 @@ const (
systemRewardPercent = 4 // it means 1/2^4 = 1/16 percentage of gas fee incoming will be distributed to system
collectAdditionalVotesRewardRatio = float64(1) // ratio of additional reward for collecting more votes than needed
collectAdditionalVotesRewardRatio = 100 // ratio of additional reward for collecting more votes than needed, the denominator is 100
)
var (
@ -1027,7 +1027,7 @@ func (p *Parlia) distributeFinalityReward(chain consensus.ChainHeaderReader, sta
}
quorum := cmath.CeilDiv(len(snap.Validators)*2, 3)
if validVoteCount > quorum {
accumulatedWeights[head.Coinbase] += uint64(float64(validVoteCount-quorum) * collectAdditionalVotesRewardRatio)
accumulatedWeights[head.Coinbase] += uint64((validVoteCount - quorum) * collectAdditionalVotesRewardRatio / 100)
}
}
@ -1788,7 +1788,7 @@ func encodeSigHeader(w io.Writer, header *types.Header, chainId *big.Int) {
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra[:len(header.Extra)-65], // this will panic if extra is too short, should check before calling encodeSigHeader
header.Extra[:len(header.Extra)-extraSeal], // this will panic if extra is too short, should check before calling encodeSigHeader
header.MixDigest,
header.Nonce,
})

@ -944,6 +944,8 @@ func (h *handler) voteBroadcastLoop() {
for {
select {
case event := <-h.voteCh:
// The timeliness of votes is very important,
// so one vote will be sent instantly without waiting for other votes for batch sending by design.
h.BroadcastVote(event.Vote)
case <-h.votesSub.Err():
return

@ -61,9 +61,15 @@ func (h *bscHandler) Handle(peer *bsc.Peer, packet bsc.Packet) error {
// handleVotesBroadcast is invoked from a peer's message handler when it transmits a
// votes broadcast for the local node to process.
func (h *bscHandler) handleVotesBroadcast(peer *bsc.Peer, votes []*types.VoteEnvelope) error {
// Try to put votes into votepool
for _, vote := range votes {
h.votepool.PutVote(vote)
if peer.IsOverLimitAfterReceiving() {
peer.Log().Warn("peer sending votes too much, votes dropped; it may be a ddos attack, please check!")
return nil
}
// Here we only put the first vote, to avoid ddos attack by sending a large batch of votes.
// This won't abandon any valid vote, because one vote is sent every time referring to func voteBroadcastLoop
if len(votes) > 0 {
h.votepool.PutVote(votes[0])
}
return nil
}

@ -1,6 +1,8 @@
package bsc
import (
"time"
mapset "github.com/deckarep/golang-set"
"github.com/ethereum/go-ethereum/common"
@ -16,6 +18,15 @@ const (
// voteBufferSize is the maximum number of batch votes can be hold before sending
voteBufferSize = 21 * 2
// used to avoid of DDOS attack
// It's the max number of received votes per second from one peer
// 21 validators exist now, so 21 votes will be produced every one block interval
// so the limit is 7 = 21/3, here set it to 10 with a buffer.
receiveRateLimitPerSecond = 10
// the time span of one period
secondsPerPeriod = float64(10)
)
// max is a helper function which returns the larger of the two given integers.
@ -31,6 +42,8 @@ type Peer struct {
id string // Unique ID for the peer, cached
knownVotes *knownCache // Set of vote hashes known to be known by this peer
voteBroadcast chan []*types.VoteEnvelope // Channel used to queue votes propagation requests
periodBegin time.Time // Begin time of the latest period for votes counting
periodCounter uint // Votes number in the latest period
*p2p.Peer // The embedded P2P package peer
rw p2p.MsgReadWriter // Input/output streams for bsc
@ -47,6 +60,8 @@ func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) *Peer {
id: id,
knownVotes: newKnownCache(maxKnownVotes),
voteBroadcast: make(chan []*types.VoteEnvelope, voteBufferSize),
periodBegin: time.Now(),
periodCounter: 0,
Peer: p,
rw: rw,
version: version,
@ -114,6 +129,18 @@ func (p *Peer) AsyncSendVotes(votes []*types.VoteEnvelope) {
}
}
// Step into the next period when secondsPerPeriod seconds passed,
// Otherwise, check whether the number of received votes extra (secondsPerPeriod * receiveRateLimitPerSecond)
func (p *Peer) IsOverLimitAfterReceiving() bool {
if timeInterval := time.Since(p.periodBegin).Seconds(); timeInterval >= secondsPerPeriod {
p.periodBegin = time.Now()
p.periodCounter = 0
return false
}
p.periodCounter += 1
return p.periodCounter > uint(secondsPerPeriod*receiveRateLimitPerSecond)
}
// broadcastVotes is a write loop that schedules votes broadcasts
// to the remote peer. The goal is to have an async writer that does not lock up
// node internals and at the same time rate limits queued data.