Skip to content

Fix crashes when using variable-length data after closing another handle of the same dataset - #6722

Open
headtr1ck wants to merge 5 commits into
HDFGroup:developfrom
headtr1ck:fix-vlen-stale-file
Open

headtr1ck wants to merge 5 commits into
HDFGroup:developfrom
headtr1ck:fix-vlen-stale-file

Conversation

@headtr1ck

@headtr1ck headtr1ck commented Oct 8, 2026 •

Copy link
Copy Markdown

Describe your changes

A dataset's datatype is shared by all open handles of the dataset, and its variable-length types store the file of the handle that opened the dataset first. After that file handle is closed, e.g. when the same file was opened twice, converting variable-length data through another handle of the dataset crashes in H5F_addr_decode(). H5T_patch_vlen_file() repairs this, but only for a top-level variable-length type and only before reading or writing data.

Issue ticket number (GitHub or JIRA)

Fixes #6721 (see also h5py/h5py#2920 and pydata/xarray#11692)

Checklist before requesting a review

  • My code conforms to the guidelines in CONTRIBUTING.md
  • I made an entry in release_docs/CHANGELOG.md (bug fixes, new features)
  • I added a test (bug fixes, new features)

[This is Claude Code on behalf of Michael Niklas]

🤖 Generated with Claude Code

headtr1ck and others added 2 commits October 8, 2026 13:27
A dataset's datatype is shared by all open handles of the dataset, and
H5T_set_loc() stamps the file of the handle that opened it first into every
variable-length type it can reach, also below compound members and array base
types. H5T_patch_vlen_file() repairs that file before reading or writing
data, but only for a variable-length type at the top level. After the file
of the first handle was closed, reading through another handle therefore
crashed in H5F_addr_decode() for a variable-length string in a compound type
(h5py/h5py#2920). Walk the same subtypes as H5T_set_loc() instead.

Based on the analysis and patch in https://github.com/beitler/hdf5-vlen.

Co-authored-by: anton <2046752+beitler@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
H5D_get_create_plist() and the storage initialization of H5D__alloc_storage()
and H5D__set_extent() convert the fill value with the dataset's shared
datatype, without repairing the file of its variable-length types first as
H5Dread() and H5Dwrite() do. After the file of the handle that opened the
dataset first was closed, this crashed in H5F_addr_decode() for a dataset with
a variable-length fill value. netCDF-C calls H5Dget_create_plist() when
opening a file, so it crashed when opening a netCDF-4 file with string
variables again after closing one of several handles to it.

Add a test that uses variable-length data through a dataset handle after
closing another handle of the same dataset, for these cases and for the
variable-length type in a compound type from the previous commit.

Co-authored-by: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Review Checklist

This PR touches the following areas. Each needs a sign-off
from its listed owners before merging.

hyoklee
hyoklee previously approved these changes Oct 8, 2026
Comment thread release_docs/CHANGELOG.md Outdated
@headtr1ck

Copy link
Copy Markdown
Author

Any chance we could get this into a 2.2.1 release? We already had multiple people complaining about segfaults.

@jhendersonHDF

Copy link
Copy Markdown
Collaborator

Any chance we could get this into a 2.2.1 release? We already had multiple people complaining about segfaults.

Currently, we're looking toward having a 2.3.0 release soon. We don't have any plans at the moment to have a 2.2.1 release though.

Comment thread src/H5T.c
* in H5T_set_loc(), so that they stay part of the same blob. */
if (dt->shared->parent && dt->shared->parent->shared->force_conv &&
H5T_IS_COMPOSITE(dt->shared->parent->shared->type) &&
(dt->shared->parent->shared->type != H5T_REFERENCE))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Since the H5T_REFERENCE case below is a no-op, it probably doesn't need to be explicitly skipped here (looks to just be a copy-paste from H5T_set_loc() where that case actually does something). Since the H5T_ARRAY case below doesn't have this, either drop it here or make both have it (with a preference for the former)

Comment thread src/H5T.c
assert(file);

if ((dt->shared->type == H5T_VLEN) && dt->shared->u.vlen.file != file)
dt->shared->u.vlen.file = file;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

From the header comments, usages of the similar H5T_patch_file() and looking at this from a design perspective as well, it seems that the original intention for these functions was for patching only the most top-level datatype's file pointer and carrying this hack down points to a design issue to me. Since cases where H5T_patch_vlen_file() needs to be called appear to keep being missed, it would seem better for variable-length types to keep a reference to the top-level file pointer and read "upward" instead of having to propagate changes downward. @fortnern any thoughts?

@headtr1ck

Copy link
Copy Markdown
Author

From the header comments, usages of the similar H5T_patch_file() and looking at this from a design perspective as well, it seems that the original intention for these functions was for patching only the most top-level datatype's file pointer and carrying this hack down points to a design issue to me. Since cases where H5T_patch_vlen_file() needs to be called appear to keep being missed, it would seem better for variable-length types to keep a reference to the top-level file pointer and read "upward" instead of having to propagate changes downward. @fortnern any thoughts?

Thanks for your honest review. I have by no means a good understanding of this codebase but I agree that the current approach seems very fragile. The approach in this PR is basically whack-a-mole.
The design you are asking for seems like a major refactor.

May I propose to clean up this PR and ship it as is since it fixes real issues. And then continue with a follow up issue that discusses the approach to fix the design?

@jhendersonHDF

Copy link
Copy Markdown
Collaborator

May I propose to clean up this PR and ship it as is since it fixes real issues. And then continue with a follow up issue that discusses the approach to fix the design?

Sure, I'd just generally like for this to get more review because the previous assumption seemed to be that the vlen file pointer only needed to be patched at the start of I/O calls and as that appears to not be the case here, I'd like to look for additional places it needs to be done.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: To be triaged

Development

Successfully merging this pull request may close these issues.

Use-after-free of a closed file in variable-length conversions through another handle of the same dataset

5 participants