Description: Fix stack overflow / out-of-bounds read with large di_depth (CVE-2026-71219)
 The directory hash table traversal code derives the hash table size
 exponentially from the on-disk di_depth field (hsize = 1 << i_depth)
 without bounds validation.
 .
 In gfs2/fsck/metawalk.c the derived size is used to size an alloca()
 buffer in dir_leaf_reada(); di_depth is read from untrusted filesystem
 metadata and can be up to 65535, so a crafted GFS2 filesystem image with
 a large di_depth value causes an excessively large stack allocation,
 leading to stack exhaustion and a denial of service when processed by
 fsck.gfs2.
 .
 In gfs2/edit/gfs2hex.c the same unbounded di_depth value is used as a
 loop bound in do_dinode_extended(), reading leaf pointers past the end
 of the single-block buffer, causing a heap out-of-bounds read when
 processed by gfs2_edit.
 .
 Validate that i_depth does not exceed GFS2_DIR_MAX_DEPTH before computing
 the hash table size, rejecting such directories as corrupt.  The check is
 also enforced in the shared get_dir_hash() helper so all callers,
 including pass2, are covered, and the gfs2_edit leaf pointer walk is
 bounded to the block size with a depth clamped to avoid a shift overflow.
Author: Valentin Vidic <vvidic@debian.org>
Last-Update: 2026-10-06
---
This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
--- a/gfs2/fsck/metawalk.c
+++ b/gfs2/fsck/metawalk.c
@@ -619,7 +619,7 @@ static void dir_leaf_reada(struct lgfs2_inode *ip, __be64 *tbl, unsigned hsize)
 int check_leaf_blks(struct fsck_cx *cx, struct lgfs2_inode *ip, struct metawalk_fxns *pass)
 {
 	int error = 0;
-	unsigned hsize = (1 << ip->i_depth);
+	unsigned hsize;
 	uint64_t leaf_no, leaf_next;
 	uint64_t first_ok_leaf, orig_di_blocks;
 	struct lgfs2_buffer_head *lbh;
@@ -629,6 +629,15 @@ int check_leaf_blks(struct fsck_cx *cx, struct lgfs2_inode *ip, struct metawalk_
 	__be64 *tbl;
 	int chained_leaf, tbl_valid;
 
+	if (ip->i_depth > GFS2_DIR_MAX_DEPTH) {
+		log_err(_("Directory #%"PRIu64" (0x%"PRIx64") has an invalid "
+		          "depth of %u (maximum is %u); skipping it.\n"),
+		        ip->i_num.in_addr, ip->i_num.in_addr, (unsigned)ip->i_depth,
+		        GFS2_DIR_MAX_DEPTH);
+		return 1;
+	}
+	hsize = (1 << ip->i_depth);
+
 	tbl = get_dir_hash(ip);
 	if (tbl == NULL) {
 		perror("get_dir_hash");
--- a/gfs2/fsck/util.c
+++ b/gfs2/fsck/util.c
@@ -568,9 +568,17 @@ uint64_t find_free_blk(struct lgfs2_sbd *sdp)
 
 __be64 *get_dir_hash(struct lgfs2_inode *ip)
 {
-	unsigned hsize = (1 << ip->i_depth) * sizeof(uint64_t);
+	unsigned hsize;
 	int ret;
-	__be64 *tbl = malloc(hsize);
+	__be64 *tbl;
+
+	if (ip->i_depth > GFS2_DIR_MAX_DEPTH) {
+		errno = EINVAL;
+		return NULL;
+	}
+	hsize = (1 << ip->i_depth) * sizeof(uint64_t);
+
+	tbl = malloc(hsize);
 
 	if (tbl == NULL)
 		return NULL;
--- a/gfs2/edit/gfs2hex.c
+++ b/gfs2/edit/gfs2hex.c
@@ -172,11 +172,15 @@ void do_dinode_extended(char *buf)
 	else if (isdir && (be32_to_cpu(dip->di_flags) & GFS2_DIF_EXHASH) &&
 	         dip->di_height == 0) {
 		/* Leaf Pointers: */
+		unsigned int depth = be16_to_cpu(dip->di_depth);
 
+		if (depth > GFS2_DIR_MAX_DEPTH)
+			depth = GFS2_DIR_MAX_DEPTH;
 		last = be64_to_cpu(*(__be64 *)(buf + sizeof(struct gfs2_dinode)));
 
 		for (x = sizeof(struct gfs2_dinode), y = 0;
-			 y < (1 << be16_to_cpu(dip->di_depth));
+			 y < (1u << depth) &&
+			 x + sizeof(uint64_t) <= sbd.sd_bsize;
 			 x += sizeof(uint64_t), y++) {
 			p = be64_to_cpu(*(__be64 *)(buf + x));
 
