Skip to content

VRT source reads with small max_pixels reject normal-tile sources #1823

@brendancol

Description

@brendancol

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:

  1. 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.
  2. Per-tile dimension sanity (_check_dimensions(tw, th, samples, max_pixels) at _reader.py:1565 and :2022) — meant to reject crafted TIFFs whose TileWidth / TileLength headers 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions