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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions