Skip to content

Commit f2d33a1

Browse files
committed
fix: eliminate TOCTOU race in latestBlock hash/number updates
Combined latestBlockHash and latestBlockNumber into a single latestBlockInfo struct protected by ds.Locked. This ensures atomic updates and prevents race conditions where hash and number could become mismatched when multiple goroutines update concurrently. Affected handlers: - handleBlockRangeUpdate: uses atomic Update with conditional check - handleNewBlockHashes: uses atomic Update with conditional check - handleNewBlock: uses atomic Set (always authoritative)
1 parent d8ec9e3 commit f2d33a1

File tree

2 files changed

+35
-21
lines changed

2 files changed

+35
-21
lines changed

p2p/conns.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,8 @@ func (c *Conns) GetPeerLatestBlock(peerID string) (common.Hash, uint64) {
587587
defer c.mu.RUnlock()
588588

589589
if cn, ok := c.conns[peerID]; ok {
590-
return cn.latestBlockHash.Get(), cn.latestBlockNumber.Get()
590+
info := cn.latestBlock.Get()
591+
return info.Hash, info.Number
591592
}
592593

593594
return common.Hash{}, 0

p2p/protocol.go

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,16 @@ type conn struct {
100100
// version stores the negotiated eth protocol version (e.g., 68 or 69).
101101
version uint
102102

103-
// latestBlockHash and latestBlockNumber track the most recent block
104-
// information received from this peer via NewBlock, NewBlockHashes,
105-
// or BlockRangeUpdate messages. Uses ds.Locked for thread-safe API access.
106-
latestBlockHash *ds.Locked[common.Hash]
107-
latestBlockNumber *ds.Locked[uint64]
103+
// latestBlock tracks the most recent block information received from this
104+
// peer via NewBlock, NewBlockHashes, or BlockRangeUpdate messages.
105+
// Hash and number are combined in a single struct to ensure consistency.
106+
latestBlock *ds.Locked[latestBlock]
107+
}
108+
109+
// latestBlock holds the hash and number of the latest block from a peer.
110+
type latestBlock struct {
111+
Hash common.Hash
112+
Number uint64
108113
}
109114

110115
// EthProtocolOptions is the options used when creating a new eth protocol.
@@ -162,8 +167,7 @@ func NewEthProtocol(version uint, opts EthProtocolOptions) ethp2p.Protocol {
162167
blockAnnounce: make(chan eth.NewBlockHashesPacket, maxQueuedBlockAnns),
163168
closeCh: make(chan struct{}),
164169
version: version,
165-
latestBlockHash: &ds.Locked[common.Hash]{},
166-
latestBlockNumber: &ds.Locked[uint64]{},
170+
latestBlock: &ds.Locked[latestBlock]{},
167171
}
168172

169173
// Ensure cleanup happens on any exit path (including statusExchange failure)
@@ -440,11 +444,13 @@ func (c *conn) handleBlockRangeUpdate(msg ethp2p.Msg) error {
440444
Hex("hash", packet.LatestBlockHash[:]).
441445
Msg("Received BlockRangeUpdate")
442446

443-
// Update latest block info from the range update (thread-safe for API access)
444-
if packet.LatestBlock > c.latestBlockNumber.Get() {
445-
c.latestBlockHash.Set(packet.LatestBlockHash)
446-
c.latestBlockNumber.Set(packet.LatestBlock)
447-
}
447+
// Update latest block info atomically if this block is newer
448+
c.latestBlock.Update(func(curr latestBlock) (latestBlock, bool) {
449+
if packet.LatestBlock > curr.Number {
450+
return latestBlock{Hash: packet.LatestBlockHash, Number: packet.LatestBlock}, true
451+
}
452+
return curr, false
453+
})
448454

449455
return nil
450456
}
@@ -543,11 +549,13 @@ func (c *conn) handleNewBlockHashes(ctx context.Context, msg ethp2p.Msg) error {
543549
for _, entry := range packet {
544550
hash := entry.Hash
545551

546-
// Update latest block info if this block is newer (thread-safe for API access)
547-
if entry.Number > c.latestBlockNumber.Get() {
548-
c.latestBlockHash.Set(hash)
549-
c.latestBlockNumber.Set(entry.Number)
550-
}
552+
// Update latest block info atomically if this block is newer
553+
c.latestBlock.Update(func(current latestBlock) (latestBlock, bool) {
554+
if entry.Number > current.Number {
555+
return latestBlock{Hash: hash, Number: entry.Number}, true
556+
}
557+
return current, false
558+
})
551559

552560
// Mark as known from this peer
553561
c.addKnownBlock(hash)
@@ -1123,9 +1131,14 @@ func (c *conn) handleNewBlock(ctx context.Context, msg ethp2p.Msg) error {
11231131

11241132
c.countMsgReceived(packet.Name(), 1)
11251133

1126-
// Update latest block info for this peer (thread-safe for API access)
1127-
c.latestBlockHash.Set(hash)
1128-
c.latestBlockNumber.Set(packet.Block.Number().Uint64())
1134+
// Update latest block info atomically if this block is newer
1135+
blockNum := packet.Block.Number().Uint64()
1136+
c.latestBlock.Update(func(current latestBlock) (latestBlock, bool) {
1137+
if blockNum > current.Number {
1138+
return latestBlock{Hash: hash, Number: blockNum}, true
1139+
}
1140+
return current, false
1141+
})
11291142

11301143
// Mark block as known from this peer
11311144
c.addKnownBlock(hash)

0 commit comments

Comments
 (0)