@@ -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
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
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