Summary
The RPC header.GetRangeByHeight(from, to) endpoint has no upper bound on the requested range size. A client can request an arbitrarily large range (e.g. from=1, to=10_000_000), which causes the node to:
- Allocate unbounded memory for the response slice
- Perform a potentially huge number of store reads
- Serialize a massive JSON-RPC response
- Tie up the RPC server for an extended period
This is a denial-of-service vector for any node with RPC exposed.
Current State
- The P2P layer already has
MaxHeadersPerRangeRequest = 64 (configured in Header.Client), which bounds per-peer P2P range requests
- The RPC service layer (
nodebuilder/header/service.go:69-84) passes the range directly to store.GetRangeByHeight with no validation
- There is no
from > to or to - from > max check at the RPC level
Proposed Fix
Add a max range size constant (e.g. MaxRangeSize = 256 or reuse the existing MaxHeadersPerRangeRequest = 64) and validate in Service.GetRangeByHeight before hitting the store:
var ErrRangeTooLarge = fmt.Errorf("requested header range exceeds maximum of %d", MaxRangeSize)
func (s *Service) GetRangeByHeight(ctx context.Context, from *header.ExtendedHeader, to uint64) ([]*header.ExtendedHeader, error) {
if to <= from.Height() {
return nil, fmt.Errorf("invalid range: 'to' height %d must be greater than 'from' height %d", to, from.Height())
}
if to - from.Height() > MaxRangeSize {
return nil, ErrRangeTooLarge
}
// ... existing logic
}
Design Considerations
- The limit should be a well-known exported error so clients can detect it and paginate
- Should the limit be configurable via node config, or a hardcoded constant? Configurable adds complexity but lets operators tune for their use case
- The P2P
MaxHeadersPerRangeRequest (64) is a reasonable starting point, but the RPC limit could be larger since it doesn't involve network hops — something like 256 or 512
- Should also validate
to > from.Height() to catch reversed ranges early with a clear error instead of letting the store handle it
Summary
The RPC
header.GetRangeByHeight(from, to)endpoint has no upper bound on the requested range size. A client can request an arbitrarily large range (e.g.from=1, to=10_000_000), which causes the node to:This is a denial-of-service vector for any node with RPC exposed.
Current State
MaxHeadersPerRangeRequest = 64(configured inHeader.Client), which bounds per-peer P2P range requestsnodebuilder/header/service.go:69-84) passes the range directly tostore.GetRangeByHeightwith no validationfrom > toorto - from > maxcheck at the RPC levelProposed Fix
Add a max range size constant (e.g.
MaxRangeSize = 256or reuse the existingMaxHeadersPerRangeRequest = 64) and validate inService.GetRangeByHeightbefore hitting the store:Design Considerations
MaxHeadersPerRangeRequest(64) is a reasonable starting point, but the RPC limit could be larger since it doesn't involve network hops — something like 256 or 512to > from.Height()to catch reversed ranges early with a clear error instead of letting the store handle it