Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Fix crash reading variable-length region reference data - #2966

Merged
ajelenak merged 2 commits into
h5py:masterfrom
ajelenak:fix-vlen-regionref-segfault
Sep 8, 2026
Merged

Fix crash reading variable-length region reference data#2966
ajelenak merged 2 commits into
h5py:masterfrom
ajelenak:fix-vlen-regionref-segfault

Conversation

@ajelenak

Copy link
Copy Markdown
Contributor

Reading a dataset whose datatype is a variable-length sequence of region references segfaults:

dt = h5py.vlen_dtype(h5py.regionref_dtype)
d = f.create_dataset('x', (2,), dtype=dt)
d[0] = np.array([ref, ref], dtype=object)   # write: fine
d[0]                                        # read: SIGSEGV

This bug was revealed by failing CI workflows in my other PR #2964.

platform
Linux x86_64 segfaults
Linux aarch64 segfaults
Windows AMD64 / ARM64 segfaults
macOS passes

conv_vlen2ndarray allocates the background buffer for the element conversion with emalloc and never initializes it. conv_regref2pyref treats that buffer as holding the existing destination value and releases it:

bkg_obj0 = bkg_obj[0]
...
Py_XDECREF(bkg_obj0)

so it decrefs whatever pointer happened to be on the heap. The gdb backtrace lands in _Py_DECREF with a garbage op, three frames below conv_vlen2ndarray.

Object references never hit this because they register with H5T_BKG_NO and never read the buffer, while region references register H5T_BKG_YES. That asymmetry is exactly why vlen_dtype(ref_dtype) is fine and vlen_dtype(regionref_dtype) is not. MacOS escapes because its allocator hands back zeroed pages.

Fix

Zero the buffer after allocating it, in both conv_vlen2ndarray and conv_ndarray2vlen.

The second function is not causing any crash but it is the same unzeroed allocation, and garbage there would reach unwritten compound fields. Fixing one and not the other seemed worse than fixing both.

Reading a dataset whose datatype is a variable-length sequence of region
references segfaulted. `conv_vlen2ndarray` allocates the background buffer
for the element conversion with `emalloc` and never initializes it, and
`conv_regref2pyref` treats that buffer as the existing destination value
and releases it, so it decrefs whatever pointer was left on the heap.
Object references never hit this: they register with `H5T_BKG_NO` and
never read the buffer.
@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.52%. Comparing base (fbf1164) to head (c0f32b8).
⚠️ Report is 12 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2966      +/-   ##
==========================================
+ Coverage   90.47%   90.52%   +0.04%     
==========================================
  Files          16       16              
  Lines        2521     2532      +11     
==========================================
+ Hits         2281     2292      +11     
  Misses        240      240              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

ajelenak added a commit to ajelenak/h5py that referenced this pull request Aug 24, 2026
Vlen regionref tests are removed until this bug is fixed, see PR h5py#2966.
@ajelenak

Copy link
Copy Markdown
Contributor Author

Forgot to mention, I used memset() because there is no currently ecalloc() in h5py. Willing to add ecalloc() to replace the emalloc()/memset() combo.

@ajelenak

ajelenak commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

I plan to merge this on September 7, unless changes are requested before then.

@neutrinoceros

Copy link
Copy Markdown
Contributor

It's September 8, so feel free to merge now if you want but just as a note, I'm finally going back to my backlog and I could review this after lunch (a couple hours from now) if that's still on the table.

Comment thread h5py/_conv.templ.pyx Outdated
PyBuffer_Release(&view)

if needs_bkg_buffer(intype.id, outtype.id):
# Zeroed for the same reason as in conv_vlen2ndarray above

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer the comment be copied verbatim, in case the other one ever changes or goes away and this becomes a dead ref

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@neutrinoceros

Copy link
Copy Markdown
Contributor

so it decrefs whatever pointer happened to be on the heap.

Let me get this straight: back_buf is only ever included in bkg_obj if it's initialized first ? If so, where is that behavior defined ? Otherwise, clearly I must be misunderstanding something.

@ajelenak

ajelenak commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

It's September 8

I forgot yesterday was a holiday in the US. 😁

Let me get this straight: back_buf is only ever included in bkg_obj if it's initialized first ? If so, where is that behavior defined ? Otherwise, clearly I must be misunderstanding something.

You are not misunderstanding it, there is no such condition. back_buf is passed through to bkg_obj unconditionally whenever a background buffer is requested at all, initialized or not. Below is the chain of calls I followed for this bug, all in _conv.templ.pyx on this branch:

  1. conv_vlen2ndarray allocates it but does not zero the buffer at line 724, back_buf = emalloc(H5Tget_size(outtype.id)*size).
  2. The buffer goes straight into H5Tconvert(intype.id, outtype.id, size, data, back_buf, H5P_DEFAULT) at line 728, as the background argument.
  3. HDF5 hands that pointer to the registered converter as bkg_i. For this conversion that is conv_regref2pyref, registered at line 474. Note the H5T_BKG_YES there.
  4. generic_converter (line 58) does char* bkg = <char*>bkg_i and then, per element, op(buf + i*src_size, buf + i*dst_size, bkg + i*bkg_stride, priv) around lines 93-97.
  5. conv_regref2pyref (line 386) casts that straight back: bkg_obj = <PyObject**>bkg, reads bkg_obj0 = bkg_obj[0], and calls Py_XDECREF(bkg_obj0) at line 400.

So bkg_obj[0] is literally the first eight bytes of the emalloc'd block, and those bytes get treated as a PyObject* and decref'd. Py_XDECREF only prevents NULL, which is why zeroing is a sufficient fix.

As for where the behavior is defined: the H5T_BKG_YES at line 474 is the h5py side, and the HDF5 side for the background argument of H5Tconvert is documented as holding the existing destination value. HDF5 neither allocates nor initializes it. Supplying meaningful contents is the caller's job. For a conversion whose destination is H5PY_OBJ, "existing destination value" means a PyObject* so the buffer has to hold either real object pointers or NULL. emalloc gives neither.

This is also why object references never tripped on this bug: conv_objref2pyref is registered with H5T_BKG_NO at line 462. Region references are the only reference converter registered with H5T_BKG_YES.

@ajelenak
ajelenak merged commit 141448b into h5py:master Sep 8, 2026
32 checks passed
@ajelenak
ajelenak deleted the fix-vlen-regionref-segfault branch September 8, 2026 21:28
ajelenak added a commit to ajelenak/h5py that referenced this pull request Sep 9, 2026
… after h5py#2966  was merged
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants