Merge branch 'develop' into feature/filter_config_string

Brings in 9 commits, including the CVE-2026-19025 fix for chunked datasets
with mismatched chunk/dataspace rank.

One conflict, in release_docs/CHANGELOG.md: both sides appended entries at
the same point in the same section. Purely additive, so both were kept --
this branch's entries followed by develop's CVE entry.

test/dsets.c auto-merged despite both sides touching it; this branch's change
there (use H5Z_find() instead of the now-hidden H5Z_SZIP global) is in a
different region from develop's.
This commit is contained in:
Scot Breitenfeld
2026-08-28 15:16:29 -05:00
18 changed files with 555 additions and 149 deletions
+2 -2
View File
@@ -26,8 +26,8 @@ jobs:
./spack/bin/spack spec py-h5py@master+mpi
sed -i 's/hdf5@1.10.7:1.14/hdf5@1.10.7:/g' \
/home/runner/.spack/package_repos/*/repos/spack_repo/builtin/packages/py_h5py/package.py
./spack/bin/spack spec py-h5py@master+mpi ^hdf5@develop-2.0
./spack/bin/spack install py-h5py@master+mpi ^hdf5@develop-2.0
./spack/bin/spack spec py-h5py@master+mpi ^hdf5@develop-2
./spack/bin/spack install py-h5py@master+mpi ^hdf5@develop-2
./spack/bin/spack install py-pytest
./spack/bin/spack install py-pytest-mpi
spack load py-h5py
+2 -26
View File
@@ -36,34 +36,8 @@ env:
# Previous workflows must pass to get here so tag the commit that created the files
jobs:
create-tag:
runs-on: ubuntu-latest
permissions:
contents: write # In order to allow tag creation
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: Get Sources
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
ref: '${{ github.head_ref || github.ref_name }}'
- uses: rickstaa/action-create-tag@a1c7777fcb2fee4f19b0f283ba888afa11678b72 # v1.7.2
id: "tag_create"
with:
commit_sha: ${{ inputs.file_sha }}
tag: "${{ inputs.use_tag }}"
force_push_tag: false
tag_exists_error: false
message: "Latest snapshot"
# Print result using the action output.
- run: |
echo "Tag already present: ${{ steps.tag_create.outputs.tag_exists }}"
PreRelease-getfiles:
runs-on: ubuntu-latest
needs: create-tag
environment: ${{ inputs.use_environ }}
permissions:
contents: write
@@ -223,6 +197,7 @@ jobs:
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
tag_name: "${{ inputs.use_tag }}"
target_commitish: ${{ inputs.file_sha }}
prerelease: true
body_path: description.txt
files: |
@@ -264,6 +239,7 @@ jobs:
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
tag_name: "${{ steps.create_release_tag_name.outputs.TAG_BASE }}"
target_commitish: ${{ inputs.file_sha }}
name: "HDF5 Release ${{ inputs.use_tag }}"
draft: true
prerelease: false
+2 -2
View File
@@ -33,8 +33,8 @@ on:
description: "The branch used for the source tarballs"
value: ${{ jobs.check_commits.outputs.branch_ref }}
file_sha:
description: "The sha used for the source tarballs"
value: ${{ jobs.check_commits.outputs.branch_sha }}
description: "The full commit sha used as target_commitish for the release tag"
value: ${{ jobs.check_commits.outputs.branch_full_sha }}
permissions:
contents: read
+1 -24
View File
@@ -30,33 +30,11 @@
cmake_minimum_required (VERSION 3.26)
project (HDF5 C)
if (POLICY CMP0074)
# find_package() uses <PackageName>_ROOT variables.
cmake_policy (SET CMP0074 NEW)
endif ()
if (POLICY CMP0144)
# <PACKAGENAME> is the upper-cased package name.
# CMake >= 3.27 <PACKAGENAME> is the upper-cased package name.
cmake_policy (SET CMP0144 NEW)
endif ()
if (POLICY CMP0083)
# To control generation of Position Independent Executable (PIE) or not,
# some flags are required at link time.
cmake_policy (SET CMP0083 NEW)
endif ()
if (POLICY CMP0127)
# to evaluate each condition as if(<condition>), where <condition> is re-parsed
# as if literally written in a call to if().
cmake_policy (SET CMP0127 NEW)
endif ()
# Avoid warning about DOWNLOAD_EXTRACT_TIMESTAMP in CMake 3.24:
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
cmake_policy(SET CMP0135 NEW)
endif()
#-----------------------------------------------------------------------------
# Instructions for use : Normal Build
#
@@ -1571,4 +1549,3 @@ include (CMakeInstallation.cmake)
# Hide some miscellaneous CMake variables from the default GUI
#-----------------------------------------------------------------------------
mark_as_advanced (FETCHCONTENT_BASE_DIR FETCHCONTENT_FULLY_DISCONNECTED FETCHCONTENT_QUIET FETCHCONTENT_UPDATES_DISCONNECTED)
@@ -186,7 +186,7 @@ CONTAINS
! passed to H5Dwrite.
CALL H5Sselect_none_f(file_dataspace, status)
sel_type = H5S_BLOCK_F
sel_type = H5S_ALL_F
ELSE
!
! Even MPI ranks contribute data to
@@ -206,7 +206,7 @@ CONTAINS
CALL H5Sselect_hyperslab_f(file_dataspace, H5S_SELECT_SET_F, start, count, status, stride=stride)
sel_type = H5S_ALL_F
sel_type = H5S_BLOCK_F
!
! --------------------------------------
! Fill data buffer with MPI rank's rank
@@ -37,7 +37,10 @@ macro (BASIC_SETTINGS varname)
if (H5EXAMPLE_BUILD_CXX)
ENABLE_LANGUAGE (CXX)
set (CMAKE_CXX_STANDARD 98)
# Match the standard the HDF5 C++ library itself is built with.
# H5public.h includes <cinttypes>, so any C++ translation unit
# with hdf5.h needs C++11
set (CMAKE_CXX_STANDARD 11)
set (CMAKE_CXX_STANDARD_REQUIRED TRUE)
set (CMAKE_CXX_EXTENSIONS OFF)
endif ()
@@ -246,7 +249,7 @@ macro (HDF5_SUPPORT)
message (STATUS "HDF5 HL libs not found - disable build of HL examples")
else ()
if (H5EXAMPLE_BUILD_HL AND ${HDF5_PROVIDES_HL_LIB})
if (BUILD_SHARED_LIBS AND HDF5_shared_HL_FOUND)
if (H5EXAMPLE_USE_SHARED_LIBS AND HDF5_shared_HL_FOUND)
set (H5EXAMPLE_HDF5_LINK_LIBS ${H5EXAMPLE_HDF5_LINK_LIBS} ${HDF5_HL_SHARED_LIBRARY})
elseif (HDF5_static_HL_FOUND)
set (H5EXAMPLE_HDF5_LINK_LIBS ${H5EXAMPLE_HDF5_LINK_LIBS} ${HDF5_HL_STATIC_LIBRARY})
@@ -262,7 +265,7 @@ macro (HDF5_SUPPORT)
message (STATUS "HDF5 Fortran libs not found - disable build of Fortran examples")
else ()
if (H5EXAMPLE_BUILD_FORTRAN AND ${HDF5_PROVIDES_FORTRAN})
if (BUILD_SHARED_LIBS AND HDF5_shared_Fortran_FOUND)
if (H5EXAMPLE_USE_SHARED_LIBS AND HDF5_shared_Fortran_FOUND)
set (H5EXAMPLE_HDF5_LINK_LIBS ${H5EXAMPLE_HDF5_LINK_LIBS} ${HDF5_FORTRAN_SHARED_LIBRARY})
if (H5EXAMPLE_BUILD_HL AND ${HDF5_PROVIDES_HL_LIB})
set (H5EXAMPLE_HDF5_LINK_LIBS ${H5EXAMPLE_HDF5_LINK_LIBS} ${HDF5_FORTRAN_HL_SHARED_LIBRARY})
@@ -284,7 +287,7 @@ macro (HDF5_SUPPORT)
message (STATUS "HDF5 CXX libs not found - disable build of CXX examples")
else ()
if (H5EXAMPLE_BUILD_CXX AND ${HDF5_PROVIDES_CPP_LIB})
if (BUILD_SHARED_LIBS AND HDF5_shared_CXX_FOUND)
if (H5EXAMPLE_USE_SHARED_LIBS AND HDF5_shared_CXX_FOUND)
set (H5EXAMPLE_HDF5_LINK_LIBS ${H5EXAMPLE_HDF5_LINK_LIBS} ${HDF5_CXX_SHARED_LIBRARY})
if (H5EXAMPLE_BUILD_HL AND ${HDF5_PROVIDES_HL_LIB})
set (H5EXAMPLE_HDF5_LINK_LIBS ${H5EXAMPLE_HDF5_LINK_LIBS} ${HDF5_CXX_HL_SHARED_LIBRARY})
+99 -64
View File
@@ -1221,37 +1221,37 @@ the header).
<tr>
<td>Version</td>
<td>Type</td>
<td colspan="2">Records 0, 1, 2...N-1 <em>(variable size)</em></td>
<td colspan="2">Record \#0, Record \#1, ... Record \#(R-1) <em>(variable size)</em></td>
</tr>
<tr>
<td colspan="4"><br />Child Node Pointer 0<sup>O</sup><br /><br /></td>
<td colspan="4"><br />Child Node Pointer \#0<sup>O</sup><br /><br /></td>
</tr>
<tr>
<td colspan="4"><br />Number of Records N<sub>0</sub> for Child Node 0 <em>(variable size)</em></td>
<td colspan="4"><br />Number of Records for Child Node \#0 <em>(variable size)</em></td>
</tr>
<tr>
<td colspan="4"><br />Total Number of Records for Child Node 0 <em>(optional, variable size)</em></td>
<td colspan="4"><br />Total Number of Records for Child Node \#0 <em>(optional, variable size)</em></td>
</tr>
<tr>
<td colspan="4"><br />Child Node Pointer 1<sup>O</sup><br /> <br /></td>
<td colspan="4"><br />Child Node Pointer \#1<sup>O</sup><br /> <br /></td>
</tr>
<tr>
<td colspan="4"><br />Number of Records N<sub>1</sub> for Child Node 1 <em>(variable size)</em></td>
<td colspan="4"><br />Number of Records for Child Node \#1 <em>(variable size)</em></td>
</tr>
<tr>
<td colspan="4"><br />Total Number of Records for Child Node 1 <em>(optional, variable size)</em></td>
<td colspan="4"><br />Total Number of Records for Child Node \#1 <em>(optional, variable size)</em></td>
</tr>
<tr>
<td colspan="4">...</td>
</tr>
<tr>
<td colspan="4"><br />Child Node Pointer N<sup>O</sup><br /><br /></td>
<td colspan="4"><br />Child Node Pointer \#R<sup>O</sup><br /><br /></td>
</tr>
<tr>
<td colspan="4"><br />Number of Records N<sub>n</sub> for Child Node N <em>(variable size)</em></td>
<td colspan="4"><br />Number of Records for Child Node \#R <em>(variable size)</em></td>
</tr>
<tr>
<td colspan="4"><br />Total Number of Records for Child Node N <em>(optional, variable size)</em></td>
<td colspan="4"><br />Total Number of Records for Child Node \#R <em>(optional, variable size)</em></td>
</tr>
<tr>
<td colspan="4">Checksum</td>
@@ -1281,35 +1281,45 @@ the header).
the header.</td>
</tr>
<tr>
<td>Records</td>
<td>The size of this field is determined by the number of records for this node and the record size
(from the header). The format of records depends on the type of B-tree.</td>
<td>Record \#i (i = 0 to (R-1))</td>
<td>Let <em>R</em> be the number of records for this node: for the root node, <em>R</em> is given
by the B-tree Header's <em>Number of Records in Root Node</em> field; for any other node,
<em>R</em> is given by the <em>Number of Records for Child Node \#j</em> field in this node's
parent, specifically the pointer triplet whose <em>Child Node Pointer \#j</em> gave this
node's address. The size of this field is
determined by <em>R</em> and the <em>Record Size</em> (the latter always from the header); the
format of records depends on the type of B-tree. Because each internal node has one more child
pointer triplet than it has records, this node has <em>R</em>+1 child node pointers, indexed
\#0 through \#R (see below).</td>
</tr>
<tr>
<td>Child Node Pointer</td>
<td>This field is the address of the child node pointed to by the internal node.</td>
<td>Child Node Pointer \#j (j = 0 to R)</td>
<td>This field is the address of child \#j pointed to by this internal node. Together with the
following field, <em>Number of Records for Child Node \#j</em>, and (where present) the field
after that, <em>Total Number of Records for Child Node \#j</em>, this forms what the rest of
this table calls a <em>pointer triplet</em> &mdash; the repeating group of per-child fields
between <em>Child Node Pointer \#j</em> and <em>Child Node Pointer \#(j+1)</em>.</td>
</tr>
<tr>
<td>Number of Records in Child Node</td>
<td>This is the number of records in the child node pointed to by the corresponding <em>Node Pointer</em>.<br />
<td>Number of Records for Child Node \#j (j = 0 to R)</td>
<td>This is the number of records in child \#j (see the <em>Child Node Pointer</em> field above).<br />
The number of bytes used to store this field is determined by the maximum possible number of records able
to be stored in the child node.<br />
The maximum number of records in a child node is computed in the following way:
<ul>
<li>Subtract the fixed size overhead for the child node (for example, its signature, version,
checksum, and so on and <em>one</em> pointer triplet of information for the child node
(because there is one more pointer triplet than records in each internal node)) from the size
of nodes for the B-tree.</li>
<li>Divide that result by the size of a record plus the pointer triplet of information stored to
reach each child node from this node.</li>
checksum, and so on, and one pointer triplet of information for the child node, per the
R+1 child pointers noted above) from the <em>Node Size</em> (see the header).</li>
<li>Divide that result by the sum of the <em>Record Size</em> and the pointer triplet of information
stored to reach each child node from this node.</li>
</ul><br />
Note that leaf nodes do not encode any child pointer triplets, so the maximum number of records in a
leaf node is just the node size minus the leaf node overhead, divided by the record size.<br />
Also note that the first level of internal nodes above the leaf nodes do not encode the <em>Total
Number of Records in Child Node</em> value in the child pointer triplets (since it is the same as
the <em>Number of Records in Child Node</em>), so the maximum number of records in these nodes is
computed with the equation above, but using (<em>Child Pointer</em>, <em>Number of Records in Child
Node</em>) pairs instead of triplets.<br />
leaf node is just the <em>Node Size</em> minus the leaf node overhead, divided by the <em>Record Size</em>.<br />
Also note that the first level of internal nodes above the leaf nodes &mdash; the &ldquo;twig&rdquo;
internal nodes &mdash; do not encode a <em>Total Number of Records for Child Node \#j</em> value
(since it would be the same as <em>Number of Records for Child Node \#j</em>), so the maximum number
of records in these nodes is computed with the equation above, but using (<em>Child Node Pointer
\#j</em>, <em>Number of Records for Child Node \#j</em>) pairs instead of full triplets.<br />
The number of bytes used to encode this field is the least number of bytes required to encode the
maximum number of records in a child node value for the child nodes below this level in the B-tree.<br />
For example, if the maximum number of child records is 123, one byte will be used to encode these
@@ -1318,11 +1328,10 @@ the header).
is 8 (in other words, an unsigned 64-bit integer).</td>
</tr>
<tr>
<td>Total Number of Records in Child Node</td>
<td>This is the total number of records for the node pointed to by the corresponding <em>Node Pointer</em>
and all its children. This field exists only in nodes whose depth in the B-tree node is greater than 1
(in other words, the &ldquo;twig&rdquo; internal nodes, just above leaf nodes, do not store this field
in their child node pointers).<br />
<td>Total Number of Records for Child Node \#j (j = 0 to R)</td>
<td>This is the total number of records for child \#j and all its descendants. This field exists
only in nodes whose depth in the B-tree node is greater than 1; see the note about
&ldquo;twig&rdquo; internal nodes under <em>Number of Records for Child Node \#j</em> above.<br />
The number of bytes used to store this field is determined by the maximum possible number of records
able to be stored in the child node and its descendants.<br />
The maximum possible number of records able to be stored in a child node and its descendants is
@@ -1332,7 +1341,7 @@ the header).
maximum possible number of records in child node pointers for the level of nodes two levels above
leaf nodes. This process is continued up to any level in the B-tree.<br />
The number of bytes used to encode this value is computed in the same way as for the <em>Number
of Records in Child Node</em> field.</td>
of Records for Child Node \#j</em> field.</td>
</tr>
<tr>
<td>Checksum</td>
@@ -1354,7 +1363,7 @@ the header).
<tr>
<td>Version</td>
<td>Type</td>
<td colspan="2">Record 0, 1, 2...N-1 <em>(variable size)</em></td>
<td colspan="2">Record \#0, Record \#1, ... Record \#(R-1) <em>(variable size)</em></td>
</tr>
<tr>
<td colspan="4">Checksum</td>
@@ -1382,9 +1391,13 @@ the header).
the header.</td>
</tr>
<tr>
<td>Records</td>
<td>The size of this field is determined by the number of records for this node and the record size
(from the header). The format of records depends on the type of B-tree.</td>
<td>Record \#i (i = 0 to (R-1))</td>
<td>Let <em>R</em> be the number of records for this node: for the root node, <em>R</em> is given
by the B-tree Header's <em>Number of Records in Root Node</em> field; for any other node,
<em>R</em> is given by the <em>Number of Records for Child Node \#j</em> field in this leaf's
parent (always a &ldquo;twig&rdquo; internal node, so this is one of a pointer pair, not a full
triplet). The size of this field is determined by <em>R</em> and the <em>Record Size</em> (the
latter always from the header); the format of records depends on the type of B-tree.</td>
</tr>
<tr>
<td>Checksum</td>
@@ -2386,10 +2399,10 @@ as an atomic object, addressing goal A.
When a global heap object is deleted from a collection (which occurs when its reference count falls to zero),
objects located after the deleted object in the collection are packed down toward the beginning of the
collection and the collection&rsquo;s global heap object 0 is created (if possible) or its size is increased
collection and the collection&rsquo;s global heap object \#0 is created (if possible) or its size is increased
to account for the recently freed space. There are no gaps between objects in each collection, with the possible
exception of the final space in the collection, if it is not large enough to hold the header for the
collection&rsquo;s global heap object 0. These features address goal C.
collection&rsquo;s global heap object \#0. These features address goal C.
The HDF5 library creates global heap collections as needed, so there may be multiple collections throughout
the file. The set of all of them is abstractly called the &ldquo;global heap&rdquo;, although they do not
@@ -2417,19 +2430,19 @@ global heap ID. The format for global heap IDs is described at the end of this s
<td colspan="4"><br />Collection Size<sup>L</sup><br /><br /></td>
</tr>
<tr align="center">
<td colspan="4"><br />Global Heap Object 1<br /><br /></td>
<td colspan="4"><br />Global Heap Object \#1<br /><br /></td>
</tr>
<tr align="center">
<td colspan="4"><br />Global Heap Object 2<br /><br /></td>
<td colspan="4"><br />Global Heap Object \#2<br /><br /></td>
</tr>
<tr align="center">
<td colspan="4"><br />...<br /><br /></td>
</tr>
<tr align="center">
<td colspan="4"><br />Global Heap Object <em>N</em><br /><br /></td>
<td colspan="4"><br />Global Heap Object \#N<br /><br /></td>
</tr>
<tr align="center">
<td colspan="4"><br />Global Heap Object 0 <em>(free space)</em><br /><br /></td>
<td colspan="4"><br />Global Heap Object \#0 <em>(free space)</em><br /><br /></td>
</tr>
</table>
\li Items marked with an &lsquo;L&rsquo; in the above table are of the size specified in
@@ -2459,16 +2472,19 @@ global heap ID. The format for global heap IDs is described at the end of this s
about each heap object).</td>
</tr>
<tr valign=top>
<td>Global Heap Object 1 through <em>N</em></td>
<td>The objects are stored in any order with no intervening unused space.</td>
<td>Global Heap Object \#1 through \#N</td>
<td>Let <em>N</em> be the number of real (non-free-space) objects in this collection. <em>N</em> is not
stored explicitly; a reader determines it by parsing objects sequentially, in the order stored, until
either the free-space object (Global Heap Object \#0, Heap Object Index 0) is reached or the
Collection Size is exhausted. The objects are stored in any order with no intervening unused space.</td>
</tr>
<tr valign=top>
<td>Global Heap Object 0</td>
<td>Global Heap Object 0 (zero), when present, represents the free space in the collection. Free space always
appears at the end of the collection. If the free space is too small to store the header for Object 0
(described below) then the header is implied and is not written.<br />
The field <em>Object Size</em> for Object 0 indicates the amount of possible free space in the collection
including the 16-byte header size of Object 0.</td>
<td>Global Heap Object \#0</td>
<td>Global Heap Object \#0 (zero), when present, represents the free space in the collection. Free space
always appears at the end of the collection. If the free space is too small to store the header for
Object \#0 (described below) then the header is implied and is not written.<br />
The field <em>Object Size</em> for Object \#0 indicates the amount of possible free space in the
collection including the 16-byte header size of Object \#0.</td>
</tr>
</table>
@@ -2512,7 +2528,7 @@ global heap ID. The format for global heap IDs is described at the end of this s
<tr valign=top>
<td>Reference Count</td>
<td>All heap objects have a reference count field. An object which is referenced from some other part of the
file will have a positive reference count. The reference count for Object 0 is always zero.</td>
file will have a positive reference count. The reference count for Object \#0 is always zero.</td>
</tr>
<tr valign=top>
<td>Reserved</td>
@@ -5020,6 +5036,16 @@ Header messages are aligned on 8-byte boundaries for version 1 object headers.
</table>
\subsubsection subsubsec_fmt4_dataobject_hdr_prefix_two IV.A.1.b Version 2 Data Object Header Prefix
\anchor FMT4OhdrChunkDef A version 2 object header&rsquo;s messages are stored in one or more <em>chunks</em>.
<em>Chunk \#0</em> is the chunk embedded directly in this prefix, below: it runs from the <code>OHDR</code>
signature through this chunk&rsquo;s own <em>Gap</em> and <em>Checksum</em> fields. Additional chunks
(<em>chunk \#1</em> through <em>chunk \#n</em>) are stored in continuation blocks, each located and sized
by an Object Header Continuation message in some earlier chunk (see
@ref subsubsec_fmt4_dataobject_hdr_msg_continuation). Each chunk, including chunk \#0, ends with its
<em>own</em> <em>Gap</em> and <em>Checksum</em> fields, computed only over that chunk&rsquo;s bytes. This
use of &ldquo;chunk&rdquo; is unrelated to the raw data <em>chunks</em> of a chunked-storage dataset (see
@ref subsubsec_fmt4_dataobject_hdr_msg_layout and @ref sec_fmt4_appendixc).
Note that the &ldquo;total number of messages&rdquo; field has been dropped from the data object header
prefix in this version. The number of messages in the data object header is just determined by the
messages encountered in all the object header blocks.
@@ -5204,9 +5230,10 @@ or padding bytes inserted - they are stored packed together.
</tr>
<tr>
<td>Size of Chunk \#0</td>
<td>This unsigned value specifies the number of bytes of header message data following this field
that contain object header information. This value does not include the size of object header
continuation blocks for this object elsewhere in the file. The length of this field varies
<td>This unsigned value specifies the number of bytes of header message data following this field,
within @ref FMT4OhdrChunkDef "chunk \#0" (the messages, and the Gap, if present, but not the
Checksum field). This value does not include the size of object header continuation blocks
(chunk \#1 and beyond) for this object elsewhere in the file. The length of this field varies
depending on bits 0 and 1 of the <em>flags</em> field.</td>
</tr>
<tr>
@@ -5234,16 +5261,20 @@ or padding bytes inserted - they are stored packed together.
</tr>
<tr>
<td>Gap</td>
<td>A gap in an object header chunk is inferred by the end of the messages for the chunk before the
beginning of the chunk&rsquo;s checksum. Gaps are always smaller than the size of an object header
message prefix (message type + message size + message flags).<br />
<td>A gap in @ref FMT4OhdrChunkDef "chunk \#0" is inferred by the end of the messages for the chunk
before the beginning of this chunk&rsquo;s Checksum field. Gaps are always smaller than the size
of an object header message prefix (message type + message size + message flags).<br />
Gaps are formed when a message (typically an attribute message) in an earlier chunk is deleted
and a message from a later chunk that does not quite fit into the free space is moved into the
earlier chunk.</td>
</tr>
<tr>
<td>Checksum</td>
<td>This is the checksum for the object header chunk.</td>
<td>This is the checksum for chunk \#0 only (see @ref FMT4OhdrChunkDef "object header chunks"),
computed over every byte of this chunk from the <code>OHDR</code> signature through the end of
this chunk&rsquo;s Gap field (if present), excluding this Checksum field itself. Each continuation
chunk, if any, carries its own separate checksum; see
@ref subsubsec_fmt4_dataobject_hdr_msg_continuation.</td>
</tr>
</table>
@@ -10005,16 +10036,20 @@ described here (see also the description of @ref subsubsec_fmt4_dataobject_hdr_p
</tr>
<tr>
<td>Gap</td>
<td>A gap in an object header chunk is inferred by the end of the messages for the chunk before the
beginning of the chunk&rsquo;s checksum. Gaps are always smaller than the size of an object header
message prefix (message type + message size + message flags).<br />
<td>A gap in this @ref FMT4OhdrChunkDef "continuation chunk" is inferred by the end of the messages for
the chunk before the beginning of this chunk&rsquo;s Checksum field. Gaps are always smaller than the
size of an object header message prefix (message type + message size + message flags).<br />
Gaps are formed when a message (typically an attribute message) in an earlier chunk is deleted
and a message from a later chunk that does not quite fit into the free space is moved into the
earlier chunk.</td>
</tr>
<tr>
<td>Checksum</td>
<td>This is the checksum for the object header chunk.</td>
<td>This is the checksum for this continuation chunk only (see
@ref FMT4OhdrChunkDef "object header chunks"), computed over every byte of this chunk from the
<code>OCHK</code> signature through the end of this chunk&rsquo;s Gap field (if present), excluding
this Checksum field itself. Chunk \#0, in the object header prefix, and every other continuation
chunk each carry their own separate checksum.</td>
</tr>
</table>
+46
View File
@@ -176,6 +176,17 @@ We would like to thank the many HDF5 community members who contributed to this r
## Library
### Fixed memory leaks and ID reference count issues when pushing an error to an error stack that is full
When an error is pushed to an error stack, the library may make a copy of the file
and function strings to ensure that they exist for the same duration as the error
stack entry. When an error stack is full, the library simply makes any further pushes
no-ops, but previously gave no information to calling code that this happened. This
caused calling code to assume that the duplicated strings were owned by an error stack
entry that was never pushed, leaking the duplicated strings. Additionally, IDs
associated with the error stack entry were left with incremented reference counts,
resulting in an infinite loop while closing the library.
### Library shutdown no longer aborts on a detected infinite loop
When the library detects that it cannot make progress closing itself (an "infinite loop closing library"), it no longer calls `abort()`. The abort behaved inconsistently, only firing when automatic error message display was enabled. Additionally, terminating the entire host process on a shutdown-time condition is undesirable for applications that embed HDF5. The library now reports the condition (when error display is enabled) and returns without aborting.
@@ -225,6 +236,13 @@ We would like to thank the many HDF5 community members who contributed to this r
### Fixed an issue with an incorrect file format validation check when decoding metadata cache entries
Fixed a bug where a flag in H5Cimage.c wasn't getting set correctly for release builds of HDF5, leading to incorrect error checking when reconstructing metadata cache entries.
### Fixed a crash when reading a chunked dataset whose chunk rank does not match the dataspace rank
The chunk layout's stored dimensionality was validated against the dataspace rank at creation time, but not at open time, so a file whose stored chunk rank disagreed with its dataspace rank was not caught. The resulting inconsistent selection ranks during chunk I/O caused a divide-by-zero in the hyperslab iterator. The chunk dimensionality is now also validated on open, and such a dataset is rejected with an error instead of crashing.
Fixes GitHub issue #6491
Fixes CVE-2026-19025
## Java Library
@@ -234,6 +252,34 @@ We would like to thank the many HDF5 community members who contributed to this r
The installed CMake package version configuration file for the library previously used `SameMinorVersion` for the version compatibility logic, causing a `find_package(HDF5 X.Y.Z)` call to fail unless the version of a located HDF5 installation matched both `X` and `Y` of the version number exactly (i.e., releases with a greater minor version number weren't considered backward compatible). This reflected the version compatibility of HDF5 releases prior to version 2.0.0, but doesn't reflect the version compatibility of HDF5 version 2.0.0+ releases. The version compatibility logic now uses `SameMajorVersion`, so a `find_package(HDF5 X.Y.Z)` call will accept all versions of HDF5 where the major version matches `X` (i.e., only releases with a greater major version number will be rejected as not backward compatible).
### Fixed the C++ examples failing to compile when built standalone
The standalone examples build used C++98, but `H5public.h` includes
`<cinttypes>`, which requires C++11. This affected any C++ translation unit
including `hdf5.h`, and did not match the HDF5 C++ library itself, which is
built as C++11. The C++ examples did not compile, against either static or
shared HDF5. The examples are now built as C++11.
Only the standalone build was affected. Examples built as part of the HDF5
build inherit the library's own C++ standard.
### Fixed the examples skipping the HL, Fortran and C++ programs in some configurations
When built standalone against an installed HDF5, the examples chose between
the shared and static HL, Fortran and C++ libraries using `BUILD_SHARED_LIBS`,
while the C library used `H5EXAMPLE_USE_SHARED_LIBS`. Since
`H5EXAMPLE_USE_SHARED_LIBS` determines which component is requested from
`find_package`, and therefore which `HDF5_<linkage>_<lang>_FOUND` variables
exist, `BUILD_SHARED_LIBS` could not select a linkage on its own. With
`H5EXAMPLE_USE_SHARED_LIBS` on and `BUILD_SHARED_LIBS` unset, those examples
were disabled with a "libs not found" message even though the libraries were
installed and had been found. The selection now uses
`H5EXAMPLE_USE_SHARED_LIBS`, matching the C library.
Builds driven through `CTestScript.cmake` were not affected, since its cache
file forces `BUILD_SHARED_LIBS` on. This affected cases where the examples
were built directly without that cache file.
## Tools
### Fixed an issue with quoting of data values in h5ls and h5dump when displaying as ASCII characters
+33 -10
View File
@@ -525,12 +525,16 @@ herr_t
H5Epush2(hid_t err_stack, const char *file, const char *func, unsigned line, hid_t cls_id, hid_t maj_id,
hid_t min_id, const char *fmt, ...)
{
H5E_stack_t *estack; /* Pointer to error stack to modify */
va_list ap; /* Varargs info */
bool va_started = false; /* Whether the variable argument list is open */
const char *tmp_file; /* Copy of the file name */
const char *tmp_func; /* Copy of the function name */
herr_t ret_value = SUCCEED; /* Return value */
H5E_stack_t *estack; /* Pointer to error stack to modify */
va_list ap; /* Varargs info */
htri_t push_ret = true; /* Was an error stack entry actually pushed? */
bool va_started = false; /* Whether the variable argument list is open */
char *tmp_file = NULL; /* Copy of the file name */
char *tmp_func = NULL; /* Copy of the function name */
bool inc_cls_id = false; /* Incremented error class ID ref. count? */
bool inc_maj_id = false; /* Incremented major error ID ref. count? */
bool inc_min_id = false; /* Incremented minor error ID ref. count? */
herr_t ret_value = SUCCEED; /* Return value */
/* Don't clear the error stack! :-) */
FUNC_ENTER_API_NOCLEAR(FAIL)
@@ -562,18 +566,25 @@ H5Epush2(hid_t err_stack, const char *file, const char *func, unsigned line, hid
HGOTO_ERROR(H5E_ERROR, H5E_CANTALLOC, FAIL, "can't duplicate function string");
/* Increment refcount on non-library IDs */
if (cls_id != H5E_ERR_CLS_g)
if (cls_id != H5E_ERR_CLS_g) {
if (H5I_inc_ref(cls_id, false) < 0)
HGOTO_ERROR(H5E_ERROR, H5E_CANTINC, FAIL, "can't increment class ID");
if (maj_id < H5E_first_maj_id_g || maj_id > H5E_last_maj_id_g)
inc_cls_id = true;
}
if (maj_id < H5E_first_maj_id_g || maj_id > H5E_last_maj_id_g) {
if (H5I_inc_ref(maj_id, false) < 0)
HGOTO_ERROR(H5E_ERROR, H5E_CANTINC, FAIL, "can't increment major error ID");
if (min_id < H5E_first_min_id_g || min_id > H5E_last_min_id_g)
inc_maj_id = true;
}
if (min_id < H5E_first_min_id_g || min_id > H5E_last_min_id_g) {
if (H5I_inc_ref(min_id, false) < 0)
HGOTO_ERROR(H5E_ERROR, H5E_CANTINC, FAIL, "can't increment minor error ID");
inc_min_id = true;
}
/* Push the error on the stack */
if (H5E__push_stack(estack, true, tmp_file, tmp_func, line, cls_id, maj_id, min_id, fmt, &ap) < 0)
push_ret = H5E__push_stack(estack, true, tmp_file, tmp_func, line, cls_id, maj_id, min_id, fmt, &ap);
if (push_ret < 0)
HGOTO_ERROR(H5E_ERROR, H5E_CANTSET, FAIL, "can't push error on stack");
}
@@ -581,6 +592,18 @@ done:
if (va_started)
va_end(ap);
if (ret_value < 0 || !push_ret) {
if (inc_cls_id && H5I_dec_ref(cls_id) < 0)
HDONE_ERROR(H5E_ERROR, H5E_CANTDEC, FAIL, "can't decrement class ID");
if (inc_maj_id && H5I_dec_ref(maj_id) < 0)
HDONE_ERROR(H5E_ERROR, H5E_CANTDEC, FAIL, "can't decrement major error ID");
if (inc_min_id && H5I_dec_ref(min_id) < 0)
HDONE_ERROR(H5E_ERROR, H5E_CANTDEC, FAIL, "can't decrement minor error ID");
free(tmp_func);
free(tmp_file);
}
FUNC_LEAVE_API(ret_value)
} /* end H5Epush2() */
+26 -7
View File
@@ -182,10 +182,13 @@ done:
herr_t
H5Epush1(const char *file, const char *func, unsigned line, H5E_major_t maj, H5E_minor_t min, const char *str)
{
H5E_stack_t *estack; /* Pointer to error stack to modify */
const char *tmp_file; /* Copy of the file name */
const char *tmp_func; /* Copy of the function name */
herr_t ret_value = SUCCEED; /* Return value */
H5E_stack_t *estack; /* Pointer to error stack to modify */
htri_t push_ret = true; /* Was an error stack entry actually pushed? */
char *tmp_file = NULL; /* Copy of the file name */
char *tmp_func = NULL; /* Copy of the function name */
bool inc_maj_id = false; /* Incremented major error ID ref. count? */
bool inc_min_id = false; /* Incremented minor error ID ref. count? */
herr_t ret_value = SUCCEED; /* Return value */
/* Don't clear the error stack! :-) */
FUNC_ENTER_API_NOCLEAR(FAIL)
@@ -203,19 +206,35 @@ H5Epush1(const char *file, const char *func, unsigned line, H5E_major_t maj, H5E
HGOTO_ERROR(H5E_ERROR, H5E_CANTALLOC, FAIL, "can't duplicate function string");
/* Increment refcount on non-library IDs */
if (maj < H5E_first_maj_id_g || maj > H5E_last_maj_id_g)
if (maj < H5E_first_maj_id_g || maj > H5E_last_maj_id_g) {
if (H5I_inc_ref(maj, false) < 0)
HGOTO_ERROR(H5E_ERROR, H5E_CANTINC, FAIL, "can't increment major error ID");
if (min < H5E_first_min_id_g || min > H5E_last_min_id_g)
inc_maj_id = true;
}
if (min < H5E_first_min_id_g || min > H5E_last_min_id_g) {
if (H5I_inc_ref(min, false) < 0)
HGOTO_ERROR(H5E_ERROR, H5E_CANTINC, FAIL, "can't increment minor error ID");
inc_min_id = true;
}
/* Push the error on the default error stack */
if (H5E__push_stack(estack, true, tmp_file, tmp_func, line, H5E_ERR_CLS_g, maj, min, str, NULL) < 0)
push_ret =
H5E__push_stack(estack, true, tmp_file, tmp_func, line, H5E_ERR_CLS_g, maj, min, str, NULL);
if (push_ret < 0)
HGOTO_ERROR(H5E_ERROR, H5E_CANTSET, FAIL, "can't push error on stack");
}
done:
if (ret_value < 0 || !push_ret) {
if (inc_maj_id && H5I_dec_ref(maj) < 0)
HDONE_ERROR(H5E_ERROR, H5E_CANTDEC, FAIL, "can't decrement major error ID");
if (inc_min_id && H5I_dec_ref(min) < 0)
HDONE_ERROR(H5E_ERROR, H5E_CANTDEC, FAIL, "can't decrement minor error ID");
free(tmp_func);
free(tmp_file);
}
FUNC_LEAVE_API(ret_value)
} /* end H5Epush1() */
+11 -7
View File
@@ -1594,20 +1594,22 @@ done:
* MIN_ID, the name of a function where the error was detected,
* the name of the file where the error was detected, the
* line within that file, and an error description string. The
* function name, file name, and error description strings must
* be statically allocated (the FUNC_ENTER() macro takes care of
* the function name and file name automatically, but the
* programmer is responsible for the description string).
* error description string must be statically allocated (the
* FUNC_ENTER() macro takes care of the function name and file
* name automatically, but the programmer is responsible for
* the description string).
*
* Return: SUCCEED/FAIL
* Return: true if an error stack entry was pushed
* false if an error stack entry was not pushed
* FAIL on failure
*
*-------------------------------------------------------------------------
*/
herr_t
htri_t
H5E__push_stack(H5E_stack_t *estack, bool app_entry, const char *file, const char *func, unsigned line,
hid_t cls_id, hid_t maj_id, hid_t min_id, const char *fmt, va_list *ap)
{
herr_t ret_value = SUCCEED; /* Return value */
htri_t ret_value = true;
/*
* WARNING: We cannot call HERROR() from within this function or else we
@@ -1633,6 +1635,8 @@ H5E__push_stack(H5E_stack_t *estack, bool app_entry, const char *file, const cha
HGOTO_DONE(FAIL);
estack->nused++;
} /* end if */
else
HGOTO_DONE(false);
done:
FUNC_LEAVE_NOAPI(ret_value)
+1 -1
View File
@@ -142,7 +142,7 @@ H5_DLL void H5E__set_default_auto(H5E_stack_t *stk);
H5_DLL H5E_stack_t *H5E__get_current_stack(void);
H5_DLL herr_t H5E__set_current_stack(H5E_stack_t *estack);
H5_DLL ssize_t H5E__get_num(const H5E_stack_t *err_stack);
H5_DLL herr_t H5E__push_stack(H5E_stack_t *estack, bool app_entry, const char *file, const char *func,
H5_DLL htri_t H5E__push_stack(H5E_stack_t *estack, bool app_entry, const char *file, const char *func,
unsigned line, hid_t cls_id, hid_t maj_id, hid_t min_id, const char *fmt,
va_list *ap);
H5_DLL herr_t H5E__print(const H5E_stack_t *estack, FILE *stream, bool bk_compat);
+32
View File
@@ -23,6 +23,7 @@
#include "H5FLprivate.h" /* Free Lists */
#include "H5MMprivate.h" /* Memory management */
#include "H5Opkg.h" /* Object headers */
#include "H5Sprivate.h" /* Dataspaces */
/* Local macros */
@@ -561,6 +562,37 @@ H5O__layout_decode(H5F_t *f, H5O_t H5_ATTR_UNUSED *open_oh, unsigned H5_ATTR_UNU
}
}
/* For a chunked layout, the stored dimensionality includes an extra
* element-size dimension, so it must be exactly one greater than the
* dataspace rank. Validate that here
* to reject malformed files before the inconsistent
* ranks can cause problems during chunk I/O.
*/
if (mesg->type == H5D_CHUNKED && open_oh != NULL) {
htri_t space_exists; /* Whether the dataspace message exists */
if ((space_exists = H5O_msg_exists_oh(open_oh, H5O_SDSPACE_ID)) < 0)
HGOTO_ERROR(H5E_OHDR, H5E_CANTGET, NULL, "can't check for dataspace message");
if (space_exists) {
H5S_extent_t *extent; /* Dataspace extent from the sibling message */
int rank; /* Dataspace rank */
if (NULL == (extent = (H5S_extent_t *)H5O_msg_read_oh(f, open_oh, H5O_SDSPACE_ID, NULL)))
HGOTO_ERROR(H5E_OHDR, H5E_CANTGET, NULL, "can't read dataspace message");
rank = H5S_extent_get_dims(extent, NULL, NULL);
/* Done with the sibling dataspace message */
H5O_msg_free(H5O_SDSPACE_ID, extent);
if (rank < 0)
HGOTO_ERROR(H5E_OHDR, H5E_CANTGET, NULL, "can't get dataspace rank");
if (mesg->u.chunk.ndims != (unsigned)rank + 1)
HGOTO_ERROR(H5E_OHDR, H5E_BADVALUE, NULL,
"dimensionality of chunks doesn't match the dataspace");
}
}
/* Set return value */
ret_value = mesg;
+2
View File
@@ -115,6 +115,7 @@ endforeach ()
# --------------------------------------------------------------------
set (HDF5_REFERENCE_TEST_FILES
aggr.h5
bad_chunk_ndims.h5
bad_compound.h5
bad_offset.h5
be_data.h5
@@ -1165,6 +1166,7 @@ endmacro ()
# generator executables
set (H5_GENERATORS
gen_bad_chunk
gen_bad_offset
gen_bad_ohdr
gen_bogus
+59
View File
@@ -8095,6 +8095,64 @@ error:
return FAIL;
} /* end test_filters_endianess() */
/*-------------------------------------------------------------------------
* Function: test_chunk_dims_mismatch
*
* Purpose: Test that a malformed file whose stored chunk layout
* dimensionality does not match the dataset's dataspace rank
* is properly rejected at open-time.
*
* Return: Success: 0
* Failure: -1
*
*-------------------------------------------------------------------------
*/
static herr_t
test_chunk_dims_mismatch(void)
{
hid_t fid = H5I_INVALID_HID;
hid_t did = H5I_INVALID_HID;
const char *data_file = H5_get_srcdir_filename("bad_chunk_ndims.h5");
TESTING("rejection of chunk dimensionality that mismatches the dataspace");
if ((fid = H5Fopen(data_file, H5F_ACC_RDONLY, H5P_DEFAULT)) < 0) {
printf(" Could not open file %s. Try setting $srcdir to point at the "
"source directory of the test suite\n",
data_file);
goto error;
}
/* Opening the dataset must fail cleanly */
H5E_BEGIN_TRY
{
did = H5Dopen2(fid, "dset", H5P_DEFAULT);
}
H5E_END_TRY
if (did >= 0) {
H5_FAILED();
puts(" Opening a dataset with mismatched chunk/dataspace rank should have failed.");
goto error;
}
if (H5Fclose(fid) < 0)
FAIL_STACK_ERROR;
PASSED();
return SUCCEED;
error:
H5E_BEGIN_TRY
{
H5Dclose(did);
H5Fclose(fid);
}
H5E_END_TRY
return FAIL;
} /* end test_chunk_dims_mismatch() */
/*-------------------------------------------------------------------------
* Function: test_zero_dims
*
@@ -19552,6 +19610,7 @@ main(void)
if (driver_is_default_compatible) {
nerrors += (test_filters_endianess() < 0 ? 1 : 0);
nerrors += (test_chunk_dims_mismatch() < 0 ? 1 : 0);
}
nerrors += (test_zero_dims(file) < 0 ? 1 : 0);
+51
View File
@@ -16,6 +16,9 @@
#include "h5test.h"
#include "H5srcdir.h"
#define H5E_FRIEND
#include "H5Epkg.h" /* For access to H5E-specific macros */
#ifdef H5_USE_16_API
int
main(void)
@@ -836,6 +839,50 @@ error:
return -1;
} /* end test_pause() */
/*-------------------------------------------------------------------------
* Function: test_overflow_stack
*
* Purpose: Test pushing more than H5E_MAX_ENTRIES entries to an error
* stack. Once the stack is full, the next push is simply a
* no-op.
*
* Return: Success: 0
* Failure: -1
*
*-------------------------------------------------------------------------
*/
static herr_t
test_overflow_stack(void)
{
ssize_t err_num;
char err_buf[32];
if (H5Eclear2(H5E_DEFAULT) < 0)
TEST_ERROR;
err_num = H5Eget_num(H5E_DEFAULT);
if (err_num != 0)
TEST_ERROR;
for (int i = 0; i < H5E_MAX_ENTRIES; i++) {
snprintf(err_buf, sizeof(err_buf), "error number %d", i + 1);
if (H5Epush(H5E_DEFAULT, __FILE__, __func__, __LINE__, ERR_CLS, ERR_MAJ_TEST, ERR_MIN_SUBROUTINE,
"%s", err_buf) < 0)
TEST_ERROR;
}
snprintf(err_buf, sizeof(err_buf), "error number %d", H5E_MAX_ENTRIES + 1);
if (H5Epush(H5E_DEFAULT, __FILE__, __func__, __LINE__, ERR_CLS, ERR_MAJ_TEST, ERR_MIN_SUBROUTINE, "%s",
err_buf) < 0)
TEST_ERROR;
return 0;
error:
return -1;
}
/*-------------------------------------------------------------------------
* Function: close_error
*
@@ -1023,6 +1070,10 @@ main(void)
if (test_pause() < 0)
TEST_ERROR;
/* Test pushing more than H5E_MAX_ENTRIES entries to an error stack */
if (test_overflow_stack() < 0)
TEST_ERROR;
/* Close error information */
if (close_error() < 0)
TEST_ERROR;
+179
View File
@@ -0,0 +1,179 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Copyright by The HDF Group. *
* All rights reserved. *
* *
* This file is part of HDF5. The full HDF5 copyright notice, including *
* terms governing use, modification, and redistribution, is contained in *
* the LICENSE file, which can be found at the root of the source code *
* distribution tree, or in https://www.hdfgroup.org/licenses. *
* If you do not have access to either file, you may request a copy from *
* help@hdfgroup.org. *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
* Generate an HDF5 file with a chunked dataset whose stored chunk layout
* dimensionality does not match the dataset's dataspace rank.
*
* A valid 3-D chunked dataset of native int is written, then the version-3
* chunk layout message's "dimensionality" byte is patched from 4 down to 3.
* The stored chunk rank includes an extra element-size dimension, so a valid
* 3-D dataset stores 4. Patching it to 3 makes the layout describe a 2-D
* chunk over a 3-D dataspace.
*
*/
#include "h5test.h"
#define BAD_CHUNK_FILE "bad_chunk_ndims.h5"
#define BAD_CHUNK_DSET "dset"
/* Read an entire file into a newly allocated buffer. */
static unsigned char *
slurp(const char *name, size_t *len_out)
{
FILE *f = fopen(name, "rb");
long len;
unsigned char *buf;
if (!f)
return NULL;
if (fseek(f, 0, SEEK_END) != 0 || (len = ftell(f)) < 0 || fseek(f, 0, SEEK_SET) != 0) {
fclose(f);
return NULL;
}
if (NULL == (buf = malloc((size_t)len))) {
fclose(f);
return NULL;
}
if (fread(buf, 1, (size_t)len, f) != (size_t)len) {
free(buf);
fclose(f);
return NULL;
}
fclose(f);
*len_out = (size_t)len;
return buf;
}
/* Write a buffer back out to a file. */
static int
spew(const char *name, const unsigned char *buf, size_t len)
{
FILE *f = fopen(name, "wb");
if (!f)
return -1;
if (fwrite(buf, 1, len, f) != len) {
fclose(f);
return -1;
}
return fclose(f) == 0 ? 0 : -1;
}
/* Find the unique occurrence of pattern in buf; return offset or (size_t)-1. */
static size_t
find_once(const unsigned char *buf, size_t len, const unsigned char *pat, size_t patlen)
{
size_t i, found = (size_t)-1;
if (patlen == 0 || len < patlen)
return (size_t)-1;
for (i = 0; i <= len - patlen; i++) {
if (memcmp(buf + i, pat, patlen) == 0) {
if (found != (size_t)-1)
return (size_t)-1; /* not unique */
found = i;
}
}
return found;
}
int
main(void)
{
hid_t fapl = H5I_INVALID_HID, file = H5I_INVALID_HID, sid = H5I_INVALID_HID;
hid_t dcpl = H5I_INVALID_HID, dset = H5I_INVALID_HID;
hsize_t dims[3] = {3, 4, 5};
hsize_t chunk[3] = {2, 2, 4}; /* chunk edge lengths, in elements */
int data[3 * 4 * 5];
unsigned char *buf = NULL;
size_t len, off;
int i;
/* The version-3 chunk layout stores, after the 3-byte header (version,
* class, dimensionality) and the 8-byte b-tree address, one little-endian
* uint32 per stored dimension: the three chunk edge lengths (in elements)
* followed by the element size in bytes. For this dataset those on-disk
* values are literally {2, 2, 4, 4} -- the three chunk edge lengths and a
* trailing 4-byte element size; this byte pattern uniquely locates the
* layout message. */
static const unsigned char layout_dims[] = {2, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0};
for (i = 0; i < 3 * 4 * 5; i++)
data[i] = i;
if ((fapl = H5Pcreate(H5P_FILE_ACCESS)) < 0)
TEST_ERROR;
/* The high bound caps the layout message at version 3 to prevent
* drift if new chunk layout formats are introduced in the future. */
if (H5Pset_libver_bounds(fapl, H5F_LIBVER_EARLIEST, H5F_LIBVER_V18) < 0)
TEST_ERROR;
if ((file = H5Fcreate(BAD_CHUNK_FILE, H5F_ACC_TRUNC, H5P_DEFAULT, fapl)) < 0)
TEST_ERROR;
if ((sid = H5Screate_simple(3, dims, NULL)) < 0)
TEST_ERROR;
if ((dcpl = H5Pcreate(H5P_DATASET_CREATE)) < 0)
TEST_ERROR;
if (H5Pset_chunk(dcpl, 3, chunk) < 0)
TEST_ERROR;
if ((dset = H5Dcreate2(file, BAD_CHUNK_DSET, H5T_NATIVE_INT, sid, H5P_DEFAULT, dcpl, H5P_DEFAULT)) < 0)
TEST_ERROR;
if (H5Dwrite(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, data) < 0)
TEST_ERROR;
if (H5Dclose(dset) < 0 || H5Pclose(dcpl) < 0 || H5Sclose(sid) < 0 || H5Fclose(file) < 0 ||
H5Pclose(fapl) < 0)
TEST_ERROR;
/* Patch the chunk layout's dimensionality byte from 4 to 3. */
if (NULL == (buf = slurp(BAD_CHUNK_FILE, &len)))
TEST_ERROR;
if ((off = find_once(buf, len, layout_dims, sizeof(layout_dims))) == (size_t)-1)
TEST_ERROR;
/* Layout header is 3 bytes (version, class, ndims) + 8-byte address before
* the chunk sizes, so the ndims byte is 11 bytes before the sizes.
*
* Patching ndims from 4 to 3 makes the layout describe a 2-D chunk over the
* 3-D dataspace. The decoder then reads only the first three stored sizes,
* {2, 2, 4}, and treats the last of those (4) as the element-size
* dimension. Because the dataset's third chunk edge was chosen to be 4,
* that reinterpreted element size still matches the stored 4-byte element
* size, so the layout decodes consistently and the dataset opens instead
* of being rejected by the element-size check -- which is what let the
* original bug reach the I/O path and crash. */
if (off < 11 || buf[off - 11] != 3 /* version */ || buf[off - 10] != 2 /* chunked */ ||
buf[off - 9] != 4 /* ndims */)
TEST_ERROR;
buf[off - 9] = 3;
if (spew(BAD_CHUNK_FILE, buf, len) < 0)
TEST_ERROR;
free(buf);
printf("Generated %s\n", BAD_CHUNK_FILE);
return EXIT_SUCCESS;
error:
free(buf);
H5E_BEGIN_TRY
{
H5Dclose(dset);
H5Pclose(dcpl);
H5Sclose(sid);
H5Fclose(file);
H5Pclose(fapl);
}
H5E_END_TRY
fprintf(stderr, "failed to generate %s\n", BAD_CHUNK_FILE);
return EXIT_FAILURE;
}
Binary file not shown.