From: Egbert Eich <eich@suse.com>
Date: Sat Oct 1 15:13:52 2022 +0200
Subject: Pass compact chunk size info to ensure requested elements are within bounds
Patch-mainline: Not yet
Git-repo: https://github.com/HDFGroup/hdf5
Git-commit: 0bce664fd82795520bf706a741a183acb69476c3
References: 

To avoid reading/writing elements out of bounds of a compact chunk, pass
size info and check whether all elements are within the size before attempting
to read/write these elements. Such accesses can occur when accessing malformed
hdf5 files.

This fixes CVE-2018-11205

Signed-off-by: Egbert Eich <eich@suse.com>
Signed-off-by: Egbert Eich <eich@suse.de>
---
 src/H5Dchunk.c   | 36 ++++++++++++++++++++++++++++--------
 src/H5Dcompact.c |  6 ++++++
 src/H5Dpkg.h     |  1 +
 3 files changed, 35 insertions(+), 8 deletions(-)
diff --git a/src/H5Dchunk.c b/src/H5Dchunk.c
index a727d88602..6c3c69b28d 100644
--- a/src/H5Dchunk.c
+++ b/src/H5Dchunk.c
@@ -128,6 +128,7 @@ typedef struct H5D_rdcc_ent_t {
     H5F_block_t            chunk_block;              /*offset/length of chunk in file        */
     hsize_t                chunk_idx;                /*index of chunk in dataset             */
     uint8_t *              chunk;                    /*the unfiltered chunk data        */
+    size_t                 size;                     /*size of chunk          */
     unsigned               idx;                      /*index in hash table            */
     struct H5D_rdcc_ent_t *next;                     /*next item in doubly-linked list    */
     struct H5D_rdcc_ent_t *prev;                     /*previous item in doubly-linked list    */
@@ -303,7 +304,7 @@ static herr_t   H5D__chunk_cache_evict(const H5D_t *dset, H5D_rdcc_ent_t *ent, h
 static hbool_t  H5D__chunk_is_partial_edge_chunk(unsigned dset_ndims, const uint32_t *chunk_dims,
                                                  const hsize_t *chunk_scaled, const hsize_t *dset_dims);
 static void *   H5D__chunk_lock(const H5D_io_info_t *io_info, H5D_chunk_ud_t *udata, hbool_t relax,
-                                hbool_t prev_unfilt_chunk);
+                                hbool_t prev_unfilt_chunk, size_t *ret_size);
 static herr_t   H5D__chunk_unlock(const H5D_io_info_t *io_info, const H5D_chunk_ud_t *udata, hbool_t dirty,
                                   void *chunk, uint32_t naccessed);
 static herr_t   H5D__chunk_cache_prune(const H5D_t *dset, size_t size);
@@ -2455,6 +2456,7 @@ H5D__chunk_read(H5D_io_info_t *io_info, const H5D_type_info_t *type_info, hsize_
     uint32_t      src_accessed_bytes  = 0;       /* Total accessed size in a chunk */
     hbool_t       skip_missing_chunks = FALSE;   /* Whether to skip missing chunks */
     herr_t        ret_value           = SUCCEED; /*return value        */
+    size_t        chunk_size = 0;
 
     FUNC_ENTER_STATIC
 
@@ -2540,11 +2542,12 @@ H5D__chunk_read(H5D_io_info_t *io_info, const H5D_type_info_t *type_info, hsize_
                 src_accessed_bytes = chunk_info->chunk_points * (uint32_t)type_info->src_type_size;
 
                 /* Lock the chunk into the cache */
-                if (NULL == (chunk = H5D__chunk_lock(io_info, &udata, FALSE, FALSE)))
+                if (NULL == (chunk = H5D__chunk_lock(io_info, &udata, FALSE, FALSE, &chunk_size)))
                     HGOTO_ERROR(H5E_IO, H5E_READERROR, FAIL, "unable to read raw data chunk")
 
                 /* Set up the storage buffer information for this chunk */
                 cpt_store.compact.buf = chunk;
+		cpt_store.compact.size = chunk_size;
 
                 /* Point I/O info at contiguous I/O info for this chunk */
                 chk_io_info = &cpt_io_info;
@@ -2604,6 +2607,7 @@ H5D__chunk_write(H5D_io_info_t *io_info, const H5D_type_info_t *type_info, hsize
     hbool_t       cpt_dirty;                    /* Temporary placeholder for compact storage "dirty" flag */
     uint32_t      dst_accessed_bytes = 0;       /* Total accessed size in a chunk */
     herr_t        ret_value          = SUCCEED; /* Return value        */
+    size_t        chunk_size;
 
     FUNC_ENTER_STATIC
 
@@ -2674,11 +2678,12 @@ H5D__chunk_write(H5D_io_info_t *io_info, const H5D_type_info_t *type_info, hsize
                 entire_chunk = FALSE;
 
             /* Lock the chunk into the cache */
-            if (NULL == (chunk = H5D__chunk_lock(io_info, &udata, entire_chunk, FALSE)))
+            if (NULL == (chunk = H5D__chunk_lock(io_info, &udata, entire_chunk, FALSE, &chunk_size)))
                 HGOTO_ERROR(H5E_IO, H5E_READERROR, FAIL, "unable to read raw data chunk")
 
             /* Set up the storage buffer information for this chunk */
             cpt_store.compact.buf = chunk;
+	    cpt_store.compact.size = chunk_size;
 
             /* Point I/O info at main I/O info for this chunk */
             chk_io_info = &cpt_io_info;
@@ -3674,7 +3679,7 @@ done:
  *-------------------------------------------------------------------------
  */
 static void *
-H5D__chunk_lock(const H5D_io_info_t *io_info, H5D_chunk_ud_t *udata, hbool_t relax, hbool_t prev_unfilt_chunk)
+H5D__chunk_lock(const H5D_io_info_t *io_info, H5D_chunk_ud_t *udata, hbool_t relax, hbool_t prev_unfilt_chunk, size_t *ret_size)
 {
     const H5D_t *      dset = io_info->dset; /* Local pointer to the dataset info */
     const H5O_pline_t *pline =
@@ -3691,6 +3696,7 @@ H5D__chunk_lock(const H5D_io_info_t *io_info, H5D_chunk_ud_t *udata, hbool_t rel
     hbool_t             disable_filters = FALSE; /* Whether to disable filters (when adding to cache) */
     void *              chunk           = NULL;  /*the file chunk    */
     void *              ret_value       = NULL;  /* Return value         */
+    size_t              chunk_size_ret = 0;
 
     FUNC_ENTER_STATIC
 
@@ -3756,6 +3762,7 @@ H5D__chunk_lock(const H5D_io_info_t *io_info, H5D_chunk_ud_t *udata, hbool_t rel
                 ent->chunk = (uint8_t *)H5D__chunk_mem_xfree(ent->chunk, old_pline);
                 ent->chunk = (uint8_t *)chunk;
                 chunk      = NULL;
+		ent->size = chunk_size;
 
                 /* Mark the chunk as having filters disabled as well as "newly
                  * disabled" so it is inserted on flush */
@@ -3783,6 +3790,7 @@ H5D__chunk_lock(const H5D_io_info_t *io_info, H5D_chunk_ud_t *udata, hbool_t rel
                 ent->chunk = (uint8_t *)H5D__chunk_mem_xfree(ent->chunk, old_pline);
                 ent->chunk = (uint8_t *)chunk;
                 chunk      = NULL;
+		ent->size = chunk_size;
 
                 /* Mark the chunk as having filters enabled */
                 ent->edge_chunk_state &= ~(H5D_RDCC_DISABLE_FILTERS | H5D_RDCC_NEWLY_DISABLED_FILTERS);
@@ -3862,6 +3870,7 @@ H5D__chunk_lock(const H5D_io_info_t *io_info, H5D_chunk_ud_t *udata, hbool_t rel
             /* In the case that some dataset functions look through this data,
              * clear it to all 0s. */
             HDmemset(chunk, 0, chunk_size);
+	    chunk_size_ret = chunk_size;
         } /* end if */
         else {
             /*
@@ -3883,6 +3892,7 @@ H5D__chunk_lock(const H5D_io_info_t *io_info, H5D_chunk_ud_t *udata, hbool_t rel
                 if (H5F_block_read(dset->oloc.file, H5FD_MEM_DRAW, chunk_addr, my_chunk_alloc, chunk) < 0)
                     HGOTO_ERROR(H5E_IO, H5E_READERROR, NULL, "unable to read raw data chunk")
 
+		chunk_size_ret = my_chunk_alloc;
                 if (old_pline && old_pline->nused) {
                     H5Z_EDC_t err_detect; /* Error detection info */
                     H5Z_cb_t  filter_cb;  /* I/O filter callback function */
@@ -3896,6 +3906,7 @@ H5D__chunk_lock(const H5D_io_info_t *io_info, H5D_chunk_ud_t *udata, hbool_t rel
                     if (H5Z_pipeline(old_pline, H5Z_FLAG_REVERSE, &(udata->filter_mask), err_detect,
                                      filter_cb, &my_chunk_alloc, &buf_alloc, &chunk) < 0)
                         HGOTO_ERROR(H5E_DATASET, H5E_CANTFILTER, NULL, "data pipeline read failed")
+		    chunk_size_ret = buf_alloc;
 
                     /* Reallocate chunk if necessary */
                     if (udata->new_unfilt_chunk) {
@@ -3906,6 +3917,8 @@ H5D__chunk_lock(const H5D_io_info_t *io_info, H5D_chunk_ud_t *udata, hbool_t rel
                             HGOTO_ERROR(H5E_RESOURCE, H5E_NOSPACE, NULL,
                                         "memory allocation failed for raw data chunk")
                         } /* end if */
+			chunk_size_ret = my_chunk_alloc;
+			/* orly? */
                         H5MM_memcpy(chunk, tmp_chunk, chunk_size);
                         (void)H5D__chunk_mem_xfree(tmp_chunk, old_pline);
                     } /* end if */
@@ -3926,6 +3939,7 @@ H5D__chunk_lock(const H5D_io_info_t *io_info, H5D_chunk_ud_t *udata, hbool_t rel
                     HGOTO_ERROR(H5E_RESOURCE, H5E_NOSPACE, NULL,
                                 "memory allocation failed for raw data chunk")
 
+		chunk_size_ret = chunk_size;
                 if (H5P_is_fill_value_defined(fill, &fill_status) < 0)
                     HGOTO_ERROR(H5E_PLIST, H5E_CANTGET, NULL, "can't tell if fill value defined")
 
@@ -3991,6 +4005,7 @@ H5D__chunk_lock(const H5D_io_info_t *io_info, H5D_chunk_ud_t *udata, hbool_t rel
                 H5_CHECKED_ASSIGN(ent->rd_count, uint32_t, chunk_size, size_t);
                 H5_CHECKED_ASSIGN(ent->wr_count, uint32_t, chunk_size, size_t);
                 ent->chunk = (uint8_t *)chunk;
+		ent->size = chunk_size_ret;
 
                 /* Add it to the cache */
                 HDassert(NULL == rdcc->slot[udata->idx_hint]);
@@ -4024,6 +4039,7 @@ H5D__chunk_lock(const H5D_io_info_t *io_info, H5D_chunk_ud_t *udata, hbool_t rel
         HDassert(!ent->locked);
         ent->locked = TRUE;
         chunk       = ent->chunk;
+	chunk_size_ret = ent->size;
     } /* end if */
     else
         /*
@@ -4035,6 +4051,8 @@ H5D__chunk_lock(const H5D_io_info_t *io_info, H5D_chunk_ud_t *udata, hbool_t rel
 
     /* Set return value */
     ret_value = chunk;
+    if (ret_size != NULL)
+        *ret_size = chunk_size_ret;
 
 done:
     /* Release the fill buffer info, if it's been initialized */
@@ -4043,9 +4061,11 @@ done:
 
     /* Release the chunk allocated, on error */
     if (!ret_value)
-        if (chunk)
+        if (chunk) {
             chunk = H5D__chunk_mem_xfree(chunk, pline);
-
+	    if (ret_size != NULL)
+	        *ret_size = 0;
+	}
     FUNC_LEAVE_NOAPI(ret_value)
 } /* end H5D__chunk_lock() */
 
@@ -4836,7 +4856,7 @@ H5D__chunk_update_old_edge_chunks(H5D_t *dset, hsize_t old_dim[])
             if (H5F_addr_defined(chk_udata.chunk_block.offset) || (UINT_MAX != chk_udata.idx_hint)) {
                 /* Lock the chunk into cache.  H5D__chunk_lock will take care of
                  * updating the chunk to no longer be an edge chunk. */
-                if (NULL == (chunk = (void *)H5D__chunk_lock(&chk_io_info, &chk_udata, FALSE, TRUE)))
+	        if (NULL == (chunk = (void *)H5D__chunk_lock(&chk_io_info, &chk_udata, FALSE, TRUE, NULL)))
                     HGOTO_ERROR(H5E_DATASET, H5E_READERROR, FAIL, "unable to lock raw data chunk")
 
                 /* Unlock the chunk */
@@ -5143,7 +5163,7 @@ H5D__chunk_prune_fill(H5D_chunk_it_ud1_t *udata, hbool_t new_unfilt_chunk)
         HGOTO_ERROR(H5E_DATASET, H5E_CANTSELECT, FAIL, "unable to select hyperslab")
 
     /* Lock the chunk into the cache, to get a pointer to the chunk buffer */
-    if (NULL == (chunk = (void *)H5D__chunk_lock(io_info, &chk_udata, FALSE, FALSE)))
+	  if (NULL == (chunk = (void *)H5D__chunk_lock(io_info, &chk_udata, FALSE, FALSE, NULL)))
         HGOTO_ERROR(H5E_DATASET, H5E_READERROR, FAIL, "unable to lock raw data chunk")
 
     /* Fill the selection in the memory buffer */
diff --git a/src/H5Dcompact.c b/src/H5Dcompact.c
index 8d823adcea..d0831c019a 100644
--- a/src/H5Dcompact.c
+++ b/src/H5Dcompact.c
@@ -234,6 +234,7 @@ H5D__compact_io_init(const H5D_io_info_t *io_info, const H5D_type_info_t H5_ATTR
     FUNC_ENTER_STATIC_NOERR
 
     io_info->store->compact.buf   = io_info->dset->shared->layout.storage.u.compact.buf;
+    io_info->store->compact.size = io_info->dset->shared->layout.storage.u.compact.size;
     io_info->store->compact.dirty = &io_info->dset->shared->layout.storage.u.compact.dirty;
 
     FUNC_LEAVE_NOAPI(SUCCEED)
@@ -267,6 +268,8 @@ H5D__compact_readvv(const H5D_io_info_t *io_info, size_t dset_max_nseq, size_t *
     FUNC_ENTER_STATIC
 
     HDassert(io_info);
+    if (io_info->store->compact.size < *(dset_offset_arr + dset_max_nseq - 1) + *(dset_size_arr + dset_max_nseq - 1))
+       HGOTO_ERROR(H5E_IO, H5E_WRITEERROR, FAIL, "source size less than requested data")
 
     /* Use the vectorized memory copy routine to do actual work */
     if ((ret_value = H5VM_memcpyvv(io_info->u.rbuf, mem_max_nseq, mem_curr_seq, mem_size_arr, mem_offset_arr,
@@ -309,6 +312,9 @@ H5D__compact_writevv(const H5D_io_info_t *io_info, size_t dset_max_nseq, size_t
     FUNC_ENTER_STATIC
 
     HDassert(io_info);
+    if (io_info->store->compact.size < *(dset_offset_arr + dset_max_nseq - 1) + *(dset_size_arr + dset_max_nseq - 1)) {
+                 HGOTO_ERROR(H5E_IO, H5E_WRITEERROR, FAIL, "source size less than requested data")
+    }
 
     /* Use the vectorized memory copy routine to do actual work */
     if ((ret_value = H5VM_memcpyvv(io_info->store->compact.buf, dset_max_nseq, dset_curr_seq, dset_size_arr,
diff --git a/src/H5Dpkg.h b/src/H5Dpkg.h
index 16e101c012..ccdd1675a1 100644
--- a/src/H5Dpkg.h
+++ b/src/H5Dpkg.h
@@ -194,6 +194,7 @@ typedef struct {
 typedef struct {
     void *   buf;   /* Buffer for compact dataset */
     hbool_t *dirty; /* Pointer to dirty flag to mark */
+    size_t size;    /* Buffer size for compact dataset */
 } H5D_compact_storage_t;
 
 typedef union H5D_storage_t {
