fix(format): refuse a local heap whose free list leaves the heap

libhdf5 walks a local heap's free list when it loads the heap's data and
refuses the heap ("bad heap free list") when a free block starts or ends
outside the data segment, or links to offset 0. We never looked at the
free list, so a damaged old-style group listed names read from the broken
heap: once the user block of cve-2021-36977.h5 was applied, its root
listed eight garbage names where libhdf5 fails.

LocalHeap::validate_free_list (new) mirrors H5HL__fl_deserialize, with a
cycle bound, and accepts H5HL_FREE_NULL (1) or an all-ones head as the
end of the list. Like libhdf5 it runs when the first name is needed, not
on parse, so an empty group with a damaged heap still lists as empty
(cve-2018-13871.h5, cve-2024-29166.h5, gh-4431-poc-03.h5 keep matching
h5py).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
osobh
2026-09-25 22:07:50 -05:00
co-authored by Claude Opus 5.5
parent a6e90f3ee3
commit 90e050944f
6 changed files with 258 additions and 2 deletions
+7
View File
@@ -273,6 +273,13 @@
- CI keeps zlib-ng building and tested; the arm64 job no longer needs cmake.
### Correctness
- `clawhdf5-format` reader: an old-style group whose local heap has a free
list pointing outside the heap was listed with names read from the broken
heap (garbage names on `cve-2021-36977.h5` once its user block was
applied). libhdf5 refuses such a heap ("bad heap free list"); so do we now,
with `FormatError::InvalidLocalHeapFreeList`. As in libhdf5 the free list
is checked when the first name is read (`LocalHeap::validate_free_list`,
new), so an empty group with a damaged heap still lists as empty.
- **Files with a user block** (`h5py.File(..., userblock_size=N)`, `h5jam`;
the superblock at 512, 1024, …) could not be read: every address in the
file is relative to the superblock, but it was applied from byte 0