Skip to content

Commit eaa68ee

Browse files
committed
Fix issue with handling of failure during discard of metadata cache entries
When discarding a metadata cache entry after flushing it, errors during the discard process could cause the library to skip calling the 'free_icr' callback for the entry. This could result in resource leaks and the inability of the cache to be fully flushed and closed due to issues such as pinned entries remaining in the cache. This has been fixed by noting errors during the discard process, but attempting to fully free a cache entry before signalling that an error has occurred. Fixes CVE-2025-7068
1 parent 6caaf23 commit eaa68ee

File tree

2 files changed

+154
-111
lines changed

2 files changed

+154
-111
lines changed

release_docs/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,12 @@ HDF5 release, platforms tested, and known problems in this release.
454454
# 🪲 Bug Fixes
455455
456456
## Library
457+
- Fixed security issue CVE-2025-7068
458+
459+
Failures during the discard process on a metadata cache entry could cause the library to skip calling the callback to free the cache entry. This could result in resource leaks and issues with flushing and closing the metadata cache during file close. This has been fixed by noting errors during the discard process, but attempting to fully free a cache entry before signalling that an error has occurred.
460+
461+
Fixes GitHub issues #5578 and #4586
462+
457463
- Check for overflow in decoded heap block addresses
458464
459465
Currently, we do not check for overflow when decoding addresses from the heap, which can cause overflow problems. We've added a check in H5HL__fl_deserialize to ensure no overflow can occur.

src/H5Centry.c

Lines changed: 148 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ static herr_t H5C__pin_entry_from_client(H5C_t *cache_ptr, H5C_cache_entry_t *en
6363
static herr_t H5C__unpin_entry_real(H5C_t *cache_ptr, H5C_cache_entry_t *entry_ptr, bool update_rp);
6464
static herr_t H5C__unpin_entry_from_client(H5C_t *cache_ptr, H5C_cache_entry_t *entry_ptr, bool update_rp);
6565
static herr_t H5C__generate_image(H5F_t *f, H5C_t *cache_ptr, H5C_cache_entry_t *entry_ptr);
66+
static herr_t H5C__discard_single_entry(H5F_t *f, H5C_t *cache_ptr, H5C_cache_entry_t *entry_ptr,
67+
bool destroy_entry, bool free_file_space,
68+
bool suppress_image_entry_frees);
6669
static herr_t H5C__verify_len_eoa(H5F_t *f, const H5C_class_t *type, haddr_t addr, size_t *len, bool actual);
6770
static void *H5C__load_entry(H5F_t *f,
6871
#ifdef H5_HAVE_PARALLEL
@@ -753,118 +756,13 @@ H5C__flush_single_entry(H5F_t *f, H5C_cache_entry_t *entry_ptr, unsigned flags)
753756
* Now discard the entry if appropriate.
754757
*/
755758
if (destroy) {
756-
/* Sanity check */
757-
assert(0 == entry_ptr->flush_dep_nparents);
758-
759-
/* if both suppress_image_entry_frees and entry_ptr->include_in_image
760-
* are true, simply set entry_ptr->image_ptr to NULL, as we have
761-
* another pointer to the buffer in an instance of H5C_image_entry_t
762-
* in cache_ptr->image_entries.
763-
*
764-
* Otherwise, free the buffer if it exists.
765-
*/
766-
if (suppress_image_entry_frees && entry_ptr->include_in_image)
767-
entry_ptr->image_ptr = NULL;
768-
else if (entry_ptr->image_ptr != NULL)
769-
entry_ptr->image_ptr = H5MM_xfree(entry_ptr->image_ptr);
770-
771-
/* If the entry is not a prefetched entry, verify that the flush
772-
* dependency parents addresses array has been transferred.
773-
*
774-
* If the entry is prefetched, the free_isr routine will dispose of
775-
* the flush dependency parents addresses array if necessary.
776-
*/
777-
if (!entry_ptr->prefetched) {
778-
assert(0 == entry_ptr->fd_parent_count);
779-
assert(NULL == entry_ptr->fd_parent_addrs);
780-
} /* end if */
781-
782-
/* Check whether we should free the space in the file that
783-
* the entry occupies
784-
*/
785-
if (free_file_space) {
786-
hsize_t fsf_size;
787-
788-
/* Sanity checks */
789-
assert(H5_addr_defined(entry_ptr->addr));
790-
assert(!H5F_IS_TMP_ADDR(f, entry_ptr->addr));
791-
#ifndef NDEBUG
792-
{
793-
size_t curr_len;
794-
795-
/* Get the actual image size for the thing again */
796-
entry_ptr->type->image_len((void *)entry_ptr, &curr_len);
797-
assert(curr_len == entry_ptr->size);
798-
}
799-
#endif
800-
801-
/* If the file space free size callback is defined, use
802-
* it to get the size of the block of file space to free.
803-
* Otherwise use entry_ptr->size.
804-
*/
805-
if (entry_ptr->type->fsf_size) {
806-
if ((entry_ptr->type->fsf_size)((void *)entry_ptr, &fsf_size) < 0)
807-
HGOTO_ERROR(H5E_CACHE, H5E_CANTFREE, FAIL, "unable to get file space free size");
808-
} /* end if */
809-
else /* no file space free size callback -- use entry size */
810-
fsf_size = entry_ptr->size;
811-
812-
/* Release the space on disk */
813-
if (H5MF_xfree(f, entry_ptr->type->mem_type, entry_ptr->addr, fsf_size) < 0)
814-
HGOTO_ERROR(H5E_CACHE, H5E_CANTFREE, FAIL, "unable to free file space for cache entry");
815-
} /* end if ( free_file_space ) */
816-
817-
/* Reset the pointer to the cache the entry is within. -QAK */
818-
entry_ptr->cache_ptr = NULL;
819-
820-
/* increment entries_removed_counter and set
821-
* last_entry_removed_ptr. As we are likely abuut to
822-
* free the entry, recall that last_entry_removed_ptr
823-
* must NEVER be dereferenced.
824-
*
825-
* Recall that these fields are maintained to allow functions
826-
* that perform scans of lists of entries to detect the
827-
* unexpected removal of entries (via expunge, eviction,
828-
* or take ownership at present), so that they can re-start
829-
* their scans if necessary.
830-
*
831-
* Also check if the entry we are watching for removal is being
832-
* removed (usually the 'next' entry for an iteration) and reset
833-
* it to indicate that it was removed.
834-
*/
835-
cache_ptr->entries_removed_counter++;
836-
cache_ptr->last_entry_removed_ptr = entry_ptr;
837-
838-
if (entry_ptr == cache_ptr->entry_watched_for_removal)
839-
cache_ptr->entry_watched_for_removal = NULL;
840-
841-
/* Check for actually destroying the entry in memory */
842-
/* (As opposed to taking ownership of it) */
843-
if (destroy_entry) {
844-
if (entry_ptr->is_dirty) {
845-
/* Reset dirty flag */
846-
entry_ptr->is_dirty = false;
847-
848-
/* If the entry's type has a 'notify' callback send a
849-
* 'entry cleaned' notice now that the entry is fully
850-
* integrated into the cache.
851-
*/
852-
if (entry_ptr->type->notify &&
853-
(entry_ptr->type->notify)(H5C_NOTIFY_ACTION_ENTRY_CLEANED, entry_ptr) < 0)
854-
HGOTO_ERROR(H5E_CACHE, H5E_CANTNOTIFY, FAIL,
855-
"can't notify client about entry dirty flag cleared");
856-
} /* end if */
857-
858-
/* verify that the image has been freed */
859-
assert(entry_ptr->image_ptr == NULL);
759+
/* Make sure one of either `destroy_entry` or `take_ownership` are true */
760+
assert(destroy_entry != take_ownership);
860761

861-
if (entry_ptr->type->free_icr((void *)entry_ptr) < 0)
862-
HGOTO_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "free_icr callback failed");
863-
} /* end if */
864-
else {
865-
assert(take_ownership);
866-
} /* end else */
867-
} /* if (destroy) */
762+
if (H5C__discard_single_entry(f, cache_ptr, entry_ptr, destroy_entry, free_file_space,
763+
suppress_image_entry_frees) < 0)
764+
HGOTO_ERROR(H5E_CACHE, H5E_CANTFREE, FAIL, "can't discard cache entry");
765+
}
868766

