Skip to content

Commit efcfa22

Browse files
karalabeobscuren
authored andcommitted
[release/1.4.4] eth/downloader: bound fork ancestry and allow heavy short forks
1 parent aa18aad commit efcfa22

File tree

2 files changed

+201
-43
lines changed

2 files changed

+201
-43
lines changed

eth/downloader/downloader.go

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"github.com/ethereum/go-ethereum/event"
3535
"github.com/ethereum/go-ethereum/logger"
3636
"github.com/ethereum/go-ethereum/logger/glog"
37+
"github.com/ethereum/go-ethereum/params"
3738
"github.com/rcrowley/go-metrics"
3839
)
3940

@@ -45,6 +46,8 @@ var (
4546
MaxReceiptFetch = 256 // Amount of transaction receipts to allow fetching per request
4647
MaxStateFetch = 384 // Amount of node state values to allow fetching per request
4748

49+
MaxForkAncestry = 3 * params.EpochDuration.Uint64() // Maximum chain reorganisation
50+
4851
hashTTL = 3 * time.Second // [eth/61] Time it takes for a hash request to time out
4952
blockTargetRTT = 3 * time.Second / 2 // [eth/61] Target time for completing a block retrieval request
5053
blockTTL = 3 * blockTargetRTT // [eth/61] Maximum time allowance before a block request is considered expired
@@ -79,6 +82,7 @@ var (
7982
errEmptyHeaderSet = errors.New("empty header set by peer")
8083
errPeersUnavailable = errors.New("no peers available or all tried for download")
8184
errAlreadyInPool = errors.New("hash already in pool")
85+
errInvalidAncestor = errors.New("retrieved ancestor is invalid")
8286
errInvalidChain = errors.New("retrieved hash chain is invalid")
8387
errInvalidBlock = errors.New("retrieved block is invalid")
8488
errInvalidBody = errors.New("retrieved block body is invalid")
@@ -266,7 +270,7 @@ func (d *Downloader) Synchronise(id string, head common.Hash, td *big.Int, mode
266270
case errBusy:
267271
glog.V(logger.Detail).Infof("Synchronisation already in progress")
268272

269-
case errTimeout, errBadPeer, errStallingPeer, errEmptyHashSet, errEmptyHeaderSet, errPeersUnavailable, errInvalidChain:
273+
case errTimeout, errBadPeer, errStallingPeer, errEmptyHashSet, errEmptyHeaderSet, errPeersUnavailable, errInvalidAncestor, errInvalidChain:
270274
glog.V(logger.Debug).Infof("Removing peer %v: %v", id, err)
271275
d.dropPeer(id)
272276

@@ -353,7 +357,7 @@ func (d *Downloader) syncWithPeer(p *peer, hash common.Hash, td *big.Int) (err e
353357
if err != nil {
354358
return err
355359
}
356-
origin, err := d.findAncestor61(p)
360+
origin, err := d.findAncestor61(p, latest)
357361
if err != nil {
358362
return err
359363
}
@@ -380,7 +384,7 @@ func (d *Downloader) syncWithPeer(p *peer, hash common.Hash, td *big.Int) (err e
380384
if err != nil {
381385
return err
382386
}
383-
origin, err := d.findAncestor(p)
387+
origin, err := d.findAncestor(p, latest)
384388
if err != nil {
385389
return err
386390
}
@@ -536,11 +540,19 @@ func (d *Downloader) fetchHeight61(p *peer) (uint64, error) {
536540
// on the correct chain, checking the top N blocks should already get us a match.
537541
// In the rare scenario when we ended up on a long reorganisation (i.e. none of
538542
// the head blocks match), we do a binary search to find the common ancestor.
539-
func (d *Downloader) findAncestor61(p *peer) (uint64, error) {
543+
func (d *Downloader) findAncestor61(p *peer, height uint64) (uint64, error) {
540544
glog.V(logger.Debug).Infof("%v: looking for common ancestor", p)
541545

542-
// Request out head blocks to short circuit ancestor location
543-
head := d.headBlock().NumberU64()
546+
// Figure out the valid ancestor range to prevent rewrite attacks
547+
floor, ceil := int64(-1), d.headBlock().NumberU64()
548+
if ceil >= MaxForkAncestry {
549+
floor = int64(ceil - MaxForkAncestry)
550+
}
551+
// Request the topmost blocks to short circuit binary ancestor lookup
552+
head := ceil
553+
if head > height {
554+
head = height
555+
}
544556
from := int64(head) - int64(MaxHashFetch) + 1
545557
if from < 0 {
546558
from = 0
@@ -600,11 +612,18 @@ func (d *Downloader) findAncestor61(p *peer) (uint64, error) {
600612
}
601613
// If the head fetch already found an ancestor, return
602614
if !common.EmptyHash(hash) {
615+
if int64(number) <= floor {
616+
glog.V(logger.Warn).Infof("%v: potential rewrite attack: #%d [%x…] <= #%d limit", p, number, hash[:4], floor)
617+
return 0, errInvalidAncestor
618+
}
603619
glog.V(logger.Debug).Infof("%v: common ancestor: #%d [%x…]", p, number, hash[:4])
604620
return number, nil
605621
}
606622
// Ancestor not found, we need to binary search over our chain
607623
start, end := uint64(0), head
624+
if floor > 0 {
625+
start = uint64(floor)
626+
}
608627
for start+1 < end {
609628
// Split our chain interval in two, and request the hash to cross check
610629
check := (start + end) / 2
@@ -660,6 +679,12 @@ func (d *Downloader) findAncestor61(p *peer) (uint64, error) {
660679
}
661680
}
662681
}
682+
// Ensure valid ancestry and return
683+
if int64(start) <= floor {
684+
glog.V(logger.Warn).Infof("%v: potential rewrite attack: #%d [%x…] <= #%d limit", p, start, hash[:4], floor)
685+
return 0, errInvalidAncestor
686+
}
687+
glog.V(logger.Debug).Infof("%v: common ancestor: #%d [%x…]", p, start, hash[:4])
663688
return start, nil
664689
}
665690

@@ -961,15 +986,23 @@ func (d *Downloader) fetchHeight(p *peer) (uint64, error) {
961986
// on the correct chain, checking the top N links should already get us a match.
962987
// In the rare scenario when we ended up on a long reorganisation (i.e. none of
963988
// the head links match), we do a binary search to find the common ancestor.
964-
func (d *Downloader) findAncestor(p *peer) (uint64, error) {
989+
func (d *Downloader) findAncestor(p *peer, height uint64) (uint64, error) {
965990
glog.V(logger.Debug).Infof("%v: looking for common ancestor", p)
966991

967-
// Request our head headers to short circuit ancestor location
968-
head := d.headHeader().Number.Uint64()
992+
// Figure out the valid ancestor range to prevent rewrite attacks
993+
floor, ceil := int64(-1), d.headHeader().Number.Uint64()
969994
if d.mode == FullSync {
970-
head = d.headBlock().NumberU64()
995+
ceil = d.headBlock().NumberU64()
971996
} else if d.mode == FastSync {
972-
head = d.headFastBlock().NumberU64()
997+
ceil = d.headFastBlock().NumberU64()
998+
}
999+
if ceil >= MaxForkAncestry {
1000+
floor = int64(ceil - MaxForkAncestry)
1001+
}
1002+
// Request the topmost blocks to short circuit binary ancestor lookup
1003+
head := ceil
1004+
if head > height {
1005+
head = height
9731006
}
9741007
from := int64(head) - int64(MaxHeaderFetch) + 1
9751008
if from < 0 {
@@ -1040,11 +1073,18 @@ func (d *Downloader) findAncestor(p *peer) (uint64, error) {
10401073
}
10411074
// If the head fetch already found an ancestor, return
10421075
if !common.EmptyHash(hash) {
1076+
if int64(number) <= floor {
1077+
glog.V(logger.Warn).Infof("%v: potential rewrite attack: #%d [%x…] <= #%d limit", p, number, hash[:4], floor)
1078+
return 0, errInvalidAncestor
1079+
}
10431080
glog.V(logger.Debug).Infof("%v: common ancestor: #%d [%x…]", p, number, hash[:4])
10441081
return number, nil
10451082
}
10461083
// Ancestor not found, we need to binary search over our chain
10471084
start, end := uint64(0), head
1085+
if floor > 0 {
1086+
start = uint64(floor)
1087+
}
10481088
for start+1 < end {
10491089
// Split our chain interval in two, and request the hash to cross check
10501090
check := (start + end) / 2
@@ -1100,6 +1140,12 @@ func (d *Downloader) findAncestor(p *peer) (uint64, error) {
11001140
}
11011141
}
11021142
}
1143+
// Ensure valid ancestry and return
1144+
if int64(start) <= floor {
1145+
glog.V(logger.Warn).Infof("%v: potential rewrite attack: #%d [%x…] <= #%d limit", p, start, hash[:4], floor)
1146+
return 0, errInvalidAncestor
1147+
}
1148+
glog.V(logger.Debug).Infof("%v: common ancestor: #%d [%x…]", p, start, hash[:4])
11031149
return start, nil
11041150
}
11051151

0 commit comments

Comments
 (0)