Skip to content

Cap per-tile compressed byte_count in HTTP COG reader (DoS) #1536

Closed

Description

@brendancol

Describe the bug

The HTTP COG reader has no cap on per-tile compressed byte sizes. A crafted TIFF (or a malicious HTTP server) can declare any TileByteCounts value it wants, and the reader passes those values straight into read_ranges_coalesced, which then issues a single Range GET sized by the sum of attacker-controlled byte counts.

Where

  • xrspatial/geotiff/_reader.py:1320-1354: _fetch_decode_cog_http_tiles builds fetch_ranges from raw byte_counts[tile_idx] with no validation.
  • xrspatial/geotiff/_reader.py:288-358: coalesce_ranges sums adjacent ranges into one merged GET, also unbounded.
  • xrspatial/geotiff/_reader.py:407-461: _HTTPSource.read_ranges_coalesced issues the merged Range request.

Exploit (verified against a local HTTP server)

I built a tiled COG with a real header, then patched the four TileByteCounts entries to claim 100 MB each. When the client called _read_cog_http(url), it sent one Range request asking for 100 MB. Same trick with one tile claiming 4 GB gets you a 4 GB Range GET.

The decode fails at the size-mismatch check, but the memory is already allocated. If the server trickles the response, that allocation stays open.

Why this slips past existing guards

_check_dimensions(tw, th, samples, max_pixels) at line 1277 caps the decompressed tile size, not the compressed network fetch. The local-mmap path at line 1095 is naturally bounded: mmap slicing past EOF silently truncates. So this only bites on HTTP.

Threat model

Two ways to trigger it:

  • Crafted TIFF hosted by the attacker (full control of header values plus server response).
  • Honest TIFF hosted by a malicious server that returns oversized Range responses.

xarray-spatial's HTTP reader is meant to be pointed at public COGs from the open web, so the input boundary is wide.

Fix proposal

  1. Add MAX_TILE_BYTES_DEFAULT = 256 << 20 (256 MiB) per-tile compressed-size cap in _reader.py, overridable via XRSPATIAL_COG_MAX_TILE_BYTES.
  2. Reject any tile in _fetch_decode_cog_http_tiles whose byte_counts[tile_idx] exceeds the cap, with an error that names the offending byte count.
  3. Add tests under xrspatial/geotiff/tests/test_security.py:
    • Crafted COG with one huge byte_count is rejected.
    • Normal COG with realistic tile sizes still reads correctly.

Related PRs

New attack surface from #1534 (HTTP COG range coalescing). The local-file path is unaffected.

Severity

MEDIUM. Network DoS, no RCE. Triggered by crafted file or malicious server.

Activity

  1. added a commit that references this issue on May 10, 2026
    2dbcdc9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinginput-validationInput validation and error messages

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions