public inbox for linux-ext4@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/3] Data in direntry (dirdata) feature
@ 2026-04-17 21:37 Artem Blagodarenko
  2026-04-17 21:37 ` [PATCH 1/3] ext4: make dirdata work with metadata_csum Artem Blagodarenko
                   ` (4 more replies)
  0 siblings, 5 replies; 10+ messages in thread
From: Artem Blagodarenko @ 2026-04-17 21:37 UTC (permalink / raw)
  To: linux-ext4; +Cc: adilger.kernel, Artem Blagodarenko

EXT4 currently stores a hash in the directory entry
(dirent) immediately after the file name to support
simultaneous fscrypt and casefold functionality.

It has been discussed within the EXT4 community that
this hash could instead be stored in dirdata. This
would make it the second (or third, in the case of
64-bit inode counts) user of dirdata.

At the same time, the existing format—where the hash
is placed after the file name—must continue to be
supported. With these patches, EXT4 can handle the
hash in both formats.

The first user of this feature, LUFID, has been
tested in the Lustre filesystem backend (LDISKFS)
[1].

Support for fscrypt and case-insensitive directories
with dirdata enabled has been verified using a
dedicated xfstest submitted to the EXT4 community as
a separate patch.

e2fsprogs support is provided in a separate patch.

[1] https://review.whamcloud.com/c/fs/lustre-release/+/64439

Artem Blagodarenko (3):
  ext4: make dirdata work with metadata_csum
  ext4: add dirdata support structures and helpers
  ext4: dirdata feature

 fs/ext4/dir.c    |   9 +-
 fs/ext4/ext4.h   | 169 +++++++++++++++++++--
 fs/ext4/inline.c |  22 +--
 fs/ext4/namei.c  | 379 ++++++++++++++++++++++++++++++-----------------
 fs/ext4/super.c  |   4 +-
 fs/ext4/sysfs.c  |   2 +
 6 files changed, 422 insertions(+), 163 deletions(-)

-- 
2.43.5


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH 1/3] ext4: make dirdata work with metadata_csum
  2026-04-17 21:37 [PATCH 0/3] Data in direntry (dirdata) feature Artem Blagodarenko
@ 2026-04-17 21:37 ` Artem Blagodarenko
  2026-04-17 21:37 ` [PATCH 2/3] ext4: add dirdata support structures and helpers Artem Blagodarenko
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 10+ messages in thread
From: Artem Blagodarenko @ 2026-04-17 21:37 UTC (permalink / raw)
  To: linux-ext4
  Cc: adilger.kernel, Artem Blagodarenko, Pravin Shelar, Andreas Dilger

Split monolithic definition of dx_root struct to separate dx_root_info
from fake struct ext4_dir_entry2 for improved code readability.
This allows "." and ".." dirents to have different sizes if necessary,
since we can't assume the rec_len 12 if dx_root dirents have dirdata.
Adds dx_get_dx_info() accessor instead of complex typecast at callers.
Does not change any functionality.

Signed-off-by: Pravin Shelar <pravin.shelar@sun.com>
Signed-off-by: Artem Blagodarenko <artem.blagodarenko@gmail.com>
Reviewed-by: Andreas Dilger <adilger@dilger.ca>
---
 fs/ext4/namei.c | 177 ++++++++++++++++++++++++------------------------
 1 file changed, 89 insertions(+), 88 deletions(-)

diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index c4b5e252af0e..ab2b4bb4a93d 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -244,22 +244,13 @@ struct dx_entry
  * hash version mod 4 should never be 0.  Sincerely, the paranoia department.
  */
 
-struct dx_root
+struct dx_root_info
 {
-	struct fake_dirent dot;
-	char dot_name[4];
-	struct fake_dirent dotdot;
-	char dotdot_name[4];
-	struct dx_root_info
-	{
-		__le32 reserved_zero;
-		u8 hash_version;
-		u8 info_length; /* 8 */
-		u8 indirect_levels;
-		u8 unused_flags;
-	}
-	info;
-	struct dx_entry	entries[];
+	__le32 reserved_zero;
+	u8 hash_version;
+	u8 info_length; /* 8 */
+	u8 indirect_levels;
+	u8 unused_flags;
 };
 
 struct dx_node
