Repository navigation
Description
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_tilesbuildsfetch_rangesfrom rawbyte_counts[tile_idx]with no validation.xrspatial/geotiff/_reader.py:288-358:coalesce_rangessums adjacent ranges into one merged GET, also unbounded.xrspatial/geotiff/_reader.py:407-461:_HTTPSource.read_ranges_coalescedissues 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
- Add
MAX_TILE_BYTES_DEFAULT = 256 << 20(256 MiB) per-tile compressed-size cap in_reader.py, overridable viaXRSPATIAL_COG_MAX_TILE_BYTES. - Reject any tile in
_fetch_decode_cog_http_tileswhosebyte_counts[tile_idx]exceeds the cap, with an error that names the offending byte count. - 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
Cap per-tile compressed byte_count in HTTP COG reader (fixes #1536) (#…