869767
/* Check if we have to update the page buffer with cleared entries
870768
* so it doesn't go out of date
@@ -891,6 +789,145 @@ H5C__flush_single_entry(H5F_t *f, H5C_cache_entry_t *entry_ptr, unsigned flags)
891789
FUNC_LEAVE_NOAPI(ret_value)
892790
} /* H5C__flush_single_entry() */
893791

792+
/*-------------------------------------------------------------------------
793+
* Function: H5C__discard_single_entry
794+
*
795+
* Purpose: Helper routine to discard a cache entry, freeing as much
796+
* of the relevant file space and data structures as possible
797+
* along the way.
798+
*
799+
* Return: FAIL if error is detected, SUCCEED otherwise.
800+
*
801+
*-------------------------------------------------------------------------
802+
*/
803+
static herr_t
804+
H5C__discard_single_entry(H5F_t *f, H5C_t *cache_ptr, H5C_cache_entry_t *entry_ptr, bool destroy_entry,
805+
bool free_file_space, bool suppress_image_entry_frees)
806+
{
807+
herr_t ret_value = SUCCEED;
808+
809+
FUNC_ENTER_PACKAGE
810+
811+
assert(f);
812+
assert(cache_ptr);
813+
assert(entry_ptr);
814+
815+
/* Sanity check */
816+
assert(0 == entry_ptr->flush_dep_nparents);
817+
818+
/* if both suppress_image_entry_frees and entry_ptr->include_in_image
819+
* are true, simply set entry_ptr->image_ptr to NULL, as we have
820+
* another pointer to the buffer in an instance of H5C_image_entry_t
821+
* in cache_ptr->image_entries.
822+
*
823+
* Otherwise, free the buffer if it exists.
824+
*/
825+
if (suppress_image_entry_frees && entry_ptr->include_in_image)
826+
entry_ptr->image_ptr = NULL;
827+
else if (entry_ptr->image_ptr != NULL)
828+
entry_ptr->image_ptr = H5MM_xfree(entry_ptr->image_ptr);
829+
830+
/* If the entry is not a prefetched entry, verify that the flush
831+
* dependency parents addresses array has been transferred.
832+
*
833+
* If the entry is prefetched, the free_isr routine will dispose of
834+
* the flush dependency parents addresses array if necessary.
835+
*/
836+
if (!entry_ptr->prefetched) {
837+
assert(0 == entry_ptr->fd_parent_count);
838+
assert(NULL == entry_ptr->fd_parent_addrs);
839+
} /* end if */
840+
841+
/* Check whether we should free the space in the file that
842+
* the entry occupies
843+
*/
844+
if (free_file_space) {
845+
hsize_t fsf_size;
846+
847+
/* Sanity checks */
848+
assert(H5_addr_defined(entry_ptr->addr));
849+
assert(!H5F_IS_TMP_ADDR(f, entry_ptr->addr));
850+
#ifndef NDEBUG
851+
{
852+
size_t curr_len;
853+
854+
/* Get the actual image size for the thing again */
855+
entry_ptr->type->image_len((void *)entry_ptr, &curr_len);
856+
assert(curr_len == entry_ptr->size);
857+
}
858+
#endif
859+
860+
/* If the file space free size callback is defined, use
861+
* it to get the size of the block of file space to free.
862+
* Otherwise use entry_ptr->size.
863+
*/
864+
if (entry_ptr->type->fsf_size) {
865+
if ((entry_ptr->type->fsf_size)((void *)entry_ptr, &fsf_size) < 0)
866+
/* Note error but keep going */
867+
HDONE_ERROR(H5E_CACHE, H5E_CANTFREE, FAIL, "unable to get file space free size");
868+
} /* end if */
869+
else /* no file space free size callback -- use entry size */
870+
fsf_size = entry_ptr->size;
871+
872+
/* Release the space on disk */
873+
if ((ret_value >= 0) && H5MF_xfree(f, entry_ptr->type->mem_type, entry_ptr->addr, fsf_size) < 0)
874+
/* Note error but keep going */
875+
HDONE_ERROR(H5E_CACHE, H5E_CANTFREE, FAIL, "unable to free file space for cache entry");
876+
} /* end if ( free_file_space ) */
877+
878+
/* Reset the pointer to the cache the entry is within. -QAK */
879+
entry_ptr->cache_ptr = NULL;
880+
881+
/* increment entries_removed_counter and set
882+
* last_entry_removed_ptr. As we are likely about to
883+
* free the entry, recall that last_entry_removed_ptr
884+
* must NEVER be dereferenced.
885+
*
886+
* Recall that these fields are maintained to allow functions
887+
* that perform scans of lists of entries to detect the
888+
* unexpected removal of entries (via expunge, eviction,
889+
* or take ownership at present), so that they can re-start
890+
* their scans if necessary.
891+
*
892+
* Also check if the entry we are watching for removal is being
893+
* removed (usually the 'next' entry for an iteration) and reset
894+
* it to indicate that it was removed.
895+
*/
896+
cache_ptr->entries_removed_counter++;
897+
cache_ptr->last_entry_removed_ptr = entry_ptr;
898+
899+
if (entry_ptr == cache_ptr->entry_watched_for_removal)
900+
cache_ptr->entry_watched_for_removal = NULL;
901+
902+
/* Check for actually destroying the entry in memory */
903+
/* (As opposed to taking ownership of it) */
904+
if (destroy_entry) {
905+
if (entry_ptr->is_dirty) {
906+
/* Reset dirty flag */
907+
entry_ptr->is_dirty = false;
908+
909+
/* If the entry's type has a 'notify' callback send a
910+
* 'entry cleaned' notice now that the entry is fully
911+
* integrated into the cache.
912+
*/
913+
if (entry_ptr->type->notify &&
914+
(entry_ptr->type->notify)(H5C_NOTIFY_ACTION_ENTRY_CLEANED, entry_ptr) < 0)
915+
/* Note error but keep going */
916+
HDONE_ERROR(H5E_CACHE, H5E_CANTNOTIFY, FAIL,
917+
"can't notify client about entry dirty flag cleared");
918+
} /* end if */
919+
920+
/* verify that the image has been freed */
921+
assert(entry_ptr->image_ptr == NULL);
922+
923+
if (entry_ptr->type->free_icr((void *)entry_ptr) < 0)
924+
/* Note error but keep going */
925+
HDONE_ERROR(H5E_CACHE, H5E_CANTFLUSH, FAIL, "free_icr callback failed");
926+
} /* end if */
927+
928+
FUNC_LEAVE_NOAPI(ret_value)
929+
} /* end H5C__discard_single_entry() */
930+
894931
/*-------------------------------------------------------------------------
895932
* Function: H5C__verify_len_eoa
896933
*

0 commit comments

Comments
 (0)