Skip to content

Commit 7dd1102

Browse files
Fix issue with handling of failure during discard of metadata cache entries (#5817)
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 92ca55c commit 7dd1102

File tree

2 files changed

+156
-113
lines changed

2 files changed

+156
-113
lines changed

release_docs/CHANGELOG.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -556,13 +556,19 @@ Added Fortran wrapper h5fdsubfiling_get_file_mapping_f() for the subfiling file
556556
# 🪲 Bug Fixes
557557

558558
## Library
559+
560+
### Fixed security issue CVE-2025-7068
561+
562+
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.
563+
564+
Fixes GitHub issue #5578
565+
559566
### Fix bugs in object header operations
560567

561568
In some rare circumstances, such as deleting hard links that point to their own parent group in a file using the new file format, memory corruption could occur due to recursive operations changing data structures being operated on by multiple levels of recursion. Made changes to delay changing the data structure in a dangerous way until recursion is complete.
562569

563570
Fixes GitHub issue #5854
564571

565-
566572
### Fixed security issues CVE-2025-6816, CVE-2025-6856 and CVE-2025-2923
567573

568574
A specially constructed HDF5 file could contain a corrupted object header with a continuation message that points back to itself. This could result in an internal buffer being allocated with too small of a size, leading to a heap buffer overflow. This has been fixed by checking the expected number of object header chunks against the actual value as chunks are being deserialized.
@@ -588,7 +594,7 @@ Added Fortran wrapper h5fdsubfiling_get_file_mapping_f() for the subfiling file
588594
Fixes GitHub issue #5861
589595

590596
### Fixed security issue CVE-2025-2153
591-
597+
592598
The message flags field could be modified such that a message that is not sharable according to the share_flags field in H5O_msg_class_t can be treated as sharable. An assert has been added in H5O__msg_write_real to make sure messages that are not sharable can't be modified to shared. Additionally, the check in H5O__chunk_deserialize that catches unsharable messages being marked as sharable has been improved.
593599

594600
Fixes GitHub issue #5329

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_icr 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)