mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-18 02:40:45 +00:00
eth, les: add sanity checks for unbounded block fields (#19573)
This PR adds some hardening in the lower levels of the protocol stack, to bail early on invalid data. Primarily, attacks that this PR protects against are on the "annoyance"-level, which would otherwise write a couple of megabytes of data into the log output, which is a bit resource intensive.
This commit is contained in:
parent
3f87990a71
commit
8f4a269437
6 changed files with 56 additions and 5 deletions
|
|
@ -154,6 +154,25 @@ func (h *Header) Size() common.StorageSize {
|
||||||
return common.StorageSize(unsafe.Sizeof(*h)) + common.StorageSize(len(h.Extra)+(h.Difficulty.BitLen()+h.Number.BitLen()+h.Time.BitLen())/8)
|
return common.StorageSize(unsafe.Sizeof(*h)) + common.StorageSize(len(h.Extra)+(h.Difficulty.BitLen()+h.Number.BitLen()+h.Time.BitLen())/8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SanityCheck checks a few basic things -- these checks are way beyond what
|
||||||
|
// any 'sane' production values should hold, and can mainly be used to prevent
|
||||||
|
// that the unbounded fields are stuffed with junk data to add processing
|
||||||
|
// overhead
|
||||||
|
func (h *Header) SanityCheck() error {
|
||||||
|
if h.Number != nil && !h.Number.IsUint64() {
|
||||||
|
return fmt.Errorf("too large block number: bitlen %d", h.Number.BitLen())
|
||||||
|
}
|
||||||
|
if h.Difficulty != nil {
|
||||||
|
if diffLen := h.Difficulty.BitLen(); diffLen > 80 {
|
||||||
|
return fmt.Errorf("too large block difficulty: bitlen %d", diffLen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if eLen := len(h.Extra); eLen > 100*1024 {
|
||||||
|
return fmt.Errorf("too large block extradata: size %d", eLen)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Body is a simple (mutable, non-safe) data container for storing and moving
|
// Body is a simple (mutable, non-safe) data container for storing and moving
|
||||||
// a block's data contents (transactions and uncles) together.
|
// a block's data contents (transactions and uncles) together.
|
||||||
type Body struct {
|
type Body struct {
|
||||||
|
|
@ -369,6 +388,12 @@ func (b *Block) Size() common.StorageSize {
|
||||||
return common.StorageSize(c)
|
return common.StorageSize(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SanityCheck can be used to prevent that unbounded fields are
|
||||||
|
// stuffed with junk data to add processing overhead
|
||||||
|
func (b *Block) SanityCheck() error {
|
||||||
|
return b.header.SanityCheck()
|
||||||
|
}
|
||||||
|
|
||||||
type writeCounter common.StorageSize
|
type writeCounter common.StorageSize
|
||||||
|
|
||||||
func (c *writeCounter) Write(b []byte) (int, error) {
|
func (c *writeCounter) Write(b []byte) (int, error) {
|
||||||
|
|
|
||||||
|
|
@ -804,7 +804,7 @@ func (f *BlockFetcher) forgetHash(hash common.Hash) {
|
||||||
// Remove all pending announces and decrement DOS counters
|
// Remove all pending announces and decrement DOS counters
|
||||||
for _, announce := range f.announced[hash] {
|
for _, announce := range f.announced[hash] {
|
||||||
f.announces[announce.origin]--
|
f.announces[announce.origin]--
|
||||||
if f.announces[announce.origin] == 0 {
|
if f.announces[announce.origin] <= 0 {
|
||||||
delete(f.announces, announce.origin)
|
delete(f.announces, announce.origin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -815,7 +815,7 @@ func (f *BlockFetcher) forgetHash(hash common.Hash) {
|
||||||
// Remove any pending fetches and decrement the DOS counters
|
// Remove any pending fetches and decrement the DOS counters
|
||||||
if announce := f.fetching[hash]; announce != nil {
|
if announce := f.fetching[hash]; announce != nil {
|
||||||
f.announces[announce.origin]--
|
f.announces[announce.origin]--
|
||||||
if f.announces[announce.origin] == 0 {
|
if f.announces[announce.origin] <= 0 {
|
||||||
delete(f.announces, announce.origin)
|
delete(f.announces, announce.origin)
|
||||||
}
|
}
|
||||||
delete(f.fetching, hash)
|
delete(f.fetching, hash)
|
||||||
|
|
@ -824,7 +824,7 @@ func (f *BlockFetcher) forgetHash(hash common.Hash) {
|
||||||
// Remove any pending completion requests and decrement the DOS counters
|
// Remove any pending completion requests and decrement the DOS counters
|
||||||
for _, announce := range f.fetched[hash] {
|
for _, announce := range f.fetched[hash] {
|
||||||
f.announces[announce.origin]--
|
f.announces[announce.origin]--
|
||||||
if f.announces[announce.origin] == 0 {
|
if f.announces[announce.origin] <= 0 {
|
||||||
delete(f.announces, announce.origin)
|
delete(f.announces, announce.origin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -833,7 +833,7 @@ func (f *BlockFetcher) forgetHash(hash common.Hash) {
|
||||||
// Remove any pending completions and decrement the DOS counters
|
// Remove any pending completions and decrement the DOS counters
|
||||||
if announce := f.completing[hash]; announce != nil {
|
if announce := f.completing[hash]; announce != nil {
|
||||||
f.announces[announce.origin]--
|
f.announces[announce.origin]--
|
||||||
if f.announces[announce.origin] == 0 {
|
if f.announces[announce.origin] <= 0 {
|
||||||
delete(f.announces, announce.origin)
|
delete(f.announces, announce.origin)
|
||||||
}
|
}
|
||||||
delete(f.completing, hash)
|
delete(f.completing, hash)
|
||||||
|
|
|
||||||
|
|
@ -810,6 +810,9 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
if err := msg.Decode(&request); err != nil {
|
if err := msg.Decode(&request); err != nil {
|
||||||
return errResp(ErrDecode, "%v: %v", msg, err)
|
return errResp(ErrDecode, "%v: %v", msg, err)
|
||||||
}
|
}
|
||||||
|
if err := request.sanityCheck(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
request.Block.ReceivedAt = msg.ReceivedAt
|
request.Block.ReceivedAt = msg.ReceivedAt
|
||||||
request.Block.ReceivedFrom = p
|
request.Block.ReceivedFrom = p
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,19 @@ type newBlockData struct {
|
||||||
TD *big.Int
|
TD *big.Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sanityCheck verifies that the values are reasonable, as a DoS protection
|
||||||
|
func (request *newBlockData) sanityCheck() error {
|
||||||
|
if err := request.Block.SanityCheck(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
//TD at mainnet block #7753254 is 76 bits. If it becomes 100 million times
|
||||||
|
// larger, it will still fit within 100 bits
|
||||||
|
if tdlen := request.TD.BitLen(); tdlen > 100 {
|
||||||
|
return fmt.Errorf("too large block TD: bitlen %d", tdlen)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// blockBody represents the data content of a single block.
|
// blockBody represents the data content of a single block.
|
||||||
type blockBody struct {
|
type blockBody struct {
|
||||||
Transactions []*types.Transaction // Transactions contained within a block
|
Transactions []*types.Transaction // Transactions contained within a block
|
||||||
|
|
|
||||||
|
|
@ -385,7 +385,9 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
|
||||||
if err := msg.Decode(&req); err != nil {
|
if err := msg.Decode(&req); err != nil {
|
||||||
return errResp(ErrDecode, "%v: %v", msg, err)
|
return errResp(ErrDecode, "%v: %v", msg, err)
|
||||||
}
|
}
|
||||||
|
if err := req.sanityCheck(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if p.requestAnnounceType == announceTypeSigned {
|
if p.requestAnnounceType == announceTypeSigned {
|
||||||
if err := req.checkSignature(p.ID()); err != nil {
|
if err := req.checkSignature(p.ID()); err != nil {
|
||||||
p.Log().Trace("Invalid announcement signature", "err", err)
|
p.Log().Trace("Invalid announcement signature", "err", err)
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,14 @@ type announceData struct {
|
||||||
Update keyValueList
|
Update keyValueList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sanityCheck verifies that the values are reasonable, as a DoS protection
|
||||||
|
func (a *announceData) sanityCheck() error {
|
||||||
|
if tdlen := a.Td.BitLen(); tdlen > 100 {
|
||||||
|
return fmt.Errorf("too large block TD: bitlen %d", tdlen)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// sign adds a signature to the block announcement by the given privKey
|
// sign adds a signature to the block announcement by the given privKey
|
||||||
func (a *announceData) sign(privKey *ecdsa.PrivateKey) {
|
func (a *announceData) sign(privKey *ecdsa.PrivateKey) {
|
||||||
rlp, _ := rlp.EncodeToBytes(announceBlock{a.Hash, a.Number, a.Td})
|
rlp, _ := rlp.EncodeToBytes(announceBlock{a.Hash, a.Number, a.Td})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue