Summary
The public morphology API (morph_erode, morph_dilate, morph_opening, morph_closing, morph_gradient, morph_white_tophat, morph_black_tophat) takes a user-supplied kernel argument. The only checks are 2D shape, uint8 dtype, and odd dimensions. Kernel size drives the padded input allocation on every backend, with no cap.
Problem
In xrspatial/morphology.py:
_morph_numpy (around line 152): np.pad(arr, ((hy, hy), (hx, hx)), ...) builds a padded float64 array of shape (rows + ky - 1, cols + kx - 1).
_morph_cupy (around line 245): same pattern on the GPU via cp.pad.
- Dask paths call
map_overlap(..., depth=(hy, hx)), which grows per-chunk memory by the same factor.
A kernel like np.ones((99999, 99999), dtype=np.uint8) on a 1000x1000 raster will ask the process for roughly 80 GB of padded float64 memory with no warning. bilateral.py (#1236), convolution.py (#1241), and bump.py (#1231) have already fixed this class of issue with an _available_memory_bytes() guard.
Proposed fix
- Add a local
_available_memory_bytes() helper copied from convolution.py. It reads /proc/meminfo, falls back to psutil, then to a 2 GB default.
- Add
_check_kernel_memory(rows, cols, ky, kx) that raises MemoryError if the padded float64 array would exceed 50% of available RAM. The message should name the kernel shape, the raster shape, and suggest a smaller kernel.
- Call the check inside
_dispatch() after _validate_kernel so every public entry point is covered across all four backends.
Impact
Severity: HIGH (Cat 1, Unbounded Allocation / DoS). Found during the morphology security sweep on 2026-04-24. Nobody passes a kernel larger than their raster in practice, so the guard should not affect real workflows.
Tests
Add a test that patches _available_memory_bytes to a tiny value and checks that morph_erode raises MemoryError with an informative message when a large kernel is passed.
Summary
The public morphology API (
morph_erode,morph_dilate,morph_opening,morph_closing,morph_gradient,morph_white_tophat,morph_black_tophat) takes a user-suppliedkernelargument. The only checks are 2D shape, uint8 dtype, and odd dimensions. Kernel size drives the padded input allocation on every backend, with no cap.Problem
In
xrspatial/morphology.py:_morph_numpy(around line 152):np.pad(arr, ((hy, hy), (hx, hx)), ...)builds a padded float64 array of shape(rows + ky - 1, cols + kx - 1)._morph_cupy(around line 245): same pattern on the GPU viacp.pad.map_overlap(..., depth=(hy, hx)), which grows per-chunk memory by the same factor.A kernel like
np.ones((99999, 99999), dtype=np.uint8)on a 1000x1000 raster will ask the process for roughly 80 GB of padded float64 memory with no warning.bilateral.py(#1236),convolution.py(#1241), andbump.py(#1231) have already fixed this class of issue with an_available_memory_bytes()guard.Proposed fix
_available_memory_bytes()helper copied fromconvolution.py. It reads/proc/meminfo, falls back topsutil, then to a 2 GB default._check_kernel_memory(rows, cols, ky, kx)that raisesMemoryErrorif the padded float64 array would exceed 50% of available RAM. The message should name the kernel shape, the raster shape, and suggest a smaller kernel._dispatch()after_validate_kernelso every public entry point is covered across all four backends.Impact
Severity: HIGH (Cat 1, Unbounded Allocation / DoS). Found during the morphology security sweep on 2026-04-24. Nobody passes a kernel larger than their raster in practice, so the guard should not affect real workflows.
Tests
Add a test that patches
_available_memory_bytesto a tiny value and checks thatmorph_eroderaisesMemoryErrorwith an informative message when a large kernel is passed.