Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions release_docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ We would like to thank the many HDF5 community members who contributed to this r

## Library

### Fixed a crash when reading a dataset with a malformed fill value

An old-style (either version 1 or version 2) fill value message that is marked "defined" but encodes a negative size leaves the fill value with a negative size and no datatype; `H5Pget_fill_value()` then passed that NULL datatype to `H5T_path_find()` and dereferenced it. `H5P_get_fill_value()` now rejects a fill value that has no datatype and returns an error, so the dataset itself remains readable while the corrupt fill value is reported cleanly.

Fixes GitHub issue #6487

Fixes CVE-2026-19024

## Java Library

## Configuration
Expand All @@ -87,6 +95,14 @@ We would like to thank the many HDF5 community members who contributed to this r

## Tools

### Fixed a crash in h5dump binary output of variable-length string datasets

Dumping a variable-length string dataset with more than one element to native binary (`h5dump -b`) could crash. `render_bin_output()` reused a single variable as both the per-element stride and the length of the current string, so after the first element the stride was corrupted and subsequent elements were read from misaligned addresses, dereferencing a garbage pointer. The two uses are now kept separate and variable-length string datasets can be binary dumped safely.

Fixes GitHub issue #6486

Fixes CVE-2026-19023

## Performance

## Fortran API
Expand Down
7 changes: 7 additions & 0 deletions src/H5Pdcpl.c
Original file line number Diff line number Diff line change
Expand Up @@ -3207,6 +3207,13 @@ H5P_get_fill_value(H5P_genplist_t *plist, const H5T_t *type, void *value /*out*/
HGOTO_DONE(SUCCEED);
} /* end if */

/*
* A fill value with a nonzero size must have an associated datatype in
* order to be converted to the destination type.
*/
if (NULL == fill.type)
HGOTO_ERROR(H5E_PLIST, H5E_CANTGET, FAIL, "fill value has no datatype");

