@@ -34,6 +34,7 @@ import (
34
34
"github.com/ethereum/go-ethereum/event"
35
35
"github.com/ethereum/go-ethereum/logger"
36
36
"github.com/ethereum/go-ethereum/logger/glog"
37
+ "github.com/ethereum/go-ethereum/params"
37
38
"github.com/rcrowley/go-metrics"
38
39
)
39
40
45
46
MaxReceiptFetch = 256 // Amount of transaction receipts to allow fetching per request
46
47
MaxStateFetch = 384 // Amount of node state values to allow fetching per request
47
48
49
+ MaxForkAncestry = 3 * params .EpochDuration .Uint64 () // Maximum chain reorganisation
50
+
48
51
hashTTL = 3 * time .Second // [eth/61] Time it takes for a hash request to time out
49
52
blockTargetRTT = 3 * time .Second / 2 // [eth/61] Target time for completing a block retrieval request
50
53
blockTTL = 3 * blockTargetRTT // [eth/61] Maximum time allowance before a block request is considered expired
79
82
errEmptyHeaderSet = errors .New ("empty header set by peer" )
80
83
errPeersUnavailable = errors .New ("no peers available or all tried for download" )
81
84
errAlreadyInPool = errors .New ("hash already in pool" )
85
+ errInvalidAncestor = errors .New ("retrieved ancestor is invalid" )
82
86
errInvalidChain = errors .New ("retrieved hash chain is invalid" )
83
87
errInvalidBlock = errors .New ("retrieved block is invalid" )
84
88
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
266
270
case errBusy :
267
271
glog .V (logger .Detail ).Infof ("Synchronisation already in progress" )
268
272
269
- case errTimeout , errBadPeer , errStallingPeer , errEmptyHashSet , errEmptyHeaderSet , errPeersUnavailable , errInvalidChain :
273
+ case errTimeout , errBadPeer , errStallingPeer , errEmptyHashSet , errEmptyHeaderSet , errPeersUnavailable , errInvalidAncestor , errInvalidChain :
270
274
glog .V (logger .Debug ).Infof ("Removing peer %v: %v" , id , err )
271
275
d .dropPeer (id )
272
276
@@ -353,7 +357,7 @@ func (d *Downloader) syncWithPeer(p *peer, hash common.Hash, td *big.Int) (err e
353
357
if err != nil {
354
358
return err
355
359
}
356
- origin , err := d .findAncestor61 (p )
360
+ origin , err := d .findAncestor61 (p , latest )
357
361
if err != nil {
358
362
return err
359
363
}
@@ -380,7 +384,7 @@ func (d *Downloader) syncWithPeer(p *peer, hash common.Hash, td *big.Int) (err e
380
384
if err != nil {
381
385
return err
382
386
}
383
- origin , err := d .findAncestor (p )
387
+ origin , err := d .findAncestor (p , latest )
384
388
if err != nil {
385
389
return err
386
390
}
@@ -536,11 +540,19 @@ func (d *Downloader) fetchHeight61(p *peer) (uint64, error) {
536
540
// on the correct chain, checking the top N blocks should already get us a match.
537
541
// In the rare scenario when we ended up on a long reorganisation (i.e. none of
538
542
// 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 ) {
540
544
glog .V (logger .Debug ).Infof ("%v: looking for common ancestor" , p )
541
545
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
+ }
544
556
from := int64 (head ) - int64 (MaxHashFetch ) + 1
545
557
if from < 0 {
546
558
from = 0
@@ -600,11 +612,18 @@ func (d *Downloader) findAncestor61(p *peer) (uint64, error) {
600
612
}
601
613
// If the head fetch already found an ancestor, return
602
614
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
+ }
603
619
glog .V (logger .Debug ).Infof ("%v: common ancestor: #%d [%x…]" , p , number , hash [:4 ])
604
620
return number , nil
605
621
}
606
622
// Ancestor not found, we need to binary search over our chain
607
623
start , end := uint64 (0 ), head
624
+ if floor > 0 {
625
+ start = uint64 (floor )
626
+ }
608
627
for start + 1 < end {
609
628
// Split our chain interval in two, and request the hash to cross check
610
629
check := (start + end ) / 2
@@ -660,6 +679,12 @@ func (d *Downloader) findAncestor61(p *peer) (uint64, error) {
660
679
}
661
680
}
662
681
}
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 ])
663
688
return start , nil
664
689
}
665
690
@@ -961,15 +986,23 @@ func (d *Downloader) fetchHeight(p *peer) (uint64, error) {
961
986
// on the correct chain, checking the top N links should already get us a match.
962
987
// In the rare scenario when we ended up on a long reorganisation (i.e. none of
963
988
// 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 ) {
965
990
glog .V (logger .Debug ).Infof ("%v: looking for common ancestor" , p )
966
991
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 ()
969
994
if d .mode == FullSync {
970
- head = d .headBlock ().NumberU64 ()
995
+ ceil = d .headBlock ().NumberU64 ()
971
996
} 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
973
1006
}
974
1007
from := int64 (head ) - int64 (MaxHeaderFetch ) + 1
975
1008
if from < 0 {
@@ -1040,11 +1073,18 @@ func (d *Downloader) findAncestor(p *peer) (uint64, error) {
1040
1073
}
1041
1074
// If the head fetch already found an ancestor, return
1042
1075
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
+ }
1043
1080
glog .V (logger .Debug ).Infof ("%v: common ancestor: #%d [%x…]" , p , number , hash [:4 ])
1044
1081
return number , nil
1045
1082
}
1046
1083
// Ancestor not found, we need to binary search over our chain
1047
1084
start , end := uint64 (0 ), head
1085
+ if floor > 0 {
1086
+ start = uint64 (floor )
1087
+ }
1048
1088
for start + 1 < end {
1049
1089
// Split our chain interval in two, and request the hash to cross check
1050
1090
check := (start + end ) / 2
@@ -1100,6 +1140,12 @@ func (d *Downloader) findAncestor(p *peer) (uint64, error) {
1100
1140
}
1101
1141
}
1102
1142
}
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 ])
1103
1149
return start , nil
1104
1150
}
1105
1151
0 commit comments