From cbce4c2ecf6f5557605890eec125ecfaa4371131 Mon Sep 17 00:00:00 2001 From: Libo Chen Date: Fri, 30 Jan 2026 16:43:04 +0800 Subject: [PATCH] Fix CVE-2025-2308 (#5960) A malformed file can cause the scale-offset filter to have too little input data causing a heap buffer overflow. Additional checks on the maximum buffer length are required during the decompression. This PR fixes CVE-2025-2308. CVE: CVE-2025-2308 Upstream-Status: Backport [https://github.com/HDFGroup/hdf5/commit/2ce7fdc4cf147d280aa6d49686297faacc250e40] Signed-off-by: Libo Chen --- src/H5Zscaleoffset.c | 177 ++-- src/H5Zscaleoffset.c.orig | 1781 +++++++++++++++++++++++++++++++++++++ 1 files changed, 105 insertions(+), 72 deletions(-) create mode 100644 src/H5Zscaleoffset.c.orig diff --git a/src/H5Zscaleoffset.c b/src/H5Zscaleoffset.c index fbf12d6..8355b13 100644 --- a/src/H5Zscaleoffset.c +++ b/src/H5Zscaleoffset.c @@ -69,21 +69,22 @@ static herr_t H5Z__scaleoffset_precompress_fd(void *data, unsigned d_nelmts, enu static herr_t H5Z__scaleoffset_postdecompress_fd(void *data, unsigned d_nelmts, enum H5Z_scaleoffset_t type, unsigned filavail, const unsigned cd_values[], uint32_t minbits, unsigned long long minval, double D_val); -static void H5Z__scaleoffset_next_byte(size_t *j, unsigned *buf_len); -static void H5Z__scaleoffset_decompress_one_byte(unsigned char *data, size_t data_offset, unsigned k, - unsigned begin_i, const unsigned char *buffer, size_t *j, - unsigned *buf_len, parms_atomic p, unsigned dtype_len); +static void H5Z__scaleoffset_next_byte(size_t *j, unsigned *bits_to_fill); +static herr_t H5Z__scaleoffset_decompress_one_byte(unsigned char *data, size_t data_offset, unsigned k, + unsigned begin_i, const unsigned char *buffer, + size_t buf_size, size_t *j, unsigned *bits_to_fill, + parms_atomic p, unsigned dtype_len); static void H5Z__scaleoffset_compress_one_byte(const unsigned char *data, size_t data_offset, unsigned k, unsigned begin_i, unsigned char *buffer, size_t *j, - unsigned *buf_len, parms_atomic p, unsigned dtype_len); -static void H5Z__scaleoffset_decompress_one_atomic(unsigned char *data, size_t data_offset, - unsigned char *buffer, size_t *j, unsigned *buf_len, - parms_atomic p); + unsigned *bits_to_fill, parms_atomic p, unsigned dtype_len); +static herr_t H5Z__scaleoffset_decompress_one_atomic(unsigned char *data, size_t data_offset, + unsigned char *buffer, size_t buf_size, size_t *j, + unsigned *bits_to_fill, parms_atomic p); static void H5Z__scaleoffset_compress_one_atomic(unsigned char *data, size_t data_offset, - unsigned char *buffer, size_t *j, unsigned *buf_len, + unsigned char *buffer, size_t *j, unsigned *bits_to_fill, parms_atomic p); -static void H5Z__scaleoffset_decompress(unsigned char *data, unsigned d_nelmts, unsigned char *buffer, - parms_atomic p); +static herr_t H5Z__scaleoffset_decompress(unsigned char *data, unsigned d_nelmts, unsigned char *buffer, + size_t buf_size, parms_atomic p); static void H5Z__scaleoffset_compress(unsigned char *data, unsigned d_nelmts, unsigned char *buffer, size_t buffer_size, parms_atomic p); @@ -1261,8 +1262,11 @@ H5Z__filter_scaleoffset(unsigned flags, size_t cd_nelmts, const unsigned cd_valu } /* decompress the buffer if minbits not equal to zero */ - if (minbits != 0) - H5Z__scaleoffset_decompress(outbuf, d_nelmts, (unsigned char *)(*buf) + buf_offset, p); + if (minbits != 0) { + if (H5Z__scaleoffset_decompress(outbuf, d_nelmts, (unsigned char *)(*buf) + buf_offset, + *buf_size - buf_offset, p)) + HGOTO_ERROR(H5E_PLINE, H5E_BADVALUE, 0, "Scaleoffset decompression failed"); + } else { /* fill value is not defined and all data elements have the same value */ for (i = 0; i < size_out; i++) @@ -1603,55 +1607,69 @@ done: } static void -H5Z__scaleoffset_next_byte(size_t *j, unsigned *buf_len) +H5Z__scaleoffset_next_byte(size_t *j, unsigned *bits_to_fill) { ++(*j); - *buf_len = 8 * sizeof(unsigned char); + *bits_to_fill = 8 * sizeof(unsigned char); } -static void +static herr_t H5Z__scaleoffset_decompress_one_byte(unsigned char *data, size_t data_offset, unsigned k, unsigned begin_i, - const unsigned char *buffer, size_t *j, unsigned *buf_len, - parms_atomic p, unsigned dtype_len) + const unsigned char *buffer, size_t buf_size, size_t *j, + unsigned *bits_to_fill, parms_atomic p, unsigned dtype_len) { - unsigned dat_len; /* dat_len is the number of bits to be copied in each data byte */ - unsigned char val; /* value to be copied in each data byte */ + unsigned bits_to_copy; /* bits_to_copy is the number of bits to be copied in each data byte */ + unsigned char val; /* value to be copied in each data byte */ + herr_t ret_value = SUCCEED; /* Return value */ + + FUNC_ENTER_PACKAGE + + if (*j >= buf_size) + HGOTO_ERROR(H5E_PLINE, H5E_BADVALUE, 0, "Buffer too short"); /* initialize value and bits of unsigned char to be copied */ val = buffer[*j]; if (k == begin_i) - dat_len = 8 - (dtype_len - p.minbits) % 8; + bits_to_copy = 8 - (dtype_len - p.minbits) % 8; else - dat_len = 8; + bits_to_copy = 8; - if (*buf_len > dat_len) { - data[data_offset + k] = - (unsigned char)((unsigned)(val >> (*buf_len - dat_len)) & (unsigned)(~((unsigned)~0 << dat_len))); - *buf_len -= dat_len; + if (*bits_to_fill > bits_to_copy) { + data[data_offset + k] = (unsigned char)((unsigned)(val >> (*bits_to_fill - bits_to_copy)) & + (unsigned)(~((unsigned)~0 << bits_to_copy))); + *bits_to_fill -= bits_to_copy; } /* end if */ else { data[data_offset + k] = - (unsigned char)((val & ~((unsigned)(~0) << *buf_len)) << (dat_len - *buf_len)); - dat_len -= *buf_len; - H5Z__scaleoffset_next_byte(j, buf_len); - if (dat_len == 0) - return; + (unsigned char)((val & ~((unsigned)(~0) << *bits_to_fill)) << (bits_to_copy - *bits_to_fill)); + bits_to_copy -= *bits_to_fill; + H5Z__scaleoffset_next_byte(j, bits_to_fill); + if (bits_to_copy == 0) + goto done; + else if (*j >= buf_size) + HGOTO_ERROR(H5E_PLINE, H5E_BADVALUE, 0, "Buffer too short"); val = buffer[*j]; - data[data_offset + k] |= - (unsigned char)((unsigned)(val >> (*buf_len - dat_len)) & ~((unsigned)(~0) << dat_len)); - *buf_len -= dat_len; + data[data_offset + k] |= (unsigned char)((unsigned)(val >> (*bits_to_fill - bits_to_copy)) & + ~((unsigned)(~0) << bits_to_copy)); + *bits_to_fill -= bits_to_copy; } /* end else */ + +done: + FUNC_LEAVE_NOAPI(ret_value) } -static void +static herr_t H5Z__scaleoffset_decompress_one_atomic(unsigned char *data, size_t data_offset, unsigned char *buffer, - size_t *j, unsigned *buf_len, parms_atomic p) + size_t buf_size, size_t *j, unsigned *bits_to_fill, parms_atomic p) { /* begin_i: the index of byte having first significant bit */ unsigned begin_i; unsigned dtype_len; int k; + herr_t ret_value = SUCCEED; /* Return value */ + + FUNC_ENTER_PACKAGE assert(p.minbits > 0); @@ -1661,8 +1679,9 @@ H5Z__scaleoffset_decompress_one_atomic(unsigned char *data, size_t data_offset, begin_i = p.size - 1 - (dtype_len - p.minbits) / 8; for (k = (int)begin_i; k >= 0; k--) - H5Z__scaleoffset_decompress_one_byte(data, data_offset, (unsigned)k, begin_i, buffer, j, buf_len, - p, dtype_len); + if (H5Z__scaleoffset_decompress_one_byte(data, data_offset, (unsigned)k, begin_i, buffer, + buf_size, j, bits_to_fill, p, dtype_len)) + HGOTO_ERROR(H5E_PLINE, H5E_BADVALUE, 0, "Atomic decompression failed"); } else { /* big endian */ assert(p.mem_order == H5Z_SCALEOFFSET_ORDER_BE); @@ -1670,67 +1689,81 @@ H5Z__scaleoffset_decompress_one_atomic(unsigned char *data, size_t data_offset, begin_i = (dtype_len - p.minbits) / 8; for (k = (int)begin_i; k <= (int)(p.size - 1); k++) - H5Z__scaleoffset_decompress_one_byte(data, data_offset, (unsigned)k, begin_i, buffer, j, buf_len, - p, dtype_len); + if (H5Z__scaleoffset_decompress_one_byte(data, data_offset, (unsigned)k, begin_i, buffer, + buf_size, j, bits_to_fill, p, dtype_len)) + HGOTO_ERROR(H5E_PLINE, H5E_BADVALUE, 0, "Atomic decompression failed"); } + +done: + FUNC_LEAVE_NOAPI(ret_value) } -static void -H5Z__scaleoffset_decompress(unsigned char *data, unsigned d_nelmts, unsigned char *buffer, parms_atomic p) +static herr_t +H5Z__scaleoffset_decompress(unsigned char *data, unsigned d_nelmts, unsigned char *buffer, size_t buf_size, + parms_atomic p) { /* i: index of data, j: index of buffer, - buf_len: number of bits to be filled in current byte */ + bits_to_fill: number of bits to be filled in current byte */ size_t i, j; - unsigned buf_len; + unsigned bits_to_fill; + herr_t ret_value = SUCCEED; /* Return value */ + + FUNC_ENTER_PACKAGE /* must initialize to zeros */ for (i = 0; i < d_nelmts * (size_t)p.size; i++) data[i] = 0; /* initialization before the loop */ - j = 0; - buf_len = sizeof(unsigned char) * 8; + j = 0; + bits_to_fill = sizeof(unsigned char) * 8; /* decompress */ for (i = 0; i < d_nelmts; i++) - H5Z__scaleoffset_decompress_one_atomic(data, i * p.size, buffer, &j, &buf_len, p); + if (H5Z__scaleoffset_decompress_one_atomic(data, i * p.size, buffer, buf_size, &j, &bits_to_fill, p)) + HGOTO_ERROR(H5E_PLINE, H5E_BADVALUE, 0, "Scaleoffset decompression failed"); + +done: + FUNC_LEAVE_NOAPI(ret_value) } static void H5Z__scaleoffset_compress_one_byte(const unsigned char *data, size_t data_offset, unsigned k, - unsigned begin_i, unsigned char *buffer, size_t *j, unsigned *buf_len, + unsigned begin_i, unsigned char *buffer, size_t *j, unsigned *bits_to_fill, parms_atomic p, unsigned dtype_len) { - unsigned dat_len; /* dat_len is the number of bits to be copied in each data byte */ - unsigned char val; /* value to be copied in each data byte */ + unsigned bits_to_copy; /* bits_to_copy is the number of bits to be copied in each data byte */ + unsigned char val; /* value to be copied in each data byte */ /* initialize value and bits of unsigned char to be copied */ val = data[data_offset + k]; if (k == begin_i) - dat_len = 8 - (dtype_len - p.minbits) % 8; + bits_to_copy = 8 - (dtype_len - p.minbits) % 8; else - dat_len = 8; + bits_to_copy = 8; - if (*buf_len > dat_len) { - buffer[*j] |= (unsigned char)((val & ~((unsigned)(~0) << dat_len)) << (*buf_len - dat_len)); - *buf_len -= dat_len; + if (*bits_to_fill > bits_to_copy) { + buffer[*j] |= + (unsigned char)((val & ~((unsigned)(~0) << bits_to_copy)) << (*bits_to_fill - bits_to_copy)); + *bits_to_fill -= bits_to_copy; } else { - buffer[*j] |= - (unsigned char)((unsigned)(val >> (dat_len - *buf_len)) & ~((unsigned)(~0) << *buf_len)); - dat_len -= *buf_len; - H5Z__scaleoffset_next_byte(j, buf_len); - if (dat_len == 0) + buffer[*j] |= (unsigned char)((unsigned)(val >> (bits_to_copy - *bits_to_fill)) & + ~((unsigned)(~0) << *bits_to_fill)); + bits_to_copy -= *bits_to_fill; + H5Z__scaleoffset_next_byte(j, bits_to_fill); + if (bits_to_copy == 0) return; - buffer[*j] = (unsigned char)((val & ~((unsigned)(~0) << dat_len)) << (*buf_len - dat_len)); - *buf_len -= dat_len; + buffer[*j] = + (unsigned char)((val & ~((unsigned)(~0) << bits_to_copy)) << (*bits_to_fill - bits_to_copy)); + *bits_to_fill -= bits_to_copy; } /* end else */ } static void H5Z__scaleoffset_compress_one_atomic(unsigned char *data, size_t data_offset, unsigned char *buffer, - size_t *j, unsigned *buf_len, parms_atomic p) + size_t *j, unsigned *bits_to_fill, parms_atomic p) { /* begin_i: the index of byte having first significant bit */ unsigned begin_i; @@ -1745,16 +1778,16 @@ H5Z__scaleoffset_compress_one_atomic(unsigned char *data, size_t data_offset, un begin_i = p.size - 1 - (dtype_len - p.minbits) / 8; for (k = (int)begin_i; k >= 0; k--) - H5Z__scaleoffset_compress_one_byte(data, data_offset, (unsigned)k, begin_i, buffer, j, buf_len, p, - dtype_len); + H5Z__scaleoffset_compress_one_byte(data, data_offset, (unsigned)k, begin_i, buffer, j, + bits_to_fill, p, dtype_len); } else { /* big endian */ assert(p.mem_order == H5Z_SCALEOFFSET_ORDER_BE); begin_i = (dtype_len - p.minbits) / 8; for (k = (int)begin_i; k <= (int)(p.size - 1); k++) - H5Z__scaleoffset_compress_one_byte(data, data_offset, (unsigned)k, begin_i, buffer, j, buf_len, p, - dtype_len); + H5Z__scaleoffset_compress_one_byte(data, data_offset, (unsigned)k, begin_i, buffer, j, + bits_to_fill, p, dtype_len); } } @@ -1763,19 +1796,19 @@ H5Z__scaleoffset_compress(unsigned char *data, unsigned d_nelmts, unsigned char parms_atomic p) { /* i: index of data, j: index of buffer, - buf_len: number of bits to be filled in current byte */ + bits_to_fill: number of bits to be filled in current byte */ size_t i, j; - unsigned buf_len; + unsigned bits_to_fill; /* must initialize buffer to be zeros */ for (j = 0; j < buffer_size; j++) buffer[j] = 0; /* initialization before the loop */ - j = 0; - buf_len = sizeof(unsigned char) * 8; + j = 0; + bits_to_fill = sizeof(unsigned char) * 8; /* compress */ for (i = 0; i < d_nelmts; i++) - H5Z__scaleoffset_compress_one_atomic(data, i * p.size, buffer, &j, &buf_len, p); + H5Z__scaleoffset_compress_one_atomic(data, i * p.size, buffer, &j, &bits_to_fill, p); } -- 2.34.1