/*
* Can we convert between the source and destination datatypes?
*/
Expand Down
2 changes: 2 additions & 0 deletions test/CMakeTests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ endforeach ()
set (HDF5_REFERENCE_TEST_FILES
aggr.h5
bad_compound.h5
bad_fill_value.h5
bad_offset.h5
be_data.h5
be_extlink1.h5
Expand Down Expand Up @@ -1165,6 +1166,7 @@ endmacro ()

# generator executables
set (H5_GENERATORS
gen_bad_fill
gen_bad_offset
gen_bad_ohdr
gen_bogus
Expand Down
85 changes: 84 additions & 1 deletion test/fillval.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ typedef struct {
* for version 1.4(after 1.4.3). To get this data file, simply compile
* gen_old_fill.c with HDF5 library (before v1.5) and run it. */
#define FILE_COMPATIBLE "fill_old.h5"
#define FILE_NAME_RAW "fillval.raw"
/* Crafted file (from gen_bad_fill.c) whose fill value message has a negative
* size and no datatype. */
#define FILE_BAD_FILL "bad_fill_value.h5"
#define FILE_NAME_RAW "fillval.raw"

/*-------------------------------------------------------------------------
* Function: create_compound_type
Expand Down Expand Up @@ -2327,6 +2330,85 @@ test_compatible(void)
return 1;
}

/*-------------------------------------------------------------------------
* Function: test_bad_fill_value
*
* Purpose: Tests that a dataset with a malformed fill value message
* (negative size and no datatype) can be opened and its raw data
* read, and that attempts to access its fill value will fail
* safely.
*
* Return: Success: 0
* Failure: 1
*
*-------------------------------------------------------------------------
*/
static int
test_bad_fill_value(void)
{
hid_t file = H5I_INVALID_HID, dset = H5I_INVALID_HID, dcpl = H5I_INVALID_HID;
int rd_buf[4] = {0, 0, 0, 0};
herr_t fill_status = SUCCEED;
int fill_rd = 0;
const char *testfile = H5_get_srcdir_filename(FILE_BAD_FILL);

TESTING("reading a dataset with a malformed fill value");

if ((file = H5Fopen(testfile, H5F_ACC_RDONLY, H5P_DEFAULT)) < 0) {
printf(" Could not open file %s. Try set $srcdir to point at the "
"source directory of test\n",
testfile);
goto error;
}

/* Raw data should be readable */
if ((dset = H5Dopen2(file, "dset", H5P_DEFAULT)) < 0)
goto error;
if (H5Dread(dset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, rd_buf) < 0)
goto error;
if (rd_buf[0] != 10 || rd_buf[1] != 20 || rd_buf[2] != 30 || rd_buf[3] != 40) {
H5_FAILED();
puts(" Unexpected dataset data read back.");
goto error;
}

if ((dcpl = H5Dget_create_plist(dset)) < 0)
goto error;

/* Attempting to retrieve the malformed fill value should fail gracefully */
H5E_BEGIN_TRY
{
fill_status = H5Pget_fill_value(dcpl, H5T_NATIVE_INT, &fill_rd);
}
H5E_END_TRY
if (fill_status >= 0) {
H5_FAILED();
puts(" H5Pget_fill_value() should have failed on the malformed fill value.");
goto error;
}

if (H5Pclose(dcpl) < 0)
goto error;
if (H5Dclose(dset) < 0)
goto error;
if (H5Fclose(file) < 0)
goto error;

PASSED();

return 0;

error:
H5E_BEGIN_TRY
{
H5Pclose(dcpl);
H5Dclose(dset);
H5Fclose(file);
}
H5E_END_TRY
return 1;
}

/*-------------------------------------------------------------------------
* Function: test_partalloc_cases
*
Expand Down Expand Up @@ -2674,6 +2756,7 @@ main(int argc, char *argv[])

if (driver_is_default_compatible) {
nerrors += test_compatible();
nerrors += test_bad_fill_value();
}
} /* end if */

Expand Down
168 changes: 168 additions & 0 deletions test/gen_bad_fill.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* 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 deliberately malformed fill value message, used
* by the test_bad_fill_value() regression test in fillval.c.
*
* The file contains a single contiguous integer dataset written with an
* explicit fill value using a version-2 (old-style) fill value message. After
* the file is written, the message's 4-byte signed size field is patched to a
* negative value (0xFF000000 == -16777216).
*
* On read, H5O_fill_old_decode() decodes that size with INT32DECODE and, since
* it is not > 0, leaves fill.buf == NULL without normalizing the size to the
* "undefined" sentinel (-1). The dataset's create property list therefore ends
* up with a fill value whose size is neither 0, -1, nor positive and whose
* datatype is NULL.
*
*/

#include "h5test.h"

#define BAD_FILL_FILE "bad_fill_value.h5"
#define BAD_FILL_DSET "dset"
#define BAD_FILL_VALUE 0x1a2b3c4d

/* 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[1] = {4};
int data[4] = {10, 20, 30, 40};
int fillval = BAD_FILL_VALUE;
unsigned char *buf = NULL;
size_t len, off;

/* Anchor for the fill value message's size field: the "fill defined" byte
* (0x01), the 4-byte size (4), and the distinctive fill value that follows.
* This uniquely locates the size field regardless of its file offset. */
static const unsigned char anchor[] = {0x01, 0x04, 0x00, 0x00, 0x00, 0x4d, 0x3c, 0x2b, 0x1a};

if ((fapl = H5Pcreate(H5P_FILE_ACCESS)) < 0)
TEST_ERROR;
if (H5Pset_libver_bounds(fapl, H5F_LIBVER_EARLIEST, H5F_LIBVER_LATEST) < 0)
TEST_ERROR;
if ((file = H5Fcreate(BAD_FILL_FILE, H5F_ACC_TRUNC, H5P_DEFAULT, fapl)) < 0)
TEST_ERROR;
if ((sid = H5Screate_simple(1, dims, NULL)) < 0)
TEST_ERROR;

if ((dcpl = H5Pcreate(H5P_DATASET_CREATE)) < 0)
TEST_ERROR;
if (H5Pset_layout(dcpl, H5D_CONTIGUOUS) < 0)
TEST_ERROR;
if (H5Pset_fill_value(dcpl, H5T_NATIVE_INT, &fillval) < 0)
TEST_ERROR;

if ((dset = H5Dcreate2(file, BAD_FILL_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 fill value message's size field to a negative value. */
if (NULL == (buf = slurp(BAD_FILL_FILE, &len)))
TEST_ERROR;
if ((off = find_once(buf, len, anchor, sizeof(anchor))) == (size_t)-1)
TEST_ERROR;
/* size field starts one byte past the "fill defined" byte: 0x00000000 ->
* 0xFF000000, which INT32DECODE reads as -16777216. */
buf[off + 1] = 0x00;
buf[off + 2] = 0x00;
buf[off + 3] = 0x00;
buf[off + 4] = 0xff;
if (spew(BAD_FILL_FILE, buf, len) < 0)
TEST_ERROR;

free(buf);
printf("Generated %s\n", BAD_FILL_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_FILL_FILE);
return EXIT_FAILURE;
}
Binary file added test/testfiles/bad_fill_value.h5
Binary file not shown.
9 changes: 6 additions & 3 deletions tools/lib/h5tools.c
Original file line number Diff line number Diff line change
Expand Up @@ -1825,24 +1825,27 @@ render_bin_output(FILE *stream, hid_t container, hid_t tid, void *_mem, hsize_t
H5T_str_t pad;
char *s = NULL;
unsigned char tempuchar;
size_t str_len; /* # of bytes to write for this element */

H5TOOLS_DEBUG("H5T_STRING");
pad = H5Tget_strpad(tid);

for (block_index = 0; block_index < block_nelmts; block_index++) {
/* Advance by the datatype size */
mem = ((unsigned char *)_mem) + block_index * size;

if (H5Tis_variable_str(tid)) {
s = *(char **)((void *)mem);
if (s != NULL)
size = strlen(s);
str_len = strlen(s);
else
H5TOOLS_THROW((-1), "NULL string");
}
else {
s = (char *)mem;
s = (char *)mem;
str_len = size;
}
for (i = 0; i < size && (s[i] || pad != H5T_STR_NULLTERM); i++) {
for (i = 0; i < str_len && (s[i] || pad != H5T_STR_NULLTERM); i++) {
memcpy(&tempuchar, &s[i], sizeof(unsigned char));
if (1 != fwrite(&tempuchar, sizeof(unsigned char), 1, stream))
H5TOOLS_THROW((-1), "fwrite failed");
Expand Down
Loading