From eb3af284cc0ac8c758c65f492fc693ed50539592 Mon Sep 17 00:00:00 2001 From: Libo Chen Date: Thu, 29 Jan 2026 13:59:39 +0800 Subject: [PATCH] Fix CVE-2025-6857 Add additional checks for v1 B-tree corruption An HDF5 file had a corrupted v1 B-tree that would result in a stack overflow when performing a lookup on it. This has been fixed with additional integrity checks. CVE: CVE-2025-6857 Upstream-Status: Backport [https://github.com/HDFGroup/hdf5/commit/a8ceb1d95bb997f548c1129363dad53c18540096] In addition to the upstream backport, this patch includes two adaptation changes for HDF5 1.14.4. First, the H5B_UNKNOWN_NODELEVEL macro and the exp_level field are introduced in H5Bpkg.h, as these do not exist in 1.14.4 due to differences with the 2.0.0 codebase. Second, the "cache_udata.exp_level = H5B_UNKNOWN_NODELEVEL" statements are added in H5B_* functions to initialize the new field. Signed-off-by: Libo Chen --- src/H5B.c | 92 +++++++++++++++++++++++++++++++++++++++++++--------- src/H5Bpkg.h | 6 ++++ 2 files changed, 83 insertions(+), 15 deletions(-) diff --git a/src/H5B.c b/src/H5B.c index 5a7a238..4efa679 100644 --- a/src/H5B.c +++ b/src/H5B.c @@ -140,6 +140,8 @@ typedef struct H5B_ins_ud_t { /********************/ /* Local Prototypes */ /********************/ +static herr_t H5B_find_helper(H5F_t *f, const H5B_class_t *type, haddr_t addr, int exp_level, bool *found, + void *udata); static H5B_ins_t H5B__insert_helper(H5F_t *f, H5B_ins_ud_t *bt_ud, const H5B_class_t *type, uint8_t *lt_key, bool *lt_key_changed, uint8_t *md_key, void *udata, uint8_t *rt_key, bool *rt_key_changed, H5B_ins_ud_t *split_bt_ud /*out*/); @@ -252,26 +254,67 @@ done: } /* end H5B_create() */ /*------------------------------------------------------------------------- - * Function: H5B_find + * Function: H5B_find * - * Purpose: Locate the specified information in a B-tree and return - * that information by filling in fields of the caller-supplied - * UDATA pointer depending on the type of leaf node - * requested. The UDATA can point to additional data passed - * to the key comparison function. + * Purpose: Locate the specified information in a B-tree and return + * that information by filling in fields of the + * caller-supplied UDATA pointer depending on the type of leaf + * node requested. The UDATA can point to additional data + * passed to the key comparison function. * - * Note: This function does not follow the left/right sibling - * pointers since it assumes that all nodes can be reached - * from the parent node. + * Note: This function does not follow the left/right sibling + * pointers since it assumes that all nodes can be reached + * from the parent node. * - * Return: Non-negative (true/false) on success (if found, values returned - * through the UDATA argument). Negative on failure (if not found, - * UDATA is undefined). + * Return: Non-negative (true/false) on success (if found, values + * returned through the UDATA argument). Negative on failure + * (if not found, UDATA is undefined). * *------------------------------------------------------------------------- */ herr_t H5B_find(H5F_t *f, const H5B_class_t *type, haddr_t addr, bool *found, void *udata) +{ + herr_t ret_value = SUCCEED; + + FUNC_ENTER_NOAPI(FAIL) + + /* + * Check arguments. + */ + assert(f); + assert(type); + assert(type->decode); + assert(type->cmp3); + assert(type->found); + assert(H5_addr_defined(addr)); + + if ((ret_value = H5B_find_helper(f, type, addr, H5B_UNKNOWN_NODELEVEL, found, udata)) < 0) + HGOTO_ERROR(H5E_BTREE, H5E_NOTFOUND, FAIL, "can't lookup key"); + +done: + FUNC_LEAVE_NOAPI(ret_value) +} /* end H5B_find() */ + +/*------------------------------------------------------------------------- + * Function: H5B_find_helper + * + * Purpose: Recursive helper routine for H5B_find used to track node + * levels and attempt to detect B-tree corruption during + * lookups. + * + * Note: This function does not follow the left/right sibling + * pointers since it assumes that all nodes can be reached + * from the parent node. + * + * Return: Non-negative on success (if found, values returned through + * the UDATA argument). Negative on failure (if not found, + * UDATA is undefined). + * + *------------------------------------------------------------------------- + */ +static herr_t +H5B_find_helper(H5F_t *f, const H5B_class_t *type, haddr_t addr, int exp_level, bool *found, void *udata) { H5B_t *bt = NULL; H5UC_t *rc_shared; /* Ref-counted shared info */ @@ -281,7 +324,7 @@ H5B_find(H5F_t *f, const H5B_class_t *type, haddr_t addr, bool *found, void *uda int cmp = 1; /* Key comparison value */ herr_t ret_value = SUCCEED; /* Return value */ - FUNC_ENTER_NOAPI(FAIL) + FUNC_ENTER_NOAPI_NOINIT /* * Check arguments. @@ -306,6 +349,7 @@ H5B_find(H5F_t *f, const H5B_class_t *type, haddr_t addr, bool *found, void *uda cache_udata.f = f; cache_udata.type = type; cache_udata.rc_shared = rc_shared; + cache_udata.exp_level = exp_level; if (NULL == (bt = (H5B_t *)H5AC_protect(f, H5AC_BT, addr, &cache_udata, H5AC__READ_ONLY_FLAG))) HGOTO_ERROR(H5E_BTREE, H5E_CANTPROTECT, FAIL, "unable to load B-tree node"); @@ -329,7 +373,17 @@ H5B_find(H5F_t *f, const H5B_class_t *type, haddr_t addr, bool *found, void *uda assert(idx < bt->nchildren); if (bt->level > 0) { - if ((ret_value = H5B_find(f, type, bt->child[idx], found, udata)) < 0) + /* Sanity check to catch the case where the current node points to + * itself and the current node was loaded with an expected node level + * of H5B_UNKNOWN_NODELEVEL, thus bypassing the expected node level + * check during deserialization and in the future if the node was + * cached. + */ + if (bt->child[idx] == addr) + HGOTO_ERROR(H5E_BTREE, H5E_BADVALUE, FAIL, "cyclic B-tree detected"); + + if ((ret_value = H5B_find_helper(f, type, bt->child[idx], (int)(bt->level - 1), found, udata)) < + 0) HGOTO_ERROR(H5E_BTREE, H5E_NOTFOUND, FAIL, "can't lookup key in subtree"); } /* end if */ else { @@ -343,7 +397,7 @@ done: HDONE_ERROR(H5E_BTREE, H5E_CANTUNPROTECT, FAIL, "unable to release node"); FUNC_LEAVE_NOAPI(ret_value) -} /* end H5B_find() */ +} /* end H5B_find_helper() */ /*------------------------------------------------------------------------- * Function: H5B__split @@ -425,6 +479,7 @@ H5B__split(H5F_t *f, H5B_ins_ud_t *bt_ud, unsigned idx, void *udata, H5B_ins_ud_ cache_udata.f = f; cache_udata.type = shared->type; cache_udata.rc_shared = bt_ud->bt->rc_shared; + cache_udata.exp_level = H5B_UNKNOWN_NODELEVEL; if (NULL == (split_bt_ud->bt = (H5B_t *)H5AC_protect(f, H5AC_BT, split_bt_ud->addr, &cache_udata, H5AC__NO_FLAGS_SET))) HGOTO_ERROR(H5E_BTREE, H5E_CANTPROTECT, FAIL, "unable to protect B-tree"); @@ -532,6 +587,7 @@ H5B_insert(H5F_t *f, const H5B_class_t *type, haddr_t addr, void *udata) cache_udata.f = f; cache_udata.type = type; cache_udata.rc_shared = rc_shared; + cache_udata.exp_level = H5B_UNKNOWN_NODELEVEL; bt_ud.addr = addr; if (NULL == (bt_ud.bt = (H5B_t *)H5AC_protect(f, H5AC_BT, addr, &cache_udata, H5AC__NO_FLAGS_SET))) HGOTO_ERROR(H5E_BTREE, H5E_CANTPROTECT, FAIL, "unable to locate root of B-tree"); @@ -789,6 +845,7 @@ H5B__insert_helper(H5F_t *f, H5B_ins_ud_t *bt_ud, const H5B_class_t *type, uint8 cache_udata.f = f; cache_udata.type = type; cache_udata.rc_shared = rc_shared; + cache_udata.exp_level = H5B_UNKNOWN_NODELEVEL; if (0 == bt->nchildren) { /* @@ -1077,6 +1134,7 @@ H5B__iterate_helper(H5F_t *f, const H5B_class_t *type, haddr_t addr, H5B_operato cache_udata.f = f; cache_udata.type = type; cache_udata.rc_shared = rc_shared; + cache_udata.exp_level = H5B_UNKNOWN_NODELEVEL; if (NULL == (bt = (H5B_t *)H5AC_protect(f, H5AC_BT, addr, &cache_udata, H5AC__READ_ONLY_FLAG))) HGOTO_ERROR(H5E_BTREE, H5E_CANTPROTECT, H5_ITER_ERROR, "unable to load B-tree node"); @@ -1190,6 +1248,7 @@ H5B__remove_helper(H5F_t *f, haddr_t addr, const H5B_class_t *type, int level, u cache_udata.f = f; cache_udata.type = type; cache_udata.rc_shared = rc_shared; + cache_udata.exp_level = H5B_UNKNOWN_NODELEVEL; if (NULL == (bt = (H5B_t *)H5AC_protect(f, H5AC_BT, addr, &cache_udata, H5AC__NO_FLAGS_SET))) HGOTO_ERROR(H5E_BTREE, H5E_CANTPROTECT, H5B_INS_ERROR, "unable to load B-tree node"); @@ -1542,6 +1601,7 @@ H5B_delete(H5F_t *f, const H5B_class_t *type, haddr_t addr, void *udata) cache_udata.f = f; cache_udata.type = type; cache_udata.rc_shared = rc_shared; + cache_udata.exp_level = H5B_UNKNOWN_NODELEVEL; if (NULL == (bt = (H5B_t *)H5AC_protect(f, H5AC_BT, addr, &cache_udata, H5AC__NO_FLAGS_SET))) HGOTO_ERROR(H5E_BTREE, H5E_CANTPROTECT, FAIL, "unable to load B-tree node"); @@ -1782,6 +1842,7 @@ H5B__get_info_helper(H5F_t *f, const H5B_class_t *type, haddr_t addr, const H5B_ cache_udata.f = f; cache_udata.type = type; cache_udata.rc_shared = rc_shared; + cache_udata.exp_level = H5B_UNKNOWN_NODELEVEL; if (NULL == (bt = (H5B_t *)H5AC_protect(f, H5AC_BT, addr, &cache_udata, H5AC__READ_ONLY_FLAG))) HGOTO_ERROR(H5E_BTREE, H5E_CANTPROTECT, FAIL, "unable to load B-tree node"); @@ -1923,6 +1984,7 @@ H5B_valid(H5F_t *f, const H5B_class_t *type, haddr_t addr) cache_udata.f = f; cache_udata.type = type; cache_udata.rc_shared = rc_shared; + cache_udata.exp_level = H5B_UNKNOWN_NODELEVEL; if (NULL == (bt = (H5B_t *)H5AC_protect(f, H5AC_BT, addr, &cache_udata, H5AC__READ_ONLY_FLAG))) HGOTO_ERROR(H5E_BTREE, H5E_CANTPROTECT, FAIL, "unable to protect B-tree node"); diff --git a/src/H5Bpkg.h b/src/H5Bpkg.h index d1ad647..f75e857 100644 --- a/src/H5Bpkg.h +++ b/src/H5Bpkg.h @@ -39,6 +39,11 @@ /* # of bits for node level: 1 byte */ #define LEVEL_BITS 8 +/* Indicates that the level of the current node is unknown. When the level + * is known, it can be used to detect corrupted level during decoding + */ +#define H5B_UNKNOWN_NODELEVEL -1 + /****************************/ /* Package Private Typedefs */ /****************************/ @@ -60,6 +65,7 @@ typedef struct H5B_t { typedef struct H5B_cache_ud_t { H5F_t *f; /* File that B-tree node is within */ const struct H5B_class_t *type; /* Type of tree */ + int exp_level; /* Expected level of the current node */ H5UC_t *rc_shared; /* Ref-counted shared info */ } H5B_cache_ud_t; -- 2.34.1