@@ -334,11 +325,7 @@ static struct ext4_dir_entry_tail *get_dirent_tail(struct inode *inode,
 	t = EXT4_DIRENT_TAIL(bh->b_data, EXT4_BLOCK_SIZE(inode->i_sb));
 #endif
 
-	if (t->det_reserved_zero1 ||
-	    (ext4_rec_len_from_disk(t->det_rec_len, blocksize) !=
-	     sizeof(struct ext4_dir_entry_tail)) ||
-	    t->det_reserved_zero2 ||
-	    t->det_reserved_ft != EXT4_FT_DIR_CSUM)
+	if (!ext4_dir_entry_is_tail((struct ext4_dir_entry_2 *)t))
 		return NULL;
 
 	return t;
@@ -413,7 +400,7 @@ static struct dx_countlimit *get_dx_countlimit(struct inode *inode,
 					       struct ext4_dir_entry *dirent,
 					       int *offset)
 {
-	struct ext4_dir_entry *dp;
+	struct ext4_dir_entry_2 *de;
 	struct dx_root_info *root;
 	int count_offset;
 	int blocksize = EXT4_BLOCK_SIZE(inode->i_sb);
@@ -422,10 +409,10 @@ static struct dx_countlimit *get_dx_countlimit(struct inode *inode,
 	if (rlen == blocksize)
 		count_offset = 8;
 	else if (rlen == 12) {
-		dp = (struct ext4_dir_entry *)(((void *)dirent) + 12);
-		if (ext4_rec_len_from_disk(dp->rec_len, blocksize) != blocksize - 12)
+		de = (struct ext4_dir_entry_2 *)(((void *)dirent) + 12);
+		if (ext4_rec_len_from_disk(de->rec_len, blocksize) != blocksize - 12)
 			return NULL;
-		root = (struct dx_root_info *)(((void *)dp + 12));
+		root = (struct dx_root_info *)(((void *)de + 12));
 		if (root->reserved_zero ||
 		    root->info_length != sizeof(struct dx_root_info))
 			return NULL;
@@ -533,6 +520,16 @@ ext4_next_entry(struct ext4_dir_entry_2 *p, unsigned long blocksize)
  * Future: use high four bits of block for coalesce-on-delete flags
  * Mask them off for now.
  */
+static struct dx_root_info *dx_get_dx_info(void *de_buf)
+{
+	/* get dotdot first */
+	de_buf = de_buf + ext4_dirent_rec_len(1, NULL);
+
+	/* dx root info is after dotdot entry */
+	de_buf = de_buf + ext4_dirent_rec_len(2, NULL);
+
+	return (struct dx_root_info *)de_buf;
+}
 
 static inline ext4_lblk_t dx_get_block(struct dx_entry *entry)
 {
@@ -574,11 +571,16 @@ static inline void dx_set_limit(struct dx_entry *entries, unsigned value)
 	((struct dx_countlimit *) entries)->limit = cpu_to_le16(value);
 }
 
-static inline unsigned dx_root_limit(struct inode *dir, unsigned infosize)
+static inline unsigned dx_root_limit(struct inode *dir,
+	struct ext4_dir_entry_2 *dot_de, unsigned infosize)
 {
-	unsigned int entry_space = dir->i_sb->s_blocksize -
-			ext4_dir_rec_len(1, NULL) -
-			ext4_dir_rec_len(2, NULL) - infosize;
+	struct ext4_dir_entry_2 *dotdot_de;
+	unsigned entry_space;
+
+	dotdot_de = ext4_next_entry(dot_de, dir->i_sb->s_blocksize);
+	entry_space = dir->i_sb->s_blocksize -
+		ext4_dir_entry_len(dot_de, NULL) -
+		ext4_dir_entry_len(dotdot_de, NULL) - infosize;
 
 	if (ext4_has_feature_metadata_csum(dir->i_sb))
 		entry_space -= sizeof(struct dx_tail);
@@ -780,7 +782,7 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,
 {
 	unsigned count, indirect, level, i;
 	struct dx_entry *at, *entries, *p, *q, *m;
-	struct dx_root *root;
+	struct dx_root_info *info;
 	struct dx_frame *frame = frame_in;
 	struct dx_frame *ret_err = ERR_PTR(ERR_BAD_DX_DIR);
 	u32 hash;
@@ -792,23 +794,24 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,
 	if (IS_ERR(frame->bh))
 		return (struct dx_frame *) frame->bh;
 
-	root = (struct dx_root *) frame->bh->b_data;
-	if (root->info.hash_version != DX_HASH_TEA &&
-	    root->info.hash_version != DX_HASH_HALF_MD4 &&
-	    root->info.hash_version != DX_HASH_LEGACY &&
-	    root->info.hash_version != DX_HASH_SIPHASH) {
-		ext4_warning_inode(dir, "Unrecognised inode hash code %u",
-				   root->info.hash_version);
+	info = dx_get_dx_info((struct ext4_dir_entry_2 *)frame->bh->b_data);
+	if (info->hash_version != DX_HASH_TEA &&
+	    info->hash_version != DX_HASH_HALF_MD4 &&
+	    info->hash_version != DX_HASH_LEGACY &&
+	    info->hash_version != DX_HASH_SIPHASH) {
+		ext4_warning(dir->i_sb,
+			"Unrecognised inode hash code %d for directory #%lu",
+			info->hash_version, dir->i_ino);
 		goto fail;
 	}
 	if (ext4_hash_in_dirent(dir)) {
-		if (root->info.hash_version != DX_HASH_SIPHASH) {
+		if (info->hash_version != DX_HASH_SIPHASH) {
 			ext4_warning_inode(dir,
 				"Hash in dirent, but hash is not SIPHASH");
 			goto fail;
 		}
 	} else {
-		if (root->info.hash_version == DX_HASH_SIPHASH) {
+		if (info->hash_version == DX_HASH_SIPHASH) {
 			ext4_warning_inode(dir,
 				"Hash code is SIPHASH, but hash not in dirent");
 			goto fail;
@@ -816,7 +819,7 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,
 	}
 	if (fname)
 		hinfo = &fname->hinfo;
-	hinfo->hash_version = root->info.hash_version;
+	hinfo->hash_version = info->hash_version;
 	if (hinfo->hash_version <= DX_HASH_TEA)
 		hinfo->hash_version += EXT4_SB(dir->i_sb)->s_hash_unsigned;
 	hinfo->seed = EXT4_SB(dir->i_sb)->s_hash_seed;
@@ -832,13 +835,13 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,
 	}
 	hash = hinfo->hash;
 
-	if (root->info.unused_flags & 1) {
+	if (info->unused_flags & 1) {
 		ext4_warning_inode(dir, "Unimplemented hash flags: %#06x",
-				   root->info.unused_flags);
+				   info->unused_flags);
 		goto fail;
 	}
 
-	indirect = root->info.indirect_levels;
+	indirect = info->indirect_levels;
 	if (indirect >= ext4_dir_htree_level(dir->i_sb)) {
 		ext4_warning(dir->i_sb,
 			     "Directory (ino: %lu) htree depth %#06x exceed"
@@ -851,14 +854,17 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,
 		goto fail;
 	}
 
-	entries = (struct dx_entry *)(((char *)&root->info) +
-				      root->info.info_length);
+	entries = (struct dx_entry *)(((char *)info) +
+				      info->info_length);
 
-	if (dx_get_limit(entries) != dx_root_limit(dir,
-						   root->info.info_length)) {
+	if (dx_get_limit(entries) !=
+	    dx_root_limit(dir, (struct ext4_dir_entry_2 *)frame->bh->b_data,
+			  info->info_length)) {
 		ext4_warning_inode(dir, "dx entry: limit %u != root limit %u",
 				   dx_get_limit(entries),
-				   dx_root_limit(dir, root->info.info_length));
+				   dx_root_limit(dir,
+				   (struct ext4_dir_entry_2 *)frame->bh->b_data,
+				   info->info_length));
 		goto fail;
 	}
 
@@ -944,7 +950,7 @@ static void dx_release(struct dx_frame *frames)
 	if (frames[0].bh == NULL)
 		return;
 
-	info = &((struct dx_root *)frames[0].bh->b_data)->info;
+	info = dx_get_dx_info((struct ext4_dir_entry_2 *)frames[0].bh->b_data);
 	/* save local copy, "info" may be freed after brelse() */
 	indirect_levels = info->indirect_levels;
 	for (i = 0; i <= indirect_levels; i++) {
@@ -2156,44 +2162,38 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
 	return err ? err : err2;
 }
 
-static bool ext4_check_dx_root(struct inode *dir, struct dx_root *root)
+static bool ext4_check_dx_root(struct inode *dir,
+			       struct ext4_dir_entry_2 *dot_de,
+			       struct ext4_dir_entry_2 *dotdot_de,
+			       struct ext4_dir_entry_2 **entry)
 {
-	struct fake_dirent *fde;
 	const char *error_msg;
-	unsigned int rlen;
 	unsigned int blocksize = dir->i_sb->s_blocksize;
-	char *blockend = (char *)root + dir->i_sb->s_blocksize;
+	struct ext4_dir_entry_2 *de = NULL;
 
-	fde = &root->dot;
-	if (unlikely(fde->name_len != 1)) {
+	if (unlikely(dot_de->name_len != 1)) {
 		error_msg = "invalid name_len for '.'";
 		goto corrupted;
 	}
-	if (unlikely(strncmp(root->dot_name, ".", fde->name_len))) {
+	if (unlikely(strncmp(dot_de->name, ".", dot_de->name_len))) {
 		error_msg = "invalid name for '.'";
 		goto corrupted;
 	}
-	rlen = ext4_rec_len_from_disk(fde->rec_len, blocksize);
-	if (unlikely((char *)fde + rlen >= blockend)) {
-		error_msg = "invalid rec_len for '.'";
-		goto corrupted;
-	}
 
-	fde = &root->dotdot;
-	if (unlikely(fde->name_len != 2)) {
+	if (unlikely(dotdot_de->name_len != 2)) {
 		error_msg = "invalid name_len for '..'";
 		goto corrupted;
 	}
-	if (unlikely(strncmp(root->dotdot_name, "..", fde->name_len))) {
+	if (unlikely(strncmp(dotdot_de->name, "..", dotdot_de->name_len))) {
 		error_msg = "invalid name for '..'";
 		goto corrupted;
 	}
-	rlen = ext4_rec_len_from_disk(fde->rec_len, blocksize);
-	if (unlikely((char *)fde + rlen >= blockend)) {
+	de = ext4_next_entry(dotdot_de, blocksize);
+	if ((char *)de >= (((char *)dot_de) + blocksize)) {
 		error_msg = "invalid rec_len for '..'";
 		goto corrupted;
 	}
-
+	*entry = de;
 	return true;
 
 corrupted:
@@ -2211,16 +2211,15 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,
 			    struct inode *inode, struct buffer_head *bh)
 {
 	struct buffer_head *bh2;
-	struct dx_root	*root;
 	struct dx_frame	frames[EXT4_HTREE_LEVEL], *frame;
 	struct dx_entry *entries;
-	struct ext4_dir_entry_2	*de, *de2;
+	struct ext4_dir_entry_2	*de, *de2, *dot_de, *dotdot_de;
 	char		*data2, *top;
 	unsigned	len;
 	int		retval;
 	unsigned	blocksize;
 	ext4_lblk_t  block;
-	struct fake_dirent *fde;
+	struct dx_root_info *dx_info;
 	int csum_size = 0;
 
 	if (ext4_has_feature_metadata_csum(inode->i_sb))
@@ -2237,17 +2236,15 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,
 		return retval;
 	}
 
-	root = (struct dx_root *) bh->b_data;
-	if (!ext4_check_dx_root(dir, root)) {
+	dot_de = (struct ext4_dir_entry_2 *)bh->b_data;
+	dotdot_de = ext4_next_entry(dot_de, blocksize);
+	if (!ext4_check_dx_root(dir, dot_de, dotdot_de, &de)) {
 		brelse(bh);
 		return -EFSCORRUPTED;
 	}
 
 	/* The 0th block becomes the root, move the dirents out */
-	fde = &root->dotdot;
-	de = (struct ext4_dir_entry_2 *)((char *)fde +
-		ext4_rec_len_from_disk(fde->rec_len, blocksize));
-	len = ((char *) root) + (blocksize - csum_size) - (char *) de;
+	len = ((char *)dot_de) + (blocksize - csum_size) - (char *)de;
 
 	/* Allocate new block for the 0th block's dirents */
 	bh2 = ext4_append(handle, dir, &block);
@@ -2278,24 +2275,27 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,
 		ext4_initialize_dirent_tail(bh2, blocksize);
 
 	/* Initialize the root; the dot dirents already exist */
-	de = (struct ext4_dir_entry_2 *) (&root->dotdot);
-	de->rec_len = ext4_rec_len_to_disk(
-			blocksize - ext4_dir_rec_len(2, NULL), blocksize);
-	memset (&root->info, 0, sizeof(root->info));
-	root->info.info_length = sizeof(root->info);
+	dotdot_de->rec_len =
+		ext4_rec_len_to_disk(blocksize - le16_to_cpu(dot_de->rec_len),
+				     blocksize);
+
+	/* initialize hashing info */
+	dx_info = dx_get_dx_info(dot_de);
+	memset(dx_info, 0, sizeof(*dx_info));
+	dx_info->info_length = sizeof(*dx_info);
 	if (ext4_hash_in_dirent(dir))
-		root->info.hash_version = DX_HASH_SIPHASH;
+		dx_info->hash_version = DX_HASH_SIPHASH;
 	else
-		root->info.hash_version =
+		dx_info->hash_version =
 				EXT4_SB(dir->i_sb)->s_def_hash_version;
 
-	entries = root->entries;
+	entries = (void *)dx_info + sizeof(*dx_info);
 	dx_set_block(entries, 1);
 	dx_set_count(entries, 1);
-	dx_set_limit(entries, dx_root_limit(dir, sizeof(root->info)));
+	dx_set_limit(entries, dx_root_limit(dir, dot_de, sizeof(*dx_info)));
 
 	/* Initialize as for dx_probe */
-	fname->hinfo.hash_version = root->info.hash_version;
+	fname->hinfo.hash_version = dx_info->hash_version;
 	if (fname->hinfo.hash_version <= DX_HASH_TEA)
 		fname->hinfo.hash_version += EXT4_SB(dir->i_sb)->s_hash_unsigned;
 	fname->hinfo.seed = EXT4_SB(dir->i_sb)->s_hash_seed;
@@ -2598,7 +2598,7 @@ static int ext4_dx_add_entry(handle_t *handle, struct ext4_filename *fname,
 			if (restart || err)
 				goto journal_error;
 		} else {
-			struct dx_root *dxroot;
+			struct dx_root_info *info;
 			memcpy((char *) entries2, (char *) entries,
 			       icount * sizeof(struct dx_entry));
 			dx_set_limit(entries2, dx_node_limit(dir));
@@ -2606,8 +2606,9 @@ static int ext4_dx_add_entry(handle_t *handle, struct ext4_filename *fname,
 			/* Set up root */
 			dx_set_count(entries, 1);
 			dx_set_block(entries + 0, newblock);
-			dxroot = (struct dx_root *)frames[0].bh->b_data;
-			dxroot->info.indirect_levels += 1;
+			info = dx_get_dx_info((struct ext4_dir_entry_2 *)
+					      frames[0].bh->b_data);
+			info->indirect_levels = 1;
 			dxtrace(printk(KERN_DEBUG
 				       "Creating %d level index...\n",
 				       dxroot->info.indirect_levels));
-- 
2.43.5


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 2/3] ext4: add dirdata support structures and helpers
  2026-04-17 21:37 [PATCH 0/3] Data in direntry (dirdata) feature Artem Blagodarenko
  2026-04-17 21:37 ` [PATCH 1/3] ext4: make dirdata work with metadata_csum Artem Blagodarenko
@ 2026-04-17 21:37 ` Artem Blagodarenko
  2026-04-17 21:37 ` [PATCH 3/3] ext4: dirdata feature Artem Blagodarenko
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 10+ messages in thread
From: Artem Blagodarenko @ 2026-04-17 21:37 UTC (permalink / raw)
  To: linux-ext4
  Cc: adilger.kernel, Artem Blagodarenko, Pravin Shelar, Andreas Dilger

Space after the name is currently used to store hashes for
fscrypt and casefold. The dirdata feature will add structures
to store different types of data there (including the hash).

This patch adds some structures, values, and functions that
will be used by the dirdata feature.

Signed-off-by: Pravin Shelar <pravin.shelar@sun.com>
Signed-off-by: Artem Blagodarenko <artem.blagodarenko@gmail.com>
Reviewed-by: Andreas Dilger <adilger@dilger.ca>
---
 fs/ext4/ext4.h | 123 +++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 119 insertions(+), 4 deletions(-)

diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 293f698b7042..09d277e24dde 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -2472,6 +2472,34 @@ struct ext4_dir_entry_tail {
 #define EXT4_FT_SYMLINK		7
 
 #define EXT4_FT_MAX		8
+#define EXT4_FT_MASK		0xf
+
+#if EXT4_FT_MAX > EXT4_FT_MASK
+#error "conflicting EXT4_FT_MAX and EXT4_FT_MASK"
+#endif
+
+/*
+ * d_type has 4 unused bits, so it can hold four types of data. These different
+ * types of data (e.g. lustre data, high 32 bits of 64-bit inode number) can be
+ * stored, in flag order, after file-name in ext4 dirent.
+ *
+ * These flags are added to d_type if ext4 dirent has extra data after
+ * filename. This data length is variable and length is stored in first byte
+ * of data. Data starts after filename NUL byte.
+ */
+#define EXT4_DIRENT_LUFID		0x10
+#define EXT4_DIRENT_INO64		0x20
+#define EXT4_DIRENT_CFHASH		0x40
+
+struct ext4_dirent_data_header {
+	/* length of this header + the whole data blob */
+	__u8	ddh_length;
+} __packed;
+
+struct ext4_dirent_hash {
+	struct ext4_dirent_data_header	dh_header;
+	struct ext4_dir_entry_hash	dh_hash;
+} __packed;
 
 #define EXT4_FT_DIR_CSUM	0xDE
 
@@ -2489,17 +2517,25 @@ struct ext4_dir_entry_tail {
  * casefolded and encrypted need to store the hash as well, so we add room for
  * ext4_extended_dir_entry_2. For all entries related to '.' or '..' you should
  * pass NULL for dir, as those entries do not use the extra fields.
+ *
+ * For directories with the dirdata feature, extra data may follow the filename.
+ * Use ext4_dir_entry_len() to compute the length of a directory entry
+ * including any dirdata, or ext4_dirent_rec_len() directly when the total
+ * name_len (including dirdata length) is already known.
  */
-static inline unsigned int ext4_dir_rec_len(__u8 name_len,
+static inline unsigned int ext4_dirent_rec_len(unsigned int name_len,
 						const struct inode *dir)
 {
-	int rec_len = (name_len + 8 + EXT4_DIR_ROUND);
+	unsigned int rec_len = (name_len + 8 + EXT4_DIR_ROUND);
 
 	if (dir && ext4_hash_in_dirent(dir))
 		rec_len += sizeof(struct ext4_dir_entry_hash);
 	return (rec_len & ~EXT4_DIR_ROUND);
 }
 
+/* Compute rec_len for a given name_len without a directory inode context */
+#define __EXT4_DIR_REC_LEN(name_len)	ext4_dirent_rec_len((name_len), NULL)
+
 static inline unsigned int
 ext4_rec_len_from_disk(__le16 dlen, unsigned blocksize)
 {
@@ -2921,10 +2957,16 @@ static const unsigned char ext4_filetype_table[] = {
 
 static inline  unsigned char get_dtype(struct super_block *sb, int filetype)
 {
-	if (!ext4_has_feature_filetype(sb) || filetype >= EXT4_FT_MAX)
+	unsigned char  fl_index = filetype & EXT4_FT_MASK;
+
+	if (!ext4_has_feature_filetype(sb) || fl_index >= EXT4_FT_MAX)
 		return DT_UNKNOWN;
 
-	return ext4_filetype_table[filetype];
+	if (!test_opt(sb, DIRDATA))
+		return ext4_filetype_table[fl_index];
+
+	return (ext4_filetype_table[fl_index]) |
+		(filetype & ~EXT4_FT_MASK);
 }
 extern int ext4_check_all_de(struct inode *dir, struct buffer_head *bh,
 			     void *buf, int buf_size);
@@ -3917,6 +3959,79 @@ static inline void ext4_clear_io_unwritten_flag(ext4_io_end_t *io_end)
 		io_end->flag &= ~EXT4_IO_END_UNWRITTEN;
 }
 
+/*
+ * Advance to the next dirdata record header starting from @ddh.
+ */
+#define ext4_dirdata_next(ddh) \
+	((struct ext4_dirent_data_header *)((char *)(ddh) + (ddh)->ddh_length))
+
+/*
+ * ext4_dir_entry_is_tail() - Check if a directory entry is a tail entry.
+ * @de: directory entry to check
+ *
+ * Returns true if @de is a directory block tail entry (checksum record).
+ */
+static inline bool ext4_dir_entry_is_tail(struct ext4_dir_entry_2 *de)
+{
+	struct ext4_dir_entry_tail *t = (struct ext4_dir_entry_tail *)de;
+
+	return !t->det_reserved_zero1 &&
+	       le16_to_cpu(t->det_rec_len) == sizeof(*t) &&
+	       !t->det_reserved_zero2 &&
+	       t->det_reserved_ft == EXT4_FT_DIR_CSUM;
+}
+
+/*
+ * ext4_dirent_get_data_len() - Compute the total dirdata length for an entry.
+ * @de: directory entry
+ *
+ * Computes the length of optional data stored after the filename (and its
+ * implicit NUL terminator).  Each extension is indicated by a bit in the
+ * high 4 bits of de->file_type; the first byte of each extension is its
+ * length (including that length byte itself).
+ *
+ * Returns 0 for tail entries and for entries with no dirdata.
+ */
+static inline int ext4_dirent_get_data_len(struct ext4_dir_entry_2 *de)
+{
+	__u8 extra_data_flags;
+	struct ext4_dirent_data_header *ddh;
+	int dlen = 0;
+
+	if (ext4_dir_entry_is_tail(de))
+		return 0;
+
+	extra_data_flags = (de->file_type & ~EXT4_FT_MASK) >> 4;
+	ddh = (struct ext4_dirent_data_header *)(de->name + de->name_len +
+						 1 /* NUL terminator */);
+
+	while (extra_data_flags) {
+		if (extra_data_flags & 1) {
+			dlen += ddh->ddh_length + (dlen == 0);
+			ddh = ext4_dirdata_next(ddh);
+		}
+		extra_data_flags >>= 1;
+	}
+	return dlen;
+}
+
+/*
+ * ext4_dir_entry_len() - Compute the required rec_len for a directory entry.
+ * @de:  directory entry (used to read name_len and any dirdata length)
+ * @dir: directory inode (may be NULL for '.' and '..' entries)
+ *
+ * Returns the minimum record length needed to hold @de, rounded up to the
+ * directory alignment and including room for the casefold+fscrypt hash if
+ * the directory requires it.
+ */
+static inline unsigned int ext4_dir_entry_len(struct ext4_dir_entry_2 *de,
+					      const struct inode *dir)
+{
+	unsigned int dirdata = ext4_dirent_get_data_len(de);
+
+	return ext4_dirent_rec_len(de->name_len + dirdata, dir);
+}
+
 extern const struct iomap_ops ext4_iomap_ops;
 extern const struct iomap_ops ext4_iomap_report_ops;
 
-- 
2.43.5


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 3/3] ext4: dirdata feature
  2026-04-17 21:37 [PATCH 0/3] Data in direntry (dirdata) feature Artem Blagodarenko
  2026-04-17 21:37 ` [PATCH 1/3] ext4: make dirdata work with metadata_csum Artem Blagodarenko
  2026-04-17 21:37 ` [PATCH 2/3] ext4: add dirdata support structures and helpers Artem Blagodarenko
@ 2026-04-17 21:37 ` Artem Blagodarenko
  2026-04-18  6:47 ` [syzbot ci] Re: Data in direntry (dirdata) feature syzbot ci
  2026-04-18 21:43 ` [PATCH 0/3] " Theodore Tso
  4 siblings, 0 replies; 10+ messages in thread
From: Artem Blagodarenko @ 2026-04-17 21:37 UTC (permalink / raw)
  To: linux-ext4
  Cc: adilger.kernel, Artem Blagodarenko, Pravin Shelar, Andreas Dilger

When fscrypt and casefold are enabled together for a directory,
all ext4_dir_entry[_2] in that directory store a n 8-byte hash
of the filename after 'name' between 'name_len' and 'rec_len'.

However, there is no clear indication there is important data
stored in these bytes, which are only for padding and alignment
in other directory entries.  This adds complexity to code handling
the on-disk directory entries, and there is no provision for other
metadata to be stored in each dir entry after 'name'.

The dirdata feature adds a mechanism to store multiple metadata
entries in each dir entry after 'name' (including the fchash).
The unused high 4 bits of 'file_type' are used to indicate whether
additional data fields are stored after 'name'.  If a bit is set,
the corresponding dirdata record is present, starting after a NUL
filename terminator.  If present, a record starts with a 1-byte
length (including the length byte itself) and the data immediately
follows the length byte without any alignment.

This allows up to four different dirdata records to be stored in
each entry, and allows unhandled record bytes to be skipped without
having to process the contents, providing forward compatibility.

If and when the fourth and last dirdata record is needed, it is
recommended to further subdivide it into sub-records, with
the first byte being the total length, and then there being a
second byte that gives the sub-record length, etc. as long as
the total record length is less than 255 bytes.  However, this
would not affect compatibility with the current code since the
record length would allow it to be skipped without processing.

Signed-off-by: Pravin Shelar <pravin.shelar@sun.com>
Signed-off-by: Artem Blagodarenko <artem.blagodarenko@gmail.com>
Reviewed-by: Andreas Dilger <adilger@dilger.ca>
---
 fs/ext4/dir.c    |   9 +-
 fs/ext4/ext4.h   |  50 +++++++++---
 fs/ext4/inline.c |  22 ++---
 fs/ext4/namei.c  | 208 ++++++++++++++++++++++++++++++++++++-----------
 fs/ext4/super.c  |   4 +-
 fs/ext4/sysfs.c  |   2 +
 6 files changed, 219 insertions(+), 76 deletions(-)

diff --git a/fs/ext4/dir.c b/fs/ext4/dir.c
index 28b2a3deb954..08833524e499 100644
--- a/fs/ext4/dir.c
+++ b/fs/ext4/dir.c
@@ -89,16 +89,15 @@ int __ext4_check_dir_entry(const char *function, unsigned int line,
 	bool fake = is_fake_dir_entry(de);
 	bool has_csum = ext4_has_feature_metadata_csum(dir->i_sb);
 
-	if (unlikely(rlen < ext4_dir_rec_len(1, fake ? NULL : dir)))
+	if (unlikely(rlen < ext4_dirent_rec_len(1, fake ? NULL : dir)))
 		error_msg = "rec_len is smaller than minimal";
 	else if (unlikely(rlen % 4 != 0))
 		error_msg = "rec_len % 4 != 0";
-	else if (unlikely(rlen < ext4_dir_rec_len(de->name_len,
-							fake ? NULL : dir)))
+	else if (unlikely(rlen < ext4_dir_entry_len(de, fake ? NULL : dir)))
 		error_msg = "rec_len is too small for name_len";
 	else if (unlikely(next_offset > size))
 		error_msg = "directory entry overrun";
-	else if (unlikely(next_offset > size - ext4_dir_rec_len(1,
+	else if (unlikely(next_offset > size - ext4_dirent_rec_len(1,
 						  has_csum ? NULL : dir) &&
 			  next_offset != size))
 		error_msg = "directory entry too close to block end";
@@ -245,7 +244,7 @@ static int ext4_readdir(struct file *file, struct dir_context *ctx)
 				 * failure will be detected in the
 				 * dirent test below. */
 				if (ext4_rec_len_from_disk(de->rec_len,
-					sb->s_blocksize) < ext4_dir_rec_len(1,
+					sb->s_blocksize) < ext4_dirent_rec_len(1,
 									inode))
 					break;
 				i += ext4_rec_len_from_disk(de->rec_len,
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 09d277e24dde..28271d42bfaf 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -1217,6 +1217,7 @@ struct ext4_inode_info {
  * Mount flags set via mount options or defaults
  */
 #define EXT4_MOUNT_NO_MBCACHE		0x00001 /* Do not use mbcache */
+#define EXT4_MOUNT_DIRDATA		0x00002 /* Data in directory entries */
 #define EXT4_MOUNT_GRPID		0x00004	/* Create files with directory's group */
 #define EXT4_MOUNT_DEBUG		0x00008	/* Some debugging messages */
 #define EXT4_MOUNT_ERRORS_CONT		0x00010	/* Continue on errors */
@@ -2253,6 +2254,7 @@ EXT4_FEATURE_INCOMPAT_FUNCS(casefold,		CASEFOLD)
 					 EXT4_FEATURE_INCOMPAT_INLINE_DATA | \
 					 EXT4_FEATURE_INCOMPAT_ENCRYPT | \
 					 EXT4_FEATURE_INCOMPAT_CASEFOLD | \
+					 EXT4_FEATURE_INCOMPAT_DIRDATA | \
 					 EXT4_FEATURE_INCOMPAT_CSUM_SEED | \
 					 EXT4_FEATURE_INCOMPAT_LARGEDIR)
 #define EXT4_FEATURE_RO_COMPAT_SUPP	(EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER| \
@@ -2937,11 +2939,20 @@ extern void ext4_htree_free_dir_info(struct dir_private_info *p);
 extern int ext4_find_dest_de(struct inode *dir, struct buffer_head *bh,
 			     void *buf, int buf_size,
 			     struct ext4_filename *fname,
-			     struct ext4_dir_entry_2 **dest_de);
-void ext4_insert_dentry(struct inode *dir, struct inode *inode,
-			struct ext4_dir_entry_2 *de,
-			int buf_size,
-			struct ext4_filename *fname);
+			     struct ext4_dir_entry_2 **dest_de,
+			     int dlen);
+void ext4_insert_dentry_data(struct inode *dir, struct inode *inode,
+			     struct ext4_dir_entry_2 *de,
+			     int buf_size,
+			     struct ext4_filename *fname,
+			     void *data);
+static inline void ext4_insert_dentry(struct inode *dir, struct inode *inode,
+				      struct ext4_dir_entry_2 *de,
+				      int buf_size,
+				      struct ext4_filename *fname)
+{
+	ext4_insert_dentry_data(dir, inode, de, buf_size, fname, NULL);
+}
 static inline void ext4_update_dx_flag(struct inode *inode)
 {
 	if (!ext4_has_feature_dir_index(inode->i_sb) &&
@@ -2955,9 +2966,9 @@ static const unsigned char ext4_filetype_table[] = {
 	DT_UNKNOWN, DT_REG, DT_DIR, DT_CHR, DT_BLK, DT_FIFO, DT_SOCK, DT_LNK
 };
 
-static inline  unsigned char get_dtype(struct super_block *sb, int filetype)
+static inline unsigned char get_dtype(struct super_block *sb, int filetype)
 {
-	unsigned char  fl_index = filetype & EXT4_FT_MASK;
+	unsigned char fl_index = filetype & EXT4_FT_MASK;
 
 	if (!ext4_has_feature_filetype(sb) || fl_index >= EXT4_FT_MAX)
 		return DT_UNKNOWN;
@@ -3186,8 +3197,17 @@ extern int ext4_ext_migrate(struct inode *);
 extern int ext4_ind_migrate(struct inode *inode);
 
 /* namei.c */
-extern int ext4_init_new_dir(handle_t *handle, struct inode *dir,
-			     struct inode *inode);
+extern int ext4_init_new_dir_data(handle_t *handle, struct inode *dir,
+				  struct inode *inode,
+				  const void *data1, const void *data2);
+static inline int ext4_init_new_dir(handle_t *handle, struct inode *dir,
+				    struct inode *inode)
+{
+	return ext4_init_new_dir_data(handle, dir, inode, NULL, NULL);
+}
+extern int ext4_add_dot_dotdot(handle_t *handle, struct inode *dir,
+			       struct inode *inode,
+			       const void *data1, const void *data2);
 extern int ext4_dirblock_csum_verify(struct inode *inode,
 				     struct buffer_head *bh);
 extern int ext4_htree_fill_tree(struct file *dir_file, __u32 start_hash,
@@ -3762,6 +3782,9 @@ extern int __ext4_unlink(struct inode *dir, const struct qstr *d_name,
 			 struct inode *inode, struct dentry *dentry);
 extern int __ext4_link(struct inode *dir, struct inode *inode,
 		       struct dentry *dentry);
+extern unsigned char ext4_dirdata_get(struct ext4_dir_entry_2 *de,
+				      struct inode *dir,
+				      void *data, u32 *hash, u32 *minor_hash);
 
 #define S_SHIFT 12
 static const unsigned char ext4_type_by_mode[(S_IFMT >> S_SHIFT) + 1] = {
@@ -4007,7 +4030,14 @@ static inline int ext4_dirent_get_data_len(struct ext4_dir_entry_2 *de)
 
 	while (extra_data_flags) {
 		if (extra_data_flags & 1) {
-			dlen += ddh->ddh_length + (dlen == 0);
+			/*
+			 * The first dirdata field is preceded by a NUL
+			 * terminator byte that is already included in ddh's
+			 * pointer offset, but must be counted in dlen.
+			 */
+			if (dlen == 0)
+				dlen = 1; /* NUL terminator */
+			dlen += ddh->ddh_length;
 			ddh = ext4_dirdata_next(ddh);
 		}
 		extra_data_flags >>= 1;
diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c
index 1f6bc05593df..071a637c8869 100644
--- a/fs/ext4/inline.c
+++ b/fs/ext4/inline.c
@@ -969,7 +969,7 @@ static int ext4_add_dirent_to_inline(handle_t *handle,
 	struct ext4_dir_entry_2 *de;
 
 	err = ext4_find_dest_de(dir, iloc->bh, inline_start,
-				inline_size, fname, &de);
+				inline_size, fname, &de, 0);
 	if (err)
 		return err;
 
@@ -978,7 +978,7 @@ static int ext4_add_dirent_to_inline(handle_t *handle,
 					    EXT4_JTR_NONE);
 	if (err)
 		return err;
-	ext4_insert_dentry(dir, inode, de, inline_size, fname);
+	ext4_insert_dentry_data(dir, inode, de, inline_size, fname, NULL);
 
 	ext4_show_inline_dir(dir, iloc->bh, inline_start, inline_size);
 
@@ -1047,7 +1047,7 @@ static int ext4_update_inline_dir(handle_t *handle, struct inode *dir,
 	int old_size = EXT4_I(dir)->i_inline_size - EXT4_MIN_INLINE_DATA_SIZE;
 	int new_size = get_max_inline_xattr_value_size(dir, iloc);
 
-	if (new_size - old_size <= ext4_dir_rec_len(1, NULL))
+	if (new_size - old_size <= ext4_dirent_rec_len(1, NULL))
 		return -ENOSPC;
 
 	ret = ext4_update_inline_data(handle, dir,
@@ -1301,7 +1301,7 @@ int ext4_inlinedir_to_tree(struct file *dir_file,
 			fake.name_len = 1;
 			memcpy(fake.name, ".", 2);
 			fake.rec_len = ext4_rec_len_to_disk(
-					  ext4_dir_rec_len(fake.name_len, NULL),
+					  ext4_dirent_rec_len(fake.name_len, NULL),
 					  inline_size);
 			ext4_set_de_type(inode->i_sb, &fake, S_IFDIR);
 			de = &fake;
@@ -1311,7 +1311,7 @@ int ext4_inlinedir_to_tree(struct file *dir_file,
 			fake.name_len = 2;
 			memcpy(fake.name, "..", 3);
 			fake.rec_len = ext4_rec_len_to_disk(
-					  ext4_dir_rec_len(fake.name_len, NULL),
+					  ext4_dirent_rec_len(fake.name_len, NULL),
 					  inline_size);
 			ext4_set_de_type(inode->i_sb, &fake, S_IFDIR);
 			de = &fake;
@@ -1327,9 +1327,9 @@ int ext4_inlinedir_to_tree(struct file *dir_file,
 			}
 		}
 
-		if (ext4_hash_in_dirent(dir)) {
-			hinfo->hash = EXT4_DIRENT_HASH(de);
-			hinfo->minor_hash = EXT4_DIRENT_MINOR_HASH(de);
+		if (ext4_dirdata_get(de, dir, NULL, &hinfo->hash,
+				     &hinfo->minor_hash) & EXT4_DIRENT_CFHASH) {
+			/* hash retrieved from dirdata or inline hash */
 		} else {
 			err = ext4fs_dirhash(dir, de->name, de->name_len, hinfo);
 			if (err) {
@@ -1419,8 +1419,8 @@ int ext4_read_inline_dir(struct file *file,
 	 * So we will use extra_offset and extra_size to indicate them
 	 * during the inline dir iteration.
 	 */
-	dotdot_offset = ext4_dir_rec_len(1, NULL);
-	dotdot_size = dotdot_offset + ext4_dir_rec_len(2, NULL);
+	dotdot_offset = ext4_dirent_rec_len(1, NULL);
+	dotdot_size = dotdot_offset + ext4_dirent_rec_len(2, NULL);
 	extra_offset = dotdot_size - EXT4_INLINE_DOTDOT_SIZE;
 	extra_size = extra_offset + inline_size;
 
@@ -1455,7 +1455,7 @@ int ext4_read_inline_dir(struct file *file,
 			 * failure will be detected in the
 			 * dirent test below. */
 			if (ext4_rec_len_from_disk(de->rec_len, extra_size)
-				< ext4_dir_rec_len(1, NULL))
+				< ext4_dirent_rec_len(1, NULL))
 				break;
 			i += ext4_rec_len_from_disk(de->rec_len,
 						    extra_size);
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index ab2b4bb4a93d..3d478d7ef339 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -305,7 +305,6 @@ static struct ext4_dir_entry_tail *get_dirent_tail(struct inode *inode,
 						   struct buffer_head *bh)
 {
 	struct ext4_dir_entry_tail *t;
-	int blocksize = EXT4_BLOCK_SIZE(inode->i_sb);
 
 #ifdef PARANOID
 	struct ext4_dir_entry *d, *top;
@@ -402,23 +401,24 @@ static struct dx_countlimit *get_dx_countlimit(struct inode *inode,
 {
 	struct ext4_dir_entry_2 *de;
 	struct dx_root_info *root;
-	int count_offset;
+	int count_offset, dot_rec_len, dotdot_rec_len;
 	int blocksize = EXT4_BLOCK_SIZE(inode->i_sb);
 	unsigned int rlen = ext4_rec_len_from_disk(dirent->rec_len, blocksize);
 
-	if (rlen == blocksize)
+	if (rlen == blocksize) {
 		count_offset = 8;
-	else if (rlen == 12) {
-		de = (struct ext4_dir_entry_2 *)(((void *)dirent) + 12);
-		if (ext4_rec_len_from_disk(de->rec_len, blocksize) != blocksize - 12)
+	} else {
+		dot_rec_len = le16_to_cpu(dirent->rec_len);
+		de = (struct ext4_dir_entry_2 *)(((char *)dirent) + dot_rec_len);
+		if (le16_to_cpu(de->rec_len) != (blocksize - dot_rec_len))
 			return NULL;
-		root = (struct dx_root_info *)(((void *)de + 12));
+		dotdot_rec_len = ext4_dir_entry_len((struct ext4_dir_entry_2 *)de, NULL);
+		root = (struct dx_root_info *)(((char *)de + dotdot_rec_len));
 		if (root->reserved_zero ||
 		    root->info_length != sizeof(struct dx_root_info))
 			return NULL;
-		count_offset = 32;
-	} else
-		return NULL;
+		count_offset = 8 + dot_rec_len + dotdot_rec_len;
+	}
 
 	if (offset)
 		*offset = count_offset;
@@ -590,7 +590,7 @@ static inline unsigned dx_root_limit(struct inode *dir,
 static inline unsigned dx_node_limit(struct inode *dir)
 {
 	unsigned int entry_space = dir->i_sb->s_blocksize -
-			ext4_dir_rec_len(0, dir);
+			ext4_dirent_rec_len(0, dir);
 
 	if (ext4_has_feature_metadata_csum(dir->i_sb))
 		entry_space -= sizeof(struct dx_tail);
@@ -700,7 +700,7 @@ static struct stats dx_show_leaf(struct inode *dir,
 				       (unsigned) ((char *) de - base));
 #endif
 			}
-			space += ext4_dir_rec_len(de->name_len, dir);
+			space += ext4_dir_entry_len(de, dir);
 			names++;
 		}
 		de = ext4_next_entry(de, size);
@@ -1062,7 +1062,7 @@ static int htree_dirblock_to_tree(struct file *dir_file,
 	/* csum entries are not larger in the casefolded encrypted case */
 	top = (struct ext4_dir_entry_2 *) ((char *) de +
 					   dir->i_sb->s_blocksize -
-					   ext4_dir_rec_len(0,
+					   ext4_dirent_rec_len(0,
 							   csum ? NULL : dir));
 	/* Check if the directory is encrypted */
 	if (IS_ENCRYPTED(dir)) {
@@ -1087,21 +1087,20 @@ static int htree_dirblock_to_tree(struct file *dir_file,
 			/* silently ignore the rest of the block */
 			break;
 		}
-		if (ext4_hash_in_dirent(dir)) {
-			if (de->name_len && de->inode) {
-				hinfo->hash = EXT4_DIRENT_HASH(de);
-				hinfo->minor_hash = EXT4_DIRENT_MINOR_HASH(de);
-			} else {
-				hinfo->hash = 0;
-				hinfo->minor_hash = 0;
-			}
-		} else {
+		if (de->name_len && de->inode &&
+		    (ext4_dirdata_get(de, dir, NULL, &hinfo->hash,
+				      &hinfo->minor_hash) & EXT4_DIRENT_CFHASH)) {
+			/* hash retrieved from dirdata or inline hash */
+		} else if (de->name_len && de->inode) {
 			err = ext4fs_dirhash(dir, de->name,
 					     de->name_len, hinfo);
 			if (err < 0) {
 				count = err;
 				goto errout;
 			}
+		} else {
+			hinfo->hash = 0;
+			hinfo->minor_hash = 0;
 		}
 		if ((hinfo->hash < start_hash) ||
 		    ((hinfo->hash == start_hash) &&
@@ -1280,9 +1279,95 @@ static inline int search_dirblock(struct buffer_head *bh,
  */
 
 /*
- * Create map of hash values, offsets, and sizes, stored at end of block.
- * Returns number of entries mapped.
+ * ext4_dirdata_get() - Read dirdata fields from a directory entry.
+ * @de:         directory entry
+ * @dir:        directory inode (used for fscrypt+casefold hash fallback)
+ * @data:       if non-NULL and EXT4_DIRENT_LUFID is set, LUFID data is copied here
+ * @hash:       if non-NULL, receives the casefold hash
+ * @minor_hash: if non-NULL, receives the casefold minor hash
+ *
+ * Reads any dirdata stored in @de.  If the dirdata feature is not enabled,
+ * falls back to reading the hash stored inline after the filename (for
+ * compatibility with the older casefold+fscrypt format).
+ *
+ * Returns a bitmask of EXT4_DIRENT_* flags indicating which fields were read.
+ */
+unsigned char ext4_dirdata_get(struct ext4_dir_entry_2 *de, struct inode *dir,
+			       void *data, u32 *hash, u32 *minor_hash)
+{
+	unsigned char ret = 0;
+	int data_offset = de->name_len + 1;
+
+	/* compatibility: hash stored inline after filename (no dirdata) */
+	if (!ext4_has_feature_dirdata(dir->i_sb) && ext4_hash_in_dirent(dir)) {
+		*hash = EXT4_DIRENT_HASH(de);
+		*minor_hash = EXT4_DIRENT_MINOR_HASH(de);
+		ret |= EXT4_DIRENT_CFHASH;
+		return ret;
+	}
+
+	/*  EXT4_DIRENT_* are not expected without flag in i_sb */
+	if (de->file_type & EXT4_DIRENT_LUFID) {
+		if (data) {
+			memcpy(data, de->name + de->name_len + 1 + 1,
+			       de->name[de->name_len + 1]);
+			ret |= EXT4_DIRENT_LUFID;
+		}
+		data_offset += de->name[data_offset] + 1;
+	}
+
+	if (!hash || !minor_hash)
+		return ret;
+
+	if (de->file_type & EXT4_DIRENT_CFHASH) {
+		struct ext4_dirent_hash *dh =
+			(struct ext4_dirent_hash *)(de->name + data_offset);
+
+		*hash = le32_to_cpu(dh->dh_hash.hash);
+		*minor_hash = le32_to_cpu(dh->dh_hash.minor_hash);
+		ret |= EXT4_DIRENT_CFHASH;
+		return ret;
+	}
+
+	return ret;
+}
+
+/*
+ * ext4_dirdata_set() - Write dirdata fields into a directory entry.
+ * @de:    directory entry (name must already be set)
+ * @dir:   directory inode
+ * @data:  LUFID data to store (or NULL)
+ * @fname: filename info carrying the casefold hash
+ *
+ * Writes any required dirdata into @de after the filename.  If the dirdata
+ * feature is not enabled, falls back to writing the hash inline after the
+ * filename (for compatibility with the older casefold+fscrypt format).
  */
+static void ext4_dirdata_set(struct ext4_dir_entry_2 *de, struct inode *dir,
+			     void *data, struct ext4_filename *fname)
+{
+	int data_offset = de->name_len + 1;
+
+	if (data) {
+		de->name[de->name_len] = 0;
+		memcpy(&de->name[de->name_len + 1], data, *(char *)data);
+		de->file_type |= EXT4_DIRENT_LUFID;
+		data_offset += *(char *)data + 1;
+	}
+
+	if (ext4_hash_in_dirent(dir)) {
+		struct ext4_dirent_hash *dh =
+			(struct ext4_dirent_hash *)(de->name + data_offset);
+		struct dx_hash_info *hinfo = &fname->hinfo;
+
+		dh->dh_header.ddh_length = sizeof(*dh);
+		dh->dh_hash.hash = cpu_to_le32(hinfo->hash);
+		dh->dh_hash.minor_hash = cpu_to_le32(hinfo->minor_hash);
+		de->file_type |= EXT4_DIRENT_CFHASH;
+	}
+}
+
+
 static int dx_make_map(struct inode *dir, struct buffer_head *bh,
 		       struct dx_hash_info *hinfo,
 		       struct dx_map_entry *map_tail)
@@ -1302,9 +1387,9 @@ static int dx_make_map(struct inode *dir, struct buffer_head *bh,
 					 ((char *)de) - base))
 			return -EFSCORRUPTED;
 		if (de->name_len && de->inode) {
-			if (ext4_hash_in_dirent(dir))
-				h.hash = EXT4_DIRENT_HASH(de);
-			else {
+			if (!(ext4_dirdata_get(de, dir, NULL, &h.hash,
+					       &h.minor_hash) &
+						EXT4_DIRENT_CFHASH)) {
 				int err = ext4fs_dirhash(dir, de->name,
 						     de->name_len, &h);
 				if (err < 0)
@@ -1856,7 +1941,7 @@ dx_move_dirents(struct inode *dir, char *from, char *to,
 	while (count--) {
 		struct ext4_dir_entry_2 *de = (struct ext4_dir_entry_2 *)
 						(from + (map->offs<<2));
-		rec_len = ext4_dir_rec_len(de->name_len, dir);
+		rec_len = ext4_dir_entry_len(de, dir);
 
 		memcpy (to, de, rec_len);
 		((struct ext4_dir_entry_2 *) to)->rec_len =
@@ -1889,7 +1974,7 @@ static struct ext4_dir_entry_2 *dx_pack_dirents(struct inode *dir, char *base,
 	while ((char*)de < base + blocksize) {
 		next = ext4_next_entry(de, blocksize);
 		if (de->inode && de->name_len) {
-			rec_len = ext4_dir_rec_len(de->name_len, dir);
+			rec_len = ext4_dir_entry_len(de, dir);
 			if (de > to)
 				memmove(to, de, rec_len);
 			to->rec_len = ext4_rec_len_to_disk(rec_len, blocksize);
@@ -2041,10 +2126,11 @@ static struct ext4_dir_entry_2 *do_split(handle_t *handle, struct inode *dir,
 int ext4_find_dest_de(struct inode *dir, struct buffer_head *bh,
 		      void *buf, int buf_size,
 		      struct ext4_filename *fname,
-		      struct ext4_dir_entry_2 **dest_de)
+		      struct ext4_dir_entry_2 **dest_de,
+		      int dlen)
 {
 	struct ext4_dir_entry_2 *de;
-	unsigned short reclen = ext4_dir_rec_len(fname_len(fname), dir);
+	unsigned short reclen = ext4_dirent_rec_len(fname_len(fname) + dlen, dir);
 	int nlen, rlen;
 	unsigned int offset = 0;
 	char *top;
@@ -2057,7 +2143,7 @@ int ext4_find_dest_de(struct inode *dir, struct buffer_head *bh,
 			return -EFSCORRUPTED;
 		if (ext4_match(dir, fname, de))
 			return -EEXIST;
-		nlen = ext4_dir_rec_len(de->name_len, dir);
+		nlen = ext4_dir_entry_len(de, dir);
 		rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
 		if ((de->inode ? rlen - nlen : rlen) >= reclen)
 			break;
@@ -2071,16 +2157,17 @@ int ext4_find_dest_de(struct inode *dir, struct buffer_head *bh,
 	return 0;
 }
 
-void ext4_insert_dentry(struct inode *dir,
-			struct inode *inode,
-			struct ext4_dir_entry_2 *de,
-			int buf_size,
-			struct ext4_filename *fname)
+void ext4_insert_dentry_data(struct inode *dir,
+			     struct inode *inode,
+			     struct ext4_dir_entry_2 *de,
+			     int buf_size,
+			     struct ext4_filename *fname,
+			     void *data)
 {
 
 	int nlen, rlen;
 
-	nlen = ext4_dir_rec_len(de->name_len, dir);
+	nlen = ext4_dir_entry_len(de, dir);
 	rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
 	if (de->inode) {
 		struct ext4_dir_entry_2 *de1 =
@@ -2094,7 +2181,9 @@ void ext4_insert_dentry(struct inode *dir,
 	ext4_set_de_type(inode->i_sb, de, inode->i_mode);
 	de->name_len = fname_len(fname);
 	memcpy(de->name, fname_name(fname), fname_len(fname));
-	if (ext4_hash_in_dirent(dir)) {
+	if (ext4_has_feature_dirdata(inode->i_sb)) {
+		ext4_dirdata_set(de, dir, data, fname);
+	} else if (ext4_hash_in_dirent(dir)) {
 		struct dx_hash_info *hinfo = &fname->hinfo;
 
 		EXT4_DIRENT_HASHES(de)->hash = cpu_to_le32(hinfo->hash);
@@ -2118,14 +2207,18 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
 {
 	unsigned int	blocksize = dir->i_sb->s_blocksize;
 	int		csum_size = 0;
-	int		err, err2;
+	int		err, err2, dlen = 0;
+	unsigned char	*data = NULL;
 
+	/* Deliver data in any appropriate way here. Now it is NULL */
 	if (ext4_has_feature_metadata_csum(inode->i_sb))
 		csum_size = sizeof(struct ext4_dir_entry_tail);
 
 	if (!de) {
+		if (data)
+			dlen = (*data) + 1;
 		err = ext4_find_dest_de(dir, bh, bh->b_data,
-					blocksize - csum_size, fname, &de);
+					blocksize - csum_size, fname, &de, dlen);
 		if (err)
 			return err;
 	}
@@ -2138,7 +2231,7 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
 	}
 
 	/* By now the buffer is marked for journaling */
-	ext4_insert_dentry(dir, inode, de, blocksize, fname);
+	ext4_insert_dentry_data(dir, inode, de, blocksize, fname, data);
 
 	/*
 	 * XXX shouldn't update any times until successful
@@ -2927,7 +3020,7 @@ int ext4_init_dirblock(handle_t *handle, struct inode *inode,
 
 	de->inode = cpu_to_le32(inode->i_ino);
 	de->name_len = 1;
-	de->rec_len = ext4_rec_len_to_disk(ext4_dir_rec_len(de->name_len, NULL),
+	de->rec_len = ext4_rec_len_to_disk(ext4_dirent_rec_len(de->name_len, NULL),
 					   blocksize);
 	memcpy(de->name, ".", 2);
 	ext4_set_de_type(inode->i_sb, de, S_IFDIR);
@@ -2939,7 +3032,7 @@ int ext4_init_dirblock(handle_t *handle, struct inode *inode,
 	ext4_set_de_type(inode->i_sb, de, S_IFDIR);
 	if (inline_buf) {
 		de->rec_len = ext4_rec_len_to_disk(
-					ext4_dir_rec_len(de->name_len, NULL),
+					ext4_dirent_rec_len(de->name_len, NULL),
 					blocksize);
 		de = ext4_next_entry(de, blocksize);
 		header_size = (char *)de - bh->b_data;
@@ -2948,7 +3041,7 @@ int ext4_init_dirblock(handle_t *handle, struct inode *inode,
 			blocksize - csum_size);
 	} else {
 		de->rec_len = ext4_rec_len_to_disk(blocksize -
-					(csum_size + ext4_dir_rec_len(1, NULL)),
+					(csum_size + ext4_dirent_rec_len(1, NULL)),
 					blocksize);
 	}
 
@@ -2960,8 +3053,9 @@ int ext4_init_dirblock(handle_t *handle, struct inode *inode,
 	return ext4_handle_dirty_dirblock(handle, inode, bh);
 }
 
-int ext4_init_new_dir(handle_t *handle, struct inode *dir,
-			     struct inode *inode)
+int ext4_init_new_dir_data(handle_t *handle, struct inode *dir,
+			   struct inode *inode,
+			   const void *data1, const void *data2)
 {
 	struct buffer_head *dir_block = NULL;
 	ext4_lblk_t block = 0;
@@ -2986,6 +3080,22 @@ int ext4_init_new_dir(handle_t *handle, struct inode *dir,
 	return err;
 }
 
+int ext4_add_dot_dotdot(handle_t *handle, struct inode *dir,
+			struct inode *inode,
+			const void *data1, const void *data2)
+{
+	struct buffer_head *dir_block = NULL;
+	ext4_lblk_t block = 0;
+	int err;
+
+	dir_block = ext4_append(handle, inode, &block);
+	if (IS_ERR(dir_block))
+		return PTR_ERR(dir_block);
+	err = ext4_init_dirblock(handle, inode, dir_block, dir->i_ino, NULL, 0);
+	brelse(dir_block);
+	return err;
+}
+
 static struct dentry *ext4_mkdir(struct mnt_idmap *idmap, struct inode *dir,
 				 struct dentry *dentry, umode_t mode)
 {
@@ -3071,8 +3181,8 @@ bool ext4_empty_dir(struct inode *inode)
 	}
 
 	sb = inode->i_sb;
-	if (inode->i_size < ext4_dir_rec_len(1, NULL) +
-					ext4_dir_rec_len(2, NULL)) {
+	if (inode->i_size < ext4_dirent_rec_len(1, NULL) +
+					ext4_dirent_rec_len(2, NULL)) {
 		EXT4_ERROR_INODE(inode, "invalid size");
 		return false;
 	}
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index 43f680c750ae..28151a99e126 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1673,7 +1673,7 @@ enum {
 	Opt_data_err_abort, Opt_data_err_ignore, Opt_test_dummy_encryption,
 	Opt_inlinecrypt,
 	Opt_usrjquota, Opt_grpjquota, Opt_quota,
-	Opt_noquota, Opt_barrier, Opt_nobarrier, Opt_err,
+	Opt_noquota, Opt_barrier, Opt_nobarrier, Opt_err, Opt_dirdata,
 	Opt_usrquota, Opt_grpquota, Opt_prjquota,
 	Opt_dax, Opt_dax_always, Opt_dax_inode, Opt_dax_never,
 	Opt_stripe, Opt_delalloc, Opt_nodelalloc, Opt_warn_on_error,
@@ -1783,6 +1783,7 @@ static const struct fs_parameter_spec ext4_param_specs[] = {
 	fsparam_u32	("stripe",		Opt_stripe),
 	fsparam_flag	("delalloc",		Opt_delalloc),
 	fsparam_flag	("nodelalloc",		Opt_nodelalloc),
+	fsparam_flag	("dirdata",		Opt_dirdata),
 	fsparam_flag	("warn_on_error",	Opt_warn_on_error),
 	fsparam_flag	("nowarn_on_error",	Opt_nowarn_on_error),
 	fsparam_u32	("debug_want_extra_isize",
@@ -1911,6 +1912,7 @@ static const struct mount_opts {
 							MOPT_CLEAR | MOPT_Q},
 	{Opt_usrjquota, 0, MOPT_Q},
 	{Opt_grpjquota, 0, MOPT_Q},
+	{Opt_dirdata, EXT4_MOUNT_DIRDATA, MOPT_SET},
 	{Opt_jqfmt, 0, MOPT_QFMT},
 	{Opt_nombcache, EXT4_MOUNT_NO_MBCACHE, MOPT_SET},
 	{Opt_no_prefetch_block_bitmaps, EXT4_MOUNT_NO_PREFETCH_BLOCK_BITMAPS,
diff --git a/fs/ext4/sysfs.c b/fs/ext4/sysfs.c
index b87d7bdab06a..1f5e9627e735 100644
--- a/fs/ext4/sysfs.c
+++ b/fs/ext4/sysfs.c
@@ -362,6 +362,7 @@ EXT4_ATTR_FEATURE(verity);
 #endif
 EXT4_ATTR_FEATURE(metadata_csum_seed);
 EXT4_ATTR_FEATURE(fast_commit);
+EXT4_ATTR_FEATURE(dirdata);
 #if IS_ENABLED(CONFIG_UNICODE) && defined(CONFIG_FS_ENCRYPTION)
 EXT4_ATTR_FEATURE(encrypted_casefold);
 #endif
@@ -385,6 +386,7 @@ static struct attribute *ext4_feat_attrs[] = {
 #endif
 	ATTR_LIST(metadata_csum_seed),
 	ATTR_LIST(fast_commit),
+	ATTR_LIST(dirdata),
 #if IS_ENABLED(CONFIG_UNICODE) && defined(CONFIG_FS_ENCRYPTION)
 	ATTR_LIST(encrypted_casefold),
 #endif
-- 
2.43.5


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [syzbot ci] Re: Data in direntry (dirdata) feature
  2026-04-17 21:37 [PATCH 0/3] Data in direntry (dirdata) feature Artem Blagodarenko
                   ` (2 preceding siblings ...)
  2026-04-17 21:37 ` [PATCH 3/3] ext4: dirdata feature Artem Blagodarenko
@ 2026-04-18  6:47 ` syzbot ci
  2026-04-18 21:43 ` [PATCH 0/3] " Theodore Tso
  4 siblings, 0 replies; 10+ messages in thread
From: syzbot ci @ 2026-04-18  6:47 UTC (permalink / raw)
  To: adilger.kernel, adilger, artem.blagodarenko, linux-ext4,
	pravin.shelar
  Cc: syzbot, syzkaller-bugs

syzbot ci has tested the following series

[v1] Data in direntry (dirdata) feature
https://lore.kernel.org/all/20260417213723.74204-1-artem.blagodarenko@gmail.com
* [PATCH 1/3] ext4: make dirdata work with metadata_csum
* [PATCH 2/3] ext4: add dirdata support structures and helpers
* [PATCH 3/3] ext4: dirdata feature

and found the following issues:
* KASAN: slab-out-of-bounds Read in __ext4_check_dir_entry
* KASAN: slab-out-of-bounds Read in dx_probe
* KASAN: slab-use-after-free Read in __ext4_check_dir_entry
* KASAN: slab-use-after-free Read in dx_probe
* KASAN: use-after-free Read in __ext4_check_dir_entry

Full report is available here:
https://ci.syzbot.org/series/590e846e-42c0-4497-b6ae-b95ed4468941

***

KASAN: slab-out-of-bounds Read in __ext4_check_dir_entry

tree:      torvalds
URL:       https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux
base:      70b672833f4025341c11b22c7f83778a5cd611bc
arch:      amd64
compiler:  Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8
config:    https://ci.syzbot.org/builds/7b860156-1ca9-441f-b899-ebfb5a09620d/config
syz repro: https://ci.syzbot.org/findings/d27eccd2-4663-4047-abb9-9c24cb32f887/syz_repro

loop0: lost filesystem error report for type 5 error -117
EXT4-fs (loop0): mounted filesystem 00000000-0000-0000-0000-000000000000 r/w without journal. Quota mode: none.
==================================================================
BUG: KASAN: slab-out-of-bounds in ext4_dirent_get_data_len fs/ext4/ext4.h:4040 [inline]
BUG: KASAN: slab-out-of-bounds in ext4_dir_entry_len fs/ext4/ext4.h:4060 [inline]
BUG: KASAN: slab-out-of-bounds in __ext4_check_dir_entry+0x527/0xa70 fs/ext4/dir.c:96
Read of size 1 at addr ffff8881090bfe5c by task syz.0.20/5967

CPU: 1 UID: 0 PID: 5967 Comm: syz.0.20 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
Call Trace:
 <TASK>
 dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0xba/0x230 mm/kasan/report.c:482
 kasan_report+0x117/0x150 mm/kasan/report.c:595
 ext4_dirent_get_data_len fs/ext4/ext4.h:4040 [inline]
 ext4_dir_entry_len fs/ext4/ext4.h:4060 [inline]
 __ext4_check_dir_entry+0x527/0xa70 fs/ext4/dir.c:96
 ext4_inlinedir_to_tree+0x6be/0xbf0 fs/ext4/inline.c:1322
 ext4_htree_fill_tree+0x50b/0x1230 fs/ext4/namei.c:1184
 ext4_dx_readdir fs/ext4/dir.c:600 [inline]
 ext4_readdir+0x2f7b/0x3870 fs/ext4/dir.c:146
 iterate_dir+0x399/0x570 fs/readdir.c:108
 __do_sys_getdents64 fs/readdir.c:412 [inline]
 __se_sys_getdents64+0xf1/0x280 fs/readdir.c:397
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fe47219c819
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fe4730c0028 EFLAGS: 00000246 ORIG_RAX: 00000000000000d9
RAX: ffffffffffffffda RBX: 00007fe472415fa0 RCX: 00007fe47219c819
RDX: 000000000000ff80 RSI: 9999999999999999 RDI: 0000000000000004
RBP: 00007fe472232c91 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007fe472416038 R14: 00007fe472415fa0 R15: 00007ffff595c618
 </TASK>

Allocated by task 5967:
 kasan_save_stack mm/kasan/common.c:57 [inline]
 kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
 poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
 __kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
 kasan_kmalloc include/linux/kasan.h:263 [inline]
 __do_kmalloc_node mm/slub.c:5260 [inline]
 __kmalloc_noprof+0x35c/0x760 mm/slub.c:5272
 kmalloc_noprof include/linux/slab.h:954 [inline]
 ext4_mb_init+0x15d/0x2ad0 fs/ext4/mballoc.c:3729
 __ext4_fill_super fs/ext4/super.c:5623 [inline]
 ext4_fill_super+0x5647/0x6320 fs/ext4/super.c:5793
 get_tree_bdev_flags+0x431/0x4f0 fs/super.c:1694
 vfs_get_tree+0x92/0x2a0 fs/super.c:1754
 fc_mount fs/namespace.c:1193 [inline]
 do_new_mount_fc fs/namespace.c:3763 [inline]
 do_new_mount+0x341/0xd30 fs/namespace.c:3839
 do_mount fs/namespace.c:4172 [inline]
 __do_sys_mount fs/namespace.c:4361 [inline]
 __se_sys_mount+0x31d/0x420 fs/namespace.c:4338
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

The buggy address belongs to the object at ffff8881090bfe00
 which belongs to the cache kmalloc-64 of size 64
The buggy address is located 44 bytes to the right of
 allocated 48-byte region [ffff8881090bfe00, ffff8881090bfe30)

The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x1090bf
flags: 0x17ff00000000000(node=0|zone=2|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 017ff00000000000 ffff8881000418c0 dead000000000100 dead000000000122
raw: 0000000000000000 0000000800200020 00000000f5000000 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0xd2cc0(GFP_KERNEL|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 1, tgid 1 (swapper/0), ts 16414655371, free_ts 15537272896
 set_page_owner include/linux/page_owner.h:32 [inline]
 post_alloc_hook+0x231/0x280 mm/page_alloc.c:1889
 prep_new_page mm/page_alloc.c:1897 [inline]
 get_page_from_freelist+0x24dc/0x2580 mm/page_alloc.c:3962
 __alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5250
 alloc_slab_page mm/slub.c:3292 [inline]
 allocate_slab+0x77/0x660 mm/slub.c:3481
 new_slab mm/slub.c:3539 [inline]
 refill_objects+0x331/0x3c0 mm/slub.c:7175
 refill_sheaf mm/slub.c:2812 [inline]
 __pcs_replace_empty_main+0x2e6/0x730 mm/slub.c:4615
 alloc_from_pcs mm/slub.c:4717 [inline]
 slab_alloc_node mm/slub.c:4851 [inline]
 __do_kmalloc_node mm/slub.c:5259 [inline]
 __kmalloc_noprof+0x474/0x760 mm/slub.c:5272
 kmalloc_noprof include/linux/slab.h:954 [inline]
 kzalloc_noprof include/linux/slab.h:1188 [inline]
 kobject_get_path+0xc5/0x2f0 lib/kobject.c:161
 kobject_uevent_env+0x2a1/0x9e0 lib/kobject_uevent.c:545
 really_probe+0x789/0xaf0 drivers/base/dd.c:771
 __driver_probe_device+0x18c/0x320 drivers/base/dd.c:863
 driver_probe_device+0x4f/0x240 drivers/base/dd.c:893
 __device_attach_driver+0x279/0x430 drivers/base/dd.c:1021
 bus_for_each_drv+0x258/0x2f0 drivers/base/bus.c:500
 __device_attach+0x2c5/0x450 drivers/base/dd.c:1093
 device_initial_probe+0xa1/0xd0 drivers/base/dd.c:1148
page last free pid 33 tgid 33 stack trace:
 reset_page_owner include/linux/page_owner.h:25 [inline]
 __free_pages_prepare mm/page_alloc.c:1433 [inline]
 __free_frozen_pages+0xc2b/0xdb0 mm/page_alloc.c:2978
 vfree+0x25a/0x400 mm/vmalloc.c:3479
 delayed_vfree_work+0x55/0x80 mm/vmalloc.c:3398
 process_one_work kernel/workqueue.c:3276 [inline]
 process_scheduled_works+0xb6e/0x18c0 kernel/workqueue.c:3359
 worker_thread+0xa53/0xfc0 kernel/workqueue.c:3440
 kthread+0x388/0x470 kernel/kthread.c:436
 ret_from_fork+0x51e/0xb90 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245

Memory state around the buggy address:
 ffff8881090bfd00: 00 00 00 00 00 00 00 04 fc fc fc fc fc fc fc fc
 ffff8881090bfd80: 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc fc
>ffff8881090bfe00: 00 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc
                                                    ^
 ffff8881090bfe80: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
 ffff8881090bff00: 00 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc
==================================================================


***

KASAN: slab-out-of-bounds Read in dx_probe

tree:      torvalds
URL:       https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux
base:      70b672833f4025341c11b22c7f83778a5cd611bc
arch:      amd64
compiler:  Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8
config:    https://ci.syzbot.org/builds/7b860156-1ca9-441f-b899-ebfb5a09620d/config
syz repro: https://ci.syzbot.org/findings/a5fcf3bd-f1ae-4b81-b5d2-f96899ea7690/syz_repro

==================================================================
BUG: KASAN: slab-out-of-bounds in ext4_dir_entry_is_tail fs/ext4/ext4.h:4001 [inline]
BUG: KASAN: slab-out-of-bounds in ext4_dirent_get_data_len fs/ext4/ext4.h:4024 [inline]
BUG: KASAN: slab-out-of-bounds in ext4_dir_entry_len fs/ext4/ext4.h:4060 [inline]
BUG: KASAN: slab-out-of-bounds in dx_root_limit fs/ext4/namei.c:583 [inline]
BUG: KASAN: slab-out-of-bounds in dx_probe+0x1ac9/0x1d90 fs/ext4/namei.c:861
Read of size 4 at addr ffff88816a408c10 by task syz.2.19/5980

CPU: 1 UID: 0 PID: 5980 Comm: syz.2.19 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
Call Trace:
 <TASK>
 dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0xba/0x230 mm/kasan/report.c:482
 kasan_report+0x117/0x150 mm/kasan/report.c:595
 ext4_dir_entry_is_tail fs/ext4/ext4.h:4001 [inline]
 ext4_dirent_get_data_len fs/ext4/ext4.h:4024 [inline]
 ext4_dir_entry_len fs/ext4/ext4.h:4060 [inline]
 dx_root_limit fs/ext4/namei.c:583 [inline]
 dx_probe+0x1ac9/0x1d90 fs/ext4/namei.c:861
 ext4_dx_find_entry fs/ext4/namei.c:1812 [inline]
 __ext4_find_entry+0x5a9/0x2140 fs/ext4/namei.c:1652
 ext4_lookup_entry fs/ext4/namei.c:1794 [inline]
 ext4_lookup+0x17b/0x710 fs/ext4/namei.c:1860
 __lookup_slow+0x2b7/0x410 fs/namei.c:1916
 lookup_slow+0x53/0x70 fs/namei.c:1933
 walk_component fs/namei.c:2279 [inline]
 lookup_last fs/namei.c:2780 [inline]
 path_lookupat+0x3f5/0x8c0 fs/namei.c:2804
 filename_lookup+0x256/0x5d0 fs/namei.c:2833
 user_path_at+0x40/0x160 fs/namei.c:3612
 do_mount fs/namespace.c:4169 [inline]
 __do_sys_mount fs/namespace.c:4361 [inline]
 __se_sys_mount+0x2dc/0x420 fs/namespace.c:4338
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f518919c819
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f5189fbb028 EFLAGS: 00000246 ORIG_RAX: 00000000000000a5
RAX: ffffffffffffffda RBX: 00007f5189415fa0 RCX: 00007f518919c819
RDX: 0000200000000140 RSI: 0000200000000100 RDI: 0000000000000000
RBP: 00007f5189232c91 R08: 0000200000000d80 R09: 0000000000000000
R10: 0000000001302060 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f5189416038 R14: 00007f5189415fa0 R15: 00007ffc5d8d15b8
 </TASK>

Allocated by task 5243:
 kasan_save_stack mm/kasan/common.c:57 [inline]
 kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
 poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
 __kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
 kasan_kmalloc include/linux/kasan.h:263 [inline]
 __do_kmalloc_node mm/slub.c:5260 [inline]
 __kvmalloc_node_noprof+0x528/0x8a0 mm/slub.c:6752
 evdev_open+0xeb/0x5b0 drivers/input/evdev.c:468
 chrdev_open+0x4cd/0x5e0 fs/char_dev.c:411
 do_dentry_open+0x785/0x14e0 fs/open.c:949
 vfs_open+0x3b/0x340 fs/open.c:1081
 do_open fs/namei.c:4671 [inline]
 path_openat+0x2e08/0x3860 fs/namei.c:4830
 do_file_open+0x23e/0x4a0 fs/namei.c:4859
 do_sys_openat2+0x113/0x200 fs/open.c:1366
 do_sys_open fs/open.c:1372 [inline]
 __do_sys_openat fs/open.c:1388 [inline]
 __se_sys_openat fs/open.c:1383 [inline]
 __x64_sys_openat+0x138/0x170 fs/open.c:1383
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

The buggy address belongs to the object at ffff88816a408000
 which belongs to the cache kmalloc-2k of size 2048
The buggy address is located 1088 bytes to the right of
 allocated 2000-byte region [ffff88816a408000, ffff88816a4087d0)

The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x16a408
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0x57ff00000000040(head|node=1|zone=2|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 057ff00000000040 ffff888100042000 dead000000000100 dead000000000122
raw: 0000000000000000 0000000800080008 00000000f5000000 0000000000000000
head: 057ff00000000040 ffff888100042000 dead000000000100 dead000000000122
head: 0000000000000000 0000000800080008 00000000f5000000 0000000000000000
head: 057ff00000000003 ffffea0005a90201 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd20c0(__GFP_IO|__GFP_FS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 5243, tgid 5243 (acpid), ts 25906150911, free_ts 21905842969
 set_page_owner include/linux/page_owner.h:32 [inline]
 post_alloc_hook+0x231/0x280 mm/page_alloc.c:1889
 prep_new_page mm/page_alloc.c:1897 [inline]
 get_page_from_freelist+0x24dc/0x2580 mm/page_alloc.c:3962
 __alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5250
 alloc_slab_page mm/slub.c:3292 [inline]
 allocate_slab+0x77/0x660 mm/slub.c:3481
 new_slab mm/slub.c:3539 [inline]
 refill_objects+0x331/0x3c0 mm/slub.c:7175
 refill_sheaf mm/slub.c:2812 [inline]
 __pcs_replace_empty_main+0x2e6/0x730 mm/slub.c:4615
 alloc_from_pcs mm/slub.c:4717 [inline]
 slab_alloc_node mm/slub.c:4851 [inline]
 __do_kmalloc_node mm/slub.c:5259 [inline]
 __kvmalloc_node_noprof+0x657/0x8a0 mm/slub.c:6752
 evdev_open+0xeb/0x5b0 drivers/input/evdev.c:468
 chrdev_open+0x4cd/0x5e0 fs/char_dev.c:411
 do_dentry_open+0x785/0x14e0 fs/open.c:949
 vfs_open+0x3b/0x340 fs/open.c:1081
 do_open fs/namei.c:4671 [inline]
 path_openat+0x2e08/0x3860 fs/namei.c:4830
 do_file_open+0x23e/0x4a0 fs/namei.c:4859
 do_sys_openat2+0x113/0x200 fs/open.c:1366
 do_sys_open fs/open.c:1372 [inline]
 __do_sys_openat fs/open.c:1388 [inline]
 __se_sys_openat fs/open.c:1383 [inline]
 __x64_sys_openat+0x138/0x170 fs/open.c:1383
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
page last free pid 9 tgid 9 stack trace:
 reset_page_owner include/linux/page_owner.h:25 [inline]
 __free_pages_prepare mm/page_alloc.c:1433 [inline]
 __free_frozen_pages+0xc2b/0xdb0 mm/page_alloc.c:2978
 vfree+0x25a/0x400 mm/vmalloc.c:3479
 delayed_vfree_work+0x55/0x80 mm/vmalloc.c:3398
 process_one_work kernel/workqueue.c:3276 [inline]
 process_scheduled_works+0xb6e/0x18c0 kernel/workqueue.c:3359
 worker_thread+0xa53/0xfc0 kernel/workqueue.c:3440
 kthread+0x388/0x470 kernel/kthread.c:436
 ret_from_fork+0x51e/0xb90 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245

Memory state around the buggy address:
 ffff88816a408b00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
 ffff88816a408b80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
>ffff88816a408c00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
                         ^
 ffff88816a408c80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
 ffff88816a408d00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
==================================================================


***

KASAN: slab-use-after-free Read in __ext4_check_dir_entry

tree:      torvalds
URL:       https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux
base:      70b672833f4025341c11b22c7f83778a5cd611bc
arch:      amd64
compiler:  Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8
config:    https://ci.syzbot.org/builds/7b860156-1ca9-441f-b899-ebfb5a09620d/config
syz repro: https://ci.syzbot.org/findings/e493ff66-4032-4979-9b5d-5118b2768ca5/syz_repro

EXT4-fs (loop0): mounted filesystem 00000000-0000-0000-0000-000000000000 r/w without journal. Quota mode: none.
==================================================================
BUG: KASAN: slab-use-after-free in ext4_dirent_get_data_len fs/ext4/ext4.h:4040 [inline]
BUG: KASAN: slab-use-after-free in ext4_dir_entry_len fs/ext4/ext4.h:4060 [inline]
BUG: KASAN: slab-use-after-free in __ext4_check_dir_entry+0x527/0xa70 fs/ext4/dir.c:96
Read of size 1 at addr ffff88810138889c by task syz.0.24/5978

CPU: 0 UID: 0 PID: 5978 Comm: syz.0.24 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
Call Trace:
 <TASK>
 dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0xba/0x230 mm/kasan/report.c:482
 kasan_report+0x117/0x150 mm/kasan/report.c:595
 ext4_dirent_get_data_len fs/ext4/ext4.h:4040 [inline]
 ext4_dir_entry_len fs/ext4/ext4.h:4060 [inline]
 __ext4_check_dir_entry+0x527/0xa70 fs/ext4/dir.c:96
 ext4_inlinedir_to_tree+0x6be/0xbf0 fs/ext4/inline.c:1322
 ext4_htree_fill_tree+0x50b/0x1230 fs/ext4/namei.c:1184
 ext4_dx_readdir fs/ext4/dir.c:600 [inline]
 ext4_readdir+0x2f7b/0x3870 fs/ext4/dir.c:146
 iterate_dir+0x399/0x570 fs/readdir.c:108
 __do_sys_getdents64 fs/readdir.c:412 [inline]
 __se_sys_getdents64+0xf1/0x280 fs/readdir.c:397
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7ff3fe19c819
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ff3fefac028 EFLAGS: 00000246 ORIG_RAX: 00000000000000d9
RAX: ffffffffffffffda RBX: 00007ff3fe415fa0 RCX: 00007ff3fe19c819
RDX: 000000000000ff80 RSI: 9999999999999999 RDI: 0000000000000004
RBP: 00007ff3fe232c91 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007ff3fe416038 R14: 00007ff3fe415fa0 R15: 00007ffd39a74998
 </TASK>

Allocated by task 5766:
 kasan_save_stack mm/kasan/common.c:57 [inline]
 kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
 poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
 __kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
 kasan_kmalloc include/linux/kasan.h:263 [inline]
 __do_kmalloc_node mm/slub.c:5260 [inline]
 __kmalloc_noprof+0x35c/0x760 mm/slub.c:5272
 kmalloc_noprof include/linux/slab.h:954 [inline]
 kzalloc_noprof include/linux/slab.h:1188 [inline]
 tomoyo_encode2 security/tomoyo/realpath.c:45 [inline]
 tomoyo_encode+0x28b/0x550 security/tomoyo/realpath.c:80
 tomoyo_realpath_from_path+0x58d/0x5d0 security/tomoyo/realpath.c:283
 tomoyo_get_realpath security/tomoyo/file.c:151 [inline]
 tomoyo_check_open_permission+0x229/0x470 security/tomoyo/file.c:776
 security_file_open+0xa9/0x240 security/security.c:2637
 do_dentry_open+0x384/0x14e0 fs/open.c:926
 vfs_open+0x3b/0x340 fs/open.c:1081
 do_open fs/namei.c:4671 [inline]
 path_openat+0x2e08/0x3860 fs/namei.c:4830
 do_file_open+0x23e/0x4a0 fs/namei.c:4859
 do_sys_openat2+0x113/0x200 fs/open.c:1366
 do_sys_open fs/open.c:1372 [inline]
 __do_sys_openat fs/open.c:1388 [inline]
 __se_sys_openat fs/open.c:1383 [inline]
 __x64_sys_openat+0x138/0x170 fs/open.c:1383
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Freed by task 5766:
 kasan_save_stack mm/kasan/common.c:57 [inline]
 kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
 kasan_save_free_info+0x46/0x50 mm/kasan/generic.c:584
 poison_slab_object mm/kasan/common.c:253 [inline]
 __kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
 kasan_slab_free include/linux/kasan.h:235 [inline]
 slab_free_hook mm/slub.c:2685 [inline]
 slab_free mm/slub.c:6165 [inline]
 kfree+0x1c1/0x630 mm/slub.c:6483
 tomoyo_check_open_permission+0x32c/0x470 security/tomoyo/file.c:791
 security_file_open+0xa9/0x240 security/security.c:2637
 do_dentry_open+0x384/0x14e0 fs/open.c:926
 vfs_open+0x3b/0x340 fs/open.c:1081
 do_open fs/namei.c:4671 [inline]
 path_openat+0x2e08/0x3860 fs/namei.c:4830
 do_file_open+0x23e/0x4a0 fs/namei.c:4859
 do_sys_openat2+0x113/0x200 fs/open.c:1366
 do_sys_open fs/open.c:1372 [inline]
 __do_sys_openat fs/open.c:1388 [inline]
 __se_sys_openat fs/open.c:1383 [inline]
 __x64_sys_openat+0x138/0x170 fs/open.c:1383
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

The buggy address belongs to the object at ffff888101388880
 which belongs to the cache kmalloc-64 of size 64
The buggy address is located 28 bytes inside of
 freed 64-byte region [ffff888101388880, ffff8881013888c0)

The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x101388
flags: 0x17ff00000000000(node=0|zone=2|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 017ff00000000000 ffff8881000418c0 dead000000000100 dead000000000122
raw: 0000000000000000 0000000800200020 00000000f5000000 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0xd2cc0(GFP_KERNEL|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 26, tgid 26 (kworker/u9:0), ts 4100832473, free_ts 4100767003
 set_page_owner include/linux/page_owner.h:32 [inline]
 post_alloc_hook+0x231/0x280 mm/page_alloc.c:1889
 prep_new_page mm/page_alloc.c:1897 [inline]
 get_page_from_freelist+0x24dc/0x2580 mm/page_alloc.c:3962
 __alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5250
 alloc_slab_page mm/slub.c:3292 [inline]
 allocate_slab+0x77/0x660 mm/slub.c:3481
 new_slab mm/slub.c:3539 [inline]
 refill_objects+0x331/0x3c0 mm/slub.c:7175
 refill_sheaf mm/slub.c:2812 [inline]
 alloc_full_sheaf mm/slub.c:2834 [inline]
 __pcs_replace_empty_main+0x40a/0x730 mm/slub.c:4626
 alloc_from_pcs mm/slub.c:4717 [inline]
 slab_alloc_node mm/slub.c:4851 [inline]
 __do_kmalloc_node mm/slub.c:5259 [inline]
 __kmalloc_noprof+0x474/0x760 mm/slub.c:5272
 kmalloc_noprof include/linux/slab.h:954 [inline]
 kzalloc_noprof include/linux/slab.h:1188 [inline]
 lsm_blob_alloc security/security.c:193 [inline]
 lsm_task_alloc security/security.c:245 [inline]
 security_task_alloc+0x4d/0x330 security/security.c:2683
 copy_process+0x16df/0x3cd0 kernel/fork.c:2206
 kernel_clone+0x248/0x8e0 kernel/fork.c:2658
 user_mode_thread+0x110/0x180 kernel/fork.c:2734
 call_usermodehelper_exec_work+0x5c/0x230 kernel/umh.c:171
 process_one_work kernel/workqueue.c:3276 [inline]
 process_scheduled_works+0xb6e/0x18c0 kernel/workqueue.c:3359
 worker_thread+0xa53/0xfc0 kernel/workqueue.c:3440
 kthread+0x388/0x470 kernel/kthread.c:436
 ret_from_fork+0x51e/0xb90 arch/x86/kernel/process.c:158
page last free pid 26 tgid 26 stack trace:
 reset_page_owner include/linux/page_owner.h:25 [inline]
 __free_pages_prepare mm/page_alloc.c:1433 [inline]
 __free_frozen_pages+0xc2b/0xdb0 mm/page_alloc.c:2978
 __kasan_populate_vmalloc_do mm/kasan/shadow.c:393 [inline]
 __kasan_populate_vmalloc+0x1b2/0x1d0 mm/kasan/shadow.c:424
 kasan_populate_vmalloc include/linux/kasan.h:580 [inline]
 alloc_vmap_area+0xd73/0x14b0 mm/vmalloc.c:2129
 __get_vm_area_node+0x1f8/0x300 mm/vmalloc.c:3232
 __vmalloc_node_range_noprof+0x372/0x1730 mm/vmalloc.c:4024
 __vmalloc_node_noprof+0xc2/0x100 mm/vmalloc.c:4124
 alloc_thread_stack_node kernel/fork.c:355 [inline]
 dup_task_struct+0x292/0x9e0 kernel/fork.c:924
 copy_process+0x508/0x3cd0 kernel/fork.c:2051
 kernel_clone+0x248/0x8e0 kernel/fork.c:2658
 user_mode_thread+0x110/0x180 kernel/fork.c:2734
 call_usermodehelper_exec_work+0x5c/0x230 kernel/umh.c:171
 process_one_work kernel/workqueue.c:3276 [inline]
 process_scheduled_works+0xb6e/0x18c0 kernel/workqueue.c:3359
 worker_thread+0xa53/0xfc0 kernel/workqueue.c:3440
 kthread+0x388/0x470 kernel/kthread.c:436
 ret_from_fork+0x51e/0xb90 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245

Memory state around the buggy address:
 ffff888101388780: 00 00 00 00 00 00 00 04 fc fc fc fc fc fc fc fc
 ffff888101388800: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
>ffff888101388880: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
                            ^
 ffff888101388900: 00 00 00 00 00 00 00 fc fc fc fc fc fc fc fc fc
 ffff888101388980: 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc fc
==================================================================


***

KASAN: slab-use-after-free Read in dx_probe

tree:      torvalds
URL:       https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux
base:      70b672833f4025341c11b22c7f83778a5cd611bc
arch:      amd64
compiler:  Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8
config:    https://ci.syzbot.org/builds/7b860156-1ca9-441f-b899-ebfb5a09620d/config
syz repro: https://ci.syzbot.org/findings/5524eff5-62fb-4cff-8d4e-7e3750aa921b/syz_repro

EXT4-fs (loop2): mounted filesystem 00000000-0000-0000-0000-000000000000 ro without journal. Quota mode: none.
==================================================================
BUG: KASAN: slab-use-after-free in ext4_dir_entry_is_tail fs/ext4/ext4.h:4001 [inline]
BUG: KASAN: slab-use-after-free in ext4_dirent_get_data_len fs/ext4/ext4.h:4024 [inline]
BUG: KASAN: slab-use-after-free in ext4_dir_entry_len fs/ext4/ext4.h:4060 [inline]
BUG: KASAN: slab-use-after-free in dx_root_limit fs/ext4/namei.c:583 [inline]
BUG: KASAN: slab-use-after-free in dx_probe+0x1ac9/0x1d90 fs/ext4/namei.c:861
Read of size 4 at addr ffff888109c42c10 by task syz.2.20/5984

CPU: 0 UID: 0 PID: 5984 Comm: syz.2.20 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
Call Trace:
 <TASK>
 dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0xba/0x230 mm/kasan/report.c:482
 kasan_report+0x117/0x150 mm/kasan/report.c:595
 ext4_dir_entry_is_tail fs/ext4/ext4.h:4001 [inline]
 ext4_dirent_get_data_len fs/ext4/ext4.h:4024 [inline]
 ext4_dir_entry_len fs/ext4/ext4.h:4060 [inline]
 dx_root_limit fs/ext4/namei.c:583 [inline]
 dx_probe+0x1ac9/0x1d90 fs/ext4/namei.c:861
 ext4_dx_find_entry fs/ext4/namei.c:1812 [inline]
 __ext4_find_entry+0x5a9/0x2140 fs/ext4/namei.c:1652
 ext4_lookup_entry fs/ext4/namei.c:1794 [inline]
 ext4_lookup+0x17b/0x710 fs/ext4/namei.c:1860
 lookup_open fs/namei.c:4456 [inline]
 open_last_lookups fs/namei.c:4583 [inline]
 path_openat+0x11ac/0x3860 fs/namei.c:4827
 do_file_open+0x23e/0x4a0 fs/namei.c:4859
 do_sys_openat2+0x113/0x200 fs/open.c:1366
 do_sys_open fs/open.c:1372 [inline]
 __do_sys_openat fs/open.c:1388 [inline]
 __se_sys_openat fs/open.c:1383 [inline]
 __x64_sys_openat+0x138/0x170 fs/open.c:1383
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fb97dd9c819
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fb97ec96028 EFLAGS: 00000246 ORIG_RAX: 0000000000000101
RAX: ffffffffffffffda RBX: 00007fb97e015fa0 RCX: 00007fb97dd9c819
RDX: 0000000000042041 RSI: 0000200000000700 RDI: ffffffffffffff9c
RBP: 00007fb97de32c91 R08: 0000000000000000 R09: 0000000000000000
R10: 000000000000001d R11: 0000000000000246 R12: 0000000000000000
R13: 00007fb97e016038 R14: 00007fb97e015fa0 R15: 00007fffcac5f5a8
 </TASK>

Allocated by task 5828:
 kasan_save_stack mm/kasan/common.c:57 [inline]
 kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
 poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
 __kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
 kasan_kmalloc include/linux/kasan.h:263 [inline]
 __do_kmalloc_node mm/slub.c:5260 [inline]
 __kmalloc_noprof+0x35c/0x760 mm/slub.c:5272
 kmalloc_noprof include/linux/slab.h:954 [inline]
 tomoyo_realpath_from_path+0xe3/0x5d0 security/tomoyo/realpath.c:251
 tomoyo_get_realpath security/tomoyo/file.c:151 [inline]
 tomoyo_path_perm+0x283/0x560 security/tomoyo/file.c:827
 tomoyo_path_symlink+0xab/0xf0 security/tomoyo/tomoyo.c:212
 security_path_symlink+0x16f/0x360 security/security.c:1477
 filename_symlinkat+0x134/0x410 fs/namei.c:5638
 __do_sys_symlink fs/namei.c:5667 [inline]
 __se_sys_symlink+0x4d/0x2b0 fs/namei.c:5663
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Freed by task 5828:
 kasan_save_stack mm/kasan/common.c:57 [inline]
 kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
 kasan_save_free_info+0x46/0x50 mm/kasan/generic.c:584
 poison_slab_object mm/kasan/common.c:253 [inline]
 __kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
 kasan_slab_free include/linux/kasan.h:235 [inline]
 slab_free_hook mm/slub.c:2685 [inline]
 slab_free mm/slub.c:6165 [inline]
 kfree+0x1c1/0x630 mm/slub.c:6483
 tomoyo_realpath_from_path+0x598/0x5d0 security/tomoyo/realpath.c:286
 tomoyo_get_realpath security/tomoyo/file.c:151 [inline]
 tomoyo_path_perm+0x283/0x560 security/tomoyo/file.c:827
 tomoyo_path_symlink+0xab/0xf0 security/tomoyo/tomoyo.c:212
 security_path_symlink+0x16f/0x360 security/security.c:1477
 filename_symlinkat+0x134/0x410 fs/namei.c:5638
 __do_sys_symlink fs/namei.c:5667 [inline]
 __se_sys_symlink+0x4d/0x2b0 fs/namei.c:5663
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

The buggy address belongs to the object at ffff888109c42000
 which belongs to the cache kmalloc-4k of size 4096
The buggy address is located 3088 bytes inside of
 freed 4096-byte region [ffff888109c42000, ffff888109c43000)

The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x109c40
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0x17ff00000000040(head|node=0|zone=2|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 017ff00000000040 ffff888100042140 dead000000000100 dead000000000122
raw: 0000000000000000 0000000800040004 00000000f5000000 0000000000000000
head: 017ff00000000040 ffff888100042140 dead000000000100 dead000000000122
head: 0000000000000000 0000000800040004 00000000f5000000 0000000000000000
head: 017ff00000000003 ffffea0004271001 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd2040(__GFP_IO|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 5828, tgid 5828 (udevd), ts 67573015608, free_ts 47549723112
 set_page_owner include/linux/page_owner.h:32 [inline]
 post_alloc_hook+0x231/0x280 mm/page_alloc.c:1889
 prep_new_page mm/page_alloc.c:1897 [inline]
 get_page_from_freelist+0x24dc/0x2580 mm/page_alloc.c:3962
 __alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5250
 alloc_slab_page mm/slub.c:3292 [inline]
 allocate_slab+0x77/0x660 mm/slub.c:3481
 new_slab mm/slub.c:3539 [inline]
 refill_objects+0x331/0x3c0 mm/slub.c:7175
 refill_sheaf mm/slub.c:2812 [inline]
 __pcs_replace_empty_main+0x2e6/0x730 mm/slub.c:4615
 alloc_from_pcs mm/slub.c:4717 [inline]
 slab_alloc_node mm/slub.c:4851 [inline]
 __do_kmalloc_node mm/slub.c:5259 [inline]
 __kmalloc_noprof+0x474/0x760 mm/slub.c:5272
 kmalloc_noprof include/linux/slab.h:954 [inline]
 tomoyo_realpath_from_path+0xe3/0x5d0 security/tomoyo/realpath.c:251
 tomoyo_get_realpath security/tomoyo/file.c:151 [inline]
 tomoyo_path_perm+0x283/0x560 security/tomoyo/file.c:827
 tomoyo_path_symlink+0xab/0xf0 security/tomoyo/tomoyo.c:212
 security_path_symlink+0x16f/0x360 security/security.c:1477
 filename_symlinkat+0x134/0x410 fs/namei.c:5638
 __do_sys_symlink fs/namei.c:5667 [inline]
 __se_sys_symlink+0x4d/0x2b0 fs/namei.c:5663
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 5718 tgid 5718 stack trace:
 reset_page_owner include/linux/page_owner.h:25 [inline]
 __free_pages_prepare mm/page_alloc.c:1433 [inline]
 __free_frozen_pages+0xc2b/0xdb0 mm/page_alloc.c:2978
 __slab_free+0x263/0x2b0 mm/slub.c:5573
 qlink_free mm/kasan/quarantine.c:163 [inline]
 qlist_free_all+0x97/0x100 mm/kasan/quarantine.c:179
 kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
 __kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
 kasan_slab_alloc include/linux/kasan.h:253 [inline]
 slab_post_alloc_hook mm/slub.c:4538 [inline]
 slab_alloc_node mm/slub.c:4866 [inline]
 kmem_cache_alloc_noprof+0x2bc/0x650 mm/slub.c:4873
 alloc_filename fs/namei.c:142 [inline]
 do_getname+0x2e/0x250 fs/namei.c:182
 getname include/linux/fs.h:2512 [inline]
 getname_maybe_null include/linux/fs.h:2519 [inline]
 class_filename_maybe_null_constructor include/linux/fs.h:2543 [inline]
 vfs_fstatat+0x45/0x170 fs/stat.c:368
 __do_sys_newfstatat fs/stat.c:538 [inline]
 __se_sys_newfstatat fs/stat.c:532 [inline]
 __x64_sys_newfstatat+0x151/0x200 fs/stat.c:532
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Memory state around the buggy address:
 ffff888109c42b00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
 ffff888109c42b80: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff888109c42c00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
                         ^
 ffff888109c42c80: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
 ffff888109c42d00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================


***

KASAN: use-after-free Read in __ext4_check_dir_entry

tree:      torvalds
URL:       https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux
base:      70b672833f4025341c11b22c7f83778a5cd611bc
arch:      amd64
compiler:  Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8
config:    https://ci.syzbot.org/builds/7b860156-1ca9-441f-b899-ebfb5a09620d/config
syz repro: https://ci.syzbot.org/findings/21333690-a422-407b-92c7-9247a0075b74/syz_repro

loop1: lost filesystem error report for type 5 error -117
EXT4-fs (loop1): mounted filesystem 00000000-0000-0000-0000-000000000000 r/w without journal. Quota mode: none.
==================================================================
BUG: KASAN: use-after-free in ext4_dirent_get_data_len fs/ext4/ext4.h:4040 [inline]
BUG: KASAN: use-after-free in ext4_dir_entry_len fs/ext4/ext4.h:4060 [inline]
BUG: KASAN: use-after-free in __ext4_check_dir_entry+0x527/0xa70 fs/ext4/dir.c:96
Read of size 1 at addr ffff88810da3609c by task syz.1.20/5966

CPU: 0 UID: 0 PID: 5966 Comm: syz.1.20 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
Call Trace:
 <TASK>
 dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0xba/0x230 mm/kasan/report.c:482
 kasan_report+0x117/0x150 mm/kasan/report.c:595
 ext4_dirent_get_data_len fs/ext4/ext4.h:4040 [inline]
 ext4_dir_entry_len fs/ext4/ext4.h:4060 [inline]
 __ext4_check_dir_entry+0x527/0xa70 fs/ext4/dir.c:96
 ext4_inlinedir_to_tree+0x6be/0xbf0 fs/ext4/inline.c:1322
 ext4_htree_fill_tree+0x50b/0x1230 fs/ext4/namei.c:1184
 ext4_dx_readdir fs/ext4/dir.c:600 [inline]
 ext4_readdir+0x2f7b/0x3870 fs/ext4/dir.c:146
 iterate_dir+0x399/0x570 fs/readdir.c:108
 __do_sys_getdents64 fs/readdir.c:412 [inline]
 __se_sys_getdents64+0xf1/0x280 fs/readdir.c:397
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fb82a19c819
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fb82b01a028 EFLAGS: 00000246 ORIG_RAX: 00000000000000d9
RAX: ffffffffffffffda RBX: 00007fb82a415fa0 RCX: 00007fb82a19c819
RDX: 000000000000ff80 RSI: 9999999999999999 RDI: 0000000000000004
RBP: 00007fb82a232c91 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007fb82a416038 R14: 00007fb82a415fa0 R15: 00007ffdc30464e8
 </TASK>

The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0xffff88810da36000 pfn:0x10da36
flags: 0x17ff00000000000(node=0|zone=2|lastcpupid=0x7ff)
page_type: f0(buddy)
raw: 017ff00000000000 ffffea0004364708 ffffea000436a7c8 0000000000000000
raw: ffff88810da36000 0000000000000000 00000000f0000000 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as freed
page last allocated via order 0, migratetype Unmovable, gfp_mask 0xd2c00(GFP_NOIO|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 1, tgid 1 (swapper/0), ts 15037633903, free_ts 26770192426
 set_page_owner include/linux/page_owner.h:32 [inline]
 post_alloc_hook+0x231/0x280 mm/page_alloc.c:1889
 prep_new_page mm/page_alloc.c:1897 [inline]
 get_page_from_freelist+0x24dc/0x2580 mm/page_alloc.c:3962
 __alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5250
 alloc_slab_page mm/slub.c:3292 [inline]
 allocate_slab+0x77/0x660 mm/slub.c:3481
 new_slab mm/slub.c:3539 [inline]
 refill_objects+0x331/0x3c0 mm/slub.c:7175
 refill_sheaf mm/slub.c:2812 [inline]
 __pcs_replace_empty_main+0x2e6/0x730 mm/slub.c:4615
 alloc_from_pcs mm/slub.c:4717 [inline]
 slab_alloc_node mm/slub.c:4851 [inline]
 __do_kmalloc_node mm/slub.c:5259 [inline]
 __kmalloc_noprof+0x474/0x760 mm/slub.c:5272
 kmalloc_noprof include/linux/slab.h:954 [inline]
 usb_alloc_urb+0x46/0x150 drivers/usb/core/urb.c:75
 usb_internal_control_msg drivers/usb/core/message.c:110 [inline]
 usb_control_msg+0x118/0x3e0 drivers/usb/core/message.c:167
 usb_get_descriptor+0xb1/0x3e0 drivers/usb/core/message.c:852
 usb_get_configuration+0x3b9/0x54f0 drivers/usb/core/config.c:986
 usb_enumerate_device drivers/usb/core/hub.c:2527 [inline]
 usb_new_device+0x145/0x16f0 drivers/usb/core/hub.c:2665
 register_root_hub+0x270/0x5f0 drivers/usb/core/hcd.c:990
 usb_add_hcd+0xba1/0x10b0 drivers/usb/core/hcd.c:2987
 vhci_hcd_probe+0x1fa/0x3e0 drivers/usb/usbip/vhci_hcd.c:1401
 platform_probe+0xf9/0x190 drivers/base/platform.c:1418
page last free pid 5262 tgid 5262 stack trace:
 reset_page_owner include/linux/page_owner.h:25 [inline]
 __free_pages_prepare mm/page_alloc.c:1433 [inline]
 __free_frozen_pages+0xc2b/0xdb0 mm/page_alloc.c:2978
 __slab_free+0x263/0x2b0 mm/slub.c:5573
 qlink_free mm/kasan/quarantine.c:163 [inline]
 qlist_free_all+0x97/0x100 mm/kasan/quarantine.c:179
 kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
 __kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
 kasan_slab_alloc include/linux/kasan.h:253 [inline]
 slab_post_alloc_hook mm/slub.c:4538 [inline]
 slab_alloc_node mm/slub.c:4866 [inline]
 kmem_cache_alloc_noprof+0x2bc/0x650 mm/slub.c:4873
 alloc_empty_file+0x55/0x1d0 fs/file_table.c:237
 path_openat+0x10f/0x3860 fs/namei.c:4816
 do_file_open+0x23e/0x4a0 fs/namei.c:4859
 do_sys_openat2+0x113/0x200 fs/open.c:1366
 do_sys_open fs/open.c:1372 [inline]
 __do_sys_openat fs/open.c:1388 [inline]
 __se_sys_openat fs/open.c:1383 [inline]
 __x64_sys_openat+0x138/0x170 fs/open.c:1383
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Memory state around the buggy address:
 ffff88810da35f80: 00 00 00 00 00 00 00 04 fc fc fc fc fc fc fc fc
 ffff88810da36000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
>ffff88810da36080: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
                            ^
 ffff88810da36100: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
 ffff88810da36180: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
==================================================================


***

If these findings have caused you to resend the series or submit a
separate fix, please add the following tag to your commit message:
  Tested-by: syzbot@syzkaller.appspotmail.com

---
This report is generated by a bot. It may contain errors.
syzbot ci engineers can be reached at syzkaller@googlegroups.com.

To test a patch for this bug, please reply with `#syz test`
(should be on a separate line).

The patch should be attached to the email.
Note: arguments like custom git repos and branches are not supported.

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 0/3] Data in direntry (dirdata) feature
  2026-04-17 21:37 [PATCH 0/3] Data in direntry (dirdata) feature Artem Blagodarenko
                   ` (3 preceding siblings ...)
  2026-04-18  6:47 ` [syzbot ci] Re: Data in direntry (dirdata) feature syzbot ci
@ 2026-04-18 21:43 ` Theodore Tso
  2026-04-18 22:24   ` Artem Blagodarenko
  4 siblings, 1 reply; 10+ messages in thread
From: Theodore Tso @ 2026-04-18 21:43 UTC (permalink / raw)
  To: Artem Blagodarenko; +Cc: linux-ext4, adilger.kernel

Hi Artem,

Thank you for sending this patch.  My primary concern with it is that
it seems to be optimized for minimizing the changes for the
out-of-tree Lustre Server patches, but not for necessasrily from the
upstream perspective.

First, there is no way to actually set any dirdata feature using just
the upstream patch.  Presumably, the out-of-tree Lustre patches call
ext4_insert_dentry_data() with a non-NULL data, or add_dirent_to_buf()
in fs/ext4/namei.c was further patched so that the data variable on
the stack gets set to a non-NULL --- but in your patches, data is
always NULL, which manes the data passed into
ext4_insert_dentry_data() is always NULL.

Since there is no way to actually set dirdata after the upstream
kernel, nor is there any way to set the dirdata using debugfs (the
patches to e2fsprogs only affect e2fsck, and there is no way to
examine the dirdata or set dirdata via debugfs), it's going to make it
very hard to test the kernel patches without using pre-existing file
system images.  I haven't seen the xfstests patches from you yet, but
this is the only way you could test the code paths as far as I can
tell.

Secondly, there are a lot of changes because you rename functions like
ext4_dir_rec_len() to ext4_dirent_rec_len() and ext4_insert_dirent()
to ext4_insert_dentry_data().  Especially for the ext4_dir_rec_len()
change, it's not strictly speaking necessary, and while I can see the
argument that it might make it more readable / understandable, if you
*really* want to do the rename, it's better to break that out into a
separate patch which *just* renames the function, and then change the
function prototype in a subsequent patch.


As far as how get this patch upstream, the challenge with LUFID (and
dirdata in general), is that the dirdata has to set when the directory
entry is created, and is immutable afterwards.  And so if we want to
have a way to create directory entries with an LUFID, we either need
to (a) use the out-of-tree Lustre patches, (b) plumb through some new
interface through the VFS and potentially some new openat3() system
call, or (c) add some new ext4 ioctl EXT4_IOC_SET_LUFID which sets the
LUFID on an already existing pathname.

(c) is problematic since it's a different code path than what the
Lustre server would use, and it would require using the dentry used to
opened the file to decide which directory entry to mutate, and it also
violates the presumption that the directory entry's dirdata is
immutable after the directory entry is created.

Speaking of which, I'm really confused with *why* the LUFID is being
stored in the diretory entry.  From what I can tell, from [1], the FID
is essentially a distributed inode number.  Given that, why isn't the
LUFID stored in an xattr?  What am I missing?

[1] https://wiki.lustre.org/Understanding_Lustre_Internals

						- Ted

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 0/3] Data in direntry (dirdata) feature
  2026-04-18 21:43 ` [PATCH 0/3] " Theodore Tso
@ 2026-04-18 22:24   ` Artem Blagodarenko
  2026-04-19  0:47     ` Theodore Tso
  0 siblings, 1 reply; 10+ messages in thread
From: Artem Blagodarenko @ 2026-04-18 22:24 UTC (permalink / raw)
  To: Theodore Tso; +Cc: linux-ext4, adilger.kernel

Hi Theodore,

Thank you for the quick response.

Upstream perspectives are aligned with having a unified way to store
additional data that can be useful for:

- storing hash for encryption + casefold
- storing LUFID for Lustre FS
- storing the high part of the inode number (64-bit inode number)
- potentially other extensions to overcome current limitations

There is now a way to set dirdata. Following the decisions from [1]
the patches provided change how the encryption + casefold
hash is stored. If the dirdata feature is enabled,
the hash is stored as dirdata. Therefore, to test dirdata, it is
sufficient to test the combination of encryption + casefold + dirdata.

At the same time, if some hashes were stored before the dirdata era, the
old hash format continues to be used without issues.

I will send xfstests soon:

ext4/064: encryption + casefold (WITHOUT dirdata)

This test verifies that files created in directories with both
encryption and case-insensitive (casefold) attributes work correctly.

ext4/065: encryption + casefold + dirdata

This test verifies that files created in directories with encryption,
case-insensitive (casefold), and dirdata attributes work correctly.

Regarding the rename functions, I will follow your suggestions and
rewrite the fixes.

LUFID is an optimization that allows Lustre to store file identifiers
efficiently directly in directory entries, avoiding additional I/O
operations to look them up separately.

I am open to adding mechanisms to access LUFID outside the Lustre
filesystem. However, since: 1) dirdata is tested using the encryption +
casefold features, and LUFID is primarily useful within the Lustre filesystem
and is already tested there, I would prefer to rely on testing LUFID
within Lustre FS.

That said, I am open to any suggestions.

[1] https://patchwork.ozlabs.org/project/linux-ext4/patch/20210203090745.4103054-2-drosen@google.com/

Best regards,
Artem Blagodarenko

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 0/3] Data in direntry (dirdata) feature
  2026-04-18 22:24   ` Artem Blagodarenko
@ 2026-04-19  0:47     ` Theodore Tso
  2026-04-19 19:37       ` Artem Blagodarenko
  0 siblings, 1 reply; 10+ messages in thread
From: Theodore Tso @ 2026-04-19  0:47 UTC (permalink / raw)
  To: Artem Blagodarenko; +Cc: linux-ext4, adilger.kernel

On Sat, Apr 18, 2026 at 11:24:32PM +0100, Artem Blagodarenko wrote:
> There is now a way to set dirdata. Following the decisions from [1]
> the patches provided change how the encryption + casefold
> hash is stored. If the dirdata feature is enabled,
> the hash is stored as dirdata. Therefore, to test dirdata, it is
> sufficient to test the combination of encryption + casefold + dirdata.

I'm not seeing that in the patches that was sent out to the list last
week.  Where is that?

I traced all of the places where ext4_insert_dentry_data() and
ext4_dirdata_set() and I don't see *anything* where dirdata was
stored, including the fscrypt + casefold hash.   What am I missing?

It *really* would be a good idea if the e2fsprogs patches included a
way to list and set the dirdata using debugfs.  That way we could
easily verify that dirdata field was getting set when you expected it
to be.

> ext4/064: encryption + casefold (WITHOUT dirdata)
> 
> This test verifies that files created in directories with both
> encryption and case-insensitive (casefold) attributes work correctly.

Yes, but that doesn't actually verify that the dirdata field was set
when you expected it to be.  Just that it works correctly....

> LUFID is an optimization that allows Lustre to store file identifiers
> efficiently directly in directory entries, avoiding additional I/O
> operations to look them up separately.

Oh, I see.  So this is for readdir(), right?

> I am open to adding mechanisms to access LUFID outside the Lustre
> filesystem. However, since: 1) dirdata is tested using the encryption +
> casefold features, and LUFID is primarily useful within the Lustre filesystem
> and is already tested there, I would prefer to rely on testing LUFID
> within Lustre FS.

Well, the one advantage of having a way to set and get LUFID would be
if you ever wanted to ressurrect the userspace Lustre server[1].  :-)

[1]  https://wiki.lustre.org/images/5/56/LUG08-Lustre-uOSS.pdf

And I *do* think it would be useful to have a way to set and get the
LUFID using debugfs.

Cheers,

						- Ted

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 0/3] Data in direntry (dirdata) feature
  2026-04-19  0:47     ` Theodore Tso
@ 2026-04-19 19:37       ` Artem Blagodarenko
  2026-04-19 21:57         ` Theodore Tso
  0 siblings, 1 reply; 10+ messages in thread
From: Artem Blagodarenko @ 2026-04-19 19:37 UTC (permalink / raw)
  To: Theodore Tso; +Cc: linux-ext4, adilger.kernel

On Sun, 19 Apr 2026 at 01:48, Theodore Tso <tytso@mit.edu> wrote:
> I'm not seeing that in the patches that was sent out to the list last
> week.  Where is that?

I have just sent it to the xfstests mail list and placed you to cc

> I traced all of the places where ext4_insert_dentry_data() and
> ext4_dirdata_set() and I don't see *anything* where dirdata was
> stored, including the fscrypt + casefold hash.   What am I missing?

dirdata feature should be enabled, and  fscrypt + casefold used
The new xfstest ext4/065 should help here

> It *really* would be a good idea if the e2fsprogs patches included a
> way to list and set the dirdata using debugfs.  That way we could
> easily verify that dirdata field was getting set when you expected it
> to be.
I will send a patch for debugfs with such a function soon

> Yes, but that doesn't actually verify that the dirdata field was set
> when you expected it to be.  Just that it works correctly....

Ok. I think debugfs should be improved here to solve this.

> Oh, I see.  So this is for readdir(), right?

Yes

> Well, the one advantage of having a way to set and get LUFID would be
> if you ever wanted to ressurrect the userspace Lustre server[1].  :-)
>
> [1]  https://wiki.lustre.org/images/5/56/LUG08-Lustre-uOSS.pdf

Уes that would be convenient. I asked the proposal’s author about this
earlier today. He said the code cannot be moved to userspace due to
its reliance on transactions.

> And I *do* think it would be useful to have a way to set and get the
> LUFID using debugfs.
Ok. Will do.

Best regards,
Artem Blagodarenko

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 0/3] Data in direntry (dirdata) feature
  2026-04-19 19:37       ` Artem Blagodarenko
@ 2026-04-19 21:57         ` Theodore Tso
  0 siblings, 0 replies; 10+ messages in thread
From: Theodore Tso @ 2026-04-19 21:57 UTC (permalink / raw)
  To: Artem Blagodarenko; +Cc: linux-ext4, adilger.kernel

On Sun, Apr 19, 2026 at 08:37:51PM +0100, Artem Blagodarenko wrote:
> On Sun, 19 Apr 2026 at 01:48, Theodore Tso <tytso@mit.edu> wrote:
> > I'm not seeing that in the patches that was sent out to the list last
> > week.  Where is that?
> 
> I have just sent it to the xfstests mail list and placed you to cc
> 
> > I traced all of the places where ext4_insert_dentry_data() and
> > ext4_dirdata_set() and I don't see *anything* where dirdata was
> > stored, including the fscrypt + casefold hash.   What am I missing?
> 
> dirdata feature should be enabled, and  fscrypt + casefold used
> The new xfstest ext4/065 should help here

Can you show *where* in the patched sources the dirdata gets set in
the fscrypt + casefold case?

Also, there is no real value to users for storing the hash with
fscrypt and casefold using dirdata --- unless they are using dirdata
for some other uses --- but the 64-bit inode number will require
significantly more code changes that's not here.  So there is no
user-visible additional functionality of dirdata.  That's why the
tests that you sent aren't particularly useful.  It doesn't actually
test that the fscrypt hash was stored in dirdata when the dirdata
feature enabled.  It just shows that nothing broke, which is *not* the
same thing.

Another way to demonstrate that that that your tests didn't really
test anything is due to another flaw in your patches.  In addition to
the dirdata feature, there is *also* a dirdata mount option.  If the
dirdata mount option is not specified, then the dirdata flags will get
omitted:

static inline unsigned char get_dtype(struct super_block *sb, int filetype)
{
	unsigned char fl_index = filetype & EXT4_FT_MASK;

	if (!ext4_has_feature_filetype(sb) || fl_index >= EXT4_FT_MAX)
		return DT_UNKNOWN;

	if (!test_opt(sb, DIRDATA))
		return ext4_filetype_table[fl_index];

	return (ext4_filetype_table[fl_index]) |
		(filetype & ~EXT4_FT_MASK);
}

But your proposed new test doesn't *actually* set the dirdata mount
option.  So in addition to my not able to find any place where dirdata
gets *set*, this flaw in your patch (I think it was left over from
when you were using a mount option instead of a file system feature),
meant that nothing could possibly *get* the dirdata flag.  Despite
that, your tests are apparently passing, and you apparently didn't
notice that dirdata support is a no-op.


Finally, please take a look at the KASAN bugs which syzbot found,
which includes some use after frees and out-of-bounds bugs.  (This may
mean that there are some real security bugs in your Luster production
patches that could be found by mechanisms such as Anthropic's Mythos,
so you may want to consider whether the bugs found by Syzkaller might
be applicable in your current product patches.)

https://ci.syzbot.org/series/590e846e-42c0-4497-b6ae-b95ed4468941

Also, if you can rebase your patches onto Linux 7.0 when you send the
next around, then Sashiko will be able to review your patches.

I also suggest that you consider breaking up the patches into smaller
chunks.  This makes them easier to review, and perhaps you would have
noticed that the patch was still defining a worse-than-useless dirdata
mount option.

Finally, perhaps you should consider adding a Kunit test for dirdata?
That would allow you to test the LUFID functionality without needing
to plumb through extra userspace interfaces such as an
EXT4_IOC_SET_LUFID ioctl.

Cheers,

						- Ted

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2026-04-19 21:59 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-17 21:37 [PATCH 0/3] Data in direntry (dirdata) feature Artem Blagodarenko
2026-04-17 21:37 ` [PATCH 1/3] ext4: make dirdata work with metadata_csum Artem Blagodarenko
2026-04-17 21:37 ` [PATCH 2/3] ext4: add dirdata support structures and helpers Artem Blagodarenko
2026-04-17 21:37 ` [PATCH 3/3] ext4: dirdata feature Artem Blagodarenko
2026-04-18  6:47 ` [syzbot ci] Re: Data in direntry (dirdata) feature syzbot ci
2026-04-18 21:43 ` [PATCH 0/3] " Theodore Tso
2026-04-18 22:24   ` Artem Blagodarenko
2026-04-19  0:47     ` Theodore Tso
2026-04-19 19:37       ` Artem Blagodarenko
2026-04-19 21:57         ` Theodore Tso

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox