--- tags: - security - vulnerability - poc - anndata - h5py - hdf5 - decompression-bomb - cwe-789 - silent-load license: mit --- # anndata — .h5ad HDF5 Sparse Data Decompression Bomb (PoC) **Repo:** `MBM7/anndata-h5ad-sparse-bomb-poc` **Status:** Responsible disclosure — submitted to Huntr **Severity:** High / CWE-789 **Packages:** `anndata` + `h5py` (PyPI) — HDF5 format (.h5ad) --- ## Summary A crafted **68 KB** `.h5ad` file causes `anndata.read_h5ad()` to allocate **2 GB of memory silently** — no exception is raised, the returned `AnnData` object appears completely valid. | File size | Claimed X/data shape | Peak allocation | Amplification | |-----------|----------------------|-----------------|---------------| | 68 KB | (500,000,000,) | **2,000 MB** | 1 : 29,391 | --- ## Root Cause `anndata/_io/specs/methods.py`, `read_sparse()`: ```python # No size validation before reading sparse_dataset(elem).to_memory() → h5py: numpy.empty(ds.shape, dtype=ds.dtype) # ← 2 GB allocated # ds.shape = (500_000_000,) from HDF5 metadata → reads actual chunks (only 3 values on disk) ``` HDF5 chunked datasets support **sparse storage** — unwritten chunks exist only as metadata. The `X/data` dataset can claim `shape=(500_000_000,)` float32 (= 2 GB) while storing only 3 values. `h5py` pre-allocates `numpy.empty(ds.shape)` before reading any chunks. --- ## Distinct from keras/h5py finding | | keras/h5py | **anndata** | |--|--|--| | Result | OOM kill (MemoryError) | **Silent successful load** | | File format | `.h5` | `.h5ad` | | Code path | `legacy_h5_format.py` | `sparse_dataset.py` | | Detectability | Process crashes | **No error — harder to detect** | --- ## Reproduce ```bash pip install anndata h5py scipy numpy python poc_anndata_h5ad_bomb.py ``` Expected output: ``` Malicious .h5ad : 68,048 bytes X/data claimed : 2,000,000,000 bytes (2.0 GB) Amplification : 1:29,391 Result : LOADED — no exception! Peak memory : 2000 MB ``` --- ## Impact Any service loading user-supplied `.h5ad` files (HuggingFace single-cell genomics datasets, bioinformatics ML pipelines, spatial transcriptomics tools) can be OOM-killed by a 68 KB payload. The silent load makes the attack harder to detect than typical decompression bombs. --- ## Suggested Fix In `SparseDataset.to_memory()` / `read_sparse()`, validate before reading: ```python MAX_ARRAY_BYTES = 512 * 1024 * 1024 for key in ['data', 'indices', 'indptr']: ds = elem[key] if ds.size * ds.dtype.itemsize > MAX_ARRAY_BYTES: raise ValueError(f"Dataset '{key}' too large — possible bomb") ``` --- ## Environment | Package | Version | |----------|---------| | anndata | 0.13.2 | | h5py | 3.14.0 | | Python | 3.12 | --- *Distinct from keras HDF5 finding — different library, different code path,* *different behavior (silent vs crash). anndata is used in HuggingFace Hub* *for single-cell ML datasets.*