Repository navigation
Description
PR #1803 added max_pixels=max_pixels to the source read_to_array call inside read_vrt's source loop so that a tiny VRT output cannot force a huge source decode (#1796). The intent is correct, but _read_tiles and _read_tiles_cog_http use the same max_pixels for two separate things:
- Output window budget (
_check_dimensions(out_w, out_h, samples, max_pixels)) — enforced for the data the caller will receive. This is what catches the VRT source reads do not honor max_pixels safety limit #1796 attack. - Per-tile dimension sanity (
_check_dimensions(tw, th, samples, max_pixels)at_reader.py:1565and:2022) — meant to reject crafted TIFFs whoseTileWidth/TileLengthheaders declare absurd per-tile sizes (e.g.tw = 2^31).
After #1803 a user who sets max_pixels=10000 to bound their output now also fails the per-tile check on every normal source: a default 256×256 tile (65,536 pixels) trips the cap even when the requested window is much smaller.
Repro:
import numpy as np, tempfile, os
from xrspatial.geotiff import to_geotiff, read_vrt
with tempfile.TemporaryDirectory() as td:
to_geotiff(np.zeros((10, 10), dtype=np.uint8),
os.path.join(td, 'src.tif'), compression='none')
open(os.path.join(td, 'm.vrt'), 'w').write(
'<VRTDataset rasterXSize="10" rasterYSize="10">'
'<VRTRasterBand dataType="Byte" band="1"><SimpleSource>'
'<SourceFilename relativeToVRT="1">src.tif</SourceFilename>'
'<SourceBand>1</SourceBand>'
'<SrcRect xOff="0" yOff="0" xSize="10" ySize="10"/>'
'<DstRect xOff="0" yOff="0" xSize="100" ySize="100"/>'
'</SimpleSource></VRTRasterBand></VRTDataset>')
read_vrt(os.path.join(td, 'm.vrt'), max_pixels=10000)
# PixelSafetyLimitError: TIFF image dimensions (256 x 256 x 1 = 65,536 pixels)
# exceed the safety limit of 10,000 pixels.The output is 100×100=10,000 pixels, exactly at the cap; the source-tile check rejects 256×256=65,536 first.
Fix: the per-tile dimension sanity check should use a separate, large absolute cap (e.g. MAX_PIXELS_DEFAULT = 1e9) rather than the caller's output budget. The output-window check at the same call sites continues to enforce the user-supplied max_pixels, preserving the #1796 protection.
Existing test xrspatial/geotiff/tests/test_vrt_dstrect_resample_cap_1737.py::test_dstrect_at_cap_succeeds is the regression: it was authored against the pre-#1803 contract and now fails. Six open PRs share this failure on Python 3.14 (the matrix's first-fail) and have the rest of the matrix cancelled fail-fast, so they all appear BLOCKED downstream.
Activity
geotiff: per-tile dim check uses default cap, not caller budget (#1823)
geotiff: per-tile dim check uses default cap, not caller budget (#1823)
geotiff: per-tile dim check uses default cap, not caller budget (#1823)
geotiff: per-tile dim check uses default cap, not caller budget (#1823)
geotiff: per-tile dim check uses default cap, not caller budget (#1823)
geotiff: per-tile dim check uses default cap, not caller budget (#1823)
geotiff: per-tile dim check uses default cap, not caller budget (#1823)
Make read_vrt chunks mode lazy (#1807)
geotiff: cap VRT XML read size (#1818)
geotiff: reject ambiguous 3D writer inputs (#1812) (#1820)
geotiff: apply nodata mask against post-MinIsWhite sentinel (#1809) (#…
geotiff: update #1796 and #1823 VRT tests for new minimal-read semant…
brendancol commentedon May 14, 2026
Fix merged via #1817/#1818/#1820/#1807 cherry-picks + test refresh in #1827.
geotiff: forward missing_sources from open_geotiff to read_vrt (#1810) (
geotiff: close HTTP source when COG tile fetch raises (#1819)
geotiff: read_vrt(chunks=) builds a lazy dask graph (#1822)
geotiff: honour window and max_pixels for stripped HTTP reads (#1842) (…