Fix OOB read / segfault when reading SHAPE-scalar HDF5 datasets#63
Fix OOB read / segfault when reading SHAPE-scalar HDF5 datasets#63SimonPinches wants to merge 1 commit into
Conversation
readIntNDFromBuffer and readDoubleNDFromBuffer unconditionally appended a trailing value-dimension index (indices.push_back(0)) whenever rank != dim, then computed a flat offset with indices_to_flat_index. For a *_SHAPE dataset whose rank equals the AOS depth (dim == 0), the index vector already matched the dataset rank, so the extra element pushed the loop one past the dataspace dims array and produced an out-of-range flat offset. The subsequent memcpy(*data, v + index, ...) then read unmapped memory and crashed (SIGSEGV in __intel_avx_rep_memcpy). Guard the push_back with `if (dim != 0)`, mirroring the existing, correct logic in fillFullBuffers() (the buffered write path): when dim == 0 the scalar is addressed directly by current_arrctx_indices with no extra index. Fixes the segfault reported when reading GGD geometry (issue iterorganization#51), e.g. grid_ggd[0]/space[0]/objects_per_dimension[3]/object[0]/geometry, where the geometry_SHAPE dataset has one fewer dimension than the geometry values. Verified against the reproduction D3D dataset: with the patched libal the previously-crashing empty-geometry read returns an empty array, while non-empty FLT_1D (point coordinates) and INT_1D (nodes) reads remain correct.
|
Compiled and check with above PR parallel to #52 It avoids segfault and provides following message but does not tell about underlying issue with the rank.. #52 (comment) import imas
entry = imas.DBEntry("imas:hdf5?path=/home/ITER/pankina/public/d3d/4/163518/75", "r")
ids = entry.get("mhd", occurrence=1, lazy=True, autoconvert=False)
ids.grid_ggd[0].space[0].objects_per_dimension[3].object[0].geometry[0] |
|
Thanks for testing it. The The geometry really is empty for that element. The original snippet does There is no "rank 4 vs rank 5" problem. For a 1D field stored in homogeneous-AOS form, the So What #63 does. It reads the SHAPE correctly in both cases by branching on
So both branches (and both int/double readers) are exercised and return correct data when present, and correct empties when the stored length is 0. On surfacing the rank. If we do want a diagnostic for genuinely inconsistent SHAPE datasets, the right invariant is |
|
Correction to my earlier comment — @prasad-sawantdesai is right, and I was wrong about the layout. I wrote a 1D GGD geometry with pure IMAS-Core and inspected the datasets with h5py: So a conformant The file in #51 has That means #52's Given that, the real fix belongs in the producer — nimrod2imas should write the SHAPE dataset with the trailing dimension (or simply use IMAS-Core
Either way, the one thing both PRs agree on and that we clearly want is: no segfault / no out-of-bounds read on malformed input — it should be a clear error (or a safe read), never UB. I'm happy to converge #63 onto that: keep the bounds-safe guard but raise the same kind of explicit "non-conformant SHAPE dataset" diagnostic as #52 instead of silently reading, so the two PRs aren't at odds. Apologies for the confusion in my previous comment. |
|
Closing in favour of #52. As established above, the file in #51 is non-conformant — its The root-cause fix belongs in the producer (nimrod2imas/dump2imas.py), which should write the SHAPE dataset with the trailing dimension or use IMAS-Core Thanks @prasad-sawantdesai for the clarification. |
Fix OOB read / segfault when reading SHAPE-scalar HDF5 datasets
Closes #51. Alternative to #52.
Problem
Reading GGD geometry data segfaults inside the HDF5 reader, e.g.:
crashes with
SIGSEGVin__intel_avx_rep_memcpy(see the backtrace in #51).Root cause
HDF5HsSelectionReadercomputesdim = dataset_rank - AOSRank. For a*_SHAPEdataset whose rank equals the AOS depth,
dim == 0whilerank > 0.readIntNDFromBufferandreadDoubleNDFromBufferthen take therank != dimbranch and unconditionally append a trailing value-dimension index before
computing the flat offset:
indices_to_flat_indexiteratesindices.size()times readingdataSpaceDims[i],so the extra element reads one past the dims array and folds it into the offset.
The resulting
indexis out of range and thememcpyreads unmapped memory.Notably, the buffered write path (
fillFullBuffers()) already handles thiscorrectly — it guards the same
push_backwithif (request_dim != 0)and, whendim == 0, indexes directly from the AOS indices. The read-from-buffer functionswere simply missing that guard.
Fix
Mirror the existing write-path guard into both read functions: only append the
trailing index when
dim != 0; otherwise address the scalar directly fromcurrent_arrctx_indices. Minimal, symmetric withfillFullBuffers(), and itlets the read return the correct value instead of crashing.
Why this as an alternative to #52
#52 addresses the same issue from the caller side by adding rank checks in
readPersistentShapes_Get/_GetSlicethat throw anALBackendExceptionwhenthe SHAPE-dataset rank does not match
arrctx_indices.size() + 1. That removesthe undefined behaviour, but for the reproduction case the rank is exactly
arrctx_indices.size(), so the guard converts the crash into a thrownexception — the GGD geometry still cannot be read. (The PR description for #52
also describes a conditional-
push_backfix and a write-path change that are notpresent in its diff.)
This PR instead fixes the indexing at its source, so the affected reads succeed.
The two approaches are not mutually exclusive: #52's guard could be kept as a
defence-in-depth safety net (with its condition relaxed so it does not reject the
legitimate
rank == AOSRankSHAPE case), but it is not required once theindexing is correct.
Verification
Reproduced the crash on the released
IMAS-Core/5.6.0modules (bothintel-2023band
foss-2023b) with the script from #51 →SIGSEGV(exit 139).Built
libalfrom this branch and re-ran with the patched library preloaded overthe released Python extension (so the patched C library is the only thing that
changed):
objects_per_dimension[0])nodes, sampled across the full object range)Both value-dimension branches (
dim != 0, int and double) and the newdim == 0branch are exercised.
Follow-up suggestions (not in this PR)
indices_to_flat_indexto take a rank/length and assert/throw if theindex count exceeds it, so any future caller bug fails loudly instead of
reading out of bounds.
*_SHAPEdataset whose rank equals the AOS depth.