Skip to content

fix(header): add max range size check to RPC GetRangeByHeight #4858

@walldiss

Description

@walldiss

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:

  1. Allocate unbounded memory for the response slice
  2. Perform a potentially huge number of store reads
  3. Serialize a massive JSON-RPC response
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions