docs: record the 2026-09-25 HDF5 audit fixes and open gaps
CI / test-arm64 (pull_request) Successful in 1m21s
CI / test (pull_request) Successful in 5m59s

CHANGELOG: upgrade notes (changed read results for max-shape files,
saturating conversions, new writer errors, format-crate API changes) and
the reader/writer correctness fixes. known-issues: the silent-wrong-data
table with before/after sweep numbers, the gaps still open, and a
correction to the Extensible Array entry, which said files we wrote were
unaffected. CLAUDE.md: clawhdf5-gpu is vector distance computation, not
I/O, and clawhdf5-filters holds only deflate backends (no Blosc).

Also a facade test that libhdf5's 20-bit N-Bit float test data reads as
libhdf5's values.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
osobh
2026-09-25 21:26:56 -05:00
co-authored by Claude Opus 5.5
parent 650f355219
commit 72b9cfb1e1
4 changed files with 206 additions and 5 deletions
+97 -1
View File
@@ -7,6 +7,99 @@ deleting it.
---
## Silent wrong data found by the 2026-09-25 HDF5 audit
**Status:** fixed after v2.7.0 (2026-09-25). **Every release up
to and including v2.7.0 is affected.**
An audit on tank checked clawhdf5 against libhdf5 in three ways:
- a sweep of 686 public files: the libhdf5 test files, the HDF Group's
`cve_hdf5` reproducers, and the pyfive, netcdf-c, netcdf4-python, h5wasm,
h5py and xarray corpora;
- 567 read cases generated with h5py 3.16 / HDF5 2.0;
- 96 write cases checked with h5py builds linking HDF5 1.10, 1.12, 1.14 and
2.0, plus h5dump 1.14.6.
It found these cases where a value came back wrong **without an error**:
| Area | What happened | Who is affected |
|---|---|---|
| Chunk index (read) | Fixed/Extensible Array indexes laid out by the current shape, not the max shape: chunks returned from the wrong place | any file with a max shape larger than its shape and `libver='latest'` (h5py `maxshape=(10, None)`, `(20, 10)`) |
| Chunk index (write) | Extensible Array chunks from index 244 on never indexed (read as 0); unlimited dimension not first: data scrambled | files we wrote with one unlimited dimension and > 244 chunks, or e.g. `maxshape=(20, None)` |
| 4-byte offsets | unfiltered chunked datasets read as zeros | files created with `sizeof_addr = 4` |
| Filter mask | any skipped filter skipped the whole pipeline | files with partially filtered chunks (optional filters, direct chunk writes) |
| Numeric reads | float read as integer returned the bit pattern; narrowing integer reads kept the low bits; bfloat16 decoded as IEEE half | `read_i32`/`read_i64`/`read_u64` callers on float or wider data; HDF5 2.0 bf16 data |
| SZIP | garbage or zeros | every libhdf5-written SZIP dataset |
| Scale-offset | float values 1 ULP off | libhdf5 D-scale float data |
| Shared fill value | read as zero fill | fill values stored as shared messages |
| VL sequences | `read_vl_bytes` truncated non-byte base types | VL int/float sequences |
| Chunk cache | two threads reading two chunked datasets through one `File` could get each other's chunks | multi-threaded readers, including Python with the GIL released |
The audit also found files we wrote that libhdf5 **refuses**, now fixed:
- Fixed Array datasets with more than 1 024 chunks.
- Header messages over 64 KiB (large attributes).
- Reference, Opaque, BitField and Time datatypes.
- Files written with `with_page_size`.
- Several unlimited dimensions.
- A finite max shape larger than the shape.
- An empty-string attribute, which broke every attribute on its object.
- `FillTime` codes, which were rotated.
Our LZ4 and Zstd output could not be read by libhdf5's registered plugins, and
our pcodec filter used Granular BitRound's ID. The details are in
`CHANGELOG.md` under Correctness and Interop.
Before the fix, 419 of the 686 files read correctly and 43 differed from h5py.
After it, 448 read correctly and 23 differ. Of those 23:
- 17 are N-Bit float files. The probe compares raw file-type bytes; the typed
reader returns libhdf5's values (`nbit_custom_float_decodes_like_libhdf5`).
- 2 are an h5py bug: VL data with a big-endian base type comes back
byte-swapped in h5py, and h5dump agrees with us.
- The rest are object or attribute listing differences.
There were no panics, hangs or crashes before or after, including on all 147
CVE and fuzzer files. On some of those files, h5dump 1.14.6 and h5py/HDF5 2.0
segfault or abort.
## Gaps found by the 2026-09-25 HDF5 audit (open)
**Status:** open. These fail with an error; none returns wrong data, except
the VDS item, which is marked.
- **Layout message versions 1 and 2** (HDF5 1.6-era files): 84 of the 686
sweep files, `InvalidLayoutVersion`. This is the largest single gap.
- **Virtual datasets:**
- **Wrong data:** unmapped regions read as 0 instead of the fill value.
- `%b` printf-style source names are not expanded.
- Hyperslab selection versions 1 and 2 are refused.
- **Files with a user block:** the base address is not applied.
- **Old-style shared messages (version 1)** read the wrong address.
- **Groups and links:**
- Groups with a user-defined link type (e.g. 187) cannot be listed.
- Dense groups with more than about 22 000 links cannot be listed.
- Soft links are left out of `datasets()`.
- **Dense attributes:** a large attribute stored as a fractal-heap "huge"
object makes every attribute on the object fail. This affects real NetCDF
files (`issue671.nc`).
- **Other readers:**
- VL-string datasets are not readable through `File`.
- Metadata cache images are not supported.
- x87 long double and binary128 are refused.
- N-Bit on 64-bit scale-offset data and some N-Bit parameter layouts fail.
- **Filters:** blosc, blosc2, bitshuffle, bzip2, LZF and zfp are not
implemented.
- **Header checks:** on 12 CVE datasets libhdf5 rejects a corrupt header and
we read data anyway. We need stricter header checks.
- **Writer:**
- Nested groups beyond one level: path-like names are now refused, not
created.
- Dense attribute storage for attributes over 64 KiB.
- Output that HDF5 1.8 can read.
- A B-tree v2 chunk index larger than one leaf, so datasets with several
unlimited dimensions are limited to 65 535 chunks.
---
## Compound datatype message version 5 is not parsed (HDF5 2.0)
**Status:** fixed on `main` in `a13ff51` (2026-06-03); **not in the v2.1.0
@@ -226,7 +319,10 @@ block-offset field in the super block, and a page-init bitmap read from the
wrong structure. All four are fixed and covered by interop tests against
HDF5 2.0 at sizes that cross each boundary, including paged data blocks.
Files written by this crate are unaffected — this was purely a read-path bug.
Files written by this crate were not affected by *this* read bug, but the
writer had its own: it indexed only the first 244 chunks, so later chunks
read back as 0 in libhdf5 and in clawhdf5. See "Silent wrong data found by
the 2026-09-25 HDF5 audit" below.
## Every `f32` dataset we wrote was unreadable by h5py / libhdf5