* [RFC PATCH v2 1/6] ext4: add common helper to check whether dirdata is applied
From: Artem Blagodarenko @ 2026-06-19 19:52 UTC (permalink / raw)
To: linux-ext4; +Cc: adilger.kernel, Artem Blagodarenko
In-Reply-To: <20260619195205.29384-1-ablagodarenko@thelustrecollective.com>
From: Artem Blagodarenko <artem.blagodarenko@gmail.com>
Add a helper that lists a directory with the -lD flags and checks
whether any dirdata fields exist.
This helper will be used by subsequent dirdata-related patches.
Signed-off-by: Artem Blagodarenko <artem.blagodarenko@gmail.com>
---
common/ext4 | 34 ++++++++++++++++++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/common/ext4 b/common/ext4
index a2ce456d..47c31db9 100644
--- a/common/ext4
+++ b/common/ext4
@@ -242,3 +242,37 @@ _ext4_get_inum_iflags() {
debugfs -R "stat <${inumber}>" "${dev}" 2> /dev/null | \
sed -n 's/^.*Flags: \([0-9a-fx]*\).*$/\1/p'
}
+
+# Helper to dump directory structure with hash info (requires dirdata feature)
+# This is useful for verifying that dirdata is storing hash information
+_dump_dir_structure()
+{
+ local dir=$1
+ local dir_name=$(basename $dir)
+ local expected=$3
+
+ local debugfs_output=$({
+ echo "cd $dir_name"
+ echo "ls -lD ."
+ echo "quit"
+ } | debugfs $SCRATCH_DEV 2>/dev/null)
+
+ # DEBUG: uncomment to see full debugfs output
+ # echo " [DEBUG] debugfs output for $dir_name:"
+ # echo "$debugfs_output" | grep -v "^debugfs:" | sed 's/^/ /'
+
+ # Check if hash data is present (encryption+casefold+dirdata case)
+ # or if fid data is present (dirdata+encryption or dirdata only case)
+ if echo "$debugfs_output" | grep -q "fid="; then
+ local fid_value=$(echo "$debugfs_output" | grep -o "fid=[^ ]*" | head -1 | sed 's/^fid=//')
+ if [ "$fid_value" = "$expected" ]; then
+ echo " Directory structure of $dir_name: OK (dirdata verified)"
+ else
+ echo " Directory structure of $dir_name: FAILED (fid mismatch: got '$fid_value', expected '$expected')"
+ fi
+ elif echo "$debugfs_output" | grep -q "hash="; then
+ echo " Directory structure of $dir_name: OK (dirdata verified)"
+ else
+ echo " Directory structure of $dir_name: FAILED (no dirdata)"
+ fi
+}
--
2.43.7
^ permalink raw reply related
* [RFC PATCH v2 0/6] ext4: tests for the dirdata feature (encryption+casefold, LUFID)
From: Artem Blagodarenko @ 2026-06-19 19:51 UTC (permalink / raw)
To: linux-ext4; +Cc: adilger.kernel, Artem Blagodarenko
These tests cover the ext4 "dirdata" feature (storing extra metadata
in directory entries beyond the file name), sent separately from the
kernel and e2fsprogs dirdata patch series for reference and review.
ext4/064 and ext4/065 verify that encryption and case-insensitive
(casefold) directories continue to work both without and with
dirdata enabled. ext4/066 and ext4/067 exercise the LUFID (Locally
Unique File ID) use of dirdata via a new EXT4_IOC_SET_LUFID ioctl,
using a small set_lufid helper utility added in this series.
Changes in v2:
- Ted Ts'o pointed out that the v1 tests exercised the
encryption+casefold/dirdata feature combination without actually
validating that the encrypted hash was stored as a dirdata
attribute.
ext4/064 and ext4/065 now use the new _dump_dir_structure helper
(debugfs-based) to dump and check the on-disk directory entry
content, confirming the hash is actually present as dirdata rather
than just exercising the feature combination.
- Added ext4/066 and ext4/067, plus a new common/ext4 helper and the
src/set_lufid.c utility, to directly verify LUFID data is correctly
stored in and retrieved from dirdata via EXT4_IOC_SET_LUFID, including
in combination with encryption+casefold.
Artem Blagodarenko (6):
ext4: add common helper to check whether dirdata is applied
ext4/064 encryption + casefold feature combination WITHOUT dirdata
ext4/065 encryption + casefold + dirdata feature combination
ext4: add set_lufid utility
ext4/066: verify LUFID dirdata operations
ext4/067: LUFID and encryption+casefold+dirdata
common/config | 1 +
common/ext4 | 34 +++++++
src/Makefile | 2 +-
src/set_lufid.c | 196 ++++++++++++++++++++++++++++++++++++++++
tests/ext4/064 | 153 ++++++++++++++++++++++++++++++++
tests/ext4/064.out | 17 ++++
tests/ext4/065 | 217 +++++++++++++++++++++++++++++++++++++++++++++
tests/ext4/065.out | 26 ++++++
tests/ext4/066 | 158 +++++++++++++++++++++++++++++++++
tests/ext4/066.out | 4 +
tests/ext4/067 | 137 ++++++++++++++++++++++++++++
tests/ext4/067.out | 4 +
12 files changed, 948 insertions(+), 1 deletion(-)
create mode 100644 src/set_lufid.c
create mode 100755 tests/ext4/064
create mode 100644 tests/ext4/064.out
create mode 100755 tests/ext4/065
create mode 100644 tests/ext4/065.out
create mode 100755 tests/ext4/066
create mode 100644 tests/ext4/066.out
create mode 100755 tests/ext4/067
create mode 100644 tests/ext4/067.out
--
2.43.7
^ permalink raw reply
* [PATCH v3 10/10] ext4: Add EXT4_IOC_SET_LUFID ioctl for setting LUFID on directory entries
From: Artem Blagodarenko @ 2026-06-19 19:10 UTC (permalink / raw)
To: linux-ext4; +Cc: adilger.kernel, Artem Blagodarenko, Andreas Dilger
In-Reply-To: <20260619191022.27008-1-ablagodarenko@thelustrecollective.com>
From: Artem Blagodarenko <artem.blagodarenko@gmail.com>
Add a new ioctl command that allows setting LUFID (Locally Unique File ID)
data on existing directory entries. This includes:
- ext4_ioctl_set_lufid(): ioctl handler that validates parameters and
calls the underlying implementation
- ext4_set_direntry_lufid(): Core function that performs the operation by:
* Looking up the target directory entry
* Retrieving the associated inode
* Deleting the old entry and re-creating it with LUFID data attached
This implementation requires the dirdata feature to be enabled on the
filesystem and properly handles transactions and inode locking to ensure
consistency.
Signed-off-by: Artem Blagodarenko artem.blagodarenko@gmail.com
Reviewed-by: Andreas Dilger <adilger@dilger.ca>
---
fs/ext4/ext4.h | 15 ++++
fs/ext4/ioctl.c | 62 +++++++++++++++++
fs/ext4/namei.c | 143 ++++++++++++++++++++++++++++++++++++++
include/uapi/linux/ext4.h | 13 ++++
4 files changed, 233 insertions(+)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 543c8e87f207..0a8b9116dc7b 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -1227,6 +1227,7 @@ struct ext4_inode_info {
#ifdef CONFIG_FS_ENCRYPTION
struct fscrypt_inode_info *i_crypt_info;
#endif
+ void *i_dirdata;
};
/*
@@ -2601,6 +2602,18 @@ struct ext4_dirent_hash {
struct ext4_dir_entry_hash dh_hash;
} __packed;
+static inline
+struct ext4_dirent_fid *ext4_dentry_get_fid(struct super_block *sb,
+ struct ext4_dentry_param *p)
+{
+ if (!ext4_has_feature_dirdata(sb))
+ return NULL;
+ if (p && p->edp_magic == EXT4_LUFID_MAGIC)
+ return &p->edp_dfid;
+
+ return NULL;
+}
+
#define EXT4_FT_DIR_CSUM 0xDE
/*
@@ -3302,6 +3315,8 @@ static inline int ext4_init_new_dir(handle_t *handle, struct inode *dir,
}
extern int ext4_dirblock_csum_verify(struct inode *inode,
struct buffer_head *bh);
+extern int ext4_dirdata_set_lufid(struct inode *dir, const char *filename,
+ int namelen, struct ext4_dentry_param *edp);
extern int ext4_htree_fill_tree(struct file *dir_file, __u32 start_hash,
__u32 start_minor_hash, __u32 *next_hash);
extern int ext4_search_dir(struct buffer_head *bh,
diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c
index c8387e6a2c6e..19d6588092d3 100644
--- a/fs/ext4/ioctl.c
+++ b/fs/ext4/ioctl.c
@@ -1535,6 +1535,65 @@ static int ext4_ioctl_set_tune_sb(struct file *filp,
return ret;
}
+/*
+ * ext4_ioctl_set_lufid() - Set LUFID on a directory entry
+ * @filp: file pointer (parent directory)
+ * @arg: pointer to ext4_set_lufid structure with filename and LUFID data
+ *
+ * This ioctl allows setting LUFID data on an existing
+ * directory entry. It is called on the parent directory with a filename and
+ * LUFID data.
+ */
+static long ext4_ioctl_set_lufid(struct file *filp, unsigned long arg)
+{
+ struct inode *dir = file_inode(filp);
+ struct ext4_set_lufid lufid_args;
+ struct {
+ __u32 edp_magic;
+ struct ext4_dirent_data_header df_header;
+ char df_fid[255];
+ } edp;
+ int err;
+
+ /* Check if parent is a directory */
+ if (!S_ISDIR(dir->i_mode))
+ return -ENOTDIR;
+
+ /* Copy arguments from user space */
+ if (copy_from_user(&lufid_args, (struct ext4_set_lufid __user *)arg,
+ sizeof(lufid_args)))
+ return -EFAULT;
+
+ /* Validate parameters */
+ if (lufid_args.esl_name_len == 0 || lufid_args.esl_name_len > EXT4_NAME_LEN)
+ return -EINVAL;
+
+ if (lufid_args.esl_data_len == 0 || lufid_args.esl_data_len > 255)
+ return -EINVAL;
+
+ /* Ensure filename is NUL-terminated and unmodified */
+ if (lufid_args.esl_name[lufid_args.esl_name_len - 1] != '\0')
+ return -EINVAL;
+
+ /* Prepare the dentry param struct with LUFID data */
+ edp.edp_magic = EXT4_LUFID_MAGIC;
+ edp.df_header.ddh_length = lufid_args.esl_data_len;
+ memcpy(edp.df_fid, lufid_args.esl_data, lufid_args.esl_data_len);
+
+ /* Want write access */
+ err = mnt_want_write_file(filp);
+ if (err)
+ return err;
+
+ /* Call the helper function to do the actual work */
+ err = ext4_dirdata_set_lufid(dir, lufid_args.esl_name,
+ lufid_args.esl_name_len - 1,
+ (struct ext4_dentry_param *)&edp);
+
+ mnt_drop_write_file(filp);
+ return err;
+}
+
static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct inode *inode = file_inode(filp);
@@ -1921,6 +1980,8 @@ static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
(void __user *)arg);
case EXT4_IOC_SET_TUNE_SB_PARAM:
return ext4_ioctl_set_tune_sb(filp, (void __user *)arg);
+ case EXT4_IOC_SET_LUFID:
+ return ext4_ioctl_set_lufid(filp, arg);
default:
return -ENOTTY;
}
@@ -2000,6 +2061,7 @@ long ext4_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
case FS_IOC_SETFSLABEL:
case EXT4_IOC_GETFSUUID:
case EXT4_IOC_SETFSUUID:
+ case EXT4_IOC_SET_LUFID:
break;
default:
return -ENOIOCTLCMD;
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index c8fa8d4940c2..29d5b70b84bf 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -2282,6 +2282,8 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
if (ext4_has_feature_metadata_csum(inode->i_sb))
csum_size = sizeof(struct ext4_dir_entry_tail);
+ dfid = ext4_dentry_get_fid(inode->i_sb,
+ (struct ext4_dentry_param *)EXT4_I(inode)->i_dirdata);
if (!de) {
if (dfid)
dlen = dfid->df_header.ddh_length;
@@ -2628,6 +2630,7 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
{
struct inode *dir = d_inode(dentry->d_parent);
+ EXT4_I(inode)->i_dirdata = dentry->d_fsdata;
if (fscrypt_is_nokey_name(dentry))
return -ENOKEY;
return __ext4_add_entry(handle, dir, &dentry->d_name, inode);
@@ -4389,6 +4392,146 @@ static int ext4_rename2(struct mnt_idmap *idmap,
return ext4_rename(idmap, old_dir, old_dentry, new_dir, new_dentry, flags);
}
+/*
+ * ext4_dirdata_set_lufid() - Set LUFID data on an existing directory entry
+ * @dir: parent directory inode
+ * @filename: name of the file in the directory
+ * @namelen: length of filename
+ * @edp: pointer to initialized dentry param with LUFID data
+ *
+ * This function finds an existing directory entry, deletes it, and re-creates it
+ * with LUFID data attached. Used by the EXT4_IOC_SET_LUFID ioctl.
+ *
+ * Returns 0 on success, negative error code on failure.
+ */
+int ext4_dirdata_set_lufid(struct inode *dir, const char *filename,
+ int namelen, struct ext4_dentry_param *edp)
+{
+ struct super_block *sb = dir->i_sb;
+ struct ext4_filename fname;
+ struct ext4_dir_entry_2 *de = NULL;
+ struct buffer_head *bh = NULL;
+ struct inode *inode = NULL;
+ handle_t *handle = NULL;
+ struct qstr d_name;
+ void *old_dirdata = NULL;
+ int err = 0;
+
+ /* Check if dirdata feature is enabled */
+ if (!ext4_has_feature_dirdata(sb))
+ return -ENOTSUPP;
+
+ if (namelen > EXT4_NAME_LEN)
+ return -ENAMETOOLONG;
+ if (namelen != strnlen(filename, namelen + 1))
+ return -EINVAL;
+
+ /* Setup the filename for lookup */
+ d_name.name = filename;
+ d_name.len = namelen;
+
+ /* Lookup the filename in the directory */
+ err = ext4_fname_setup_filename(dir, &d_name, 0, &fname);
+ if (err)
+ goto out_free;
+
+ bh = ext4_find_entry(dir, &d_name, &de, NULL);
+ if (!bh) {
+ err = -ENOENT;
+ goto out_free;
+ }
+
+ /* Get the inode number from the directory entry */
+ inode = ext4_iget(sb, le32_to_cpu(de->inode), EXT4_IGET_NORMAL);
+ if (IS_ERR(inode)) {
+ err = PTR_ERR(inode);
+ inode = NULL;
+ goto out_brelse;
+ }
+
+ /* Start a transaction */
+ handle = ext4_journal_start(dir, EXT4_HT_DIR,
+ 2 * EXT4_DATA_TRANS_BLOCKS(sb) +
+ EXT4_INDEX_EXTRA_TRANS_BLOCKS);
+ if (IS_ERR(handle)) {
+ err = PTR_ERR(handle);
+ handle = NULL;
+ goto out_iput;
+ }
+
+ inode_lock(dir);
+
+ /* Delete the old entry */
+ err = ext4_delete_entry(handle, dir, de, bh);
+ if (err)
+ goto out_unlock;
+
+ brelse(bh);
+ bh = NULL;
+
+ /* Re-add the entry with LUFID data
+ * We set i_dirdata before adding so the entry can include it
+ */
+ old_dirdata = EXT4_I(inode)->i_dirdata;
+ EXT4_I(inode)->i_dirdata = edp;
+
+ /* Use ext4_add_entry() to properly handle hash table management
+ * and block splitting, just like rename does. This ensures the entry
+ * is placed in the correct hash block and avoids breaking dirhash.
+ */
+ {
+ struct dentry parent_dentry = { .d_inode = dir };
+ struct dentry new_dentry = {
+ .d_name = d_name,
+ .d_parent = &parent_dentry,
+ .d_inode = inode, /* Same inode (in-place update) */
+ .d_fsdata = edp, /* required */
+ };
+ err = ext4_add_entry(handle, &new_dentry, inode);
+ }
+ EXT4_I(inode)->i_dirdata = old_dirdata;
+
+ if (err) {
+ /*
+ * The original entry was already removed above and the
+ * re-add with the new LUFID failed; try to restore the
+ * original entry so the inode isn't left without any
+ * directory entry pointing at it.
+ */
+ struct dentry parent_dentry = { .d_inode = dir };
+ struct dentry orig_dentry = {
+ .d_name = d_name,
+ .d_parent = &parent_dentry,
+ .d_inode = inode,
+ };
+ int rollback_err = ext4_add_entry(handle, &orig_dentry, inode);
+
+ if (rollback_err)
+ EXT4_ERROR_INODE(dir,
+ "Failed to set LUFID on '%.*s' (err=%d) and failed to restore the original directory entry (err=%d); inode %llu may be orphaned",
+ namelen, filename, err, rollback_err,
+ inode->i_ino);
+ goto out_unlock;
+ }
+
+ /* Update inode times */
+ inode_set_ctime_current(dir);
+ inode_inc_iversion(dir);
+ ext4_mark_inode_dirty(handle, dir);
+
+out_unlock:
+ inode_unlock(dir);
+ ext4_journal_stop(handle);
+out_iput:
+ iput(inode);
+out_brelse:
+ brelse(bh);
+out_free:
+ ext4_fname_free_filename(&fname);
+
+ return err;
+}
+
/*
* directories can handle most operations...
*/
diff --git a/include/uapi/linux/ext4.h b/include/uapi/linux/ext4.h
index 9c683991c32f..9fab8978843b 100644
--- a/include/uapi/linux/ext4.h
+++ b/include/uapi/linux/ext4.h
@@ -35,6 +35,7 @@
#define EXT4_IOC_SETFSUUID _IOW('f', 44, struct fsuuid)
#define EXT4_IOC_GET_TUNE_SB_PARAM _IOR('f', 45, struct ext4_tune_sb_params)
#define EXT4_IOC_SET_TUNE_SB_PARAM _IOW('f', 46, struct ext4_tune_sb_params)
+#define EXT4_IOC_SET_LUFID _IOW('f', 47, struct ext4_set_lufid)
#define EXT4_IOC_SHUTDOWN _IOR('X', 125, __u32)
@@ -92,6 +93,18 @@ struct move_extent {
__u64 moved_len; /* moved block length */
};
+/*
+ * Structure for EXT4_IOC_SET_LUFID
+ * Sets LUFID on a directory entry
+ * Called on parent directory with filename and LUFID data as arguments
+ */
+struct ext4_set_lufid {
+ __u8 esl_name_len; /* length of filename */
+ char esl_name[255 + 1]; /* filename (NUL-terminated) */
+ __u8 esl_data_len; /* length of LUFID data */
+ char esl_data[255]; /* LUFID data (raw bytes) */
+};
+
/*
* Flags used by EXT4_IOC_SHUTDOWN
*/
--
2.43.7
^ permalink raw reply related
* [PATCH v3 09/10] ext4: add dirdata set/get helpers
From: Artem Blagodarenko @ 2026-06-19 19:10 UTC (permalink / raw)
To: linux-ext4; +Cc: adilger.kernel, Artem Blagodarenko, Andreas Dilger
In-Reply-To: <20260619191022.27008-1-ablagodarenko@thelustrecollective.com>
From: Artem Blagodarenko <artem.blagodarenko@gmail.com>
Add helpers to set and retrieve dirdata payload and hook them up at
the appropriate call sites.
Enable dirdata for casefold+encryption hashes and storing unique
128-bit file identifier in the directory entry for testing.
Signed-off-by: Artem Blagodarenko <artem.blagodarenko@gmail.com>
Reviewed-by: Andreas Dilger <adilger@dilger.ca>
---
foofile.txt | 0
fs/ext4/ext4.h | 4 +
fs/ext4/inline.c | 6 +-
fs/ext4/namei.c | 201 +++++++++++++++++++++++++++++++++++++++++------
4 files changed, 181 insertions(+), 30 deletions(-)
diff --git a/foofile.txt b/foofile.txt
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index d4ec450c05f2..543c8e87f207 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -3874,6 +3874,10 @@ 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,
const struct qstr *d_name, struct dentry *dentry);
+extern unsigned char ext4_dirdata_get(struct ext4_dir_entry_2 *de,
+ struct inode *dir,
+ struct ext4_dirent_fid *lufid,
+ struct dx_hash_info *hinfo);
#define S_SHIFT 12
static const unsigned char ext4_type_by_mode[(S_IFMT >> S_SHIFT) + 1] = {
diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c
index c57a8ebe4f94..71c395c9a162 100644
--- a/fs/ext4/inline.c
+++ b/fs/ext4/inline.c
@@ -1346,10 +1346,8 @@ 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);
- } else {
+ if (!(ext4_dirdata_get(de, dir, NULL, hinfo) &
+ EXT4_DIRENT_CFHASH)) {
err = ext4fs_dirhash(dir, de->name, de->name_len, hinfo);
if (err) {
ret = err;
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index e230fa1094ff..c8fa8d4940c2 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -1097,22 +1097,22 @@ 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;
- }
+ if (de->name_len && de->inode) {
+ /* check for saved hash first, or generate it from name */
+ if (!(ext4_dirdata_get(de, dir, NULL, hinfo) &
+ EXT4_DIRENT_CFHASH)) {
+ err = ext4fs_dirhash(dir, de->name,
+ de->name_len, hinfo);
+ if (err < 0) {
+ count = err;
+ goto errout;
+ }
+ }
} else {
- err = ext4fs_dirhash(dir, de->name,
- de->name_len, hinfo);
- if (err < 0) {
- count = err;
- goto errout;
- }
+ hinfo->hash = 0;
+ hinfo->minor_hash = 0;
}
+
if ((hinfo->hash < start_hash) ||
((hinfo->hash == start_hash) &&
(hinfo->minor_hash < start_minor_hash)))
@@ -1290,9 +1290,165 @@ 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)
+ * @dfid: if non-NULL and EXT4_DIRENT_LUFID is set, LUFID data is copied
+ * here
+ * @hinfo: if non-NULL, receives the casefold hash and 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,
+ struct ext4_dirent_fid *dfid,
+ struct dx_hash_info *hinfo)
+{
+ unsigned char ret = 0;
+ unsigned int data_offset = de->name_len + 1;
+ unsigned int rec_len = ext4_rec_len_from_disk(de->rec_len,
+ dir->i_sb->s_blocksize);
+
+ if (data_offset > rec_len)
+ return ret;
+
+ /* compatibility: hash stored inline after filename (no dirdata) */
+ if (hinfo && !ext4_has_feature_dirdata(dir->i_sb) &&
+ ext4_hash_in_dirent(dir)) {
+ hinfo->hash = EXT4_DIRENT_HASH(de);
+ hinfo->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) {
+ struct ext4_dirent_fid *disk_fid =
+ (struct ext4_dirent_fid *)(de->name + data_offset);
+ unsigned int dlen;
+
+ if (data_offset + sizeof(disk_fid->df_header) > rec_len)
+ return ret;
+
+ dlen = disk_fid->df_header.ddh_length;
+ if (dlen < sizeof(*disk_fid) || data_offset + dlen > rec_len)
+ return ret;
+
+ if (dfid) {
+ memcpy(dfid, disk_fid->df_fid,
+ disk_fid->df_header.ddh_length);
+ ret |= EXT4_DIRENT_LUFID;
+ }
+ data_offset += dlen;
+ }
+
+ /* Skip INO64 for now*/
+ if (de->file_type & EXT4_DIRENT_INO64) {
+ struct ext4_dirent_data_header *ddh =
+ (struct ext4_dirent_data_header *)(de->name + data_offset);
+ unsigned int dlen;
+
+ if (data_offset + sizeof(*ddh) > rec_len)
+ return ret;
+
+ dlen = ddh->ddh_length;
+ if (dlen < sizeof(*ddh) || data_offset + dlen > rec_len)
+ return ret;
+
+ data_offset += dlen;
+ }
+
+ if (!hinfo)
+ return ret;
+
+ if (de->file_type & EXT4_DIRENT_CFHASH) {
+ struct ext4_dirent_hash *dh =
+ (struct ext4_dirent_hash *)(de->name + data_offset);
+ unsigned int dlen;
+
+ dlen = dh->dh_header.ddh_length;
+ if (dlen < sizeof(*dh) || data_offset + dlen > rec_len)
+ return ret;
+
+ hinfo->hash = le32_to_cpu(dh->dh_hash.hash);
+ hinfo->minor_hash = le32_to_cpu(dh->dh_hash.minor_hash);
+ ret |= EXT4_DIRENT_CFHASH;
+ }
+
+ 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,
+ struct ext4_dirent_fid *dfid,
+ struct ext4_filename *fname)
+{
+ struct dx_hash_info *hinfo = &fname->hinfo;
+ unsigned int data_offset = de->name_len + 1;
+ unsigned int rec_len = ext4_rec_len_from_disk(de->rec_len,
+ dir->i_sb->s_blocksize);
+
+
+ if (dfid) {
+ unsigned int dlen = dfid->df_header.ddh_length;
+
+ if (data_offset + dlen > rec_len) {
+ EXT4_ERROR_INODE(dir, "Can not insert FID");
+ return;
+ }
+
+
+ de->name[de->name_len] = 0;
+ memcpy(&de->name[de->name_len + 1], dfid,
+ dlen);
+ de->file_type |= EXT4_DIRENT_LUFID;
+ data_offset += dlen;
+ }
+
+ if (ext4_hash_in_dirent(dir)) {
+ if (ext4_has_feature_dirdata(dir->i_sb)) {
+ struct ext4_dirent_hash *dh =
+ (struct ext4_dirent_hash *)(de->name + data_offset);
+
+ if (data_offset + sizeof(*dh) > rec_len) {
+ EXT4_ERROR_INODE(dir, "Can not insert dhash dirdata");
+ return;
+ }
+
+ 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;
+ } else {
+ /* Compatibility: store hash inline after filename */
+ if (data_offset + sizeof(struct ext4_dir_entry_hash) >
+ rec_len) {
+ EXT4_ERROR_INODE(dir, "Can not insert dhash");
+ return;
+ }
+
+ EXT4_DIRENT_HASHES(de)->hash = cpu_to_le32(hinfo->hash);
+ EXT4_DIRENT_HASHES(de)->minor_hash =
+ cpu_to_le32(hinfo->minor_hash);
+ }
+ }
+}
+
+
static int dx_make_map(struct inode *dir, struct buffer_head *bh,
struct dx_hash_info *hinfo,
struct dx_map_entry *map_tail)
@@ -1312,9 +1468,8 @@ 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) &
+ EXT4_DIRENT_CFHASH)) {
int err = ext4fs_dirhash(dir, de->name,
de->name_len, &h);
if (err < 0)
@@ -2102,13 +2257,7 @@ void ext4_insert_dentry_data(struct inode *dir, struct inode *inode,
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)) {
- struct dx_hash_info *hinfo = &fname->hinfo;
-
- EXT4_DIRENT_HASHES(de)->hash = cpu_to_le32(hinfo->hash);
- EXT4_DIRENT_HASHES(de)->minor_hash =
- cpu_to_le32(hinfo->minor_hash);
- }
+ ext4_dirdata_set(de, dir, data, fname);
}
/*
--
2.43.7
^ permalink raw reply related
* [PATCH v3 08/10] ext4: dirdata feature
From: Artem Blagodarenko @ 2026-06-19 19:10 UTC (permalink / raw)
To: linux-ext4
Cc: adilger.kernel, Artem Blagodarenko, Pravin Shelar, Andreas Dilger
In-Reply-To: <20260619191022.27008-1-ablagodarenko@thelustrecollective.com>
From: Artem Blagodarenko <artem.blagodarenko@gmail.com>
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/ext4.h | 27 +++++++++++++++++++++------
fs/ext4/inline.c | 19 +++++++++++++++----
fs/ext4/namei.c | 43 +++++++++++++++++++++----------------------
fs/ext4/sysfs.c | 2 ++
4 files changed, 59 insertions(+), 32 deletions(-)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 682dd141072d..d4ec450c05f2 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -2334,6 +2334,7 @@ EXT4_FEATURE_INCOMPAT_FUNCS(casefold, CASEFOLD)
EXT4_FEATURE_INCOMPAT_FLEX_BG| \
EXT4_FEATURE_INCOMPAT_EA_INODE| \
EXT4_FEATURE_INCOMPAT_MMP | \
+ EXT4_FEATURE_INCOMPAT_DIRDATA | \
EXT4_FEATURE_INCOMPAT_INLINE_DATA | \
EXT4_FEATURE_INCOMPAT_ENCRYPT | \
EXT4_FEATURE_INCOMPAT_CASEFOLD | \
@@ -3035,10 +3036,18 @@ extern int ext4_find_dest_de(struct inode *dir, struct buffer_head *bh,
struct ext4_filename *fname,
struct ext4_dir_entry_2 **dest_de,
int dlen);
-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);
+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) &&
@@ -3283,8 +3292,14 @@ 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_dirblock_csum_verify(struct inode *inode,
struct buffer_head *bh);
extern int ext4_htree_fill_tree(struct file *dir_file, __u32 start_hash,
diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c
index 5b3faacdf143..c57a8ebe4f94 100644
--- a/fs/ext4/inline.c
+++ b/fs/ext4/inline.c
@@ -973,11 +973,16 @@ static int ext4_add_dirent_to_inline(handle_t *handle,
struct ext4_iloc *iloc,
void *inline_start, int inline_size)
{
- int err;
+ int err, dlen = 0;
struct ext4_dir_entry_2 *de;
+ unsigned char *data = NULL;
+
+ /* Deliver data in any appropriate way here. Now it is NULL */
+ if (data)
+ dlen = (*data) + 1;
err = ext4_find_dest_de(dir, iloc->bh, inline_start,
- inline_size, fname, &de, 0);
+ inline_size, fname, &de, dlen);
if (err)
return err;
@@ -986,7 +991,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);
@@ -1326,7 +1331,13 @@ int ext4_inlinedir_to_tree(struct file *dir_file,
pos = EXT4_INLINE_DOTDOT_SIZE;
} else {
de = (struct ext4_dir_entry_2 *)(dir_buf + pos);
- pos += ext4_rec_len_from_disk(de->rec_len, inline_size);
+ /* Use ext4_dir_entry_len to account for dirdata extensions */
+ pos += ext4_dir_entry_len(de, dir);
+ /* Validate pos doesn't exceed buffer to prevent use-after-free */
+ if (pos > inline_size) {
+ ret = count;
+ goto out;
+ }
if (ext4_check_dir_entry(inode, dir_file, de,
iloc.bh, dir_buf,
inline_size, pos)) {
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index 7bca813c0627..e230fa1094ff 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -401,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, 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 = sizeof(struct dx_node);
- 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 {
+ de = (struct ext4_dir_entry_2 *)(((char *)dirent) + rlen);
+ if (le16_to_cpu(de->rec_len) != (blocksize - rlen))
return NULL;
- root = (struct dx_root_info *)(((void *)de + 12));
+ /* de->rec_len covers whole dx_root block, calculate actual length */
+ dotdot_rec_len = ext4_dir_entry_len(de, inode);
+ 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 = root->info_length + rlen + dotdot_rec_len;
+ }
if (offset)
*offset = count_offset;
@@ -707,7 +708,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);
@@ -2081,13 +2082,10 @@ 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_entry_len(de, dir);
@@ -2129,15 +2127,15 @@ 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, dlen = 0;
- unsigned char *data = NULL;
+ struct ext4_dirent_fid *dfid = 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;
+ if (dfid)
+ dlen = dfid->df_header.ddh_length;
err = ext4_find_dest_de(dir, bh, bh->b_data,
blocksize - csum_size, fname, &de, dlen);
if (err)
@@ -2152,7 +2150,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, dfid);
/*
* XXX shouldn't update any times until successful
@@ -2991,8 +2989,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;
diff --git a/fs/ext4/sysfs.c b/fs/ext4/sysfs.c
index 923b375e017f..80074fb15ee9 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.7
^ permalink raw reply related
* [PATCH v3 07/10] ext4: rename ext4_dir_rec_len() and clarify dirdata usage
From: Artem Blagodarenko @ 2026-06-19 19:10 UTC (permalink / raw)
To: linux-ext4; +Cc: adilger.kernel, Artem Blagodarenko, Andreas Dilger
In-Reply-To: <20260619191022.27008-1-ablagodarenko@thelustrecollective.com>
From: Artem Blagodarenko <artem.blagodarenko@gmail.com>
Rename ext4_dir_rec_len() to ext4_dirent_rec_len() to better
reflect that it computes the record length for a directory
entry based on the provided name length.
Update the comment to clarify handling of dirdata-enabled
directories and document the use of ext4_dir_entry_len()
when dirdata is present.
No functional changes.
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 | 14 +++++---
fs/ext4/inline.c | 14 ++++----
fs/ext4/namei.c | 86 ++++++++++++++++++++++++++++++++----------------
4 files changed, 78 insertions(+), 45 deletions(-)
diff --git a/fs/ext4/dir.c b/fs/ext4/dir.c
index 17edd678fa87..012687822b82 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 fd979452391f..682dd141072d 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -2616,11 +2616,16 @@ struct ext4_dirent_hash {
* 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);
@@ -3028,7 +3033,8 @@ 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);
+ struct ext4_dir_entry_2 **dest_de,
+ int dlen);
void ext4_insert_dentry(struct inode *dir, struct inode *inode,
struct ext4_dir_entry_2 *de,
int buf_size,
@@ -4142,7 +4148,7 @@ static inline unsigned int ext4_dir_entry_len(struct ext4_dir_entry_2 *de,
unsigned int rec_len = ext4_rec_len_from_disk(de->rec_len, blocksize);
unsigned int dirdata = ext4_dirent_get_data_len(de, rec_len);
- return ext4_dir_rec_len(de->name_len + dirdata, dir);
+ return ext4_dirent_rec_len(de->name_len + dirdata, dir);
}
extern const struct iomap_ops ext4_iomap_ops;
diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c
index 8045e4ff270c..5b3faacdf143 100644
--- a/fs/ext4/inline.c
+++ b/fs/ext4/inline.c
@@ -977,7 +977,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;
@@ -1055,7 +1055,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,
@@ -1309,7 +1309,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;
@@ -1319,7 +1319,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;
@@ -1427,8 +1427,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;
@@ -1463,7 +1463,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 a20ba9a6a15b..7bca813c0627 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -519,13 +519,20 @@ 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)
+static struct dx_root_info *dx_get_dx_info(struct inode *dir, void *de_buf)
{
+ unsigned int blocksize = dir->i_sb->s_blocksize;
+ void *base = de_buf;
+
/* get dotdot first */
- de_buf = de_buf + ext4_dir_rec_len(1, NULL);
+ de_buf += ext4_dir_entry_len(de_buf, dir);
/* dx root info is after dotdot entry */
- de_buf = de_buf + ext4_dir_rec_len(2, NULL);
+ de_buf += ext4_dir_entry_len(de_buf, dir);
+
+ if (de_buf < base || (char *)de_buf - (char *)base +
+ sizeof(struct dx_root_info) > blocksize)
+ return ERR_PTR(-EFSCORRUPTED);
return (struct dx_root_info *)de_buf;
}
@@ -576,7 +583,9 @@ static inline unsigned dx_root_limit(struct inode *dir,
struct dx_root_info *info;
unsigned int entry_space;
- info = dx_get_dx_info(dot_de);
+ info = dx_get_dx_info(dir, dot_de);
+ if (IS_ERR(info))
+ return 0;
entry_space = dir->i_sb->s_blocksize - ((char *)info - (char *)dot_de) -
info->info_length;
@@ -588,7 +597,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);
@@ -792,7 +801,9 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,
if (IS_ERR(frame->bh))
return (struct dx_frame *) frame->bh;
- info = dx_get_dx_info((struct ext4_dir_entry_2 *)frame->bh->b_data);
+ info = dx_get_dx_info(dir, (struct ext4_dir_entry_2 *)frame->bh->b_data);
+ if (IS_ERR(info))
+ goto fail;
if (info->hash_version != DX_HASH_TEA &&
info->hash_version != DX_HASH_HALF_MD4 &&
info->hash_version != DX_HASH_LEGACY &&
@@ -937,7 +948,7 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,
return ret_err;
}
-static void dx_release(struct dx_frame *frames)
+static void dx_release(struct inode *dir, struct dx_frame *frames)
{
struct dx_root_info *info;
int i;
@@ -946,7 +957,9 @@ static void dx_release(struct dx_frame *frames)
if (frames[0].bh == NULL)
return;
- info = dx_get_dx_info((struct ext4_dir_entry_2 *)frames[0].bh->b_data);
+ info = dx_get_dx_info(dir, (struct ext4_dir_entry_2 *)frames[0].bh->b_data);
+ if (IS_ERR(info))
+ return;
/* save local copy, "info" may be freed after brelse() */
indirect_levels = info->indirect_levels;
for (i = 0; i <= indirect_levels; i++) {
@@ -1058,7 +1071,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)) {
@@ -1252,12 +1265,12 @@ int ext4_htree_fill_tree(struct file *dir_file, __u32 start_hash,
(count && ((hashval & 1) == 0)))
break;
}
- dx_release(frames);
+ dx_release(dir, frames);
dxtrace(printk(KERN_DEBUG "Fill tree: returned %d entries, "
"next hash: %x\n", count, *next_hash));
return count;
errout:
- dx_release(frames);
+ dx_release(dir, frames);
return (err);
}
@@ -1755,7 +1768,7 @@ static struct buffer_head * ext4_dx_find_entry(struct inode *dir,
errout:
dxtrace(printk(KERN_DEBUG "%s not found\n", fname->usr_fname->name));
success:
- dx_release(frames);
+ dx_release(dir, frames);
return bh;
}
@@ -1852,7 +1865,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 =
@@ -1885,7 +1898,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);
@@ -2037,10 +2050,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;
@@ -2053,7 +2067,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;
@@ -2076,7 +2090,7 @@ void ext4_insert_dentry(struct inode *dir,
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 =
@@ -2114,14 +2128,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;
}
@@ -2276,7 +2294,12 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,
blocksize);
/* initialize hashing info */
- dx_info = dx_get_dx_info(dot_de);
+ dx_info = dx_get_dx_info(dir, dot_de);
+ if (IS_ERR(dx_info)) {
+ brelse(bh2);
+ brelse(bh);
+ return PTR_ERR(dx_info);
+ }
memset(dx_info, 0, sizeof(*dx_info));
dx_info->info_length = sizeof(*dx_info);
if (ext4_hash_in_dirent(dir))
@@ -2334,7 +2357,7 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,
*/
if (retval)
ext4_mark_inode_dirty(handle, dir);
- dx_release(frames);
+ dx_release(dir, frames);
brelse(bh2);
return retval;
}
@@ -2609,8 +2632,13 @@ 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);
- info = dx_get_dx_info((struct ext4_dir_entry_2 *)
+ info = dx_get_dx_info(dir, (struct ext4_dir_entry_2 *)
frames[0].bh->b_data);
+ if (IS_ERR(info)) {
+ err = PTR_ERR(info);
+ brelse(bh2);
+ goto journal_error;
+ }
info->indirect_levels += 1;
dxtrace(printk(KERN_DEBUG
"Creating %d level index...\n",
@@ -2638,7 +2666,7 @@ static int ext4_dx_add_entry(handle_t *handle, struct ext4_filename *fname,
ext4_std_error(dir->i_sb, err); /* this is a no-op if err == 0 */
cleanup:
brelse(bh);
- dx_release(frames);
+ dx_release(dir, frames);
/* @restart is true means htree-path has been changed, we need to
* repeat dx_probe() to find out valid htree-path
*/
@@ -2930,7 +2958,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);
@@ -2942,7 +2970,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;
@@ -2951,7 +2979,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);
}
@@ -3074,8 +3102,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;
}
--
2.43.7
^ permalink raw reply related
* [PATCH v3 06/10] ext4: add ext4_dir_entry_len() and harden dirdata parsing
From: Artem Blagodarenko @ 2026-06-19 19:10 UTC (permalink / raw)
To: linux-ext4; +Cc: adilger.kernel, Artem Blagodarenko, Andreas Dilger
In-Reply-To: <20260619191022.27008-1-ablagodarenko@thelustrecollective.com>
From: Artem Blagodarenko <artem.blagodarenko@gmail.com>
Introduce ext4_dir_entry_len() helper to compute the required
rec_len for a directory entry, taking into account dirdata and
casefold+fscrypt hash space.
Convert ext4_dirent_get_data_len() to take the decoded rec_len
as an argument and add bounds checking when walking dirdata
extensions to avoid overruns on malformed entries.
Update dx_root_limit() to use ext4_dir_entry_len() instead of
open-coded ext4_dir_rec_len() for '.' and '..' entries.
Signed-off-by: Artem Blagodarenko <artem.blagodarenko@gmail.com>
Reviewed-by: Andreas Dilger <adilger@dilger.ca>
---
fs/ext4/ext4.h | 45 ++++++++++++++++++++++++++++++++++++++++++---
fs/ext4/namei.c | 23 +++++++++++++++--------
2 files changed, 57 insertions(+), 11 deletions(-)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 2f29f50a12ac..fd979452391f 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -4075,6 +4075,7 @@ static inline bool ext4_dir_entry_is_tail(struct ext4_dir_entry_2 *de)
/*
* ext4_dirent_get_data_len() - Compute the total dirdata length for an entry.
* @de: directory entry
+ * @rec_len: the record length of the directory entry (decoded)
*
* Computes the length of optional data stored after the filename (and its
* implicit NUL terminator). Each extension is indicated by a bit in the
@@ -4083,22 +4084,41 @@ static inline bool ext4_dir_entry_is_tail(struct ext4_dir_entry_2 *de)
*
* 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)
+static inline int ext4_dirent_get_data_len(struct ext4_dir_entry_2 *de,
+ unsigned int rec_len)
{
__u8 extra_data_flags;
struct ext4_dirent_data_header *ddh;
int dlen = 0;
+ unsigned int offset;
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 */);
+ /* offset from start of entry to after filename + NUL */
+ offset = EXT4_BASE_DIR_LEN + de->name_len + 1;
+ /* bounds check: ensure we start reading within the entry */
+ if (offset >= rec_len)
+ return 0;
+
+ ddh = (struct ext4_dirent_data_header *)((char *)de + offset);
+
while (extra_data_flags) {
if (extra_data_flags & 1) {
+ /* bounds check before reading ddh_length */
+ if (offset + sizeof(*ddh) >
+ rec_len)
+ return dlen;
+
+ /* validate ddh_length is reasonable */
+ if (ddh->ddh_length == 0 || ddh->ddh_length >
+ rec_len - offset)
+ return dlen;
+
dlen += ddh->ddh_length + (dlen == 0);
+ offset += ddh->ddh_length;
ddh = ext4_dirdata_next(ddh);
}
extra_data_flags >>= 1;
@@ -4106,6 +4126,25 @@ static inline int ext4_dirent_get_data_len(struct ext4_dir_entry_2 *de)
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 blocksize = (dir && dir->i_sb) ? dir->i_sb->s_blocksize : 4096;
+ unsigned int rec_len = ext4_rec_len_from_disk(de->rec_len, blocksize);
+ unsigned int dirdata = ext4_dirent_get_data_len(de, rec_len);
+
+ return ext4_dir_rec_len(de->name_len + dirdata, dir);
+}
+
extern const struct iomap_ops ext4_iomap_ops;
extern const struct iomap_ops ext4_iomap_report_ops;
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index 7692cf5184ab..a20ba9a6a15b 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -570,11 +570,15 @@ 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 int entry_space = dir->i_sb->s_blocksize -
- ext4_dir_rec_len(1, NULL) -
- ext4_dir_rec_len(2, NULL) - infosize;
+ struct dx_root_info *info;
+ unsigned int entry_space;
+
+ info = dx_get_dx_info(dot_de);
+ entry_space = dir->i_sb->s_blocksize - ((char *)info - (char *)dot_de) -
+ info->info_length;
if (ext4_has_feature_metadata_csum(dir->i_sb))
entry_space -= sizeof(struct dx_tail);
@@ -850,10 +854,13 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,
entries = (struct dx_entry *)(((char *)info) + info->info_length);
- if (dx_get_limit(entries) != dx_root_limit(dir, info->info_length)) {
+ if (dx_get_limit(entries) !=
+ dx_root_limit(dir, (struct ext4_dir_entry_2 *)frame->bh->b_data)) {
ext4_warning_inode(dir, "dx entry: limit %u != root limit %u",
dx_get_limit(entries),
- dx_root_limit(dir, info->info_length));
+ dx_root_limit(dir,
+ (struct ext4_dir_entry_2 *)frame->bh->b_data
+ ));
goto fail;
}
@@ -2278,10 +2285,10 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,
dx_info->hash_version =
EXT4_SB(dir->i_sb)->s_def_hash_version;
- entries = (void *)dx_info + sizeof(*dx_info);
+ entries = (void *)dx_info + dx_info->info_length;
dx_set_block(entries, 1);
dx_set_count(entries, 1);
- dx_set_limit(entries, dx_root_limit(dir, sizeof(*dx_info)));
+ dx_set_limit(entries, dx_root_limit(dir, dot_de));
/* Initialize as for dx_probe */
fname->hinfo.hash_version = dx_info->hash_version;
--
2.43.7
^ permalink raw reply related
* [PATCH v3 05/10] ext4: preserve dirdata bits in get_dtype()
From: Artem Blagodarenko @ 2026-06-19 19:10 UTC (permalink / raw)
To: linux-ext4; +Cc: adilger.kernel, Artem Blagodarenko, Andreas Dilger
In-Reply-To: <20260619191022.27008-1-ablagodarenko@thelustrecollective.com>
From: Artem Blagodarenko <artem.blagodarenko@gmail.com>
Mask the filetype with EXT4_FT_MASK when indexing
ext4_filetype_table[] to avoid using dirdata bits as an index.
Preserve the extra bits
stored in the upper part of filetype and propagate them to the
returned dtype value.
Signed-off-by: Artem Blagodarenko <artem.blagodarenko@gmail.com>
Reviewed-by: Andreas Dilger <adilger@dilger.ca>
---
fs/ext4/ext4.h | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 98603aa44693..2f29f50a12ac 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -3046,12 +3046,15 @@ 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)
{
- 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];
+ 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);
--
2.43.7
^ permalink raw reply related
* [PATCH v3 04/10] ext4: add dirdata format definitions and access helpers
From: Artem Blagodarenko @ 2026-06-19 19:10 UTC (permalink / raw)
To: linux-ext4
Cc: adilger.kernel, Artem Blagodarenko, Pravin Shelar, Andreas Dilger
In-Reply-To: <20260619191022.27008-1-ablagodarenko@thelustrecollective.com>
From: Artem Blagodarenko <artem.blagodarenko@gmail.com>
Define the on-disk format for ext4 directory entry extension data.
The upper four bits of de->file_type indicate the presence of
optional data stored after the filename NUL terminator. This patch
defines flags for LUFID, 64-bit inode numbers, and casefold hash
data stored in that area.
Add struct ext4_dirent_data_header to describe variable-length
extension records and struct ext4_dirent_hash for hash storage used
by casefold and fscrypt.
Provide ext4_dirdata_next() to advance to the next extension record
and ext4_dirent_get_data_len() to compute the total extension data
length associated with a directory entry.
No functional changes.
Signed-off-by: Pravin Shelar <pravin.shelar@sun.com>
Signed-off-by: Artem Blagodarenko <artem.blagodarenko@gmail.com>
Reviewed-by: Andreas Dilger <adilger@diliger.ca>
---
fs/ext4/ext4.h | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 83 insertions(+)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 9400bc2858a5..98603aa44693 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -2556,6 +2556,49 @@ 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. fscypt hash, 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_fid {
+ char fid[16]; /* 128-bit unique file identifier */
+};
+
+struct ext4_dirent_data_header {
+ /* length of this header + the whole data blob */
+ __u8 ddh_length;
+} __packed;
+
+struct ext4_dirent_fid {
+ struct ext4_dirent_data_header df_header;
+ struct ext4_fid df_fid[];
+};
+
+#define EXT4_LUFID_MAGIC 0xAD200907UL
+struct ext4_dentry_param {
+ __u32 edp_magic; /* EXT4_LUFID_MAGIC */
+ struct ext4_dirent_fid edp_dfid;
+};
+
+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
@@ -4004,6 +4047,12 @@ 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
@@ -4020,6 +4069,40 @@ static inline bool ext4_dir_entry_is_tail(struct ext4_dir_entry_2 *de)
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;
+}
+
extern const struct iomap_ops ext4_iomap_ops;
extern const struct iomap_ops ext4_iomap_report_ops;
--
2.43.7
^ permalink raw reply related
* [PATCH v3 03/10] ext4: refactor dx_root to support variable dirent sizes
From: Artem Blagodarenko @ 2026-06-19 19:10 UTC (permalink / raw)
To: linux-ext4
Cc: adilger.kernel, Artem Blagodarenko, Pravin Shelar, Andreas Dilger
In-Reply-To: <20260619191022.27008-1-ablagodarenko@thelustrecollective.com>
From: Artem Blagodarenko <artem.blagodarenko@gmail.com>
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 | 145 +++++++++++++++++++++++-------------------------
1 file changed, 70 insertions(+), 75 deletions(-)
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index f47f072a1a89..7692cf5184ab 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
@@ -528,6 +519,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_dir_rec_len(1, NULL);
+
+ /* dx root info is after dotdot entry */
+ de_buf = de_buf + ext4_dir_rec_len(2, NULL);
+
+ return (struct dx_root_info *)de_buf;
+}
static inline ext4_lblk_t dx_get_block(struct dx_entry *entry)
{
@@ -775,7 +776,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;
@@ -787,23 +788,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 #%llu",
+ 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;
@@ -811,7 +813,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;
@@ -827,13 +829,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: %llu) htree depth %#06x exceed"
@@ -846,14 +848,12 @@ 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, 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, info->info_length));
goto fail;
}
@@ -939,7 +939,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++) {
@@ -2151,44 +2151,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:
@@ -2206,16 +2200,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))
@@ -2232,17 +2225,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);
@@ -2273,24 +2264,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, 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;
@@ -2600,7 +2594,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));
@@ -2608,8 +2602,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.7
^ permalink raw reply related
* [PATCH v3 02/10] ext4: add ext4_dir_entry_is_tail()
From: Artem Blagodarenko @ 2026-06-19 19:10 UTC (permalink / raw)
To: linux-ext4; +Cc: adilger.kernel, Artem Blagodarenko, Andreas Dilger
In-Reply-To: <20260619191022.27008-1-ablagodarenko@thelustrecollective.com>
From: Artem Blagodarenko <artem.blagodarenko@gmail.com>
Replace open-coded checks for directory tail entries with a call
to ext4_dir_entry_is_tail(). This helper will also be used by
upcoming changes.
Signed-off-by: Artem Blagodarenko <artem.blagodarenko@gmail.com>
Reviewed-by: Andreas Dilger <adilger@dilger.ca>
---
fs/ext4/ext4.h | 16 ++++++++++++++++
fs/ext4/namei.c | 7 +------
2 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index b37c136ea3ab..9400bc2858a5 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -4004,6 +4004,22 @@ static inline void ext4_clear_io_unwritten_flag(ext4_io_end_t *io_end)
io_end->flag &= ~EXT4_IO_END_UNWRITTEN;
}
+/*
+ * 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;
+}
+
extern const struct iomap_ops ext4_iomap_ops;
extern const struct iomap_ops ext4_iomap_report_ops;
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index 2c951570990f..f47f072a1a89 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -314,7 +314,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_2 *d, *top;
@@ -334,11 +333,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;
--
2.43.7
^ permalink raw reply related
* [PATCH v3 01/10] ext4: replace ext4_dir_entry with ext4_dir_entry_2
From: Artem Blagodarenko @ 2026-06-19 19:10 UTC (permalink / raw)
To: linux-ext4; +Cc: adilger.kernel, Artem Blagodarenko, Andreas Dilger
In-Reply-To: <20260619191022.27008-1-ablagodarenko@thelustrecollective.com>
From: Artem Blagodarenko <artem.blagodarenko@gmail.com>
Replace remaining uses of struct ext4_dir_entry in namei.c
with struct ext4_dir_entry_2.
The code paths affected by this change already depend on the
filetype feature, so using struct ext4_dir_entry_2 is
appropriate and avoids mixing the two directory entry types
unnecessarily.
This change does not affect support for 16-bit rec_len.
Signed-off-by: Artem Blagodarenko <artem.blagodarenko@gmail.com>
Reviewed-by: Andreas Dilger <adilger@dilger.ca>
---
fs/ext4/namei.c | 38 +++++++++++++++++++-------------------
1 file changed, 19 insertions(+), 19 deletions(-)
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index cc49ae04a6f6..2c951570990f 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -102,7 +102,7 @@ static struct buffer_head *ext4_append(handle_t *handle,
}
static int ext4_dx_csum_verify(struct inode *inode,
- struct ext4_dir_entry *dirent);
+ struct ext4_dir_entry_2 *dirent);
/*
* Hints to ext4_read_dirblock regarding whether we expect a directory
@@ -128,7 +128,7 @@ static struct buffer_head *__ext4_read_dirblock(struct inode *inode,
unsigned int line)
{
struct buffer_head *bh;
- struct ext4_dir_entry *dirent;
+ struct ext4_dir_entry_2 *dirent;
int is_dx_block = 0;
if (block >= inode->i_size >> inode->i_blkbits) {
@@ -160,7 +160,7 @@ static struct buffer_head *__ext4_read_dirblock(struct inode *inode,
}
if (!bh)
return NULL;
- dirent = (struct ext4_dir_entry *) bh->b_data;
+ dirent = (struct ext4_dir_entry_2 *) bh->b_data;
/* Determine whether or not we have an index block */
if (is_dx(inode)) {
if (block == 0)
@@ -317,13 +317,13 @@ static struct ext4_dir_entry_tail *get_dirent_tail(struct inode *inode,
int blocksize = EXT4_BLOCK_SIZE(inode->i_sb);
#ifdef PARANOID
- struct ext4_dir_entry *d, *top;
+ struct ext4_dir_entry_2 *d, *top;
- d = (struct ext4_dir_entry *)bh->b_data;
- top = (struct ext4_dir_entry *)(bh->b_data +
+ d = (struct ext4_dir_entry_2 *)bh->b_data;
+ top = (struct ext4_dir_entry_2 *)(bh->b_data +
(blocksize - sizeof(struct ext4_dir_entry_tail)));
while (d < top && ext4_rec_len_from_disk(d->rec_len, blocksize))
- d = (struct ext4_dir_entry *)(((void *)d) +
+ d = (struct ext4_dir_entry_2 *)(((void *)d) +
ext4_rec_len_from_disk(d->rec_len, blocksize));
if (d != top)
@@ -410,22 +410,22 @@ int ext4_handle_dirty_dirblock(handle_t *handle,
}
static struct dx_countlimit *get_dx_countlimit(struct inode *inode,
- struct ext4_dir_entry *dirent,
+ struct ext4_dir_entry_2 *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);
unsigned int rlen = ext4_rec_len_from_disk(dirent->rec_len, blocksize);
if (rlen == blocksize)
- count_offset = 8;
+ count_offset = sizeof(struct dx_node);
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;
@@ -438,7 +438,7 @@ static struct dx_countlimit *get_dx_countlimit(struct inode *inode,
return (struct dx_countlimit *)(((void *)dirent) + count_offset);
}
-static __le32 ext4_dx_csum(struct inode *inode, struct ext4_dir_entry *dirent,
+static __le32 ext4_dx_csum(struct inode *inode, struct ext4_dir_entry_2 *dirent,
int count_offset, int count, struct dx_tail *t)
{
struct ext4_inode_info *ei = EXT4_I(inode);
@@ -456,7 +456,7 @@ static __le32 ext4_dx_csum(struct inode *inode, struct ext4_dir_entry *dirent,
}
static int ext4_dx_csum_verify(struct inode *inode,
- struct ext4_dir_entry *dirent)
+ struct ext4_dir_entry_2 *dirent)
{
struct dx_countlimit *c;
struct dx_tail *t;
@@ -485,7 +485,7 @@ static int ext4_dx_csum_verify(struct inode *inode,
return 1;
}
-static void ext4_dx_csum_set(struct inode *inode, struct ext4_dir_entry *dirent)
+static void ext4_dx_csum_set(struct inode *inode, struct ext4_dir_entry_2 *dirent)
{
struct dx_countlimit *c;
struct dx_tail *t;
@@ -515,7 +515,7 @@ static inline int ext4_handle_dirty_dx_node(handle_t *handle,
struct inode *inode,
struct buffer_head *bh)
{
- ext4_dx_csum_set(inode, (struct ext4_dir_entry *)bh->b_data);
+ ext4_dx_csum_set(inode, (struct ext4_dir_entry_2 *)bh->b_data);
return ext4_handle_dirty_metadata(handle, inode, bh);
}
@@ -1488,7 +1488,7 @@ int ext4_search_dir(struct buffer_head *bh, char *search_buf, int buf_size,
}
static int is_dx_internal_node(struct inode *dir, ext4_lblk_t block,
- struct ext4_dir_entry *de)
+ struct ext4_dir_entry_2 *de)
{
struct super_block *sb = dir->i_sb;
@@ -1619,7 +1619,7 @@ static struct buffer_head *__ext4_find_entry(struct inode *dir,
}
if (!buffer_verified(bh) &&
!is_dx_internal_node(dir, block,
- (struct ext4_dir_entry *)bh->b_data) &&
+ (struct ext4_dir_entry_2 *)bh->b_data) &&
!ext4_dirblock_csum_verify(dir, bh)) {
EXT4_ERROR_INODE_ERR(dir, EFSBADCRC,
"checksumming directory "
--
2.43.7
^ permalink raw reply related
* [PATCH v3 00/10] Data in direntry (dirdata) feature
From: Artem Blagodarenko @ 2026-06-19 19:10 UTC (permalink / raw)
To: linux-ext4; +Cc: adilger.kernel, Artem Blagodarenko, syzbot
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 is LUFID -
Locally Unique File ID.
Support for fscrypt and case-insensitive directories
with dirdata enabled has been verified using a
dedicated xfstest submitted to the xfstests list as
a separate patch.
e2fsprogs support is provided in a separate patches
series.
Changes in v3:
- Fixed issues reported by automated review of v2:
- dx_get_dx_info() and get_dx_countlimit() called
ext4_dir_entry_len() with the directory inode
hardcoded to NULL, forcing its blocksize fallback
to 4096 regardless of the real filesystem blocksize.
Both now pass the real inode through, and
dx_get_dx_info() also rejects results that fall
outside the directory block.
- ext4_dirdata_get() declared a local "dfid" that
shadowed the function's own "dfid" output parameter,
so a requested LUFID copy never reached the caller's
buffer. Renamed the local and fixed the copy.
- ext4_dirdata_get()/ext4_dirdata_set() compared
offsets against the raw on-disk rec_len instead of
decoding it via ext4_rec_len_from_disk(), which is
incorrect on big-endian hosts and mishandles the
"0/65535 means full block" sentinel. Both now decode
rec_len once and use the decoded value throughout.
- EXT4_IOC_SET_LUFID deleted the existing directory
entry before re-adding it with the new LUFID data;
if the re-add failed, the inode was left with no
directory entry at all. It now attempts to restore
the original entry on failure, and loudly flags
inode corruption if that also fails.
- syzbot ci tested the fix for these issues; per its
request, this is being submitted with the corresponding
Tested-by tag below.
- Rebased onto the latest codebase.
Artem Blagodarenko (10):
ext4: replace ext4_dir_entry with ext4_dir_entry_2
ext4: add ext4_dir_entry_is_tail()
ext4: refactor dx_root to support variable dirent sizes
ext4: add dirdata format definitions and access helpers
ext4: preserve dirdata bits in get_dtype()
ext4: add ext4_dir_entry_len() and harden dirdata parsing
ext4: rename ext4_dir_rec_len() and clarify dirdata usage
ext4: dirdata feature
ext4: add dirdata set/get helpers
ext4: Add EXT4_IOC_SET_LUFID ioctl for setting LUFID on directory
entries
foofile.txt | 0
fs/ext4/dir.c | 9 +-
fs/ext4/ext4.h | 205 +++++++++++-
fs/ext4/inline.c | 37 ++-
fs/ext4/ioctl.c | 62 ++++
fs/ext4/namei.c | 650 ++++++++++++++++++++++++++++----------
fs/ext4/sysfs.c | 2 +
include/uapi/linux/ext4.h | 13 +
8 files changed, 780 insertions(+), 198 deletions(-)
create mode 100644 foofile.txt
Tested-by: syzbot@syzkaller.appspotmail.com
--
2.43.7
^ permalink raw reply
* [PATCH RESEND 4/4] libext2fs: add ext2fs_xattrs_release_all() helper
From: Etienne AUJAMES @ 2026-06-19 15:33 UTC (permalink / raw)
To: linux-ext4, Theodore Ts'o; +Cc: Andreas Dilger, Li Dongyang
In-Reply-To: <ajVdnQUu9tSrKldW@eaujamesFR0130>
This patch adds a helper function ext2fs_xattrs_release_all() which
removes all extended attributes and updates the quota accordingly.
The main purpose of this is to handle ea_inode xattrs in e2fsck when
deleting orphan inodes:
# e2fsck -yf /tmp/ext4
e2fsck 1.47.3-wc2 (11-Nov-2025)
Clearing orphaned inode 12 (uid=0, gid=0, mode=0100644, size=0)
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Regular filesystem inode 13 has EA_INODE flag set. Clear<y>? yes
Unattached inode 13
Connect to /lost+found<y>? yes
Inode 13 ref count is 2, should be 1. Fix<y>? yes
fuse2fs, debugfs and mke2fs are updated to use this function and
handle ea_inode on inode deletion.
Update d_xattr_ea_inode to check for the inode deletion case.
Add a regression test: f_orphan_ea_inode
Signed-off-by: Etienne AUJAMES <eaujames@ddn.com>
Change-Id: I4a84a50d43b8b9aab2dfc352a92256c710a3659e
Lustre-bug-id: https://jira.whamcloud.com/browse/LU-20049
---
debugfs/debugfs.c | 33 +++++++--
e2fsck/super.c | 67 +++++++++++-------
lib/ext2fs/ext2fs.h | 3 +
lib/ext2fs/ext_attr.c | 41 +++++++++++
misc/create_inode_libarchive.c | 35 ++++-----
misc/fuse2fs.c | 117 +++++++++++--------------------
tests/d_xattr_ea_inode/expect | 51 ++++++++++++++
tests/d_xattr_ea_inode/script | 55 ++++++++++-----
tests/f_orphan_ea_inode/expect.1 | 6 ++
tests/f_orphan_ea_inode/expect.2 | 7 ++
tests/f_orphan_ea_inode/image.gz | Bin 0 -> 2139 bytes
tests/f_orphan_ea_inode/name | 1 +
tests/f_orphan_ea_inode/script | 3 +
13 files changed, 277 insertions(+), 142 deletions(-)
create mode 100644 tests/f_orphan_ea_inode/expect.1
create mode 100644 tests/f_orphan_ea_inode/expect.2
create mode 100644 tests/f_orphan_ea_inode/image.gz
create mode 100644 tests/f_orphan_ea_inode/name
create mode 100644 tests/f_orphan_ea_inode/script
diff --git a/debugfs/debugfs.c b/debugfs/debugfs.c
index b9f248be2..d316293d2 100644
--- a/debugfs/debugfs.c
+++ b/debugfs/debugfs.c
@@ -1861,21 +1861,40 @@ static int release_blocks_proc(ext2_filsys fs, blk64_t *blocknr,
static void kill_file_by_inode(ext2_ino_t inode)
{
- struct ext2_inode inode_buf;
+ struct ext2_inode_large *inode_buf;
+ size_t inode_size = EXT2_INODE_SIZE(current_fs->super);
+ errcode_t err;
- if (debugfs_read_inode(inode, &inode_buf, 0))
- return;
- ext2fs_set_dtime(current_fs, &inode_buf);
- if (debugfs_write_inode(inode, &inode_buf, 0))
+ err = ext2fs_get_memzero(inode_size, &inode_buf);
+ if (err)
return;
- if (ext2fs_inode_has_valid_blocks2(current_fs, &inode_buf)) {
+
+ err = ext2fs_read_inode_full(current_fs, inode, EXT2_INODE(inode_buf),
+ inode_size);
+ if (err) {
+ com_err(__func__, err, "while reading inode %u", inode);
+ goto out;
+ }
+
+ ext2fs_set_dtime(current_fs, EXT2_INODE(inode_buf));
+ ext2fs_xattrs_release_all(current_fs, inode, inode_buf, inode_size,
+ NULL);
+ if (ext2fs_inode_has_valid_blocks2(current_fs, EXT2_INODE(inode_buf))) {
blk64_t last_cluster = 0;
ext2fs_block_iterate3(current_fs, inode, BLOCK_FLAG_READ_ONLY,
NULL, release_blocks_proc, &last_cluster);
}
printf("\n");
ext2fs_inode_alloc_stats2(current_fs, inode, -1,
- LINUX_S_ISDIR(inode_buf.i_mode));
+ LINUX_S_ISDIR(inode_buf->i_mode));
+
+ err = ext2fs_write_inode_full(current_fs, inode, EXT2_INODE(inode_buf),
+ inode_size);
+ if (err)
+ com_err(__func__, err, "while writing inode %u", inode);
+
+out:
+ ext2fs_free_mem(&inode_buf);
}
diff --git a/e2fsck/super.c b/e2fsck/super.c
index c2ccefd54..1a94ba567 100644
--- a/e2fsck/super.c
+++ b/e2fsck/super.c
@@ -156,13 +156,14 @@ static errcode_t truncate_inode_blocks(e2fsck_t ctx, ext2_ino_t ino,
struct process_block_struct pb = { 0 };
e2_blkcnt_t truncate_block = 0;
__u32 truncate_offset = 0;
- blk64_t blk;
+ blk64_t blk, iblks;
int ret_flags;
errcode_t retval = 0;
if (!ext2fs_inode_has_valid_blocks2(fs, EXT2_INODE(inode)))
return 0;
+ iblks = ext2fs_get_stat_i_blocks(fs, EXT2_INODE(inode));
if (inode->i_links_count) {
truncate_offset = inode->i_size % fs->blocksize;
truncate_block = (e2_blkcnt_t)
@@ -190,6 +191,10 @@ static errcode_t truncate_inode_blocks(e2fsck_t ctx, ext2_ino_t ino,
"release_inode_blocks");
ext2fs_iblk_sub_blocks(fs, EXT2_INODE(inode), pb.truncated_blocks);
+ iblks -= ext2fs_get_stat_i_blocks(fs, EXT2_INODE(inode));
+ if (ctx->qctx)
+ quota_data_sub(ctx->qctx, inode, ino, iblks * 512);
+
if (!truncate_offset)
return 0;
@@ -217,17 +222,19 @@ static errcode_t truncate_inode_blocks(e2fsck_t ctx, ext2_ino_t ino,
* not deleted.
*/
static int release_inode_blocks(e2fsck_t ctx, ext2_ino_t ino,
- struct ext2_inode_large *inode, char *block_buf,
+ struct ext2_inode_large *inode,
+ size_t inode_size, char *block_buf,
struct problem_context *pctx)
{
ext2_filsys fs = ctx->fs;
- blk64_t free_blks, ino_blks;
+ blk64_t free_blks;
+ __u32 free_inodes;
char *buf;
errcode_t err;
int rc = 0;
+ free_inodes = fs->super->s_free_inodes_count;
free_blks = ext2fs_free_blocks_count(fs->super);
- ino_blks = ext2fs_get_stat_i_blocks(fs, EXT2_INODE(inode));
buf = block_buf + 3 * ctx->fs->blocksize;
if (truncate_inode_blocks(ctx, ino, inode, buf, pctx)) {
rc = 1;
@@ -236,7 +243,7 @@ static int release_inode_blocks(e2fsck_t ctx, ext2_ino_t ino,
if (inode->i_links_count)
goto update_counts;
- err = ext2fs_free_ext_attr(fs, ino, inode);
+ err = ext2fs_xattrs_release_all(fs, ino, inode, inode_size, ctx->qctx);
if (err) {
com_err(__func__, err,
_("while calling ext2fs_free_ext_attr for inode %u"),
@@ -249,9 +256,8 @@ static int release_inode_blocks(e2fsck_t ctx, ext2_ino_t ino,
update_counts:
ctx->free_blocks += ext2fs_free_blocks_count(fs->super) - free_blks;
- ino_blks -= ext2fs_get_stat_i_blocks(fs, EXT2_INODE(inode));
- if (ctx->qctx)
- quota_data_sub(ctx->qctx, inode, 0, ino_blks << 9);
+ free_inodes = fs->super->s_free_inodes_count - free_inodes;
+ ctx->free_inodes += free_inodes;
return rc;
}
@@ -312,44 +318,55 @@ static int release_orphan_inode(e2fsck_t ctx, ext2_ino_t *ino, char *block_buf)
{
ext2_filsys fs = ctx->fs;
struct problem_context pctx;
- struct ext2_inode_large inode;
+ struct ext2_inode_large *inode;
+ size_t inode_size = EXT2_INODE_SIZE(fs->super);
ext2_ino_t next_ino;
+ int rc = 1;
+
+ if (ext2fs_get_memzero(inode_size, &inode))
+ return 1;
- e2fsck_read_inode_full(ctx, *ino, EXT2_INODE(&inode),
- sizeof(inode), "release_orphan_inode");
+ e2fsck_read_inode_full(ctx, *ino, EXT2_INODE(inode),
+ inode_size, __func__);
clear_problem_context(&pctx);
pctx.ino = *ino;
- pctx.inode = EXT2_INODE(&inode);
- pctx.str = inode.i_links_count ? _("Truncating") : _("Clearing");
+ pctx.inode = EXT2_INODE(inode);
+ pctx.str = inode->i_links_count ? _("Truncating") : _("Clearing");
fix_problem(ctx, PR_0_ORPHAN_CLEAR_INODE, &pctx);
- next_ino = inode.i_dtime;
+ next_ino = inode->i_dtime;
if (next_ino &&
((next_ino < EXT2_FIRST_INODE(fs->super)) ||
(next_ino > fs->super->s_inodes_count))) {
pctx.ino = next_ino;
fix_problem(ctx, PR_0_ORPHAN_ILLEGAL_INODE, &pctx);
- return 1;
+ goto out;
}
- if (release_inode_blocks(ctx, *ino, &inode, block_buf, &pctx))
- return 1;
+ if (release_inode_blocks(ctx, *ino, inode, inode_size, block_buf,
+ &pctx))
+ goto out;
- if (!inode.i_links_count) {
+ if (!inode->i_links_count) {
if (ctx->qctx)
- quota_data_inodes(ctx->qctx, &inode, *ino, -1);
+ quota_data_inodes(ctx->qctx, inode, *ino, -1);
ext2fs_inode_alloc_stats2(fs, *ino, -1,
- LINUX_S_ISDIR(inode.i_mode));
+ LINUX_S_ISDIR(inode->i_mode));
ctx->free_inodes++;
- ext2fs_set_dtime(fs, EXT2_INODE(&inode));
+ ext2fs_set_dtime(fs, EXT2_INODE(inode));
} else {
- inode.i_dtime = 0;
+ inode->i_dtime = 0;
}
- e2fsck_write_inode_full(ctx, *ino, EXT2_INODE(&inode),
- sizeof(inode), "delete_file");
+ e2fsck_write_inode_full(ctx, *ino, EXT2_INODE(inode),
+ inode_size, __func__);
*ino = next_ino;
- return 0;
+ rc = 0;
+
+out:
+ ext2fs_free_mem(&inode);
+
+ return rc;
}
struct process_orphan_block_data {
diff --git a/lib/ext2fs/ext2fs.h b/lib/ext2fs/ext2fs.h
index 56de5ea50..cb3f1a3a1 100644
--- a/lib/ext2fs/ext2fs.h
+++ b/lib/ext2fs/ext2fs.h
@@ -1425,6 +1425,9 @@ errcode_t ext2fs_xattr_inode_max_size(ext2_filsys fs, ext2_ino_t ino,
#define XATTR_HANDLE_FLAG_RAW 0x0001
errcode_t ext2fs_xattrs_flags(struct ext2_xattr_handle *handle,
unsigned int *new_flags, unsigned int *old_flags);
+errcode_t ext2fs_xattrs_release_all(ext2_filsys fs, ext2_ino_t ino,
+ struct ext2_inode_large *inode,
+ size_t inode_size, quota_ctx_t qctx);
extern void ext2fs_ext_attr_block_rehash(struct ext2_ext_attr_header *header,
struct ext2_ext_attr_entry *end);
extern __u32 ext2fs_get_ea_inode_hash(struct ext2_inode *inode);
diff --git a/lib/ext2fs/ext_attr.c b/lib/ext2fs/ext_attr.c
index 3b90b70bb..2a2e79acd 100644
--- a/lib/ext2fs/ext_attr.c
+++ b/lib/ext2fs/ext_attr.c
@@ -1868,3 +1868,44 @@ errcode_t ext2fs_xattrs_flags(struct ext2_xattr_handle *handle,
handle->flags = *new_flags;
return 0;
}
+
+errcode_t ext2fs_xattrs_release_all(ext2_filsys fs, ext2_ino_t ino,
+ struct ext2_inode_large *inode,
+ size_t inode_size, quota_ctx_t qctx)
+{
+ struct ext2_xattr_handle *h;
+ errcode_t err = 0;
+
+ if (!ext2fs_has_feature_ea_inode(fs->super)) {
+ blk64_t blk = ext2fs_file_acl_block(fs, EXT2_INODE(inode));
+
+ if (!blk)
+ return 0;
+
+ err = ext2fs_free_ext_attr(fs, ino, inode);
+ if (err || !qctx)
+ return err;
+
+ quota_data_sub(qctx, inode, ino,
+ EXT2FS_C2B(fs, 1) * fs->blocksize);
+ return 0;
+ }
+
+ err = ext2fs_xattrs_open_inode(fs, ino, EXT2_INODE(inode), inode_size,
+ qctx, &h);
+ if (err)
+ return err;
+
+ err = ext2fs_xattrs_read(h);
+ if (err)
+ goto out_close;
+
+ err = ext2fs_xattr_remove_all(h);
+ if (err)
+ goto out_close;
+
+out_close:
+ ext2fs_xattrs_close(&h);
+
+ return err;
+}
diff --git a/misc/create_inode_libarchive.c b/misc/create_inode_libarchive.c
index fadf0721f..4736e8c22 100644
--- a/misc/create_inode_libarchive.c
+++ b/misc/create_inode_libarchive.c
@@ -261,46 +261,49 @@ static inline unsigned int __round_up(unsigned int quantity, unsigned int size)
static int remove_inode(ext2_filsys fs, ext2_ino_t ino)
{
errcode_t ret = 0;
- struct ext2_inode_large inode;
+ struct ext2_inode_large *inode;
+ size_t inode_size = EXT2_INODE_SIZE(fs->super);
- memset(&inode, 0, sizeof(inode));
- ret = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode,
- sizeof(inode));
+ ret = ext2fs_get_memzero(inode_size, &inode);
+ if (ret)
+ return ret;
+
+ ret = ext2fs_read_inode_full(fs, ino, EXT2_INODE(inode), inode_size);
if (ret)
goto out;
- switch (inode.i_links_count) {
+ switch (inode->i_links_count) {
case 0:
return 0; /* XXX: already done? */
case 1:
- inode.i_links_count--;
- ext2fs_set_dtime(fs, EXT2_INODE(&inode));
+ inode->i_links_count--;
+ ext2fs_set_dtime(fs, EXT2_INODE(inode));
break;
default:
- inode.i_links_count--;
+ inode->i_links_count--;
}
- if (inode.i_links_count)
+ if (inode->i_links_count)
goto write_out;
/* Nobody holds this file; free its blocks! */
- ret = ext2fs_free_ext_attr(fs, ino, &inode);
+ ret = ext2fs_xattrs_release_all(fs, ino, inode, inode_size, NULL);
if (ret)
goto write_out;
- if (ext2fs_inode_has_valid_blocks2(fs, (struct ext2_inode *)&inode)) {
- ret = ext2fs_punch(fs, ino, (struct ext2_inode *)&inode, NULL,
- 0, ~0ULL);
+ if (ext2fs_inode_has_valid_blocks2(fs, EXT2_INODE(inode))) {
+ ret = ext2fs_punch(fs, ino, EXT2_INODE(inode), NULL, 0, ~0ULL);
if (ret)
goto write_out;
}
- ext2fs_inode_alloc_stats2(fs, ino, -1, LINUX_S_ISDIR(inode.i_mode));
+ ext2fs_inode_alloc_stats2(fs, ino, -1, LINUX_S_ISDIR(inode->i_mode));
write_out:
- ret = ext2fs_write_inode_full(fs, ino, (struct ext2_inode *)&inode,
- sizeof(inode));
+ ret = ext2fs_write_inode_full(fs, ino, EXT2_INODE(inode), inode_size);
out:
+ ext2fs_free_mem(&inode);
+
return ret;
}
diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 94e289fab..11141f645 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -2274,123 +2274,88 @@ static int fuse2fs_unlink(struct fuse2fs *ff, const char *path,
return 0;
}
-static int remove_ea_inodes(struct fuse2fs *ff, ext2_ino_t ino,
- struct ext2_inode_large *inode)
+static int remove_inode(struct fuse2fs *ff, ext2_ino_t ino)
{
ext2_filsys fs = ff->fs;
- struct ext2_xattr_handle *h;
errcode_t err;
+ struct ext2_inode_large *inode;
+ size_t inode_size = EXT2_INODE_SIZE(fs->super);
int ret = 0;
- /*
- * The xattr handle maintains its own private copy of the inode, so
- * write ours to disk so that we can read it.
- */
- err = fuse2fs_write_inode(fs, ino, inode);
+ err = ext2fs_get_memzero(inode_size, &inode);
if (err)
return translate_error(fs, ino, err);
- err = ext2fs_xattrs_open(fs, ino, &h);
- if (err)
- return translate_error(fs, ino, err);
-
- err = ext2fs_xattrs_read(h);
+ err = ext2fs_read_inode_full(fs, ino, EXT2_INODE(inode), inode_size);
if (err) {
ret = translate_error(fs, ino, err);
- goto out_close;
- }
-
- err = ext2fs_xattr_remove_all(h);
- if (err) {
- ret = translate_error(fs, ino, err);
- goto out_close;
+ goto out;
}
-
-out_close:
- ext2fs_xattrs_close(&h);
- if (ret)
- return ret;
-
- /* Now read the inode back in. */
- err = fuse2fs_read_inode(fs, ino, inode);
- if (err)
- return translate_error(fs, ino, err);
-
- return 0;
-}
-
-static int remove_inode(struct fuse2fs *ff, ext2_ino_t ino)
-{
- ext2_filsys fs = ff->fs;
- errcode_t err;
- struct ext2_inode_large inode;
- int ret = 0;
-
- err = fuse2fs_read_inode(fs, ino, &inode);
- if (err)
- return translate_error(fs, ino, err);
-
dbg_printf(ff, "%s: put ino=%d links=%d\n", __func__, ino,
- inode.i_links_count);
+ inode->i_links_count);
- if (S_ISDIR(inode.i_mode)) {
+ if (S_ISDIR(inode->i_mode)) {
/*
* Caller should have checked that this is an empty directory
* before starting the unlink process. nlink is usually 2, but
* it could be 1 if this dir ever had more than 65000 subdirs.
* Zero the link count.
*/
- if (!ext2fs_dir_link_empty(EXT2_INODE(&inode)))
- return translate_error(fs, ino, EXT2_ET_INODE_CORRUPTED);
- inode.i_links_count = 0;
- ext2fs_set_dtime(fs, EXT2_INODE(&inode));
+ if (!ext2fs_dir_link_empty(EXT2_INODE(inode))) {
+ ret = translate_error(fs, ino, EXT2_ET_INODE_CORRUPTED);
+ goto out;
+ }
+ inode->i_links_count = 0;
+ ext2fs_set_dtime(fs, EXT2_INODE(inode));
} else {
/*
* Any other file type can be hardlinked, so all we need to do
* is decrement the nlink.
*/
- if (inode.i_links_count == 0)
- return translate_error(fs, ino, EXT2_ET_INODE_CORRUPTED);
- inode.i_links_count--;
- if (!inode.i_links_count)
- ext2fs_set_dtime(fs, EXT2_INODE(&inode));
+ if (inode->i_links_count == 0) {
+ ret = translate_error(fs, ino, EXT2_ET_INODE_CORRUPTED);
+ goto out;
+ }
+ inode->i_links_count--;
+ if (!inode->i_links_count)
+ ext2fs_set_dtime(fs, EXT2_INODE(inode));
}
- ret = update_ctime(fs, ino, &inode);
+ ret = update_ctime(fs, ino, inode);
if (ret)
- return ret;
+ goto out;
/* Still linked? Leave it be. */
- if (inode.i_links_count)
+ if (inode->i_links_count)
goto write_out;
- if (ext2fs_has_feature_ea_inode(fs->super)) {
- ret = remove_ea_inodes(ff, ino, &inode);
- if (ret)
- return ret;
- }
-
/* Nobody holds this file; free its blocks! */
- err = ext2fs_free_ext_attr(fs, ino, &inode);
- if (err)
- return translate_error(fs, ino, err);
+ err = ext2fs_xattrs_release_all(fs, ino, inode, inode_size, NULL);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
- if (ext2fs_inode_has_valid_blocks2(fs, EXT2_INODE(&inode))) {
- err = ext2fs_punch(fs, ino, EXT2_INODE(&inode), NULL,
+ if (ext2fs_inode_has_valid_blocks2(fs, EXT2_INODE(inode))) {
+ err = ext2fs_punch(fs, ino, EXT2_INODE(inode), NULL,
0, ~0ULL);
- if (err)
- return translate_error(fs, ino, err);
+ if (err) {
+ ret = translate_error(fs, ino, err);
+ goto out;
+ }
}
ext2fs_inode_alloc_stats2(fs, ino, -1,
- LINUX_S_ISDIR(inode.i_mode));
+ LINUX_S_ISDIR(inode->i_mode));
write_out:
- err = fuse2fs_write_inode(fs, ino, &inode);
+ err = ext2fs_write_inode_full(fs, ino, EXT2_INODE(inode), inode_size);
if (err)
- return translate_error(fs, ino, err);
+ ret = translate_error(fs, ino, err);
+out:
+ ext2fs_free_mem(&inode);
- return 0;
+ return ret;
}
static int __op_unlink(struct fuse2fs *ff, const char *path)
diff --git a/tests/d_xattr_ea_inode/expect b/tests/d_xattr_ea_inode/expect
index aaad9c5b3..e1878c3dc 100644
--- a/tests/d_xattr_ea_inode/expect
+++ b/tests/d_xattr_ea_inode/expect
@@ -135,3 +135,54 @@ Pass 5: Checking group summary information
test_filesys: 11/128 files (0.0% non-contiguous), 18/256 blocks
Exit status is 0
+write d_xattr_ea_inode.tmp test_file
+Allocated inode: 12
+Exit status is 0
+
+Generate xattr value (1024 bytes)
+ea_set -f d_xattr_ea_inode.tmp test_file user.test1
+Exit status is 0
+ea_get -f d_xattr_ea_inode.ver.tmp test_file user.test1
+Exit status is 0
+Compare xattr values (1024 bytes)
+stat test_file
+Blockcount: 16
+Exit status is 0
+e2fsck -yf -N test_filesys
+Pass 1: Checking inodes, blocks, and sizes
+Pass 2: Checking directory structure
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+test_filesys: 12/128 files (0.0% non-contiguous), 20/256 blocks
+Exit status is 0
+
+Generate xattr value (16384 bytes)
+ea_set -f d_xattr_ea_inode.tmp test_file user.test2
+Exit status is 0
+ea_get -f d_xattr_ea_inode.ver.tmp test_file user.test2
+Exit status is 0
+Compare xattr values (16384 bytes)
+stat test_file
+Blockcount: 48
+Exit status is 0
+e2fsck -yf -N test_filesys
+Pass 1: Checking inodes, blocks, and sizes
+Pass 2: Checking directory structure
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+test_filesys: 13/128 files (0.0% non-contiguous), 24/256 blocks
+Exit status is 0
+
+rm test_file
+
+Exit status is 0
+e2fsck -yf -N test_filesys
+Pass 1: Checking inodes, blocks, and sizes
+Pass 2: Checking directory structure
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+test_filesys: 11/128 files (0.0% non-contiguous), 18/256 blocks
+Exit status is 0
diff --git a/tests/d_xattr_ea_inode/script b/tests/d_xattr_ea_inode/script
index 84104549c..c24eb6cd5 100644
--- a/tests/d_xattr_ea_inode/script
+++ b/tests/d_xattr_ea_inode/script
@@ -15,32 +15,33 @@ VERIFY_DATA=$test_name.ver.tmp
echo "debugfs edit extended attributes with ea_inode feature" > $OUT.new
d_xattr_ea_inode_check() {
- local xattr_size=$1
- local xattr_name=$2
- local ea_rm=$3
+ local path=$1
+ local xattr_size=$2
+ local xattr_name=$3
+ local ea_rm=$4
echo "Generate xattr value ($xattr_size bytes)" >> $OUT.new
echo $xattr_size |
awk '{srand();for(i=0;i<$1;i++) printf("%c",97+int(rand()*26));}' > $TEST_DATA
- echo "ea_set -f $TEST_DATA / $xattr_name" >> $OUT.new
- $DEBUGFS -w -R "ea_set -f $TEST_DATA / $xattr_name" $TMPFILE >> $OUT.new 2>&1
+ echo "ea_set -f $TEST_DATA $path $xattr_name" >> $OUT.new
+ $DEBUGFS -w -R "ea_set -f $TEST_DATA $path $xattr_name" $TMPFILE >> $OUT.new 2>&1
echo Exit status is $? >> $OUT.new
- echo "ea_get -f $VERIFY_DATA / $xattr_name" >> $OUT.new
- $DEBUGFS -w -R "ea_get -f $VERIFY_DATA / $xattr_name" $TMPFILE >> $OUT.new 2>&1
+ echo "ea_get -f $VERIFY_DATA $path $xattr_name" >> $OUT.new
+ $DEBUGFS -w -R "ea_get -f $VERIFY_DATA $path $xattr_name" $TMPFILE >> $OUT.new 2>&1
echo Exit status is $? >> $OUT.new
echo "Compare xattr values ($xattr_size bytes)" >> $OUT.new
diff -u $TEST_DATA $VERIFY_DATA >> $OUT.new
- echo "stat /" >> $OUT.new
- ($DEBUGFS -c -R "stat /" $TMPFILE | grep -Eo "Blockcount: [0-9]+") >> $OUT.new 2>&1
+ echo "stat $path" >> $OUT.new
+ ($DEBUGFS -c -R "stat $path" $TMPFILE | grep -Eo "Blockcount: [0-9]+") >> $OUT.new 2>&1
echo Exit status is $? >> $OUT.new
if $ea_rm; then
- echo "ea_rm / $xattr_name" >> $OUT.new
- $DEBUGFS -w -R "ea_rm / $xattr_name" $TMPFILE >> $OUT.new 2>&1
+ echo "ea_rm $path $xattr_name" >> $OUT.new
+ $DEBUGFS -w -R "ea_rm $path $xattr_name" $TMPFILE >> $OUT.new 2>&1
echo Exit status is $? >> $OUT.new
fi
@@ -56,15 +57,33 @@ echo "mke2fs -Fq -b 4096 -O ea_inode test.img 1m" >> $OUT.new
$MKE2FS -Fq -b 4096 -O ea_inode $TMPFILE 1m > /dev/null 2>&1
echo Exit status is $? >> $OUT.new
-d_xattr_ea_inode_check 8292 user.test1 true
+d_xattr_ea_inode_check / 8292 user.test1 true
-d_xattr_ea_inode_check 4097 user.test1 false
-d_xattr_ea_inode_check 102 user.test2 false
-d_xattr_ea_inode_check 5005 user.test2 true
-d_xattr_ea_inode_check 512 user.test1 true
+d_xattr_ea_inode_check / 4097 user.test1 false
+d_xattr_ea_inode_check / 102 user.test2 false
+d_xattr_ea_inode_check / 5005 user.test2 true
+d_xattr_ea_inode_check / 512 user.test1 true
-d_xattr_ea_inode_check 1024 user.test1 false
-d_xattr_ea_inode_check 5000 user.test1 true
+d_xattr_ea_inode_check / 1024 user.test1 false
+d_xattr_ea_inode_check / 5000 user.test1 true
+
+# Create and remove a file with ea_inode
+echo "test_file_content" > $TEST_DATA
+echo "write $TEST_DATA test_file" >> $OUT.new
+$DEBUGFS -w -R "write $TEST_DATA test_file" $TMPFILE >> $OUT.new 2>&1
+echo Exit status is $? >> $OUT.new
+echo >> $OUT.new
+
+d_xattr_ea_inode_check test_file 1024 user.test1 false
+d_xattr_ea_inode_check test_file 16384 user.test2 false
+
+echo "rm test_file" >> $OUT.new
+$DEBUGFS -w -R "rm test_file" $TMPFILE >> $OUT.new 2>&1
+echo Exit status is $? >> $OUT.new
+
+echo e2fsck $VERIFY_FSCK_OPT -N test_filesys >> $OUT.new
+$FSCK $VERIFY_FSCK_OPT -N test_filesys $TMPFILE >> $OUT.new 2>&1
+echo Exit status is $? >> $OUT.new
sed -f $cmd_dir/filter.sed $OUT.new > $OUT
diff --git a/tests/f_orphan_ea_inode/expect.1 b/tests/f_orphan_ea_inode/expect.1
new file mode 100644
index 000000000..3eba3d718
--- /dev/null
+++ b/tests/f_orphan_ea_inode/expect.1
@@ -0,0 +1,6 @@
+test_filesys: Clearing orphaned inode 12 (uid=0, gid=0, mode=0100644, size=0)
+test_filesys: Clearing orphaned inode 13 (uid=1000, gid=1000, mode=0100644, size=6)
+test_filesys: Clearing orphaned inode 14 (uid=1001, gid=1001, mode=0100644, size=0)
+test_filesys: Clearing orphaned inode 15 (uid=1002, gid=1002, mode=0100644, size=6)
+test_filesys: clean, 13/128 files, 23/256 blocks
+Exit status is 0
diff --git a/tests/f_orphan_ea_inode/expect.2 b/tests/f_orphan_ea_inode/expect.2
new file mode 100644
index 000000000..bf76a5c25
--- /dev/null
+++ b/tests/f_orphan_ea_inode/expect.2
@@ -0,0 +1,7 @@
+Pass 1: Checking inodes, blocks, and sizes
+Pass 2: Checking directory structure
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+test_filesys: 13/128 files (0.0% non-contiguous), 23/256 blocks
+Exit status is 0
diff --git a/tests/f_orphan_ea_inode/image.gz b/tests/f_orphan_ea_inode/image.gz
new file mode 100644
index 0000000000000000000000000000000000000000..95f0e53aebd45e041b4b1f53be7c374c8144c1b7
GIT binary patch
literal 2139
zcmeH`ZB){C6vzM8Hrp}7g@%~MtYvweYvyWYCGO-yK4OW}V<wpzXd)%L%m=7B52Z~^
zQ={e6O5&KqaG4HETqaXUP01LVmg2*F0G7z3!f&v>*_*xGyS^{(x%d9g@7#Mo=U!CN
zx^;`kZBo{(O(w?UlOWkxD8Vt(5&DxMNiEw;EPH7y+Ew~8s5^)kG8_|gs>t&FoaZqP
zhX^f4FF$nsWQ%V%)-Su+(l6Bj)v?|nzO6mdz3EPOer?uZ1ll)s&Ai{i%Nu890TO2K
zshf0{b{<Zv_<<poPxa+^D#yc4?<l;OGK`3u+<CLg<*w-W<I&Z&TQxBKQZK^k`xB!I
z(xSjWKVP%@5e;nGe!!u<P+^A{(m#3T?#y#3+&#B^K;w?n8!FVs+wQPuX=y9mv_BLa
z=6Qv2PmV?vhTVTy7c;X9{Xof3m)GR|oFiSpu)UCoih#ockGL374P)C>)fKnfhQuM3
ztE3o$LWqHZD0#-n3TAl<f}q;T^&2Retol@MQ}DTA*g>%tf_w_xidVV}P2DUekE)eD
zT(0*hq{s=2DLZ|=cfjpP$NK%@=c#ATI5bt|@jRbe)6n{i=2q(KOUp^UXH_U#Uun@v
zibGUrxGp$!c<IY}PVE{YCR5k~q_VDufa}qrdE~6*s9_O;x6eT`O1@ed`+(FRn2_dR
zy?yp+&_3XFs02d_6Stp^N{Bi#Yr8ZraBTIL_E9OTb@d@SI&nN#{_FNup8eKF^qsOO
zqmqFvSOVkM=-~sio=0EA9y8h{L<fM1e&apib)L>gIAYUPiAMJD3-(ra9L!`-503X&
zkE^6#8;v0xBSQ8}OeIL;lcoEH!L8Ob?!DL*>IBEr%AXAqYhC(lf<e&c!9rpa1t~yN
zROqvHMj-DAi1se&rBWj}t3|)vFRr%EM^&ByvH|gy4IU$Un2?-Mx~$FA^ip}&LeisK
zMvT5Alb4%Cyn)UP1b6qGr{*f{1-#`qLPf<rz=XvKHf=M{A^}#ihNMxl8`(<!IBOM7
z=?Vu{qUa390%0_(p($2JAJg$)p88HTI~3?`q@4qkMSSqK%?ZGaTsb#=nv};~c|3`S
z;tkGKjZz$r3up$s<cf@zu0*Io+u5{!G4)1V1%K%aHgejF{rVU<@yn7Td!-oe>@~D*
z0Vc{mZQ6tXP$#y`cS52**Q|e%QQ8=WpBK$J4K_}_l~<2P(lgK;5~jzBUMu8J=*SB{
zu6o87DJL#ukb`Hs$o89qW=2h^DO_^T0O|gm1q7WOdI3WMASpos)5bw|%t04(St-?I
zI<+XXZ&F+7Op7CM4HI=7$IonKxlbh#x3CCTLZL{v1h~&|gDaytp6nCp!M9BLOtX(e
z)ppMY*hgR0Ug#Q`KxpXdycinpU<q<si|6gN=q#7j;8mhaAKp)|TL9rd#RA&=v>uJ!
zB?fibNa<QO0K9aI&P3I~31I2szuSflXauk3xuJ7kIrh1K({`9b!eg3<AD9nik=amQ
z4A7F1hyxGu5`+1`8GRLqB}7xt%qp6>bru0;9?UEe*0yT@Se{ymzx_(1Yw1fztv6+{
z&4>Pe{l|1(NBH&nZ1g*N%U^UYwDJ`y93DU>!vnq1xwwn%W;V>4G3=h2i~ss#c00=v
i9Rno8r?2x(P~Yhtfp-M{Zvr7`V4?Up>pcjvf&Kz@_kh^|
literal 0
HcmV?d00001
diff --git a/tests/f_orphan_ea_inode/name b/tests/f_orphan_ea_inode/name
new file mode 100644
index 000000000..b892ff960
--- /dev/null
+++ b/tests/f_orphan_ea_inode/name
@@ -0,0 +1 @@
+clearing orphan inodes with ea_inode and quota features
diff --git a/tests/f_orphan_ea_inode/script b/tests/f_orphan_ea_inode/script
new file mode 100644
index 000000000..9650d07d0
--- /dev/null
+++ b/tests/f_orphan_ea_inode/script
@@ -0,0 +1,3 @@
+FSCK_OPT=-p
+SECOND_FSCK_OPT="-yf"
+. $cmd_dir/run_e2fsck
--
2.43.7
^ permalink raw reply related
* [PATCH RESEND 3/4] libext2fs: update iblock when using ea_inode feature
From: Etienne AUJAMES @ 2026-06-19 15:32 UTC (permalink / raw)
To: linux-ext4, Theodore Ts'o; +Cc: Andreas Dilger, Li Dongyang
In-Reply-To: <ajVdnQUu9tSrKldW@eaujamesFR0130>
When a xattr is stored in an ea_inode, ext2fs_xattrs_* functions do
not update the inode block count.
This patch uses a cached inode to update the block count and quota
when writing xattrs.
It also fix an xattr remove case: the current ACL block was not
release by ext2fs_xattrs_write().
Add a helper function ext2fs_iblk_get() to get the inode block count
in cluster count unit.
Add ext2fs_xattrs_open_inode() to specify an optional cached inode to
use or update.
The function handle an optional quota context argument to update quota
accounting. It is hard to predict inode quota usage with ea_inode
deduplication.
For testing purposes, modify the debugfs "ea_set" command to handle
input file larger than the FS block size.
Add a regression test: d_xattr_ea_inode
Fixes: 50d0998cfe ("libext2fs: add ea_inode support to set xattr")
Signed-off-by: Etienne AUJAMES <eaujames@ddn.com>
Change-Id: I34733255bb76ffe2386d8cd6c19ce4561be4da3a
Lustre-bug-id: https://jira.whamcloud.com/browse/LU-20049
---
debugfs/xattrs.c | 19 ++-
e2fsck/pass1.c | 12 +-
lib/ext2fs/ext2fs.h | 7 ++
lib/ext2fs/ext_attr.c | 227 ++++++++++++++++++++++------------
lib/ext2fs/i_block.c | 14 +++
lib/support/quotaio.h | 1 -
tests/d_xattr_ea_inode/expect | 137 ++++++++++++++++++++
tests/d_xattr_ea_inode/name | 1 +
tests/d_xattr_ea_inode/script | 85 +++++++++++++
9 files changed, 415 insertions(+), 88 deletions(-)
create mode 100644 tests/d_xattr_ea_inode/expect
create mode 100644 tests/d_xattr_ea_inode/name
create mode 100644 tests/d_xattr_ea_inode/script
diff --git a/debugfs/xattrs.c b/debugfs/xattrs.c
index b518941c9..8364281f4 100644
--- a/debugfs/xattrs.c
+++ b/debugfs/xattrs.c
@@ -14,6 +14,7 @@ extern int optind;
extern char *optarg;
#endif
#include <ctype.h>
+#include <unistd.h>
#include "support/cstring.h"
#include "debugfs.h"
@@ -299,10 +300,24 @@ void do_set_xattr(int argc, ss_argv_t argv, int sci_idx EXT2FS_ATTR((unused)),
goto out;
if (fp) {
- err = ext2fs_get_mem(current_fs->blocksize, &buf);
+ struct stat st;
+
+ if (fstat(fileno(fp), &st)) {
+ err = errno;
+ goto out;
+ }
+ if (st.st_size > sysconf(_SC_ARG_MAX)) {
+ err = EFBIG;
+ goto out;
+ }
+ err = ext2fs_get_mem(st.st_size, &buf);
if (err)
goto out;
- buflen = fread(buf, 1, current_fs->blocksize, fp);
+ buflen = fread(buf, 1, st.st_size, fp);
+ if (ferror(fp)) {
+ err = errno;
+ goto out;
+ }
} else {
buf = argv[optind + 2];
buflen = parse_c_string(buf);
diff --git a/e2fsck/pass1.c b/e2fsck/pass1.c
index fdde76cc2..364128f4d 100644
--- a/e2fsck/pass1.c
+++ b/e2fsck/pass1.c
@@ -968,18 +968,18 @@ static void reserve_block_for_lnf_repair(e2fsck_t ctx)
static errcode_t get_inline_data_ea_size(ext2_filsys fs, ext2_ino_t ino,
struct ext2_inode *inode,
- size_t *sz)
+ size_t inode_size, size_t *sz)
{
void *p;
struct ext2_xattr_handle *handle;
errcode_t retval;
- retval = ext2fs_xattrs_open(fs, ino, &handle);
+ retval = ext2fs_xattrs_open_inode(fs, ino, inode, inode_size, NULL,
+ &handle);
if (retval)
return retval;
- retval = ext2fs_xattrs_read_inode(handle,
- (struct ext2_inode_large *)inode);
+ retval = ext2fs_xattrs_read(handle);
if (retval)
goto err;
@@ -1580,6 +1580,7 @@ void e2fsck_pass1(e2fsck_t ctx)
size_t size = 0;
pctx.errcode = get_inline_data_ea_size(fs, ino, inode,
+ inode_size,
&size);
if (!pctx.errcode &&
fix_problem(ctx, PR_1_INLINE_DATA_FEATURE, &pctx)) {
@@ -1603,7 +1604,8 @@ void e2fsck_pass1(e2fsck_t ctx)
flags = fs->flags;
if (failed_csum)
fs->flags |= EXT2_FLAG_IGNORE_CSUM_ERRORS;
- err = get_inline_data_ea_size(fs, ino, inode, &size);
+ err = get_inline_data_ea_size(fs, ino, inode,
+ inode_size, &size);
fs->flags = (flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) |
(fs->flags & ~EXT2_FLAG_IGNORE_CSUM_ERRORS);
diff --git a/lib/ext2fs/ext2fs.h b/lib/ext2fs/ext2fs.h
index c4fcb10be..56de5ea50 100644
--- a/lib/ext2fs/ext2fs.h
+++ b/lib/ext2fs/ext2fs.h
@@ -94,6 +94,8 @@ typedef __s64 __bitwise ext2_off64_t;
typedef __s64 __bitwise e2_blkcnt_t;
typedef __u32 __bitwise ext2_dirhash_t;
+typedef struct quota_ctx *quota_ctx_t;
+
#if EXT2_FLAT_INCLUDES
#include "com_err.h"
#include "ext2_io.h"
@@ -1408,6 +1410,10 @@ errcode_t ext2fs_xattr_set(struct ext2_xattr_handle *handle,
errcode_t ext2fs_xattr_remove(struct ext2_xattr_handle *handle,
const char *key);
errcode_t ext2fs_xattr_remove_all(struct ext2_xattr_handle *handle);
+errcode_t ext2fs_xattrs_open_inode(ext2_filsys fs, ext2_ino_t ino,
+ struct ext2_inode *inode, size_t inode_size,
+ quota_ctx_t qctx,
+ struct ext2_xattr_handle **handle);
errcode_t ext2fs_xattrs_open(ext2_filsys fs, ext2_ino_t ino,
struct ext2_xattr_handle **handle);
errcode_t ext2fs_xattrs_close(struct ext2_xattr_handle **handle);
@@ -1607,6 +1613,7 @@ errcode_t ext2fs_iblk_add_blocks(ext2_filsys fs, struct ext2_inode *inode,
errcode_t ext2fs_iblk_sub_blocks(ext2_filsys fs, struct ext2_inode *inode,
blk64_t num_blocks);
errcode_t ext2fs_iblk_set(ext2_filsys fs, struct ext2_inode *inode, blk64_t b);
+blk64_t ext2fs_iblk_get(ext2_filsys fs, struct ext2_inode *inode);
/* imager.c */
extern errcode_t ext2fs_image_inode_write(ext2_filsys fs, int fd, int flags);
diff --git a/lib/ext2fs/ext_attr.c b/lib/ext2fs/ext_attr.c
index 7723d0f91..3b90b70bb 100644
--- a/lib/ext2fs/ext_attr.c
+++ b/lib/ext2fs/ext_attr.c
@@ -22,6 +22,7 @@
#include "ext2_fs.h"
#include "ext2_ext_attr.h"
#include "ext4_acl.h"
+#include "support/quotaio.h"
#include "ext2fsP.h"
@@ -361,8 +362,14 @@ struct ext2_xattr_handle {
int capacity;
int count;
int ibody_count;
+ struct ext2_inode *in_inode;
+ size_t in_inode_size;
+ struct ext2_inode_large *alloc_inode;
+ struct ext2_inode_large *inode;
+ size_t inode_size;
ext2_ino_t ino;
unsigned int flags;
+ quota_ctx_t qctx;
};
static errcode_t ext2fs_xattrs_expand(struct ext2_xattr_handle *h,
@@ -499,9 +506,11 @@ out:
return err;
}
-static errcode_t prep_ea_block_for_write(ext2_filsys fs, ext2_ino_t ino,
- struct ext2_inode_large *inode)
+static errcode_t prep_ea_block_for_write(ext2_filsys fs,
+ struct ext2_xattr_handle *handle)
{
+ struct ext2_inode_large *inode = handle->inode;
+ ext2_ino_t ino = handle->ino;
struct ext2_ext_attr_header *header;
void *block_buf = NULL;
blk64_t blk, goal;
@@ -541,11 +550,15 @@ static errcode_t prep_ea_block_for_write(ext2_filsys fs, ext2_ino_t ino,
if (err)
goto out2;
} else {
- /* No block, we must increment i_blocks */
+ /* No block, we must increment i_blocks and quota */
err = ext2fs_iblk_add_blocks(fs, (struct ext2_inode *)inode,
1);
if (err)
goto out;
+
+ if (handle->qctx)
+ quota_data_add(handle->qctx, handle->inode, handle->ino,
+ EXT2FS_C2B(fs, 1) * fs->blocksize);
}
/* Allocate a block */
@@ -744,8 +757,7 @@ write_xattrs_to_buffer(ext2_filsys fs, struct ext2_xattr *attrs, int count,
errcode_t ext2fs_xattrs_write(struct ext2_xattr_handle *handle)
{
ext2_filsys fs = handle->fs;
- const unsigned int inode_size = EXT2_INODE_SIZE(fs->super);
- struct ext2_inode_large *inode;
+ struct ext2_inode_large *inode = handle->inode;
char *start, *block_buf = NULL;
struct ext2_ext_attr_header *header;
__u32 ea_inode_magic;
@@ -755,21 +767,12 @@ errcode_t ext2fs_xattrs_write(struct ext2_xattr_handle *handle)
errcode_t err;
EXT2_CHECK_MAGIC(handle, EXT2_ET_MAGIC_EA_HANDLE);
- i = inode_size;
- if (i < sizeof(*inode))
- i = sizeof(*inode);
- err = ext2fs_get_memzero(i, &inode);
- if (err)
- return err;
-
- err = ext2fs_read_inode_full(fs, handle->ino, EXT2_INODE(inode),
- inode_size);
- if (err)
- goto out;
+ if (!inode)
+ return EINVAL;
/* If extra_isize isn't set, we need to set it now */
if (inode->i_extra_isize == 0 &&
- inode_size > EXT2_GOOD_OLD_INODE_SIZE) {
+ handle->inode_size > EXT2_GOOD_OLD_INODE_SIZE) {
char *p = (char *)inode;
size_t extra = fs->super->s_want_extra_isize;
@@ -778,22 +781,20 @@ errcode_t ext2fs_xattrs_write(struct ext2_xattr_handle *handle)
memset(p + EXT2_GOOD_OLD_INODE_SIZE, 0, extra);
inode->i_extra_isize = extra;
}
- if (inode->i_extra_isize & 3) {
- err = EXT2_ET_INODE_CORRUPTED;
- goto out;
- }
+ if (inode->i_extra_isize & 3)
+ return EXT2_ET_INODE_CORRUPTED;
/* Does the inode have space for EA? */
if (inode->i_extra_isize < sizeof(inode->i_extra_isize) ||
- inode_size <= EXT2_GOOD_OLD_INODE_SIZE + inode->i_extra_isize +
- sizeof(__u32))
+ handle->inode_size <= EXT2_GOOD_OLD_INODE_SIZE +
+ inode->i_extra_isize + sizeof(__u32))
goto write_ea_block;
/* Write the inode EA */
ea_inode_magic = EXT2_EXT_ATTR_MAGIC;
memcpy(((char *) inode) + EXT2_GOOD_OLD_INODE_SIZE +
inode->i_extra_isize, &ea_inode_magic, sizeof(__u32));
- storage_size = inode_size - EXT2_GOOD_OLD_INODE_SIZE -
+ storage_size = handle->inode_size - EXT2_GOOD_OLD_INODE_SIZE -
inode->i_extra_isize - sizeof(__u32);
start = ((char *) inode) + EXT2_GOOD_OLD_INODE_SIZE +
inode->i_extra_isize + sizeof(__u32);
@@ -801,17 +802,16 @@ errcode_t ext2fs_xattrs_write(struct ext2_xattr_handle *handle)
err = write_xattrs_to_buffer(fs, handle->attrs, handle->ibody_count,
start, storage_size, 0, 0);
if (err)
- goto out;
+ return err;
write_ea_block:
/* Are we done? */
- if (handle->ibody_count == handle->count &&
- !ext2fs_file_acl_block(fs, EXT2_INODE(inode)))
+ if (handle->ibody_count == handle->count)
goto skip_ea_block;
/* Write the EA block */
err = ext2fs_get_memzero(fs->blocksize, &block_buf);
if (err)
- goto out;
+ return err;
storage_size = fs->blocksize - sizeof(struct ext2_ext_attr_header);
start = block_buf + sizeof(struct ext2_ext_attr_header);
@@ -820,7 +820,7 @@ write_ea_block:
handle->count - handle->ibody_count, start,
storage_size, start - block_buf, 1);
if (err)
- goto out2;
+ goto out;
/* Write a header on the EA block */
header = (struct ext2_ext_attr_header *) block_buf;
@@ -829,15 +829,15 @@ write_ea_block:
header->h_blocks = 1;
/* Get a new block for writing */
- err = prep_ea_block_for_write(fs, handle->ino, inode);
+ err = prep_ea_block_for_write(fs, handle);
if (err)
- goto out2;
+ goto out;
/* Finally, write the new EA block */
blk = ext2fs_file_acl_block(fs, EXT2_INODE(inode));
err = ext2fs_write_ext_attr3(fs, blk, block_buf, handle->ino);
if (err)
- goto out2;
+ goto out;
skip_ea_block:
blk = ext2fs_file_acl_block(fs, (struct ext2_inode *)inode);
@@ -845,19 +845,26 @@ skip_ea_block:
/* xattrs shrunk, free the block */
err = ext2fs_free_ext_attr(fs, handle->ino, inode);
if (err)
- goto out;
+ return err;
+ if (handle->qctx)
+ quota_data_sub(handle->qctx, inode, handle->ino,
+ EXT2FS_C2B(fs, 1) * fs->blocksize);
}
/* Write the inode */
err = ext2fs_write_inode_full(fs, handle->ino, EXT2_INODE(inode),
- inode_size);
+ handle->inode_size);
if (err)
- goto out2;
+ goto out;
+
+ /* Update the caller cached inode if provided */
+ if (handle->in_inode && handle->in_inode != EXT2_INODE(handle->inode))
+ memcpy(handle->in_inode, EXT2_INODE(handle->inode),
+ handle->in_inode_size);
-out2:
- ext2fs_free_mem(&block_buf);
out:
- ext2fs_free_mem(&inode);
+ ext2fs_free_mem(&block_buf);
+
return err;
}
@@ -1130,29 +1137,51 @@ out:
return err;
}
-errcode_t ext2fs_xattrs_read(struct ext2_xattr_handle *handle)
+errcode_t ext2fs_xattrs_read(struct ext2_xattr_handle *h)
{
- struct ext2_inode_large *inode;
- size_t inode_size = EXT2_INODE_SIZE(handle->fs->super);
errcode_t err;
- EXT2_CHECK_MAGIC(handle, EXT2_ET_MAGIC_EA_HANDLE);
+ EXT2_CHECK_MAGIC(h, EXT2_ET_MAGIC_EA_HANDLE);
+
+ h->inode_size = EXT2_INODE_SIZE(h->fs->super);
+ if (h->inode_size < sizeof(*h->inode))
+ h->inode_size = sizeof(*h->inode);
+
+ /* Use the caller cached inode if possible */
+ if (h->in_inode && h->in_inode_size >= h->inode_size) {
+ h->inode = (struct ext2_inode_large *) h->in_inode;
+ goto xattrs_read;
+ }
+
+ /* Flush the caller cached inode if provided */
+ if (h->in_inode) {
+ err = ext2fs_write_inode_full(h->fs, h->ino, h->in_inode,
+ h->in_inode_size);
+ if (err)
+ goto err;
+ }
- if (inode_size < sizeof(*inode))
- inode_size = sizeof(*inode);
- err = ext2fs_get_memzero(inode_size, &inode);
+ err = ext2fs_get_memzero(h->inode_size, &h->alloc_inode);
if (err)
return err;
- err = ext2fs_read_inode_full(handle->fs, handle->ino, EXT2_INODE(inode),
- EXT2_INODE_SIZE(handle->fs->super));
+ err = ext2fs_read_inode_full(h->fs, h->ino, EXT2_INODE(h->alloc_inode),
+ h->inode_size);
if (err)
- goto out;
+ goto err;
- err = ext2fs_xattrs_read_inode(handle, inode);
+ h->inode = h->alloc_inode;
-out:
- ext2fs_free_mem(&inode);
+xattrs_read:
+ err = ext2fs_xattrs_read_inode(h, h->inode);
+ if (err)
+ goto err;
+
+ return 0;
+
+err:
+ h->inode_size = 0;
+ ext2fs_free_mem(&h->alloc_inode);
return err;
}
@@ -1272,12 +1301,15 @@ out:
return err;
}
-static errcode_t xattr_create_ea_inode(ext2_filsys fs, const void *value,
- size_t value_len, ext2_ino_t *ea_ino)
+static errcode_t xattr_create_ea_inode(struct ext2_xattr_handle *handle,
+ const void *value, size_t value_len,
+ ext2_ino_t *ea_ino)
{
+ ext2_filsys fs = handle->fs;
struct ext2_inode inode;
ext2_ino_t ino;
ext2_file_t file;
+ blk64_t iblk;
__u32 hash;
errcode_t ret;
@@ -1317,16 +1349,30 @@ static errcode_t xattr_create_ea_inode(ext2_filsys fs, const void *value,
if (ret)
return ret;
+ ret = ext2fs_read_inode(fs, ino, &inode);
+ if (ret)
+ return ret;
+
ext2fs_inode_alloc_stats2(fs, ino, 1 /* inuse */, 0 /* isdir */);
+ iblk = ext2fs_iblk_get(fs, &inode);
+ ext2fs_iblk_add_blocks(fs, EXT2_INODE(handle->inode), iblk);
+ if (handle->qctx) {
+ quota_data_add(handle->qctx, handle->inode, handle->ino,
+ EXT2FS_C2B(fs, iblk) * fs->blocksize);
+ quota_data_inodes(handle->qctx, handle->inode, handle->ino, +1);
+ }
*ea_ino = ino;
return 0;
}
-static errcode_t xattr_inode_dec_ref(ext2_filsys fs, ext2_ino_t ino)
+static errcode_t xattr_inode_dec_ref(struct ext2_xattr_handle *handle,
+ ext2_ino_t ino)
{
+ ext2_filsys fs = handle->fs;
struct ext2_inode_large inode;
__u64 ref_count;
+ blk64_t iblk;
errcode_t ret;
ret = ext2fs_read_inode_full(fs, ino, (struct ext2_inode *)&inode,
@@ -1338,6 +1384,14 @@ static errcode_t xattr_inode_dec_ref(ext2_filsys fs, ext2_ino_t ino)
ref_count--;
ext2fs_set_ea_inode_ref(EXT2_INODE(&inode), ref_count);
+ iblk = ext2fs_iblk_get(fs, EXT2_INODE(&inode));
+ ext2fs_iblk_sub_blocks(fs, EXT2_INODE(handle->inode), iblk);
+ if (handle->qctx) {
+ quota_data_sub(handle->qctx, handle->inode, handle->ino,
+ EXT2FS_C2B(fs, iblk) * fs->blocksize);
+ quota_data_inodes(handle->qctx, handle->inode, handle->ino, -1);
+ }
+
if (ref_count)
goto write_out;
@@ -1365,7 +1419,8 @@ out:
return ret;
}
-static errcode_t xattr_update_entry(ext2_filsys fs, struct ext2_xattr *x,
+static errcode_t xattr_update_entry(struct ext2_xattr_handle *handle,
+ struct ext2_xattr *x,
const char *name, const char *short_name,
int index, const void *value,
size_t value_len, int in_inode)
@@ -1390,13 +1445,13 @@ static errcode_t xattr_update_entry(ext2_filsys fs, struct ext2_xattr *x,
memcpy(new_value, value, value_len);
if (in_inode) {
- ret = xattr_create_ea_inode(fs, value, value_len, &ea_ino);
+ ret = xattr_create_ea_inode(handle, value, value_len, &ea_ino);
if (ret)
goto fail;
}
if (x->ea_ino) {
- ret = xattr_inode_dec_ref(fs, x->ea_ino);
+ ret = xattr_inode_dec_ref(handle, x->ea_ino);
if (ret)
goto fail;
}
@@ -1419,7 +1474,7 @@ fail:
if (new_value)
ext2fs_free_mem(&new_value);
if (ea_ino)
- xattr_inode_dec_ref(fs, ea_ino);
+ xattr_inode_dec_ref(handle, ea_ino);
return ret;
}
@@ -1486,7 +1541,7 @@ static errcode_t xattr_array_update(struct ext2_xattr_handle *h,
}
/* Update the existing entry. */
- ret = xattr_update_entry(h->fs, &h->attrs[old_idx], name,
+ ret = xattr_update_entry(h, &h->attrs[old_idx], name,
shortname, name_idx, value,
value_len, in_inode);
if (ret)
@@ -1515,7 +1570,7 @@ static errcode_t xattr_array_update(struct ext2_xattr_handle *h,
if (old_idx >= 0) {
/* Update the existing entry. */
- ret = xattr_update_entry(h->fs, &h->attrs[old_idx], name,
+ ret = xattr_update_entry(h, &h->attrs[old_idx], name,
shortname, name_idx, value,
value_len, in_inode);
if (ret)
@@ -1551,7 +1606,7 @@ add_new:
return ret;
}
- ret = xattr_update_entry(h->fs, &h->attrs[h->count], name, shortname,
+ ret = xattr_update_entry(h, &h->attrs[h->count], name, shortname,
name_idx, value, value_len, in_inode);
if (ret)
return ret;
@@ -1594,8 +1649,7 @@ errcode_t ext2fs_xattr_set(struct ext2_xattr_handle *h,
size_t value_len)
{
ext2_filsys fs = h->fs;
- const int inode_size = EXT2_INODE_SIZE(fs->super);
- struct ext2_inode_large *inode = NULL;
+ struct ext2_inode_large *inode = h->inode;
struct ext2_xattr *x;
char *new_value;
int ibody_free, block_free;
@@ -1605,6 +1659,8 @@ errcode_t ext2fs_xattr_set(struct ext2_xattr_handle *h,
errcode_t ret;
EXT2_CHECK_MAGIC(h, EXT2_ET_MAGIC_EA_HANDLE);
+ if (!inode || !h->inode_size)
+ return EINVAL;
ret = ext2fs_get_mem(value_len, &new_value);
if (ret)
@@ -1632,23 +1688,14 @@ errcode_t ext2fs_xattr_set(struct ext2_xattr_handle *h,
break;
}
}
-
- ret = ext2fs_get_memzero(inode_size, &inode);
- if (ret)
- goto out;
- ret = ext2fs_read_inode_full(fs, h->ino,
- (struct ext2_inode *)inode,
- inode_size);
- if (ret)
- goto out;
- if (inode_size > EXT2_GOOD_OLD_INODE_SIZE) {
+ if (h->inode_size > EXT2_GOOD_OLD_INODE_SIZE) {
extra_isize = inode->i_extra_isize;
if (extra_isize == 0) {
extra_isize = fs->super->s_want_extra_isize;
if (extra_isize == 0)
extra_isize = sizeof(__u32);
}
- ibody_free = inode_size - EXT2_GOOD_OLD_INODE_SIZE;
+ ibody_free = h->inode_size - EXT2_GOOD_OLD_INODE_SIZE;
ibody_free -= extra_isize;
/* Extended attribute magic and final null entry. */
ibody_free -= sizeof(__u32) * 2;
@@ -1694,8 +1741,6 @@ errcode_t ext2fs_xattr_set(struct ext2_xattr_handle *h,
write_out:
ret = ext2fs_xattrs_write(h);
out:
- if (inode)
- ext2fs_free_mem(&inode);
ext2fs_free_mem(&new_value);
return ret;
}
@@ -1712,7 +1757,7 @@ errcode_t ext2fs_xattr_remove(struct ext2_xattr_handle *handle,
ext2fs_free_mem(&x->name);
ext2fs_free_mem(&x->value);
if (x->ea_ino)
- xattr_inode_dec_ref(handle->fs, x->ea_ino);
+ xattr_inode_dec_ref(handle, x->ea_ino);
memmove(x, x + 1, (end - x - 1)*sizeof(*x));
memset(end - 1, 0, sizeof(*end));
if (x < handle->attrs + handle->ibody_count)
@@ -1736,7 +1781,7 @@ errcode_t ext2fs_xattr_remove_all(struct ext2_xattr_handle *handle)
ext2fs_free_mem(&x->name);
ext2fs_free_mem(&x->value);
if (x->ea_ino)
- xattr_inode_dec_ref(handle->fs, x->ea_ino);
+ xattr_inode_dec_ref(handle, x->ea_ino);
}
handle->ibody_count = 0;
@@ -1744,8 +1789,14 @@ errcode_t ext2fs_xattr_remove_all(struct ext2_xattr_handle *handle)
return ext2fs_xattrs_write(handle);
}
-errcode_t ext2fs_xattrs_open(ext2_filsys fs, ext2_ino_t ino,
- struct ext2_xattr_handle **handle)
+/* If the inode size is set to EXT2_INODE_SIZE(fs), the input inode is used
+ * directly. Otherwise, the ext2fs_xattrs_* functions operate on a separate copy
+ * (with inline xattrs) and update the caller's cached inode on write.
+ */
+errcode_t ext2fs_xattrs_open_inode(ext2_filsys fs, ext2_ino_t ino,
+ struct ext2_inode *inode, size_t inode_size,
+ quota_ctx_t qctx,
+ struct ext2_xattr_handle **handle)
{
struct ext2_xattr_handle *h;
errcode_t err;
@@ -1754,6 +1805,9 @@ errcode_t ext2fs_xattrs_open(ext2_filsys fs, ext2_ino_t ino,
!ext2fs_has_feature_inline_data(fs->super))
return EXT2_ET_MISSING_EA_FEATURE;
+ if (inode && inode_size < sizeof(*inode))
+ return EINVAL;
+
err = ext2fs_get_memzero(sizeof(*h), &h);
if (err)
return err;
@@ -1768,11 +1822,23 @@ errcode_t ext2fs_xattrs_open(ext2_filsys fs, ext2_ino_t ino,
}
h->count = 0;
h->ino = ino;
+ h->in_inode = inode;
+ h->in_inode_size = inode_size;
+ h->alloc_inode = NULL;
+ h->inode = NULL;
+ h->inode_size = 0;
h->fs = fs;
+ h->qctx = qctx;
*handle = h;
return 0;
}
+errcode_t ext2fs_xattrs_open(ext2_filsys fs, ext2_ino_t ino,
+ struct ext2_xattr_handle **handle)
+{
+ return ext2fs_xattrs_open_inode(fs, ino, NULL, 0, NULL, handle);
+}
+
errcode_t ext2fs_xattrs_close(struct ext2_xattr_handle **handle)
{
struct ext2_xattr_handle *h = *handle;
@@ -1780,6 +1846,7 @@ errcode_t ext2fs_xattrs_close(struct ext2_xattr_handle **handle)
EXT2_CHECK_MAGIC(h, EXT2_ET_MAGIC_EA_HANDLE);
xattrs_free_keys(h);
ext2fs_free_mem(&h->attrs);
+ ext2fs_free_mem(&h->alloc_inode);
ext2fs_free_mem(handle);
return 0;
}
diff --git a/lib/ext2fs/i_block.c b/lib/ext2fs/i_block.c
index 2eecf02fc..064a5c989 100644
--- a/lib/ext2fs/i_block.c
+++ b/lib/ext2fs/i_block.c
@@ -88,3 +88,17 @@ errcode_t ext2fs_iblk_set(ext2_filsys fs, struct ext2_inode *inode, blk64_t b)
return EOVERFLOW;
return 0;
}
+
+blk64_t ext2fs_iblk_get(ext2_filsys fs, struct ext2_inode *inode)
+{
+ blk64_t blk = inode->i_blocks;
+
+ if (ext2fs_has_feature_huge_file(fs->super))
+ blk += ((blk64_t) inode->osd2.linux2.l_i_blocks_hi) << 32;
+
+ if (!ext2fs_has_feature_huge_file(fs->super) ||
+ !(inode->i_flags & EXT4_HUGE_FILE_FL))
+ blk /= fs->blocksize / 512;
+
+ return EXT2FS_B2C(fs, blk);
+}
diff --git a/lib/support/quotaio.h b/lib/support/quotaio.h
index 6152416fb..c76486919 100644
--- a/lib/support/quotaio.h
+++ b/lib/support/quotaio.h
@@ -58,7 +58,6 @@ enum quota_type {
#define QUOTA_PRJ_BIT (1 << PRJQUOTA)
#define QUOTA_ALL_BIT (QUOTA_USR_BIT | QUOTA_GRP_BIT | QUOTA_PRJ_BIT)
-typedef struct quota_ctx *quota_ctx_t;
struct dict_t;
struct quota_ctx {
diff --git a/tests/d_xattr_ea_inode/expect b/tests/d_xattr_ea_inode/expect
new file mode 100644
index 000000000..aaad9c5b3
--- /dev/null
+++ b/tests/d_xattr_ea_inode/expect
@@ -0,0 +1,137 @@
+debugfs edit extended attributes with ea_inode feature
+mke2fs -Fq -b 4096 -O ea_inode test.img 1m
+Exit status is 0
+Generate xattr value (8292 bytes)
+ea_set -f d_xattr_ea_inode.tmp / user.test1
+Exit status is 0
+ea_get -f d_xattr_ea_inode.ver.tmp / user.test1
+Exit status is 0
+Compare xattr values (8292 bytes)
+stat /
+Blockcount: 32
+Exit status is 0
+ea_rm / user.test1
+Exit status is 0
+e2fsck -yf -N test_filesys
+Pass 1: Checking inodes, blocks, and sizes
+Pass 2: Checking directory structure
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+test_filesys: 11/128 files (0.0% non-contiguous), 18/256 blocks
+Exit status is 0
+
+Generate xattr value (4097 bytes)
+ea_set -f d_xattr_ea_inode.tmp / user.test1
+Exit status is 0
+ea_get -f d_xattr_ea_inode.ver.tmp / user.test1
+Exit status is 0
+Compare xattr values (4097 bytes)
+stat /
+Blockcount: 24
+Exit status is 0
+e2fsck -yf -N test_filesys
+Pass 1: Checking inodes, blocks, and sizes
+Pass 2: Checking directory structure
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+test_filesys: 12/128 files (0.0% non-contiguous), 20/256 blocks
+Exit status is 0
+
+Generate xattr value (102 bytes)
+ea_set -f d_xattr_ea_inode.tmp / user.test2
+Exit status is 0
+ea_get -f d_xattr_ea_inode.ver.tmp / user.test2
+Exit status is 0
+Compare xattr values (102 bytes)
+stat /
+Blockcount: 32
+Exit status is 0
+e2fsck -yf -N test_filesys
+Pass 1: Checking inodes, blocks, and sizes
+Pass 2: Checking directory structure
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+test_filesys: 12/128 files (0.0% non-contiguous), 21/256 blocks
+Exit status is 0
+
+Generate xattr value (5005 bytes)
+ea_set -f d_xattr_ea_inode.tmp / user.test2
+Exit status is 0
+ea_get -f d_xattr_ea_inode.ver.tmp / user.test2
+Exit status is 0
+Compare xattr values (5005 bytes)
+stat /
+Blockcount: 40
+Exit status is 0
+ea_rm / user.test2
+Exit status is 0
+e2fsck -yf -N test_filesys
+Pass 1: Checking inodes, blocks, and sizes
+Pass 2: Checking directory structure
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+test_filesys: 12/128 files (0.0% non-contiguous), 20/256 blocks
+Exit status is 0
+
+Generate xattr value (512 bytes)
+ea_set -f d_xattr_ea_inode.tmp / user.test1
+Exit status is 0
+ea_get -f d_xattr_ea_inode.ver.tmp / user.test1
+Exit status is 0
+Compare xattr values (512 bytes)
+stat /
+Blockcount: 16
+Exit status is 0
+ea_rm / user.test1
+Exit status is 0
+e2fsck -yf -N test_filesys
+Pass 1: Checking inodes, blocks, and sizes
+Pass 2: Checking directory structure
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+test_filesys: 11/128 files (0.0% non-contiguous), 18/256 blocks
+Exit status is 0
+
+Generate xattr value (1024 bytes)
+ea_set -f d_xattr_ea_inode.tmp / user.test1
+Exit status is 0
+ea_get -f d_xattr_ea_inode.ver.tmp / user.test1
+Exit status is 0
+Compare xattr values (1024 bytes)
+stat /
+Blockcount: 16
+Exit status is 0
+e2fsck -yf -N test_filesys
+Pass 1: Checking inodes, blocks, and sizes
+Pass 2: Checking directory structure
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+test_filesys: 11/128 files (0.0% non-contiguous), 19/256 blocks
+Exit status is 0
+
+Generate xattr value (5000 bytes)
+ea_set -f d_xattr_ea_inode.tmp / user.test1
+Exit status is 0
+ea_get -f d_xattr_ea_inode.ver.tmp / user.test1
+Exit status is 0
+Compare xattr values (5000 bytes)
+stat /
+Blockcount: 24
+Exit status is 0
+ea_rm / user.test1
+Exit status is 0
+e2fsck -yf -N test_filesys
+Pass 1: Checking inodes, blocks, and sizes
+Pass 2: Checking directory structure
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+test_filesys: 11/128 files (0.0% non-contiguous), 18/256 blocks
+Exit status is 0
+
diff --git a/tests/d_xattr_ea_inode/name b/tests/d_xattr_ea_inode/name
new file mode 100644
index 000000000..9e36dc986
--- /dev/null
+++ b/tests/d_xattr_ea_inode/name
@@ -0,0 +1 @@
+edit extended attributes in debugfs with ea_inode feature
diff --git a/tests/d_xattr_ea_inode/script b/tests/d_xattr_ea_inode/script
new file mode 100644
index 000000000..84104549c
--- /dev/null
+++ b/tests/d_xattr_ea_inode/script
@@ -0,0 +1,85 @@
+#!/bin/bash
+
+if ! test -x $DEBUGFS_EXE; then
+ echo "$test_name: $test_description: skipped (no debugfs)"
+ return 0
+fi
+
+OUT=$test_name.log
+EXP=$test_dir/expect
+VERIFY_FSCK_OPT=-yf
+
+TEST_DATA=$test_name.tmp
+VERIFY_DATA=$test_name.ver.tmp
+
+echo "debugfs edit extended attributes with ea_inode feature" > $OUT.new
+
+d_xattr_ea_inode_check() {
+ local xattr_size=$1
+ local xattr_name=$2
+ local ea_rm=$3
+
+ echo "Generate xattr value ($xattr_size bytes)" >> $OUT.new
+ echo $xattr_size |
+ awk '{srand();for(i=0;i<$1;i++) printf("%c",97+int(rand()*26));}' > $TEST_DATA
+
+ echo "ea_set -f $TEST_DATA / $xattr_name" >> $OUT.new
+ $DEBUGFS -w -R "ea_set -f $TEST_DATA / $xattr_name" $TMPFILE >> $OUT.new 2>&1
+ echo Exit status is $? >> $OUT.new
+
+ echo "ea_get -f $VERIFY_DATA / $xattr_name" >> $OUT.new
+ $DEBUGFS -w -R "ea_get -f $VERIFY_DATA / $xattr_name" $TMPFILE >> $OUT.new 2>&1
+ echo Exit status is $? >> $OUT.new
+
+ echo "Compare xattr values ($xattr_size bytes)" >> $OUT.new
+ diff -u $TEST_DATA $VERIFY_DATA >> $OUT.new
+
+ echo "stat /" >> $OUT.new
+ ($DEBUGFS -c -R "stat /" $TMPFILE | grep -Eo "Blockcount: [0-9]+") >> $OUT.new 2>&1
+ echo Exit status is $? >> $OUT.new
+
+ if $ea_rm; then
+ echo "ea_rm / $xattr_name" >> $OUT.new
+ $DEBUGFS -w -R "ea_rm / $xattr_name" $TMPFILE >> $OUT.new 2>&1
+ echo Exit status is $? >> $OUT.new
+ fi
+
+ echo e2fsck $VERIFY_FSCK_OPT -N test_filesys >> $OUT.new
+ $FSCK $VERIFY_FSCK_OPT -N test_filesys $TMPFILE >> $OUT.new 2>&1
+ echo Exit status is $? >> $OUT.new
+ echo >> $OUT.new
+}
+
+truncate -s1M $TMPFILE 2>&1
+
+echo "mke2fs -Fq -b 4096 -O ea_inode test.img 1m" >> $OUT.new
+$MKE2FS -Fq -b 4096 -O ea_inode $TMPFILE 1m > /dev/null 2>&1
+echo Exit status is $? >> $OUT.new
+
+d_xattr_ea_inode_check 8292 user.test1 true
+
+d_xattr_ea_inode_check 4097 user.test1 false
+d_xattr_ea_inode_check 102 user.test2 false
+d_xattr_ea_inode_check 5005 user.test2 true
+d_xattr_ea_inode_check 512 user.test1 true
+
+d_xattr_ea_inode_check 1024 user.test1 false
+d_xattr_ea_inode_check 5000 user.test1 true
+
+sed -f $cmd_dir/filter.sed $OUT.new > $OUT
+
+#
+# Do the verification
+#
+
+rm -f $TMPFILE $TEST_DATA $VERIFY_DATA $OUT.new
+
+if cmp -s $OUT $EXP; then
+ echo "$test_name: $test_description: ok"
+ touch $test_name.ok
+else
+ echo "$test_name: $test_description: failed"
+ diff $DIFF_OPTS $EXP $OUT > $test_name.failed
+fi
+
+unset VERIFY_FSCK_OPT VERIFY_DATA TEST_DATA OUT EXP d_xattr_ea_inode_check
--
2.43.7
^ permalink raw reply related
* [PATCH RESEND 2/4] libext2fs: add quota to libext2fs
From: Etienne AUJAMES @ 2026-06-19 15:32 UTC (permalink / raw)
To: linux-ext4, Theodore Ts'o; +Cc: Andreas Dilger, Li Dongyang
In-Reply-To: <ajVdnQUu9tSrKldW@eaujamesFR0130>
add quota related interface to libext2fs and install the
relevant headers.
Change-Id: I17e6b5aa74e0f1bb1465168a1cf4e03184e003b0
Lustre-bug-id: https://jira.whamcloud.com/browse/LU-13241
Signed-off-by: Li Dongyang <dongyangli@ddn.com>
Signed-off-by: Etienne AUJAMES <eaujames@ddn.com>
---
lib/ext2fs/Makefile.in | 43 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 43 insertions(+)
diff --git a/lib/ext2fs/Makefile.in b/lib/ext2fs/Makefile.in
index e9a6ced24..0656c4c5c 100644
--- a/lib/ext2fs/Makefile.in
+++ b/lib/ext2fs/Makefile.in
@@ -28,6 +28,8 @@ DEBUG_OBJS= debug_cmds.o extent_cmds.o tst_cmds.o debugfs.o util.o \
create_inode_libarchive.o journal.o revoke.o recovery.o \
do_journal.o do_orphan.o
+QUOTA_LIB_OBJS= mkquota.o quotaio.o quotaio_v2.o quotaio_tree.o dict.o
+
DEBUG_SRCS= debug_cmds.c extent_cmds.c tst_cmds.c \
$(top_srcdir)/debugfs/debugfs.c \
$(top_srcdir)/debugfs/util.c \
@@ -57,6 +59,7 @@ DEBUG_SRCS= debug_cmds.c extent_cmds.c tst_cmds.c \
@TDB_CMT@TDB_OBJ= tdb.o
OBJS= $(DEBUGFS_LIB_OBJS) $(RESIZE_LIB_OBJS) $(E2IMAGE_LIB_OBJS) \
+ $(QUOTA_LIB_OBJS) \
$(TEST_IO_LIB_OBJS) \
ext2_err.o \
alloc.o \
@@ -236,6 +239,7 @@ SRCS= ext2_err.c \
HFILES= bitops.h ext2fs.h ext2_io.h ext2_fs.h ext2_ext_attr.h ext3_extents.h \
tdb.h qcow2.h hashmap.h
+QUOTA_HFILES= quotaio.h dqblk_v2.h quotaio_tree.h dict.h
HFILES_IN= ext2_err.h ext2_types.h
LIBRARY= libext2fs
@@ -459,6 +463,41 @@ do_orphan.o: $(top_srcdir)/debugfs/do_orphan.c
$(E) " CC $<"
$(Q) $(CC) $(DEBUGFS_CFLAGS) -c $< -o $@
+mkquota.o: $(top_srcdir)/lib/support/mkquota.c
+ $(E) " CC $<"
+ $(Q) $(CC) -I$(top_srcdir)/lib/support $(ALL_CFLAGS_STLIB) -c $< -o $@
+@PROFILE_CMT@ $(Q) $(CC) -I$(top_srcdir)/lib/support $(ALL_CFLAGS_STLIB) -g -pg -o profiled/$*.o -c $<
+@ELF_CMT@ $(Q) $(CC) -I$(top_srcdir)/lib/support $(ALL_CFLAGS_SHLIB) -fPIC -shared -o elfshared/$*.o -c $<
+@BSDLIB_CMT@ $(Q) $(CC) -I$(top_srcdir)/lib/support $(ALL_CFLAGS_SHLIB) $(BSDLIB_PIC_FLAG) -o pic/$*.o -c $<
+
+quotaio.o: $(top_srcdir)/lib/support/quotaio.c
+ $(E) " CC $<"
+ $(Q) $(CC) -I$(top_srcdir)/lib/support $(ALL_CFLAGS_STLIB) -c $< -o $@
+@PROFILE_CMT@ $(Q) $(CC) -I$(top_srcdir)/lib/support $(ALL_CFLAGS_STLIB) -g -pg -o profiled/$*.o -c $<
+@ELF_CMT@ $(Q) $(CC) -I$(top_srcdir)/lib/support $(ALL_CFLAGS_SHLIB) -fPIC -shared -o elfshared/$*.o -c $<
+@BSDLIB_CMT@ $(Q) $(CC) -I$(top_srcdir)/lib/support $(ALL_CFLAGS_SHLIB) $(BSDLIB_PIC_FLAG) -o pic/$*.o -c $<
+
+quotaio_v2.o: $(top_srcdir)/lib/support/quotaio_v2.c
+ $(E) " CC $<"
+ $(Q) $(CC) -I$(top_srcdir)/lib/support $(ALL_CFLAGS_STLIB) -c $< -o $@
+@PROFILE_CMT@ $(Q) $(CC) -I$(top_srcdir)/lib/support $(ALL_CFLAGS_STLIB) -g -pg -o profiled/$*.o -c $<
+@ELF_CMT@ $(Q) $(CC) -I$(top_srcdir)/lib/support $(ALL_CFLAGS_SHLIB) -fPIC -shared -o elfshared/$*.o -c $<
+@BSDLIB_CMT@ $(Q) $(CC) -I$(top_srcdir)/lib/support $(ALL_CFLAGS_SHLIB) $(BSDLIB_PIC_FLAG) -o pic/$*.o -c $<
+
+quotaio_tree.o: $(top_srcdir)/lib/support/quotaio_tree.c
+ $(E) " CC $<"
+ $(Q) $(CC) -I$(top_srcdir)/lib/support $(ALL_CFLAGS_STLIB) -c $< -o $@
+@PROFILE_CMT@ $(Q) $(CC) -I$(top_srcdir)/lib/support $(ALL_CFLAGS_STLIB) -g -pg -o profiled/$*.o -c $<
+@ELF_CMT@ $(Q) $(CC) -I$(top_srcdir)/lib/support $(ALL_CFLAGS_SHLIB) -fPIC -shared -o elfshared/$*.o -c $<
+@BSDLIB_CMT@ $(Q) $(CC) -I$(top_srcdir)/lib/support $(ALL_CFLAGS_SHLIB) $(BSDLIB_PIC_FLAG) -o pic/$*.o -c $<
+
+dict.o: $(top_srcdir)/lib/support/dict.c
+ $(E) " CC $<"
+ $(Q) $(CC) -I$(top_srcdir)/lib/support $(ALL_CFLAGS_STLIB) -c $< -o $@
+@PROFILE_CMT@ $(Q) $(CC) -I$(top_srcdir)/lib/support $(ALL_CFLAGS_STLIB) -g -pg -o profiled/$*.o -c $<
+@ELF_CMT@ $(Q) $(CC) -I$(top_srcdir)/lib/support $(ALL_CFLAGS_SHLIB) -fPIC -shared -o elfshared/$*.o -c $<
+@BSDLIB_CMT@ $(Q) $(CC) -I$(top_srcdir)/lib/support $(ALL_CFLAGS_SHLIB) $(BSDLIB_PIC_FLAG) -o pic/$*.o -c $<
+
xattrs.o: $(top_srcdir)/debugfs/xattrs.c
$(E) " CC $<"
$(Q) $(CC) $(DEBUGFS_CFLAGS) -c $< -o $@
@@ -586,6 +625,10 @@ install:: all $(HFILES) $(HFILES_IN) installdirs ext2fs.pc
echo " INSTALL_DATA $(includedir)/ext2fs/$$i"; \
$(INSTALL_DATA) $(srcdir)/$$i $(DESTDIR)$(includedir)/ext2fs/$$i; \
done
+ $(Q) for i in $(QUOTA_HFILES); do \
+ echo " INSTALL_DATA $(includedir)/ext2fs/$$i"; \
+ $(INSTALL_DATA) $(top_srcdir)/lib/support/$$i $(DESTDIR)$(includedir)/ext2fs/$$i; \
+ done
$(Q) for i in $(HFILES_IN); do \
echo " INSTALL_DATA $(includedir)/ext2fs/$$i"; \
$(INSTALL_DATA) $$i $(DESTDIR)$(includedir)/ext2fs/$$i; \
--
2.43.7
^ permalink raw reply related
* Re: [PATCH v10 03/22] ovl: use core fsverity ensure info interface
From: Eric Biggers @ 2026-06-19 16:54 UTC (permalink / raw)
To: Amir Goldstein
Cc: Andrey Albershteyn, linux-xfs, fsverity, linux-fsdevel, hch,
linux-ext4, linux-f2fs-devel, linux-btrfs, linux-unionfs, djwong
In-Reply-To: <CAOQ4uxh_hfiSwMw8ABhhrz7GguZWjHEiBmvb3eq16Wfqw0+ZrQ@mail.gmail.com>
On Fri, Jun 19, 2026 at 09:28:31AM +0200, Amir Goldstein wrote:
> On Wed, May 20, 2026 at 9:07 PM Eric Biggers <ebiggers@kernel.org> wrote:
> >
> > On Wed, May 20, 2026 at 02:37:01PM +0200, Andrey Albershteyn wrote:
> > > fsverity now exposes fsverity_ensure_verity_info() which could be used
> > > instead of opening file to ensure that fsverity info is loaded and
> > > attached to inode.
> > >
> > > Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
> > > Acked-by: Amir Goldstein <amir73il@gmail.com>
> > > ---
> > > fs/overlayfs/util.c | 14 +++-----------
> > > 1 file changed, 3 insertions(+), 11 deletions(-)
> >
> > Reviewed-by: Eric Biggers <ebiggers@kernel.org>
> >
> > I'm still confused by the new implementation of fsverity_active() that
> > got introduced by "fsverity: use a hashtable to find the fsverity_info",
> > though. I should have caught this during review of that commit. For
> > one its comment is outdated, but also the memory barrier seems to be
> > specific to the fsverity_get_info() caller and probably should be moved
> > to there. Anyway, that's not directly related to this patch.
>
> Eric, Andrey,
>
> Did you see the Sashiko review for this patch and others in this series?
>
> https://sashiko.dev/#/patchset/20260520123722.405752-1-aalbersh%40kernel.org
>
> It annotated some review comments as high and critical.
> For this patch it is about interaction with fscrypt.
>
> Please take a look and say if this is concerning or false positive.
Yes, this patch is broken and should be dropped. I need to remember to
look at the Sashiko reviews for other people's patches and not just
trust that the submitter will. Fortunately this one wasn't applied yet.
I pointed out the HIGHMEM performance bug in
"fsverity: generate and store zero-block hash" earlier
(https://lore.kernel.org/linux-fsdevel/20260401222717.GH2466@quark/). I
assume it was decided that no one will care about the combination of XFS
&& fsverity && HIGHMEM. But the XFS folks should double-check that.
Andrey, could you check the Sashiko reviews for the other patches too?
- Eric
^ permalink raw reply
* [PATCH RESEND 0/4] e2fsck: Fix orphan inodes processing
From: Etienne AUJAMES @ 2026-06-19 15:17 UTC (permalink / raw)
To: linux-ext4, Theodore Ts'o; +Cc: Andreas Dilger, Li Dongyang
e2fsck does not handle properly orphan inodes.
Case 1: bad free_blocks accounting with extent files
# e2fsck -v /tmp/ext4
e2fsck 1.47.3-wc2 (11-Nov-2025)
Truncating orphaned inode 12 (uid=0, gid=0, mode=0100644, size=4096)
Setting free blocks count to 2554682 (was 2554683)
/tmp/ext4: clean, 13/655360 files, 66758/2621440 blocks
# e2fsck -yf /tmp/ext4
e2fsck 1.47.3-wc2 (11-Nov-2025)
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
Free blocks count wrong (2554682, counted=2554683).
Fix<y>? yes
Case 2: e2fsck does not support orphan inodes with ea_inode
# e2fsck -yf /tmp/ext4
e2fsck 1.47.3-wc2 (11-Nov-2025)
Clearing orphaned inode 12 (uid=0, gid=0, mode=0100644, size=0)
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Regular filesystem inode 13 has EA_INODE flag set. Clear<y>? yes
Unattached inode 13
Connect to /lost+found<y>? yes
Inode 13 ref count is 2, should be 1. Fix<y>? yes
Pass 5: Checking group summary information
Patch 1 fixes the first case.
Patch 2 includes quota function in libext2fs (required by patch 2).
Patch 3 fixes ext2fs_xattrs_* function to update inode iblk and quota.
Patch 4 fixes the second case.
Bugs tracked by: https://jira.whamcloud.com/browse/LU-20049
Etienne AUJAMES (3):
e2fsck: fix orphaned extent files handling
libext2fs: update iblock when using ea_inode feature
libext2fs: add ext2fs_xattrs_release_all() helper
Li Dongyang (1):
libext2fs: add quota to libext2fs
debugfs/debugfs.c | 33 +-
debugfs/xattrs.c | 19 +-
e2fsck/pass1.c | 12 +-
e2fsck/super.c | 295 +++++++++---------
lib/ext2fs/Makefile.in | 43 +++
lib/ext2fs/ext2fs.h | 10 +
lib/ext2fs/ext_attr.c | 268 +++++++++++-----
lib/ext2fs/i_block.c | 14 +
lib/support/quotaio.h | 1 -
misc/create_inode_libarchive.c | 35 ++-
misc/fuse2fs.c | 117 +++----
tests/d_xattr_ea_inode/expect | 188 +++++++++++
tests/d_xattr_ea_inode/name | 1 +
tests/d_xattr_ea_inode/script | 104 ++++++
tests/f_orphan_ea_inode/expect.1 | 6 +
tests/f_orphan_ea_inode/expect.2 | 7 +
tests/f_orphan_ea_inode/image.gz | Bin 0 -> 2139 bytes
tests/f_orphan_ea_inode/name | 1 +
tests/f_orphan_ea_inode/script | 3 +
.../f_orphan_truncate_extents_inode/expect.1 | 3 +
.../f_orphan_truncate_extents_inode/expect.2 | 7 +
.../f_orphan_truncate_extents_inode/image.gz | Bin 0 -> 2854 bytes
tests/f_orphan_truncate_extents_inode/name | 1 +
tests/f_orphan_truncate_extents_inode/script | 3 +
24 files changed, 842 insertions(+), 329 deletions(-)
create mode 100644 tests/d_xattr_ea_inode/expect
create mode 100644 tests/d_xattr_ea_inode/name
create mode 100644 tests/d_xattr_ea_inode/script
create mode 100644 tests/f_orphan_ea_inode/expect.1
create mode 100644 tests/f_orphan_ea_inode/expect.2
create mode 100644 tests/f_orphan_ea_inode/image.gz
create mode 100644 tests/f_orphan_ea_inode/name
create mode 100644 tests/f_orphan_ea_inode/script
create mode 100644 tests/f_orphan_truncate_extents_inode/expect.1
create mode 100644 tests/f_orphan_truncate_extents_inode/expect.2
create mode 100644 tests/f_orphan_truncate_extents_inode/image.gz
create mode 100644 tests/f_orphan_truncate_extents_inode/name
create mode 100644 tests/f_orphan_truncate_extents_inode/script
--
2.43.7
^ permalink raw reply
* [PATCH RESEND 1/4] e2fsck: fix orphaned extent files handling
From: Etienne AUJAMES @ 2026-06-19 15:24 UTC (permalink / raw)
To: linux-ext4, Theodore Ts'o; +Cc: Andreas Dilger, Li Dongyang
In-Reply-To: <ajVdnQUu9tSrKldW@eaujamesFR0130>
release_inode_blocks() does not handle corectly multi-levels extent
files: it does not count the non-leaf blocks directly released by
ext2fs_block_iterate3().
This patch relies on ext2fs_get_stat_i_blocks() count for quota update
and ext2fs_free_blocks_count() to count number of blocks released by
release_inode_blocks().
Add regression test: f_orphan_truncate_extents_inode
Signed-off-by: Etienne AUJAMES <eaujames@ddn.com>
Change-Id: Ib0c3aaaa685e7bcfae896617cda03005d19539ff
Lustre-bug-id: https://jira.whamcloud.com/browse/LU-20049
---
e2fsck/super.c | 244 +++++++++---------
.../f_orphan_truncate_extents_inode/expect.1 | 3 +
.../f_orphan_truncate_extents_inode/expect.2 | 7 +
.../f_orphan_truncate_extents_inode/image.gz | Bin 0 -> 2854 bytes
tests/f_orphan_truncate_extents_inode/name | 1 +
tests/f_orphan_truncate_extents_inode/script | 3 +
6 files changed, 133 insertions(+), 125 deletions(-)
create mode 100644 tests/f_orphan_truncate_extents_inode/expect.1
create mode 100644 tests/f_orphan_truncate_extents_inode/expect.2
create mode 100644 tests/f_orphan_truncate_extents_inode/image.gz
create mode 100644 tests/f_orphan_truncate_extents_inode/name
create mode 100644 tests/f_orphan_truncate_extents_inode/script
diff --git a/e2fsck/super.c b/e2fsck/super.c
index cfc0919a2..c2ccefd54 100644
--- a/e2fsck/super.c
+++ b/e2fsck/super.c
@@ -62,21 +62,14 @@ static int check_super_value64(e2fsck_t ctx, const char *descr,
return 1;
}
-/*
- * helper function to release an inode
- */
struct process_block_struct {
- e2fsck_t ctx;
- char *buf;
+ e2fsck_t ctx;
+ char *buf;
struct problem_context *pctx;
- int truncating;
- int truncate_offset;
e2_blkcnt_t truncate_block;
- int truncated_blocks;
- int abort;
+ e2_blkcnt_t truncated_blocks;
errcode_t errcode;
blk64_t last_cluster;
- struct ext2_inode_large *inode;
};
static int release_inode_block(ext2_filsys fs,
@@ -91,7 +84,6 @@ static int release_inode_block(ext2_filsys fs,
struct problem_context *pctx;
blk64_t blk = *block_nr;
blk64_t cluster = EXT2FS_B2C(fs, *block_nr);
- int retval = 0;
pb = (struct process_block_struct *) priv_data;
ctx = pb->ctx;
@@ -111,155 +103,157 @@ static int release_inode_block(ext2_filsys fs,
if ((blk < fs->super->s_first_data_block) ||
(blk >= ext2fs_blocks_count(fs->super))) {
fix_problem(ctx, PR_0_ORPHAN_ILLEGAL_BLOCK_NUM, pctx);
- return_abort:
- pb->abort = 1;
+ pb->errcode = EXT2_ET_BAD_BLOCK_NUM;
return BLOCK_ABORT;
}
if (!ext2fs_test_block_bitmap2(fs->block_map, blk)) {
fix_problem(ctx, PR_0_ORPHAN_ALREADY_CLEARED_BLOCK, pctx);
- goto return_abort;
+ pb->errcode = EXT2_ET_BAD_BLOCK_NUM;
+ return BLOCK_ABORT;
}
/*
- * If we are deleting an orphan, then we leave the fields alone.
- * If we are truncating an orphan, then update the inode fields
- * and clean up any partial block data.
+ * We don't remove direct blocks until we've reached
+ * the truncation block.
*/
- if (pb->truncating) {
- /*
- * We only remove indirect blocks if they are
- * completely empty.
- */
- if (blockcnt < 0) {
- int i, limit;
- blk_t *bp;
-
- pb->errcode = io_channel_read_blk64(fs->io, blk, 1,
- pb->buf);
- if (pb->errcode)
- goto return_abort;
-
- limit = fs->blocksize >> 2;
- for (i = 0, bp = (blk_t *) pb->buf;
- i < limit; i++, bp++)
- if (*bp)
- return 0;
- }
- /*
- * We don't remove direct blocks until we've reached
- * the truncation block.
- */
- if (blockcnt >= 0 && blockcnt < pb->truncate_block)
- return 0;
- /*
- * If part of the last block needs truncating, we do
- * it here.
- */
- if ((blockcnt == pb->truncate_block) && pb->truncate_offset) {
- pb->errcode = io_channel_read_blk64(fs->io, blk, 1,
- pb->buf);
- if (pb->errcode)
- goto return_abort;
- memset(pb->buf + pb->truncate_offset, 0,
- fs->blocksize - pb->truncate_offset);
- pb->errcode = io_channel_write_blk64(fs->io, blk, 1,
- pb->buf);
- if (pb->errcode)
- goto return_abort;
- }
- pb->truncated_blocks++;
- *block_nr = 0;
- retval |= BLOCK_CHANGED;
+ if (blockcnt >= 0 && blockcnt < pb->truncate_block)
+ return 0;
+
+ /*
+ * We only remove indirect blocks if they are
+ * completely empty.
+ */
+ if (blockcnt < 0) {
+ int i, limit;
+ blk_t *bp;
+
+ pb->errcode = io_channel_read_blk64(fs->io, blk, 1,
+ pb->buf);
+ if (pb->errcode)
+ return BLOCK_ABORT;
+
+ limit = fs->blocksize >> 2;
+ for (i = 0, bp = (blk_t *) pb->buf;
+ i < limit; i++, bp++)
+ if (*bp)
+ return 0;
}
- if (ctx->qctx)
- quota_data_sub(ctx->qctx, pb->inode, 0, ctx->fs->blocksize);
ext2fs_block_alloc_stats2(fs, blk, -1);
- ctx->free_blocks++;
- return retval;
+ pb->truncated_blocks++;
+ *block_nr = 0;
+
+ return BLOCK_CHANGED;
}
-/*
- * This function releases an inode. Returns 1 if an inconsistency was
- * found. If the inode has a link count, then it is being truncated and
- * not deleted.
- */
-static int release_inode_blocks(e2fsck_t ctx, ext2_ino_t ino,
- struct ext2_inode_large *inode, char *block_buf,
- struct problem_context *pctx)
+static errcode_t truncate_inode_blocks(e2fsck_t ctx, ext2_ino_t ino,
+ struct ext2_inode_large *inode,
+ char *block_buf,
+ struct problem_context *pctx)
{
- struct process_block_struct pb;
- ext2_filsys fs = ctx->fs;
- blk64_t blk;
- errcode_t retval;
- __u32 count;
+ ext2_filsys fs = ctx->fs;
+ struct process_block_struct pb = { 0 };
+ e2_blkcnt_t truncate_block = 0;
+ __u32 truncate_offset = 0;
+ blk64_t blk;
+ int ret_flags;
+ errcode_t retval = 0;
if (!ext2fs_inode_has_valid_blocks2(fs, EXT2_INODE(inode)))
- goto release_acl;
+ return 0;
- pb.buf = block_buf + 3 * ctx->fs->blocksize;
- pb.ctx = ctx;
- pb.abort = 0;
- pb.errcode = 0;
- pb.pctx = pctx;
- pb.last_cluster = 0;
- pb.inode = inode;
if (inode->i_links_count) {
- pb.truncating = 1;
- pb.truncate_block = (e2_blkcnt_t)
+ truncate_offset = inode->i_size % fs->blocksize;
+ truncate_block = (e2_blkcnt_t)
((EXT2_I_SIZE(inode) + fs->blocksize - 1) /
fs->blocksize);
- pb.truncate_offset = inode->i_size % fs->blocksize;
- } else {
- pb.truncating = 0;
- pb.truncate_block = 0;
- pb.truncate_offset = 0;
}
- pb.truncated_blocks = 0;
+
+ pb.buf = block_buf;
+ pb.ctx = ctx;
+ pb.pctx = pctx;
+ pb.truncate_block = truncate_block;
retval = ext2fs_block_iterate3(fs, ino, BLOCK_FLAG_DEPTH_TRAVERSE,
block_buf, release_inode_block, &pb);
if (retval) {
com_err("release_inode_blocks", retval,
_("while calling ext2fs_block_iterate for inode %u"),
ino);
- return 1;
+ return retval;
}
- if (pb.abort)
- return 1;
+ if (pb.errcode)
+ return pb.errcode;
/* Refresh the inode since ext2fs_block_iterate may have changed it */
e2fsck_read_inode_full(ctx, ino, EXT2_INODE(inode), sizeof(*inode),
"release_inode_blocks");
- if (pb.truncated_blocks)
- ext2fs_iblk_sub_blocks(fs, EXT2_INODE(inode),
- pb.truncated_blocks);
-release_acl:
- blk = ext2fs_file_acl_block(fs, EXT2_INODE(inode));
- if (blk) {
- retval = ext2fs_adjust_ea_refcount3(fs, blk, block_buf, -1,
- &count, ino);
- if (retval == EXT2_ET_BAD_EA_BLOCK_NUM) {
- retval = 0;
- count = 1;
- }
- if (retval) {
- com_err("release_inode_blocks", retval,
- _("while calling ext2fs_adjust_ea_refcount2 for inode %u"),
- ino);
- return 1;
- }
- if (count == 0) {
- if (ctx->qctx)
- quota_data_sub(ctx->qctx, inode, 0,
- ctx->fs->blocksize);
- ext2fs_block_alloc_stats2(fs, blk, -1);
- ctx->free_blocks++;
- }
- ext2fs_file_acl_block_set(fs, EXT2_INODE(inode), 0);
+ ext2fs_iblk_sub_blocks(fs, EXT2_INODE(inode), pb.truncated_blocks);
+ if (!truncate_offset)
+ return 0;
+
+ /* Is there an initialized block at the end? */
+ retval = ext2fs_bmap2(fs, ino, NULL, NULL, 0,
+ truncate_block, &ret_flags, &blk);
+ if (retval)
+ return retval;
+ if ((blk == 0) || (ret_flags & BMAP_RET_UNINIT))
+ return 0;
+
+ retval = io_channel_read_blk64(fs->io, blk, 1, block_buf);
+ if (retval)
+ return retval;
+
+ memset(block_buf + truncate_offset, 0, fs->blocksize - truncate_offset);
+ retval = io_channel_write_blk64(fs->io, blk, 1, block_buf);
+
+ return retval;
+}
+
+/*
+ * This function releases an inode. Returns 1 if an inconsistency was
+ * found. If the inode has a link count, then it is being truncated and
+ * not deleted.
+ */
+static int release_inode_blocks(e2fsck_t ctx, ext2_ino_t ino,
+ struct ext2_inode_large *inode, char *block_buf,
+ struct problem_context *pctx)
+{
+ ext2_filsys fs = ctx->fs;
+ blk64_t free_blks, ino_blks;
+ char *buf;
+ errcode_t err;
+ int rc = 0;
+
+ free_blks = ext2fs_free_blocks_count(fs->super);
+ ino_blks = ext2fs_get_stat_i_blocks(fs, EXT2_INODE(inode));
+ buf = block_buf + 3 * ctx->fs->blocksize;
+ if (truncate_inode_blocks(ctx, ino, inode, buf, pctx)) {
+ rc = 1;
+ goto update_counts;
}
- return 0;
+ if (inode->i_links_count)
+ goto update_counts;
+
+ err = ext2fs_free_ext_attr(fs, ino, inode);
+ if (err) {
+ com_err(__func__, err,
+ _("while calling ext2fs_free_ext_attr for inode %u"),
+ ino);
+ rc = 1;
+ goto update_counts;
+ }
+
+ rc = 0;
+
+update_counts:
+ ctx->free_blocks += ext2fs_free_blocks_count(fs->super) - free_blks;
+ ino_blks -= ext2fs_get_stat_i_blocks(fs, EXT2_INODE(inode));
+ if (ctx->qctx)
+ quota_data_sub(ctx->qctx, inode, 0, ino_blks << 9);
+
+ return rc;
}
/* Load all quota data in preparation for orphan clearing. */
diff --git a/tests/f_orphan_truncate_extents_inode/expect.1 b/tests/f_orphan_truncate_extents_inode/expect.1
new file mode 100644
index 000000000..b24aae7ad
--- /dev/null
+++ b/tests/f_orphan_truncate_extents_inode/expect.1
@@ -0,0 +1,3 @@
+test_filesys: Truncating orphaned inode 12 (uid=0, gid=0, mode=0100644, size=4096)
+test_filesys: clean, 12/128 files, 75/1024 blocks
+Exit status is 0
diff --git a/tests/f_orphan_truncate_extents_inode/expect.2 b/tests/f_orphan_truncate_extents_inode/expect.2
new file mode 100644
index 000000000..7edff9bce
--- /dev/null
+++ b/tests/f_orphan_truncate_extents_inode/expect.2
@@ -0,0 +1,7 @@
+Pass 1: Checking inodes, blocks, and sizes
+Pass 2: Checking directory structure
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+test_filesys: 12/128 files (16.7% non-contiguous), 75/1024 blocks
+Exit status is 0
diff --git a/tests/f_orphan_truncate_extents_inode/image.gz b/tests/f_orphan_truncate_extents_inode/image.gz
new file mode 100644
index 0000000000000000000000000000000000000000..30681b879455b936e05d4dcbb4feaed6c4ff1eb9
GIT binary patch
literal 2854
zcmeHI{Z|ub7RHY)J?j?rU>6Hbay;x&EDLO;6|5w)E)secX|xtmD~TnV2mvC7HJUid
zQceX863bCRLDH29M-);L`3R7JUqnJ68v*MeBqBnh8Iq!8LP#>R!|wj*U*PHdF!S7d
zp7*))-g)mcB<cI_uW5H?EnSwC`z_~iz|44l$zvCxXV$_uLK&f*VL@Hf4~zB?`7d(G
z9Z{!g&3Sg&a>DO7t*O4TcjJk~J3vBKc=8_z<Sx_ZjK+W5OQMCD&zoA-ZaHK-k{JHC
zZM3Gff_sHcoAcz(H>U;-V<l(x4%b;(x0@n(<rf>BqHdjt*wL`GuHnw>6mHu1+Nyoz
zW#pC>z>l3<rY6NLyqxh`dRgPmwBTT=F8iEJ6rWe+`*)uuF5F_14mkHWWP@|oHuvqk
zFtsZg@btzm3uh`mVERrPNbQ_SpU6QtArK8AfQ6rCaPSmduP5Cr`@p3+K0lm-sT=o1
z<%}^hZj+oB9rI3o${4Y@Q+VB9n~x6jB~<Rlj7xR8Q=6t^&$)n|E!E7`0sp&trZS&*
zN$6dy6=sCTJUS6JNg!<9@XwVyQqmY<>0I@?)`7e%^~C9E?mTr@Do-uCS66sakURfa
zW69B;b{JjAXwR$EWrD^|QH!GH=*g;l(*nlPsL{fW*GAR_*hxR5OWtxT?7yS4mTGsR
z-Qz{f4kh`)haPQK(8^OkjS!YrD%XD=EexyN<QpQ@MPt2z+0Hrd@>&xA*JqnXp0Db;
z@R>W?h~4>m$^%q(oWgL9Y<7tSE54y~V@noy^{m!D0kZ7tIiJk9Exa3{G2eaS_1|D_
zuJ_aksd10YC-MpwE{|3*BGdA!W{E!kIQd@69%FRuNuW#a1==a%mmR}?CfC>H9(;l|
zG-HiC2x}U<q^`@RR(!qis=;$A|1!j5VKLN|K(&`C=|_#~gBWntYZ0CS7yjY|c7HSJ
zbLQr1hCLQfGzKfSU=xYCw-eBpnqimXdOoNaWMXFDytSd){e0FKs758Nn*1LW`bjPK
z*!pf1ygqYRCz)EndT+j1c=LU1VzVZeXRq3mCRJ`Ag=K{BLC{=-wPDLR^scY!65ji>
zT=#;t?f)P2t&e9#>EbU%9;j(3v#DewfZX;C{}M?EUF5dkN(EFu?JuVcKk`wZO}ZLJ
zt0*z8_n@lhh#%@MBle-mb4L(srYJ(h9TR`*l{Qmq{7>8chmh`TbWV)yBO@c2pZD4s
zB|o5no@S1!NF^@~X2Q@#{}($1NHCcK1+s)Hx%6efn9BYZIa7(k>8w8<mY>^qDMOJq
zqKC_DEs&}<j%_?=E(ck22a)e=1V~ydIIJik>KP<WoQOYn6ah_Mm4GHY1gxQ4@$a4c
z7hHc$#s&yrg7@fLAIvh<tLRBGjx0&uFQ#;Cf7#kjA60Q^0=DstL(d?NU)8Md*dW?n
zz(ZPXS~&1p2-{iaFpB>q+=T?2`jsnb3WGRWn2WqIg{kP<d3`7g)*VDvn6@ghP)(#r
zG`ACZRH=b(W}a5X-G`0l(AaWNVmN{nk&Bh?`yUUOL-+~azZ&{r4(m$}X`@wwNT+;8
zvAP-?x&qc!V^A<2)~t5#N=I(l95A<5q_ifx*_H!U2e7tkCI))6Pba|@Drc#~$RKJW
zM5%L1IQ1~)5HHfc&ReJ?Dg_m;^ZqaPt%T?oT<5``ZxzE<z3`z}i-TaC*S-I7A_Cz&
zd%i`+7F<vy<4u*ZjZsXQvU?S{$=i>&h$kh=6M{@uWd*Hb!{0-#$+%n?u3}zX?8jAr
zy*Q}BRooZxB0u8VoPOa$>Q{JHx>)1@4))@U7PB=G_H~_&IHe5db8tST9uL%0!t)eB
z81Jn+MtR8C*%S!1RoJ&7S53vric2^+Ynz0)de_0%YZs$w+bry)$@@|9$-51W5Kx+D
zM6(JwNX)fPrM%QJh^7|Mkyw)kl9V|5(G<^{t(JD0_VGmi(zhP--;(ce2YNa`^Bc;u
z`+mo3>m6G?M1`W#MlOTkj`ST-(;byJS+B@#_jLtUosXJl>q8euJ;ek<5-Fq7f7OP<
z&ZHPUx(%PI2joaq`u$r243dg0;u|i(-puz@f?oKcID(yyu*iuJ{Q*26{+u1}J!(K<
z7C9WM&!nkznL&rUiTqDHqk<mI!k0}ORMzeC!I}_C4Y+$w4S&NOC>nd>j&GfTBK6=8
z8fr(Rh`$Ae+)3_3_)rgsBRXQd&9?6$dXk$15Hu0EZz*x#io|_OF`x}^4O3P09#26U
zo&>RZB{OAkWApe$P?A%uB$dvXVM;S$&>ZsA4+Um!E%)c-B&%fik(~%`##j8u@G>0z
ztg$9S2Z(5J{|Ve;_|Px3^r!)G%f}cTJ2lVgW|T>eQwB#I@JEYAv~LiDAslF1acg>`
z_sBuk7EHy9#(k?1e<f!Lqe>GmWFfC@Q4ml@G%!BYgnLc449ITRBLDrzzQYIY9o*Wl
Qs3(SfSGqhPU{%0>0O93B*Z=?k
literal 0
HcmV?d00001
diff --git a/tests/f_orphan_truncate_extents_inode/name b/tests/f_orphan_truncate_extents_inode/name
new file mode 100644
index 000000000..6f16502b3
--- /dev/null
+++ b/tests/f_orphan_truncate_extents_inode/name
@@ -0,0 +1 @@
+truncating an orphaned extent-mapped inode in preen mode
diff --git a/tests/f_orphan_truncate_extents_inode/script b/tests/f_orphan_truncate_extents_inode/script
new file mode 100644
index 000000000..fb895e9a4
--- /dev/null
+++ b/tests/f_orphan_truncate_extents_inode/script
@@ -0,0 +1,3 @@
+FSCK_OPT=-p
+SECOND_FSCK_OPT="-yf -E no_optimize_extents"
+. $cmd_dir/run_e2fsck
--
2.43.7
^ permalink raw reply related
* [syzbot ci] Re: Data in direntry (dirdata) feature
From: syzbot ci @ 2026-06-19 14:50 UTC (permalink / raw)
To: artem.blagodarenko, adilger, linux-ext4, pravin.shelar, syzbot,
syzkaller-bugs
Cc: syzbot, syzkaller-bugs
In-Reply-To: <CA+rD4x_2wXOP=4RwPY-A2vJjK4Vv9hGUSVFzprCe1H+8MTOKhA@mail.gmail.com>
syzbot ci has tested the suggested fix patch on top of the following series:
[v2] Data in direntry (dirdata) feature
https://lore.kernel.org/all/20260610152417.13576-1-ablagodarenko@thelustrecollective.com
Patch: https://ci.syzbot.org/jobs/2471bcf5-fa8b-4932-846b-3db72cc2b56c/patch
Testing results:
* [build 0] Build Patched: passed
* [build 0] Boot test: Patched: passed
Full report is available here:
https://ci.syzbot.org/session/08769134-a853-4686-a652-a4c24e8773d7
---
This report is generated by a bot. It may contain errors.
syzbot ci engineers can be reached at syzkaller@googlegroups.com.
^ permalink raw reply
* Re: [syzbot ci] Re: Data in direntry (dirdata) feature
From: syzbot @ 2026-06-19 14:11 UTC (permalink / raw)
To: artem.blagodarenko
Cc: adilger, artem.blagodarenko, linux-ext4, pravin.shelar, syzbot,
syzkaller-bugs
In-Reply-To: <CA+rD4x_2wXOP=4RwPY-A2vJjK4Vv9hGUSVFzprCe1H+8MTOKhA@mail.gmail.com>
> Thanks for the report. The attached patch addresses the issues found in
> the dirdata series review (dx_get_dx_info/get_dx_countlimit blocksize
> fallback, dfid parameter shadowing in ext4_dirdata_get, and the unsafe
> delete-before-add in EXT4_IOC_SET_LUFID).
>
>
> #syz test
I see the command but can't find the corresponding bug.
The email is sent to syzbot+HASH@syzkaller.appspotmail.com address
but the HASH does not correspond to any known bug.
Please double check the address.
>
> On Thu, Jun 11, 2026 11:29 AM, syzbot ci <
> syzbot+cid7b922cb3d448114@syzkaller.appspotmail.com> wrote:
>
>> syzbot ci has tested the following series
>>
>> [v2] Data in direntry (dirdata) feature
>>
>> https://lore.kernel.org/all/20260610152417.13576-1-ablagodarenko@thelustrecollective.com
>> * [PATCH v2 01/10] ext4: replace ext4_dir_entry with ext4_dir_entry_2
>> * [PATCH v2 02/10] ext4: add ext4_dir_entry_is_tail()
>> * [PATCH v2 03/10] ext4: refactor dx_root to support variable dirent sizes
>> * [PATCH v2 04/10] ext4: add dirdata format definitions and access helpers
>> * [PATCH v2 05/10] ext4: preserve dirdata bits in get_dtype()
>> * [PATCH v2 06/10] ext4: add ext4_dir_entry_len() and harden dirdata
>> parsing
>> * [PATCH v2 07/10] ext4: rename ext4_dir_rec_len() and clarify dirdata
>> usage
>> * [PATCH v2 08/10] ext4: dirdata feature
>> * [PATCH v2 09/10] ext4: add dirdata set/get helpers
>> * [PATCH v2 10/10] ext4: Add EXT4_IOC_SET_LUFID ioctl for setting LUFID on
>> directory entries
>>
>> and found the following issues:
>> * KASAN: slab-out-of-bounds Read in __ext4_check_dir_entry
>> * KASAN: slab-out-of-bounds Read in ext4_inlinedir_to_tree
>> * KASAN: slab-use-after-free Read in __ext4_check_dir_entry
>> * KASAN: slab-use-after-free Read in ext4_inlinedir_to_tree
>> * KASAN: use-after-free Read in __ext4_check_dir_entry
>>
>> Full report is available here:
>> https://ci.syzbot.org/series/5bf0e2fa-2e68-4532-8396-4568879b2788
>>
>> ***
>>
>> 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: 9716c086c8e8b141d35aa61f2e96a2e83de212a7
>> 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/ddf6ee7c-dfa8-4383-b004-10140edc081c/config
>> syz repro:
>> https://ci.syzbot.org/findings/b0854918-13f9-49dd-ab30-12154f0debe2/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:4069 [inline]
>> BUG: KASAN: slab-out-of-bounds in ext4_dir_entry_len fs/ext4/ext4.h:4096
>> [inline]
>> BUG: KASAN: slab-out-of-bounds in __ext4_check_dir_entry+0x65a/0xc40
>> fs/ext4/dir.c:96
>> Read of size 1 at addr ffff8881022db7f5 by task syz.0.23/5815
>>
>> CPU: 1 UID: 0 PID: 5815 Comm: syz.0.23 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+0x55/0x1e0 mm/kasan/report.c:378
>> print_report+0x58/0x70 mm/kasan/report.c:482
>> kasan_report+0x117/0x150 mm/kasan/report.c:595
>> ext4_dirent_get_data_len fs/ext4/ext4.h:4069 [inline]
>> ext4_dir_entry_len fs/ext4/ext4.h:4096 [inline]
>> __ext4_check_dir_entry+0x65a/0xc40 fs/ext4/dir.c:96
>> ext4_check_all_de+0x66/0x150 fs/ext4/dir.c:657
>> ext4_convert_inline_data_nolock+0x1b7/0x990 fs/ext4/inline.c:1121
>> ext4_try_add_inline_entry+0x604/0x8e0 fs/ext4/inline.c:1247
>> __ext4_add_entry+0x390/0x1f40 fs/ext4/namei.c:2529
>> ext4_add_entry fs/ext4/namei.c:2613 [inline]
>> ext4_mkdir+0x5e5/0xce0 fs/ext4/namei.c:3175
>> vfs_mkdir+0x413/0x630 fs/namei.c:5271
>> filename_mkdirat+0x285/0x510 fs/namei.c:5304
>> __do_sys_mkdirat fs/namei.c:5325 [inline]
>> __se_sys_mkdirat+0x35/0x150 fs/namei.c:5322
>> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
>> do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
>> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>> RIP: 0033:0x7f669359bcc7
>> Code: 00 66 90 48 89 f2 b9 00 01 00 00 48 89 fe bf 9c ff ff ff e9 db f7 ff
>> ff 66 2e 0f 1f 84 00 00 00 00 00 90 b8 02 01 00 00 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:00007ffd42381d38 EFLAGS: 00000246 ORIG_RAX: 0000000000000102
>> RAX: ffffffffffffffda RBX: 00007ffd42381dc0 RCX: 00007f669359bcc7
>> RDX: 00000000000001ff RSI: 0000200000001200 RDI: 00000000ffffff9c
>> RBP: 00002000000024c0 R08: 0000200000000240 R09: 0000000000000000
>> R10: 00002000000024c0 R11: 0000000000000246 R12: 0000200000001200
>> R13: 00007ffd42381d80 R14: 0000000000000000 R15: 0000000000000000
>> </TASK>
>>
>> Allocated by task 5066:
>> 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]
>> __kmalloc_cache_noprof+0x31c/0x660 mm/slub.c:5420
>> kmalloc_noprof include/linux/slab.h:950 [inline]
>> kzalloc_noprof include/linux/slab.h:1188 [inline]
>> kernfs_get_open_node fs/kernfs/file.c:543 [inline]
>> kernfs_fop_open+0x862/0xda0 fs/kernfs/file.c:718
>> do_dentry_open+0x822/0x13a0 fs/open.c:947
>> vfs_open+0x3b/0x340 fs/open.c:1079
>> do_open fs/namei.c:4699 [inline]
>> path_openat+0x2e08/0x3860 fs/namei.c:4858
>> do_file_open+0x23e/0x4a0 fs/namei.c:4887
>> do_sys_openat2+0x113/0x200 fs/open.c:1364
>> do_sys_open fs/open.c:1370 [inline]
>> __do_sys_openat fs/open.c:1386 [inline]
>> __se_sys_openat fs/open.c:1381 [inline]
>> __x64_sys_openat+0x138/0x170 fs/open.c:1381
>> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
>> do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
>> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>>
>> Last potentially related work creation:
>> kasan_save_stack+0x3e/0x60 mm/kasan/common.c:57
>> kasan_record_aux_stack+0xbd/0xd0 mm/kasan/generic.c:556
>> kvfree_call_rcu+0x100/0x430 mm/slab_common.c:1970
>> kernfs_unlink_open_file+0x3fe/0x4b0 fs/kernfs/file.c:604
>> kernfs_fop_release+0x2eb/0x440 fs/kernfs/file.c:783
>> __fput+0x44f/0xa60 fs/file_table.c:510
>> fput_close_sync+0x11f/0x240 fs/file_table.c:615
>> __do_sys_close fs/open.c:1507 [inline]
>> __se_sys_close fs/open.c:1492 [inline]
>> __x64_sys_close+0x7e/0x110 fs/open.c:1492
>> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
>> do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
>> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>>
>> The buggy address belongs to the object at ffff8881022db700
>> which belongs to the cache kmalloc-128 of size 128
>> The buggy address is located 117 bytes to the right of
>> allocated 128-byte region [ffff8881022db700, ffff8881022db780)
>>
>> The buggy address belongs to the physical page:
>> page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x1022db
>> flags: 0x17ff00000000000(node=0|zone=2|lastcpupid=0x7ff)
>> page_type: f5(slab)
>> raw: 017ff00000000000 ffff888100041a00 dead000000000100 dead000000000122
>> raw: 0000000000000000 0000000800100010 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
>> 0xd2000(__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 0,
>> tgid 0 (swapper/0), ts 2408938923, free_ts 0
>> set_page_owner include/linux/page_owner.h:32 [inline]
>> post_alloc_hook+0x22d/0x280 mm/page_alloc.c:1853
>> prep_new_page mm/page_alloc.c:1861 [inline]
>> get_page_from_freelist+0x2593/0x2610 mm/page_alloc.c:3941
>> __alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5221
>> alloc_slab_page mm/slub.c:3278 [inline]
>> allocate_slab+0x77/0x660 mm/slub.c:3467
>> new_slab mm/slub.c:3525 [inline]
>> refill_objects+0x339/0x3d0 mm/slub.c:7272
>> refill_sheaf mm/slub.c:2816 [inline]
>> __pcs_replace_empty_main+0x321/0x720 mm/slub.c:4652
>> alloc_from_pcs mm/slub.c:4750 [inline]
>> slab_alloc_node mm/slub.c:4884 [inline]
>> __do_kmalloc_node mm/slub.c:5295 [inline]
>> __kmalloc_noprof+0x474/0x760 mm/slub.c:5308
>> kmalloc_noprof include/linux/slab.h:954 [inline]
>> kzalloc_noprof include/linux/slab.h:1188 [inline]
>> __alloc_empty_sheaf mm/slub.c:2768 [inline]
>> alloc_empty_sheaf mm/slub.c:2783 [inline]
>> __pcs_replace_empty_main+0x2df/0x720 mm/slub.c:4647
>> alloc_from_pcs mm/slub.c:4750 [inline]
>> slab_alloc_node mm/slub.c:4884 [inline]
>> kmem_cache_alloc_noprof+0x37d/0x650 mm/slub.c:4906
>> dup_fd+0x55/0xb40 fs/file.c:390
>> copy_files+0xc8/0x120 kernel/fork.c:1639
>> copy_process+0x1d94/0x4440 kernel/fork.c:2252
>> kernel_clone+0x2d7/0x940 kernel/fork.c:2722
>> user_mode_thread+0x110/0x180 kernel/fork.c:2798
>> rest_init+0x23/0x300 init/main.c:727
>> start_kernel+0x38a/0x3e0 init/main.c:1220
>> page_owner free stack trace missing
>>
>> Memory state around the buggy address:
>> ffff8881022db680: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
>> ffff8881022db700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>> >ffff8881022db780: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
>> ^
>> ffff8881022db800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>> ffff8881022db880: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
>> ==================================================================
>>
>>
>> ***
>>
>> KASAN: slab-out-of-bounds Read in ext4_inlinedir_to_tree
>>
>> tree: torvalds
>> URL:
>> https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux
>> base: 9716c086c8e8b141d35aa61f2e96a2e83de212a7
>> 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/ddf6ee7c-dfa8-4383-b004-10140edc081c/config
>> syz repro:
>> https://ci.syzbot.org/findings/2dff870b-f382-4c93-8d8d-b2291d921224/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: slab-out-of-bounds in ext4_dir_entry_len fs/ext4/ext4.h:4095
>> [inline]
>> BUG: KASAN: slab-out-of-bounds in ext4_inlinedir_to_tree+0xda5/0x10d0
>> fs/ext4/inline.c:1335
>> Read of size 2 at addr ffff888115a3183c by task syz.1.18/5839
>>
>> CPU: 1 UID: 0 PID: 5839 Comm: syz.1.18 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+0x55/0x1e0 mm/kasan/report.c:378
>> print_report+0x58/0x70 mm/kasan/report.c:482
>> kasan_report+0x117/0x150 mm/kasan/report.c:595
>> ext4_dir_entry_len fs/ext4/ext4.h:4095 [inline]
>> ext4_inlinedir_to_tree+0xda5/0x10d0 fs/ext4/inline.c:1335
>> ext4_htree_fill_tree+0x517/0x1230 fs/ext4/namei.c:1182
>> ext4_dx_readdir fs/ext4/dir.c:600 [inline]
>> ext4_readdir+0x2db4/0x3640 fs/ext4/dir.c:146
>> iterate_dir+0x399/0x570 fs/readdir.c:110
>> __do_sys_getdents64 fs/readdir.c:399 [inline]
>> __se_sys_getdents64+0xf1/0x280 fs/readdir.c:384
>> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
>> do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
>> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>> RIP: 0033:0x7f3e02b9ce59
>> 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:00007f3e03ad5028 EFLAGS: 00000246 ORIG_RAX: 00000000000000d9
>> RAX: ffffffffffffffda RBX: 00007f3e02e15fa0 RCX: 00007f3e02b9ce59
>> RDX: 0000000000001000 RSI: 0000200000000f80 RDI: 0000000000000004
>> RBP: 00007f3e02c32d6f R08: 0000000000000000 R09: 0000000000000000
>> R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
>> R13: 00007f3e02e16038 R14: 00007f3e02e15fa0 R15: 00007ffcaa902298
>> </TASK>
>>
>> Allocated by task 5839:
>> 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:5296 [inline]
>> __kmalloc_noprof+0x35c/0x760 mm/slub.c:5308
>> kmalloc_noprof include/linux/slab.h:954 [inline]
>> ext4_inlinedir_to_tree+0x312/0x10d0 fs/ext4/inline.c:1292
>> ext4_htree_fill_tree+0x517/0x1230 fs/ext4/namei.c:1182
>> ext4_dx_readdir fs/ext4/dir.c:600 [inline]
>> ext4_readdir+0x2db4/0x3640 fs/ext4/dir.c:146
>> iterate_dir+0x399/0x570 fs/readdir.c:110
>> __do_sys_getdents64 fs/readdir.c:399 [inline]
>> __se_sys_getdents64+0xf1/0x280 fs/readdir.c:384
>> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
>> do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
>> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>>
>> The buggy address belongs to the object at ffff888115a31800
>> which belongs to the cache kmalloc-64 of size 64
>> The buggy address is located 0 bytes to the right of
>> allocated 60-byte region [ffff888115a31800, ffff888115a3183c)
>>
>> The buggy address belongs to the physical page:
>> page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x115a31
>> 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
>> 0xd2c40(GFP_NOFS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC),
>> pid 5051, tgid 5051 (acpid), ts 27203740677, free_ts 27201732767
>> set_page_owner include/linux/page_owner.h:32 [inline]
>> post_alloc_hook+0x22d/0x280 mm/page_alloc.c:1853
>> prep_new_page mm/page_alloc.c:1861 [inline]
>> get_page_from_freelist+0x2593/0x2610 mm/page_alloc.c:3941
>> __alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5221
>> alloc_slab_page mm/slub.c:3278 [inline]
>> allocate_slab+0x77/0x660 mm/slub.c:3467
>> new_slab mm/slub.c:3525 [inline]
>> refill_objects+0x339/0x3d0 mm/slub.c:7272
>> refill_sheaf mm/slub.c:2816 [inline]
>> __pcs_replace_empty_main+0x321/0x720 mm/slub.c:4652
>> alloc_from_pcs mm/slub.c:4750 [inline]
>> slab_alloc_node mm/slub.c:4884 [inline]
>> __do_kmalloc_node mm/slub.c:5295 [inline]
>> __kmalloc_noprof+0x474/0x760 mm/slub.c:5308
>> kmalloc_noprof include/linux/slab.h:954 [inline]
>> kzalloc_noprof include/linux/slab.h:1188 [inline]
>> tomoyo_get_name+0x20c/0x590 security/tomoyo/memory.c:173
>> tomoyo_parse_name_union+0xd9/0x130 security/tomoyo/util.c:260
>> tomoyo_update_path_acl security/tomoyo/file.c:399 [inline]
>> tomoyo_write_file+0x3a6/0xc50 security/tomoyo/file.c:1027
>> tomoyo_write_domain2 security/tomoyo/common.c:1160 [inline]
>> tomoyo_add_entry security/tomoyo/common.c:2177 [inline]
>> tomoyo_supervisor+0x1208/0x1570 security/tomoyo/common.c:2238
>> tomoyo_audit_path_log security/tomoyo/file.c:169 [inline]
>> tomoyo_path_permission+0x25a/0x380 security/tomoyo/file.c:592
>> tomoyo_check_open_permission+0x2b2/0x470 security/tomoyo/file.c:782
>> security_file_open+0xa9/0x240 security/security.c:2739
>> do_dentry_open+0x4a8/0x13a0 fs/open.c:924
>> vfs_open+0x3b/0x340 fs/open.c:1079
>> page last free pid 15 tgid 15 stack trace:
>> reset_page_owner include/linux/page_owner.h:25 [inline]
>> __free_pages_prepare mm/page_alloc.c:1397 [inline]
>> __free_frozen_pages+0xc1c/0xd30 mm/page_alloc.c:2938
>> __tlb_remove_table_free mm/mmu_gather.c:228 [inline]
>> tlb_remove_table_rcu+0x85/0x100 mm/mmu_gather.c:291
>> rcu_do_batch kernel/rcu/tree.c:2617 [inline]
>> rcu_core+0x7cd/0x1070 kernel/rcu/tree.c:2869
>> handle_softirqs+0x22a/0x840 kernel/softirq.c:622
>> run_ksoftirqd+0x36/0x60 kernel/softirq.c:1076
>> smpboot_thread_fn+0x541/0xa50 kernel/smpboot.c:160
>> kthread+0x389/0x470 kernel/kthread.c:436
>> ret_from_fork+0x514/0xb70 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:
>> ffff888115a31700: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
>> ffff888115a31780: 00 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc
>> >ffff888115a31800: 00 00 00 00 00 00 00 04 fc fc fc fc fc fc fc fc
>> ^
>> ffff888115a31880: 00 00 00 00 00 00 02 fc fc fc fc fc fc fc fc fc
>> ffff888115a31900: fa fb fb fb fb fb fb fb 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: 9716c086c8e8b141d35aa61f2e96a2e83de212a7
>> 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/ddf6ee7c-dfa8-4383-b004-10140edc081c/config
>> syz repro:
>> https://ci.syzbot.org/findings/f1d48ea1-6e87-4d64-9c13-8bf8aed109fc/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-use-after-free in ext4_dirent_get_data_len
>> fs/ext4/ext4.h:4069 [inline]
>> BUG: KASAN: slab-use-after-free in ext4_dir_entry_len fs/ext4/ext4.h:4096
>> [inline]
>> BUG: KASAN: slab-use-after-free in __ext4_check_dir_entry+0x65a/0xc40
>> fs/ext4/dir.c:96
>> Read of size 1 at addr ffff888114d8c045 by task syz.0.20/5821
>>
>> CPU: 1 UID: 0 PID: 5821 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+0x55/0x1e0 mm/kasan/report.c:378
>> print_report+0x58/0x70 mm/kasan/report.c:482
>> kasan_report+0x117/0x150 mm/kasan/report.c:595
>> ext4_dirent_get_data_len fs/ext4/ext4.h:4069 [inline]
>> ext4_dir_entry_len fs/ext4/ext4.h:4096 [inline]
>> __ext4_check_dir_entry+0x65a/0xc40 fs/ext4/dir.c:96
>> ext4_find_dest_de+0x136/0x770 fs/ext4/namei.c:2203
>> ext4_add_dirent_to_inline+0xcf/0x430 fs/ext4/inline.c:984
>> ext4_try_add_inline_entry+0x235/0x8e0 fs/ext4/inline.c:1213
>> __ext4_add_entry+0x390/0x1f40 fs/ext4/namei.c:2529
>> ext4_add_entry fs/ext4/namei.c:2613 [inline]
>> ext4_add_nondir+0x111/0x310 fs/ext4/namei.c:2936
>> ext4_create+0x2e9/0x470 fs/ext4/namei.c:2982
>> lookup_open fs/namei.c:4511 [inline]
>> open_last_lookups fs/namei.c:4611 [inline]
>> path_openat+0x1395/0x3860 fs/namei.c:4855
>> do_file_open+0x23e/0x4a0 fs/namei.c:4887
>> do_sys_openat2+0x113/0x200 fs/open.c:1364
>> do_sys_open fs/open.c:1370 [inline]
>> __do_sys_openat fs/open.c:1386 [inline]
>> __se_sys_openat fs/open.c:1381 [inline]
>> __x64_sys_openat+0x138/0x170 fs/open.c:1381
>> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
>> do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
>> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>> RIP: 0033:0x7f922219ce59
>> 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:00007f9223137028 EFLAGS: 00000246 ORIG_RAX: 0000000000000101
>> RAX: ffffffffffffffda RBX: 00007f9222415fa0 RCX: 00007f922219ce59
>> RDX: 0000000000042042 RSI: 0000200000000080 RDI: 0000000000000004
>> RBP: 00007f9222232d6f R08: 0000000000000000 R09: 0000000000000000
>> R10: 000000000000014a R11: 0000000000000246 R12: 0000000000000000
>> R13: 00007f9222416038 R14: 00007f9222415fa0 R15: 00007ffd01a2d448
>> </TASK>
>>
>> Allocated by task 5484:
>> kasan_save_stack mm/kasan/common.c:57 [inline]
>> kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
>> unpoison_slab_object mm/kasan/common.c:340 [inline]
>> __kasan_slab_alloc+0x6c/0x80 mm/kasan/common.c:366
>> kasan_slab_alloc include/linux/kasan.h:253 [inline]
>> slab_post_alloc_hook mm/slub.c:4570 [inline]
>> slab_alloc_node mm/slub.c:4899 [inline]
>> kmem_cache_alloc_node_noprof+0x384/0x690 mm/slub.c:4951
>> kmalloc_reserve net/core/skbuff.c:613 [inline]
>> __alloc_skb+0x27d/0x7d0 net/core/skbuff.c:713
>> alloc_skb include/linux/skbuff.h:1385 [inline]
>> nlmsg_new include/net/netlink.h:1055 [inline]
>> mpls_netconf_notify_devconf+0x46/0x100 net/mpls/af_mpls.c:1217
>> mpls_dev_notify+0xb2d/0xd10 net/mpls/af_mpls.c:1691
>> notifier_call_chain+0x1ad/0x3d0 kernel/notifier.c:85
>> call_netdevice_notifiers_extack net/core/dev.c:2287 [inline]
>> call_netdevice_notifiers net/core/dev.c:2301 [inline]
>> unregister_netdevice_many_notify+0x17a5/0x22c0 net/core/dev.c:12421
>> ops_exit_rtnl_list net/core/net_namespace.c:187 [inline]
>> ops_undo_list+0x3d3/0x940 net/core/net_namespace.c:248
>> cleanup_net+0x56b/0x800 net/core/net_namespace.c:702
>> process_one_work kernel/workqueue.c:3314 [inline]
>> process_scheduled_works+0xb5d/0x1860 kernel/workqueue.c:3397
>> worker_thread+0xa53/0xfc0 kernel/workqueue.c:3478
>> kthread+0x389/0x470 kernel/kthread.c:436
>> ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
>> ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
>>
>> Freed by task 5484:
>> 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:2689 [inline]
>> slab_free mm/slub.c:6251 [inline]
>> kfree+0x1c5/0x640 mm/slub.c:6566
>> skb_kfree_head net/core/skbuff.c:1075 [inline]
>> skb_free_head net/core/skbuff.c:1087 [inline]
>> skb_release_data+0x828/0xa60 net/core/skbuff.c:1114
>> skb_release_all net/core/skbuff.c:1189 [inline]
>> __kfree_skb+0x5d/0x210 net/core/skbuff.c:1203
>> netlink_broadcast_filtered+0xe18/0xf20 net/netlink/af_netlink.c:1540
>> nlmsg_multicast_filtered include/net/netlink.h:1165 [inline]
>> nlmsg_multicast include/net/netlink.h:1184 [inline]
>> nlmsg_notify+0xf0/0x1a0 net/netlink/af_netlink.c:2598
>> mpls_dev_notify+0xb2d/0xd10 net/mpls/af_mpls.c:1691
>> notifier_call_chain+0x1ad/0x3d0 kernel/notifier.c:85
>> call_netdevice_notifiers_extack net/core/dev.c:2287 [inline]
>> call_netdevice_notifiers net/core/dev.c:2301 [inline]
>> unregister_netdevice_many_notify+0x17a5/0x22c0 net/core/dev.c:12421
>> ops_exit_rtnl_list net/core/net_namespace.c:187 [inline]
>> ops_undo_list+0x3d3/0x940 net/core/net_namespace.c:248
>> cleanup_net+0x56b/0x800 net/core/net_namespace.c:702
>> process_one_work kernel/workqueue.c:3314 [inline]
>> process_scheduled_works+0xb5d/0x1860 kernel/workqueue.c:3397
>> worker_thread+0xa53/0xfc0 kernel/workqueue.c:3478
>> kthread+0x389/0x470 kernel/kthread.c:436
>> ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
>> ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
>>
>> The buggy address belongs to the object at ffff888114d8c000
>> which belongs to the cache skbuff_small_head of size 704
>> The buggy address is located 69 bytes inside of
>> freed 704-byte region [ffff888114d8c000, ffff888114d8c2c0)
>>
>> The buggy address belongs to the physical page:
>> page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x114d8c
>> head: order:2 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 ffff888160416b40 dead000000000100 dead000000000122
>> raw: 0000000000000000 0000000800120012 00000000f5000000 0000000000000000
>> head: 017ff00000000040 ffff888160416b40 dead000000000100 dead000000000122
>> head: 0000000000000000 0000000800120012 00000000f5000000 0000000000000000
>> head: 017ff00000000002 ffffffffffffff01 00000000ffffffff 00000000ffffffff
>> head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000004
>> page dumped because: kasan: bad access detected
>> page_owner tracks the page as allocated
>> page last allocated via order 2, migratetype Unmovable, gfp_mask
>> 0xd20c0(__GFP_IO|__GFP_FS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC),
>> pid 5484, tgid 5484 (kworker/u8:2), ts 72573003529, free_ts 72546506446
>> set_page_owner include/linux/page_owner.h:32 [inline]
>> post_alloc_hook+0x22d/0x280 mm/page_alloc.c:1853
>> prep_new_page mm/page_alloc.c:1861 [inline]
>> get_page_from_freelist+0x2593/0x2610 mm/page_alloc.c:3941
>> __alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5221
>> alloc_slab_page mm/slub.c:3278 [inline]
>> allocate_slab+0x77/0x660 mm/slub.c:3467
>> new_slab mm/slub.c:3525 [inline]
>> refill_objects+0x339/0x3d0 mm/slub.c:7272
>> refill_sheaf mm/slub.c:2816 [inline]
>> __pcs_replace_empty_main+0x321/0x720 mm/slub.c:4652
>> alloc_from_pcs mm/slub.c:4750 [inline]
>> slab_alloc_node mm/slub.c:4884 [inline]
>> kmem_cache_alloc_node_noprof+0x441/0x690 mm/slub.c:4951
>> kmalloc_reserve net/core/skbuff.c:613 [inline]
>> __alloc_skb+0x27d/0x7d0 net/core/skbuff.c:713
>> alloc_skb include/linux/skbuff.h:1385 [inline]
>> nlmsg_new include/net/netlink.h:1055 [inline]
>> mpls_netconf_notify_devconf+0x46/0x100 net/mpls/af_mpls.c:1217
>> mpls_dev_notify+0xb2d/0xd10 net/mpls/af_mpls.c:1691
>> notifier_call_chain+0x1ad/0x3d0 kernel/notifier.c:85
>> call_netdevice_notifiers_extack net/core/dev.c:2287 [inline]
>> call_netdevice_notifiers net/core/dev.c:2301 [inline]
>> unregister_netdevice_many_notify+0x17a5/0x22c0 net/core/dev.c:12421
>> ops_exit_rtnl_list net/core/net_namespace.c:187 [inline]
>> ops_undo_list+0x3d3/0x940 net/core/net_namespace.c:248
>> cleanup_net+0x56b/0x800 net/core/net_namespace.c:702
>> process_one_work kernel/workqueue.c:3314 [inline]
>> process_scheduled_works+0xb5d/0x1860 kernel/workqueue.c:3397
>> worker_thread+0xa53/0xfc0 kernel/workqueue.c:3478
>> page last free pid 5484 tgid 5484 stack trace:
>> reset_page_owner include/linux/page_owner.h:25 [inline]
>> __free_pages_prepare mm/page_alloc.c:1397 [inline]
>> __free_frozen_pages+0xc1c/0xd30 mm/page_alloc.c:2938
>> stack_depot_save_flags+0x40e/0x810 lib/stackdepot.c:735
>> kasan_save_stack mm/kasan/common.c:58 [inline]
>> kasan_save_track+0x4f/0x80 mm/kasan/common.c:78
>> unpoison_slab_object mm/kasan/common.c:340 [inline]
>> __kasan_slab_alloc+0x6c/0x80 mm/kasan/common.c:366
>> kasan_slab_alloc include/linux/kasan.h:253 [inline]
>> slab_post_alloc_hook mm/slub.c:4570 [inline]
>> slab_alloc_node mm/slub.c:4899 [inline]
>> kmem_cache_alloc_noprof+0x2bc/0x650 mm/slub.c:4906
>> kmem_alloc_batch lib/debugobjects.c:371 [inline]
>> fill_pool+0x156/0x580 lib/debugobjects.c:420
>> debug_objects_fill_pool lib/debugobjects.c:752 [inline]
>> debug_object_activate+0x4a3/0x580 lib/debugobjects.c:841
>> debug_rcu_head_queue kernel/rcu/rcu.h:236 [inline]
>> __call_rcu_common kernel/rcu/tree.c:3116 [inline]
>> call_rcu+0x43/0x890 kernel/rcu/tree.c:3251
>> kernfs_put+0x259/0x520 fs/kernfs/dir.c:618
>> kernfs_remove_by_name_ns+0xc8/0x140 fs/kernfs/dir.c:1799
>> device_remove_class_symlinks+0x178/0x190 drivers/base/core.c:3479
>> device_del+0x400/0x8f0 drivers/base/core.c:3881
>> unregister_netdevice_many_notify+0x1d5f/0x22c0 net/core/dev.c:12456
>> ops_exit_rtnl_list net/core/net_namespace.c:187 [inline]
>> ops_undo_list+0x3d3/0x940 net/core/net_namespace.c:248
>> cleanup_net+0x56b/0x800 net/core/net_namespace.c:702
>> process_one_work kernel/workqueue.c:3314 [inline]
>> process_scheduled_works+0xb5d/0x1860 kernel/workqueue.c:3397
>>
>> Memory state around the buggy address:
>> ffff888114d8bf00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>> ffff888114d8bf80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>> >ffff888114d8c000: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>> ^
>> ffff888114d8c080: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>> ffff888114d8c100: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>> ==================================================================
>>
>>
>> ***
>>
>> KASAN: slab-use-after-free Read in ext4_inlinedir_to_tree
>>
>> tree: torvalds
>> URL:
>> https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux
>> base: 9716c086c8e8b141d35aa61f2e96a2e83de212a7
>> 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/ddf6ee7c-dfa8-4383-b004-10140edc081c/config
>> syz repro:
>> https://ci.syzbot.org/findings/f42da242-e16e-4f10-bf25-0bd7e192d989/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-use-after-free in ext4_dirent_get_data_len
>> fs/ext4/ext4.h:4069 [inline]
>> BUG: KASAN: slab-use-after-free in ext4_dir_entry_len fs/ext4/ext4.h:4096
>> [inline]
>> BUG: KASAN: slab-use-after-free in ext4_inlinedir_to_tree+0x94c/0x10d0
>> fs/ext4/inline.c:1335
>> Read of size 1 at addr ffff88816fee8825 by task syz.0.20/5867
>>
>> CPU: 1 UID: 0 PID: 5867 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+0x55/0x1e0 mm/kasan/report.c:378
>> print_report+0x58/0x70 mm/kasan/report.c:482
>> kasan_report+0x117/0x150 mm/kasan/report.c:595
>> ext4_dirent_get_data_len fs/ext4/ext4.h:4069 [inline]
>> ext4_dir_entry_len fs/ext4/ext4.h:4096 [inline]
>> ext4_inlinedir_to_tree+0x94c/0x10d0 fs/ext4/inline.c:1335
>> ext4_htree_fill_tree+0x517/0x1230 fs/ext4/namei.c:1182
>> ext4_dx_readdir fs/ext4/dir.c:600 [inline]
>> ext4_readdir+0x2db4/0x3640 fs/ext4/dir.c:146
>> iterate_dir+0x399/0x570 fs/readdir.c:110
>> __do_sys_getdents fs/readdir.c:319 [inline]
>> __se_sys_getdents+0xf1/0x270 fs/readdir.c:304
>> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
>> do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
>> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>> RIP: 0033:0x7f010ad9ce59
>> 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:00007f010bc0f028 EFLAGS: 00000246 ORIG_RAX: 000000000000004e
>> RAX: ffffffffffffffda RBX: 00007f010b015fa0 RCX: 00007f010ad9ce59
>> RDX: 0000000000000054 RSI: 0000000000000000 RDI: 0000000000000004
>> RBP: 00007f010ae32d6f R08: 0000000000000000 R09: 0000000000000000
>> R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
>> R13: 00007f010b016038 R14: 00007f010b015fa0 R15: 00007ffd93577348
>> </TASK>
>>
>> Allocated by task 5064:
>> 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:5296 [inline]
>> __kmalloc_noprof+0x35c/0x760 mm/slub.c:5308
>> 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_path_perm+0x283/0x560 security/tomoyo/file.c:827
>> security_inode_getattr+0x12b/0x310 security/security.c:1895
>> vfs_getattr fs/stat.c:259 [inline]
>> vfs_fstat fs/stat.c:281 [inline]
>> vfs_fstatat+0xb4/0x170 fs/stat.c:371
>> __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+0x174/0x580 arch/x86/entry/syscall_64.c:94
>> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>>
>> Freed by task 5064:
>> 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:2689 [inline]
>> slab_free mm/slub.c:6251 [inline]
>> kfree+0x1c5/0x640 mm/slub.c:6566
>> tomoyo_path_perm+0x403/0x560 security/tomoyo/file.c:847
>> security_inode_getattr+0x12b/0x310 security/security.c:1895
>> vfs_getattr fs/stat.c:259 [inline]
>> vfs_fstat fs/stat.c:281 [inline]
>> vfs_fstatat+0xb4/0x170 fs/stat.c:371
>> __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+0x174/0x580 arch/x86/entry/syscall_64.c:94
>> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>>
>> The buggy address belongs to the object at ffff88816fee8800
>> which belongs to the cache kmalloc-64 of size 64
>> The buggy address is located 37 bytes inside of
>> freed 64-byte region [ffff88816fee8800, ffff88816fee8840)
>>
>> The buggy address belongs to the physical page:
>> page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x16fee8
>> flags: 0x57ff00000000000(node=1|zone=2|lastcpupid=0x7ff)
>> page_type: f5(slab)
>> raw: 057ff00000000000 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 21294026082, free_ts 0
>> set_page_owner include/linux/page_owner.h:32 [inline]
>> post_alloc_hook+0x22d/0x280 mm/page_alloc.c:1853
>> prep_new_page mm/page_alloc.c:1861 [inline]
>> get_page_from_freelist+0x2593/0x2610 mm/page_alloc.c:3941
>> __alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5221
>> alloc_slab_page mm/slub.c:3278 [inline]
>> allocate_slab+0x77/0x660 mm/slub.c:3467
>> new_slab mm/slub.c:3525 [inline]
>> refill_objects+0x339/0x3d0 mm/slub.c:7272
>> refill_sheaf mm/slub.c:2816 [inline]
>> __pcs_replace_empty_main+0x321/0x720 mm/slub.c:4652
>> alloc_from_pcs mm/slub.c:4750 [inline]
>> slab_alloc_node mm/slub.c:4884 [inline]
>> __do_kmalloc_node mm/slub.c:5295 [inline]
>> __kmalloc_noprof+0x474/0x760 mm/slub.c:5308
>> kmalloc_noprof include/linux/slab.h:954 [inline]
>> kzalloc_noprof include/linux/slab.h:1188 [inline]
>> handler_new_ref+0x261/0x9c0 drivers/media/v4l2-core/v4l2-ctrls-core.c:1882
>> v4l2_ctrl_add_handler+0x19f/0x290
>> drivers/media/v4l2-core/v4l2-ctrls-core.c:2443
>> vivid_create_controls+0x332d/0x3bd0
>> drivers/media/test-drivers/vivid/vivid-ctrls.c:2072
>> vivid_create_instance drivers/media/test-drivers/vivid/vivid-core.c:1933
>> [inline]
>> vivid_probe+0x4261/0x72b0
>> drivers/media/test-drivers/vivid/vivid-core.c:2095
>> platform_probe+0xf9/0x190 drivers/base/platform.c:1432
>> call_driver_probe drivers/base/dd.c:-1 [inline]
>> really_probe+0x267/0xaf0 drivers/base/dd.c:709
>> __driver_probe_device+0x1ef/0x380 drivers/base/dd.c:871
>> driver_probe_device+0x4f/0x240 drivers/base/dd.c:901
>> __driver_attach+0x34c/0x640 drivers/base/dd.c:1295
>> page_owner free stack trace missing
>>
>> Memory state around the buggy address:
>> ffff88816fee8700: 00 00 00 00 00 00 00 fc fc fc fc fc fc fc fc fc
>> ffff88816fee8780: 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc fc
>> >ffff88816fee8800: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
>> ^
>> ffff88816fee8880: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
>> ffff88816fee8900: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
>> ==================================================================
>>
>>
>> ***
>>
>> 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: 9716c086c8e8b141d35aa61f2e96a2e83de212a7
>> 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/ddf6ee7c-dfa8-4383-b004-10140edc081c/config
>> syz repro:
>> https://ci.syzbot.org/findings/57c0b75a-8922-4dc1-9a20-ca947564792b/syz_repro
>>
>> ==================================================================
>> BUG: KASAN: use-after-free in ext4_dirent_get_data_len fs/ext4/ext4.h:4069
>> [inline]
>> BUG: KASAN: use-after-free in ext4_dir_entry_len fs/ext4/ext4.h:4096
>> [inline]
>> BUG: KASAN: use-after-free in __ext4_check_dir_entry+0x65a/0xc40
>> fs/ext4/dir.c:96
>> Read of size 1 at addr ffff88816be85045 by task syz.2.21/5880
>>
>> CPU: 1 UID: 0 PID: 5880 Comm: syz.2.21 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+0x55/0x1e0 mm/kasan/report.c:378
>> print_report+0x58/0x70 mm/kasan/report.c:482
>> kasan_report+0x117/0x150 mm/kasan/report.c:595
>> ext4_dirent_get_data_len fs/ext4/ext4.h:4069 [inline]
>> ext4_dir_entry_len fs/ext4/ext4.h:4096 [inline]
>> __ext4_check_dir_entry+0x65a/0xc40 fs/ext4/dir.c:96
>> ext4_find_dest_de+0x136/0x770 fs/ext4/namei.c:2203
>> ext4_add_dirent_to_inline+0xcf/0x430 fs/ext4/inline.c:984
>> ext4_try_add_inline_entry+0x235/0x8e0 fs/ext4/inline.c:1213
>> __ext4_add_entry+0x390/0x1f40 fs/ext4/namei.c:2529
>> ext4_add_entry fs/ext4/namei.c:2613 [inline]
>> ext4_add_nondir+0x111/0x310 fs/ext4/namei.c:2936
>> ext4_create+0x2e9/0x470 fs/ext4/namei.c:2982
>> lookup_open fs/namei.c:4511 [inline]
>> open_last_lookups fs/namei.c:4611 [inline]
>> path_openat+0x1395/0x3860 fs/namei.c:4855
>> do_file_open+0x23e/0x4a0 fs/namei.c:4887
>> do_sys_openat2+0x113/0x200 fs/open.c:1364
>> do_sys_open fs/open.c:1370 [inline]
>> __do_sys_openat fs/open.c:1386 [inline]
>> __se_sys_openat fs/open.c:1381 [inline]
>> __x64_sys_openat+0x138/0x170 fs/open.c:1381
>> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
>> do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
>> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>> RIP: 0033:0x7f5713b9ce59
>> 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:00007fff672b25f8 EFLAGS: 00000246 ORIG_RAX: 0000000000000101
>> RAX: ffffffffffffffda RBX: 00007f5713e15fa0 RCX: 00007f5713b9ce59
>> RDX: 0000000000042042 RSI: 0000200000000080 RDI: 0000000000000004
>> RBP: 00007f5713c32d6f R08: 0000000000000000 R09: 0000000000000000
>> R10: 000000000000014a R11: 0000000000000246 R12: 0000000000000000
>> R13: 00007f5713e15fac R14: 00007f5713e15fa0 R15: 00007f5713e15fa0
>> </TASK>
>>
>> The buggy address belongs to the physical page:
>> page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x16be85
>> flags: 0x57ff00000000000(node=1|zone=2|lastcpupid=0x7ff)
>> page_type: f0(buddy)
>> raw: 057ff00000000000 ffffea0005afa0c8 ffffea0005afa1c8 0000000000000000
>> raw: 0000000000000000 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
>> 0xcc0(GFP_KERNEL), pid 5630, tgid 5630 (syz-executor), ts 67290853657,
>> free_ts 69321168948
>> set_page_owner include/linux/page_owner.h:32 [inline]
>> post_alloc_hook+0x22d/0x280 mm/page_alloc.c:1853
>> prep_new_page mm/page_alloc.c:1861 [inline]
>> get_page_from_freelist+0x2593/0x2610 mm/page_alloc.c:3941
>> __alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5221
>> __alloc_pages_noprof+0x10/0x100 mm/page_alloc.c:5255
>> alloc_pages_bulk_noprof+0x5ff/0x7c0 mm/page_alloc.c:5175
>> ___alloc_pages_bulk mm/kasan/shadow.c:345 [inline]
>> __kasan_populate_vmalloc_do mm/kasan/shadow.c:370 [inline]
>> __kasan_populate_vmalloc+0xc1/0x1d0 mm/kasan/shadow.c:424
>> kasan_populate_vmalloc include/linux/kasan.h:580 [inline]
>> alloc_vmap_area+0xd47/0x1480 mm/vmalloc.c:2123
>> __get_vm_area_node+0x1f8/0x300 mm/vmalloc.c:3226
>> __vmalloc_node_range_noprof+0x36a/0x1750 mm/vmalloc.c:4024
>> vmalloc_user_noprof+0xad/0xe0 mm/vmalloc.c:4218
>> kcov_ioctl+0x55/0x620 kernel/kcov.c:726
>> vfs_ioctl fs/ioctl.c:51 [inline]
>> __do_sys_ioctl fs/ioctl.c:597 [inline]
>> __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
>> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
>> do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
>> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>> page last free pid 5693 tgid 5693 stack trace:
>> reset_page_owner include/linux/page_owner.h:25 [inline]
>> __free_pages_prepare mm/page_alloc.c:1397 [inline]
>> __free_frozen_pages+0xc1c/0xd30 mm/page_alloc.c:2938
>> kasan_depopulate_vmalloc_pte+0x6d/0x90 mm/kasan/shadow.c:484
>> apply_to_pte_range mm/memory.c:3338 [inline]
>> apply_to_pmd_range mm/memory.c:3382 [inline]
>> apply_to_pud_range mm/memory.c:3418 [inline]
>> apply_to_p4d_range mm/memory.c:3454 [inline]
>> __apply_to_page_range+0xbdc/0x1420 mm/memory.c:3490
>> __kasan_release_vmalloc+0xa2/0xd0 mm/kasan/shadow.c:602
>> kasan_release_vmalloc include/linux/kasan.h:593 [inline]
>> kasan_release_vmalloc_node mm/vmalloc.c:2284 [inline]
>> purge_vmap_node+0x220/0x960 mm/vmalloc.c:2306
>> __purge_vmap_area_lazy+0x779/0xb40 mm/vmalloc.c:2396
>> drain_vmap_area_work+0x27/0x40 mm/vmalloc.c:2430
>> process_one_work kernel/workqueue.c:3314 [inline]
>> process_scheduled_works+0xb5d/0x1860 kernel/workqueue.c:3397
>> worker_thread+0xa53/0xfc0 kernel/workqueue.c:3478
>> kthread+0x389/0x470 kernel/kthread.c:436
>> ret_from_fork+0x514/0xb70 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:
>> ffff88816be84f00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>> ffff88816be84f80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>> >ffff88816be85000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
>> ^
>> ffff88816be85080: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
>> ffff88816be85100: 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.
>>
>>
>
> --
> You received this message because you are subscribed to the Google Groups "syzkaller-bugs" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to syzkaller-bugs+unsubscribe@googlegroups.com.
> To view this discussion visit https://groups.google.com/d/msgid/syzkaller-bugs/CA%2BrD4x_2wXOP%3D4RwPY-A2vJjK4Vv9hGUSVFzprCe1H%2B8MTOKhA%40mail.gmail.com.
^ permalink raw reply
* Re: [syzbot ci] Re: Data in direntry (dirdata) feature
From: Artem Blagodarenko @ 2026-06-19 14:10 UTC (permalink / raw)
To: adilger, artem.blagodarenko, linux-ext4, pravin.shelar
Cc: syzbot, syzkaller-bugs
In-Reply-To: <6a2a8e0d.3b0a2d4e.8c8d1.000f.GAE@google.com>
[-- Attachment #1.1: Type: text/plain, Size: 43669 bytes --]
Thanks for the report. The attached patch addresses the issues found in
the dirdata series review (dx_get_dx_info/get_dx_countlimit blocksize
fallback, dfid parameter shadowing in ext4_dirdata_get, and the unsafe
delete-before-add in EXT4_IOC_SET_LUFID).
#syz test
On Thu, Jun 11, 2026 11:29 AM, syzbot ci <
syzbot+cid7b922cb3d448114@syzkaller.appspotmail.com> wrote:
> syzbot ci has tested the following series
>
> [v2] Data in direntry (dirdata) feature
>
> https://lore.kernel.org/all/20260610152417.13576-1-ablagodarenko@thelustrecollective.com
> * [PATCH v2 01/10] ext4: replace ext4_dir_entry with ext4_dir_entry_2
> * [PATCH v2 02/10] ext4: add ext4_dir_entry_is_tail()
> * [PATCH v2 03/10] ext4: refactor dx_root to support variable dirent sizes
> * [PATCH v2 04/10] ext4: add dirdata format definitions and access helpers
> * [PATCH v2 05/10] ext4: preserve dirdata bits in get_dtype()
> * [PATCH v2 06/10] ext4: add ext4_dir_entry_len() and harden dirdata
> parsing
> * [PATCH v2 07/10] ext4: rename ext4_dir_rec_len() and clarify dirdata
> usage
> * [PATCH v2 08/10] ext4: dirdata feature
> * [PATCH v2 09/10] ext4: add dirdata set/get helpers
> * [PATCH v2 10/10] ext4: Add EXT4_IOC_SET_LUFID ioctl for setting LUFID on
> directory entries
>
> and found the following issues:
> * KASAN: slab-out-of-bounds Read in __ext4_check_dir_entry
> * KASAN: slab-out-of-bounds Read in ext4_inlinedir_to_tree
> * KASAN: slab-use-after-free Read in __ext4_check_dir_entry
> * KASAN: slab-use-after-free Read in ext4_inlinedir_to_tree
> * KASAN: use-after-free Read in __ext4_check_dir_entry
>
> Full report is available here:
> https://ci.syzbot.org/series/5bf0e2fa-2e68-4532-8396-4568879b2788
>
> ***
>
> 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: 9716c086c8e8b141d35aa61f2e96a2e83de212a7
> 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/ddf6ee7c-dfa8-4383-b004-10140edc081c/config
> syz repro:
> https://ci.syzbot.org/findings/b0854918-13f9-49dd-ab30-12154f0debe2/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:4069 [inline]
> BUG: KASAN: slab-out-of-bounds in ext4_dir_entry_len fs/ext4/ext4.h:4096
> [inline]
> BUG: KASAN: slab-out-of-bounds in __ext4_check_dir_entry+0x65a/0xc40
> fs/ext4/dir.c:96
> Read of size 1 at addr ffff8881022db7f5 by task syz.0.23/5815
>
> CPU: 1 UID: 0 PID: 5815 Comm: syz.0.23 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+0x55/0x1e0 mm/kasan/report.c:378
> print_report+0x58/0x70 mm/kasan/report.c:482
> kasan_report+0x117/0x150 mm/kasan/report.c:595
> ext4_dirent_get_data_len fs/ext4/ext4.h:4069 [inline]
> ext4_dir_entry_len fs/ext4/ext4.h:4096 [inline]
> __ext4_check_dir_entry+0x65a/0xc40 fs/ext4/dir.c:96
> ext4_check_all_de+0x66/0x150 fs/ext4/dir.c:657
> ext4_convert_inline_data_nolock+0x1b7/0x990 fs/ext4/inline.c:1121
> ext4_try_add_inline_entry+0x604/0x8e0 fs/ext4/inline.c:1247
> __ext4_add_entry+0x390/0x1f40 fs/ext4/namei.c:2529
> ext4_add_entry fs/ext4/namei.c:2613 [inline]
> ext4_mkdir+0x5e5/0xce0 fs/ext4/namei.c:3175
> vfs_mkdir+0x413/0x630 fs/namei.c:5271
> filename_mkdirat+0x285/0x510 fs/namei.c:5304
> __do_sys_mkdirat fs/namei.c:5325 [inline]
> __se_sys_mkdirat+0x35/0x150 fs/namei.c:5322
> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
> do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
> RIP: 0033:0x7f669359bcc7
> Code: 00 66 90 48 89 f2 b9 00 01 00 00 48 89 fe bf 9c ff ff ff e9 db f7 ff
> ff 66 2e 0f 1f 84 00 00 00 00 00 90 b8 02 01 00 00 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:00007ffd42381d38 EFLAGS: 00000246 ORIG_RAX: 0000000000000102
> RAX: ffffffffffffffda RBX: 00007ffd42381dc0 RCX: 00007f669359bcc7
> RDX: 00000000000001ff RSI: 0000200000001200 RDI: 00000000ffffff9c
> RBP: 00002000000024c0 R08: 0000200000000240 R09: 0000000000000000
> R10: 00002000000024c0 R11: 0000000000000246 R12: 0000200000001200
> R13: 00007ffd42381d80 R14: 0000000000000000 R15: 0000000000000000
> </TASK>
>
> Allocated by task 5066:
> 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]
> __kmalloc_cache_noprof+0x31c/0x660 mm/slub.c:5420
> kmalloc_noprof include/linux/slab.h:950 [inline]
> kzalloc_noprof include/linux/slab.h:1188 [inline]
> kernfs_get_open_node fs/kernfs/file.c:543 [inline]
> kernfs_fop_open+0x862/0xda0 fs/kernfs/file.c:718
> do_dentry_open+0x822/0x13a0 fs/open.c:947
> vfs_open+0x3b/0x340 fs/open.c:1079
> do_open fs/namei.c:4699 [inline]
> path_openat+0x2e08/0x3860 fs/namei.c:4858
> do_file_open+0x23e/0x4a0 fs/namei.c:4887
> do_sys_openat2+0x113/0x200 fs/open.c:1364
> do_sys_open fs/open.c:1370 [inline]
> __do_sys_openat fs/open.c:1386 [inline]
> __se_sys_openat fs/open.c:1381 [inline]
> __x64_sys_openat+0x138/0x170 fs/open.c:1381
> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
> do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>
> Last potentially related work creation:
> kasan_save_stack+0x3e/0x60 mm/kasan/common.c:57
> kasan_record_aux_stack+0xbd/0xd0 mm/kasan/generic.c:556
> kvfree_call_rcu+0x100/0x430 mm/slab_common.c:1970
> kernfs_unlink_open_file+0x3fe/0x4b0 fs/kernfs/file.c:604
> kernfs_fop_release+0x2eb/0x440 fs/kernfs/file.c:783
> __fput+0x44f/0xa60 fs/file_table.c:510
> fput_close_sync+0x11f/0x240 fs/file_table.c:615
> __do_sys_close fs/open.c:1507 [inline]
> __se_sys_close fs/open.c:1492 [inline]
> __x64_sys_close+0x7e/0x110 fs/open.c:1492
> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
> do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>
> The buggy address belongs to the object at ffff8881022db700
> which belongs to the cache kmalloc-128 of size 128
> The buggy address is located 117 bytes to the right of
> allocated 128-byte region [ffff8881022db700, ffff8881022db780)
>
> The buggy address belongs to the physical page:
> page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x1022db
> flags: 0x17ff00000000000(node=0|zone=2|lastcpupid=0x7ff)
> page_type: f5(slab)
> raw: 017ff00000000000 ffff888100041a00 dead000000000100 dead000000000122
> raw: 0000000000000000 0000000800100010 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
> 0xd2000(__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 0,
> tgid 0 (swapper/0), ts 2408938923, free_ts 0
> set_page_owner include/linux/page_owner.h:32 [inline]
> post_alloc_hook+0x22d/0x280 mm/page_alloc.c:1853
> prep_new_page mm/page_alloc.c:1861 [inline]
> get_page_from_freelist+0x2593/0x2610 mm/page_alloc.c:3941
> __alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5221
> alloc_slab_page mm/slub.c:3278 [inline]
> allocate_slab+0x77/0x660 mm/slub.c:3467
> new_slab mm/slub.c:3525 [inline]
> refill_objects+0x339/0x3d0 mm/slub.c:7272
> refill_sheaf mm/slub.c:2816 [inline]
> __pcs_replace_empty_main+0x321/0x720 mm/slub.c:4652
> alloc_from_pcs mm/slub.c:4750 [inline]
> slab_alloc_node mm/slub.c:4884 [inline]
> __do_kmalloc_node mm/slub.c:5295 [inline]
> __kmalloc_noprof+0x474/0x760 mm/slub.c:5308
> kmalloc_noprof include/linux/slab.h:954 [inline]
> kzalloc_noprof include/linux/slab.h:1188 [inline]
> __alloc_empty_sheaf mm/slub.c:2768 [inline]
> alloc_empty_sheaf mm/slub.c:2783 [inline]
> __pcs_replace_empty_main+0x2df/0x720 mm/slub.c:4647
> alloc_from_pcs mm/slub.c:4750 [inline]
> slab_alloc_node mm/slub.c:4884 [inline]
> kmem_cache_alloc_noprof+0x37d/0x650 mm/slub.c:4906
> dup_fd+0x55/0xb40 fs/file.c:390
> copy_files+0xc8/0x120 kernel/fork.c:1639
> copy_process+0x1d94/0x4440 kernel/fork.c:2252
> kernel_clone+0x2d7/0x940 kernel/fork.c:2722
> user_mode_thread+0x110/0x180 kernel/fork.c:2798
> rest_init+0x23/0x300 init/main.c:727
> start_kernel+0x38a/0x3e0 init/main.c:1220
> page_owner free stack trace missing
>
> Memory state around the buggy address:
> ffff8881022db680: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
> ffff8881022db700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
> >ffff8881022db780: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
> ^
> ffff8881022db800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
> ffff8881022db880: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
> ==================================================================
>
>
> ***
>
> KASAN: slab-out-of-bounds Read in ext4_inlinedir_to_tree
>
> tree: torvalds
> URL:
> https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux
> base: 9716c086c8e8b141d35aa61f2e96a2e83de212a7
> 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/ddf6ee7c-dfa8-4383-b004-10140edc081c/config
> syz repro:
> https://ci.syzbot.org/findings/2dff870b-f382-4c93-8d8d-b2291d921224/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: slab-out-of-bounds in ext4_dir_entry_len fs/ext4/ext4.h:4095
> [inline]
> BUG: KASAN: slab-out-of-bounds in ext4_inlinedir_to_tree+0xda5/0x10d0
> fs/ext4/inline.c:1335
> Read of size 2 at addr ffff888115a3183c by task syz.1.18/5839
>
> CPU: 1 UID: 0 PID: 5839 Comm: syz.1.18 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+0x55/0x1e0 mm/kasan/report.c:378
> print_report+0x58/0x70 mm/kasan/report.c:482
> kasan_report+0x117/0x150 mm/kasan/report.c:595
> ext4_dir_entry_len fs/ext4/ext4.h:4095 [inline]
> ext4_inlinedir_to_tree+0xda5/0x10d0 fs/ext4/inline.c:1335
> ext4_htree_fill_tree+0x517/0x1230 fs/ext4/namei.c:1182
> ext4_dx_readdir fs/ext4/dir.c:600 [inline]
> ext4_readdir+0x2db4/0x3640 fs/ext4/dir.c:146
> iterate_dir+0x399/0x570 fs/readdir.c:110
> __do_sys_getdents64 fs/readdir.c:399 [inline]
> __se_sys_getdents64+0xf1/0x280 fs/readdir.c:384
> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
> do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
> RIP: 0033:0x7f3e02b9ce59
> 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:00007f3e03ad5028 EFLAGS: 00000246 ORIG_RAX: 00000000000000d9
> RAX: ffffffffffffffda RBX: 00007f3e02e15fa0 RCX: 00007f3e02b9ce59
> RDX: 0000000000001000 RSI: 0000200000000f80 RDI: 0000000000000004
> RBP: 00007f3e02c32d6f R08: 0000000000000000 R09: 0000000000000000
> R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
> R13: 00007f3e02e16038 R14: 00007f3e02e15fa0 R15: 00007ffcaa902298
> </TASK>
>
> Allocated by task 5839:
> 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:5296 [inline]
> __kmalloc_noprof+0x35c/0x760 mm/slub.c:5308
> kmalloc_noprof include/linux/slab.h:954 [inline]
> ext4_inlinedir_to_tree+0x312/0x10d0 fs/ext4/inline.c:1292
> ext4_htree_fill_tree+0x517/0x1230 fs/ext4/namei.c:1182
> ext4_dx_readdir fs/ext4/dir.c:600 [inline]
> ext4_readdir+0x2db4/0x3640 fs/ext4/dir.c:146
> iterate_dir+0x399/0x570 fs/readdir.c:110
> __do_sys_getdents64 fs/readdir.c:399 [inline]
> __se_sys_getdents64+0xf1/0x280 fs/readdir.c:384
> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
> do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>
> The buggy address belongs to the object at ffff888115a31800
> which belongs to the cache kmalloc-64 of size 64
> The buggy address is located 0 bytes to the right of
> allocated 60-byte region [ffff888115a31800, ffff888115a3183c)
>
> The buggy address belongs to the physical page:
> page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x115a31
> 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
> 0xd2c40(GFP_NOFS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC),
> pid 5051, tgid 5051 (acpid), ts 27203740677, free_ts 27201732767
> set_page_owner include/linux/page_owner.h:32 [inline]
> post_alloc_hook+0x22d/0x280 mm/page_alloc.c:1853
> prep_new_page mm/page_alloc.c:1861 [inline]
> get_page_from_freelist+0x2593/0x2610 mm/page_alloc.c:3941
> __alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5221
> alloc_slab_page mm/slub.c:3278 [inline]
> allocate_slab+0x77/0x660 mm/slub.c:3467
> new_slab mm/slub.c:3525 [inline]
> refill_objects+0x339/0x3d0 mm/slub.c:7272
> refill_sheaf mm/slub.c:2816 [inline]
> __pcs_replace_empty_main+0x321/0x720 mm/slub.c:4652
> alloc_from_pcs mm/slub.c:4750 [inline]
> slab_alloc_node mm/slub.c:4884 [inline]
> __do_kmalloc_node mm/slub.c:5295 [inline]
> __kmalloc_noprof+0x474/0x760 mm/slub.c:5308
> kmalloc_noprof include/linux/slab.h:954 [inline]
> kzalloc_noprof include/linux/slab.h:1188 [inline]
> tomoyo_get_name+0x20c/0x590 security/tomoyo/memory.c:173
> tomoyo_parse_name_union+0xd9/0x130 security/tomoyo/util.c:260
> tomoyo_update_path_acl security/tomoyo/file.c:399 [inline]
> tomoyo_write_file+0x3a6/0xc50 security/tomoyo/file.c:1027
> tomoyo_write_domain2 security/tomoyo/common.c:1160 [inline]
> tomoyo_add_entry security/tomoyo/common.c:2177 [inline]
> tomoyo_supervisor+0x1208/0x1570 security/tomoyo/common.c:2238
> tomoyo_audit_path_log security/tomoyo/file.c:169 [inline]
> tomoyo_path_permission+0x25a/0x380 security/tomoyo/file.c:592
> tomoyo_check_open_permission+0x2b2/0x470 security/tomoyo/file.c:782
> security_file_open+0xa9/0x240 security/security.c:2739
> do_dentry_open+0x4a8/0x13a0 fs/open.c:924
> vfs_open+0x3b/0x340 fs/open.c:1079
> page last free pid 15 tgid 15 stack trace:
> reset_page_owner include/linux/page_owner.h:25 [inline]
> __free_pages_prepare mm/page_alloc.c:1397 [inline]
> __free_frozen_pages+0xc1c/0xd30 mm/page_alloc.c:2938
> __tlb_remove_table_free mm/mmu_gather.c:228 [inline]
> tlb_remove_table_rcu+0x85/0x100 mm/mmu_gather.c:291
> rcu_do_batch kernel/rcu/tree.c:2617 [inline]
> rcu_core+0x7cd/0x1070 kernel/rcu/tree.c:2869
> handle_softirqs+0x22a/0x840 kernel/softirq.c:622
> run_ksoftirqd+0x36/0x60 kernel/softirq.c:1076
> smpboot_thread_fn+0x541/0xa50 kernel/smpboot.c:160
> kthread+0x389/0x470 kernel/kthread.c:436
> ret_from_fork+0x514/0xb70 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:
> ffff888115a31700: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
> ffff888115a31780: 00 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc
> >ffff888115a31800: 00 00 00 00 00 00 00 04 fc fc fc fc fc fc fc fc
> ^
> ffff888115a31880: 00 00 00 00 00 00 02 fc fc fc fc fc fc fc fc fc
> ffff888115a31900: fa fb fb fb fb fb fb fb 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: 9716c086c8e8b141d35aa61f2e96a2e83de212a7
> 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/ddf6ee7c-dfa8-4383-b004-10140edc081c/config
> syz repro:
> https://ci.syzbot.org/findings/f1d48ea1-6e87-4d64-9c13-8bf8aed109fc/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-use-after-free in ext4_dirent_get_data_len
> fs/ext4/ext4.h:4069 [inline]
> BUG: KASAN: slab-use-after-free in ext4_dir_entry_len fs/ext4/ext4.h:4096
> [inline]
> BUG: KASAN: slab-use-after-free in __ext4_check_dir_entry+0x65a/0xc40
> fs/ext4/dir.c:96
> Read of size 1 at addr ffff888114d8c045 by task syz.0.20/5821
>
> CPU: 1 UID: 0 PID: 5821 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+0x55/0x1e0 mm/kasan/report.c:378
> print_report+0x58/0x70 mm/kasan/report.c:482
> kasan_report+0x117/0x150 mm/kasan/report.c:595
> ext4_dirent_get_data_len fs/ext4/ext4.h:4069 [inline]
> ext4_dir_entry_len fs/ext4/ext4.h:4096 [inline]
> __ext4_check_dir_entry+0x65a/0xc40 fs/ext4/dir.c:96
> ext4_find_dest_de+0x136/0x770 fs/ext4/namei.c:2203
> ext4_add_dirent_to_inline+0xcf/0x430 fs/ext4/inline.c:984
> ext4_try_add_inline_entry+0x235/0x8e0 fs/ext4/inline.c:1213
> __ext4_add_entry+0x390/0x1f40 fs/ext4/namei.c:2529
> ext4_add_entry fs/ext4/namei.c:2613 [inline]
> ext4_add_nondir+0x111/0x310 fs/ext4/namei.c:2936
> ext4_create+0x2e9/0x470 fs/ext4/namei.c:2982
> lookup_open fs/namei.c:4511 [inline]
> open_last_lookups fs/namei.c:4611 [inline]
> path_openat+0x1395/0x3860 fs/namei.c:4855
> do_file_open+0x23e/0x4a0 fs/namei.c:4887
> do_sys_openat2+0x113/0x200 fs/open.c:1364
> do_sys_open fs/open.c:1370 [inline]
> __do_sys_openat fs/open.c:1386 [inline]
> __se_sys_openat fs/open.c:1381 [inline]
> __x64_sys_openat+0x138/0x170 fs/open.c:1381
> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
> do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
> RIP: 0033:0x7f922219ce59
> 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:00007f9223137028 EFLAGS: 00000246 ORIG_RAX: 0000000000000101
> RAX: ffffffffffffffda RBX: 00007f9222415fa0 RCX: 00007f922219ce59
> RDX: 0000000000042042 RSI: 0000200000000080 RDI: 0000000000000004
> RBP: 00007f9222232d6f R08: 0000000000000000 R09: 0000000000000000
> R10: 000000000000014a R11: 0000000000000246 R12: 0000000000000000
> R13: 00007f9222416038 R14: 00007f9222415fa0 R15: 00007ffd01a2d448
> </TASK>
>
> Allocated by task 5484:
> kasan_save_stack mm/kasan/common.c:57 [inline]
> kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
> unpoison_slab_object mm/kasan/common.c:340 [inline]
> __kasan_slab_alloc+0x6c/0x80 mm/kasan/common.c:366
> kasan_slab_alloc include/linux/kasan.h:253 [inline]
> slab_post_alloc_hook mm/slub.c:4570 [inline]
> slab_alloc_node mm/slub.c:4899 [inline]
> kmem_cache_alloc_node_noprof+0x384/0x690 mm/slub.c:4951
> kmalloc_reserve net/core/skbuff.c:613 [inline]
> __alloc_skb+0x27d/0x7d0 net/core/skbuff.c:713
> alloc_skb include/linux/skbuff.h:1385 [inline]
> nlmsg_new include/net/netlink.h:1055 [inline]
> mpls_netconf_notify_devconf+0x46/0x100 net/mpls/af_mpls.c:1217
> mpls_dev_notify+0xb2d/0xd10 net/mpls/af_mpls.c:1691
> notifier_call_chain+0x1ad/0x3d0 kernel/notifier.c:85
> call_netdevice_notifiers_extack net/core/dev.c:2287 [inline]
> call_netdevice_notifiers net/core/dev.c:2301 [inline]
> unregister_netdevice_many_notify+0x17a5/0x22c0 net/core/dev.c:12421
> ops_exit_rtnl_list net/core/net_namespace.c:187 [inline]
> ops_undo_list+0x3d3/0x940 net/core/net_namespace.c:248
> cleanup_net+0x56b/0x800 net/core/net_namespace.c:702
> process_one_work kernel/workqueue.c:3314 [inline]
> process_scheduled_works+0xb5d/0x1860 kernel/workqueue.c:3397
> worker_thread+0xa53/0xfc0 kernel/workqueue.c:3478
> kthread+0x389/0x470 kernel/kthread.c:436
> ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
> ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
>
> Freed by task 5484:
> 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:2689 [inline]
> slab_free mm/slub.c:6251 [inline]
> kfree+0x1c5/0x640 mm/slub.c:6566
> skb_kfree_head net/core/skbuff.c:1075 [inline]
> skb_free_head net/core/skbuff.c:1087 [inline]
> skb_release_data+0x828/0xa60 net/core/skbuff.c:1114
> skb_release_all net/core/skbuff.c:1189 [inline]
> __kfree_skb+0x5d/0x210 net/core/skbuff.c:1203
> netlink_broadcast_filtered+0xe18/0xf20 net/netlink/af_netlink.c:1540
> nlmsg_multicast_filtered include/net/netlink.h:1165 [inline]
> nlmsg_multicast include/net/netlink.h:1184 [inline]
> nlmsg_notify+0xf0/0x1a0 net/netlink/af_netlink.c:2598
> mpls_dev_notify+0xb2d/0xd10 net/mpls/af_mpls.c:1691
> notifier_call_chain+0x1ad/0x3d0 kernel/notifier.c:85
> call_netdevice_notifiers_extack net/core/dev.c:2287 [inline]
> call_netdevice_notifiers net/core/dev.c:2301 [inline]
> unregister_netdevice_many_notify+0x17a5/0x22c0 net/core/dev.c:12421
> ops_exit_rtnl_list net/core/net_namespace.c:187 [inline]
> ops_undo_list+0x3d3/0x940 net/core/net_namespace.c:248
> cleanup_net+0x56b/0x800 net/core/net_namespace.c:702
> process_one_work kernel/workqueue.c:3314 [inline]
> process_scheduled_works+0xb5d/0x1860 kernel/workqueue.c:3397
> worker_thread+0xa53/0xfc0 kernel/workqueue.c:3478
> kthread+0x389/0x470 kernel/kthread.c:436
> ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
> ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
>
> The buggy address belongs to the object at ffff888114d8c000
> which belongs to the cache skbuff_small_head of size 704
> The buggy address is located 69 bytes inside of
> freed 704-byte region [ffff888114d8c000, ffff888114d8c2c0)
>
> The buggy address belongs to the physical page:
> page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x114d8c
> head: order:2 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 ffff888160416b40 dead000000000100 dead000000000122
> raw: 0000000000000000 0000000800120012 00000000f5000000 0000000000000000
> head: 017ff00000000040 ffff888160416b40 dead000000000100 dead000000000122
> head: 0000000000000000 0000000800120012 00000000f5000000 0000000000000000
> head: 017ff00000000002 ffffffffffffff01 00000000ffffffff 00000000ffffffff
> head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000004
> page dumped because: kasan: bad access detected
> page_owner tracks the page as allocated
> page last allocated via order 2, migratetype Unmovable, gfp_mask
> 0xd20c0(__GFP_IO|__GFP_FS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC),
> pid 5484, tgid 5484 (kworker/u8:2), ts 72573003529, free_ts 72546506446
> set_page_owner include/linux/page_owner.h:32 [inline]
> post_alloc_hook+0x22d/0x280 mm/page_alloc.c:1853
> prep_new_page mm/page_alloc.c:1861 [inline]
> get_page_from_freelist+0x2593/0x2610 mm/page_alloc.c:3941
> __alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5221
> alloc_slab_page mm/slub.c:3278 [inline]
> allocate_slab+0x77/0x660 mm/slub.c:3467
> new_slab mm/slub.c:3525 [inline]
> refill_objects+0x339/0x3d0 mm/slub.c:7272
> refill_sheaf mm/slub.c:2816 [inline]
> __pcs_replace_empty_main+0x321/0x720 mm/slub.c:4652
> alloc_from_pcs mm/slub.c:4750 [inline]
> slab_alloc_node mm/slub.c:4884 [inline]
> kmem_cache_alloc_node_noprof+0x441/0x690 mm/slub.c:4951
> kmalloc_reserve net/core/skbuff.c:613 [inline]
> __alloc_skb+0x27d/0x7d0 net/core/skbuff.c:713
> alloc_skb include/linux/skbuff.h:1385 [inline]
> nlmsg_new include/net/netlink.h:1055 [inline]
> mpls_netconf_notify_devconf+0x46/0x100 net/mpls/af_mpls.c:1217
> mpls_dev_notify+0xb2d/0xd10 net/mpls/af_mpls.c:1691
> notifier_call_chain+0x1ad/0x3d0 kernel/notifier.c:85
> call_netdevice_notifiers_extack net/core/dev.c:2287 [inline]
> call_netdevice_notifiers net/core/dev.c:2301 [inline]
> unregister_netdevice_many_notify+0x17a5/0x22c0 net/core/dev.c:12421
> ops_exit_rtnl_list net/core/net_namespace.c:187 [inline]
> ops_undo_list+0x3d3/0x940 net/core/net_namespace.c:248
> cleanup_net+0x56b/0x800 net/core/net_namespace.c:702
> process_one_work kernel/workqueue.c:3314 [inline]
> process_scheduled_works+0xb5d/0x1860 kernel/workqueue.c:3397
> worker_thread+0xa53/0xfc0 kernel/workqueue.c:3478
> page last free pid 5484 tgid 5484 stack trace:
> reset_page_owner include/linux/page_owner.h:25 [inline]
> __free_pages_prepare mm/page_alloc.c:1397 [inline]
> __free_frozen_pages+0xc1c/0xd30 mm/page_alloc.c:2938
> stack_depot_save_flags+0x40e/0x810 lib/stackdepot.c:735
> kasan_save_stack mm/kasan/common.c:58 [inline]
> kasan_save_track+0x4f/0x80 mm/kasan/common.c:78
> unpoison_slab_object mm/kasan/common.c:340 [inline]
> __kasan_slab_alloc+0x6c/0x80 mm/kasan/common.c:366
> kasan_slab_alloc include/linux/kasan.h:253 [inline]
> slab_post_alloc_hook mm/slub.c:4570 [inline]
> slab_alloc_node mm/slub.c:4899 [inline]
> kmem_cache_alloc_noprof+0x2bc/0x650 mm/slub.c:4906
> kmem_alloc_batch lib/debugobjects.c:371 [inline]
> fill_pool+0x156/0x580 lib/debugobjects.c:420
> debug_objects_fill_pool lib/debugobjects.c:752 [inline]
> debug_object_activate+0x4a3/0x580 lib/debugobjects.c:841
> debug_rcu_head_queue kernel/rcu/rcu.h:236 [inline]
> __call_rcu_common kernel/rcu/tree.c:3116 [inline]
> call_rcu+0x43/0x890 kernel/rcu/tree.c:3251
> kernfs_put+0x259/0x520 fs/kernfs/dir.c:618
> kernfs_remove_by_name_ns+0xc8/0x140 fs/kernfs/dir.c:1799
> device_remove_class_symlinks+0x178/0x190 drivers/base/core.c:3479
> device_del+0x400/0x8f0 drivers/base/core.c:3881
> unregister_netdevice_many_notify+0x1d5f/0x22c0 net/core/dev.c:12456
> ops_exit_rtnl_list net/core/net_namespace.c:187 [inline]
> ops_undo_list+0x3d3/0x940 net/core/net_namespace.c:248
> cleanup_net+0x56b/0x800 net/core/net_namespace.c:702
> process_one_work kernel/workqueue.c:3314 [inline]
> process_scheduled_works+0xb5d/0x1860 kernel/workqueue.c:3397
>
> Memory state around the buggy address:
> ffff888114d8bf00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
> ffff888114d8bf80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
> >ffff888114d8c000: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
> ^
> ffff888114d8c080: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
> ffff888114d8c100: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
> ==================================================================
>
>
> ***
>
> KASAN: slab-use-after-free Read in ext4_inlinedir_to_tree
>
> tree: torvalds
> URL:
> https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux
> base: 9716c086c8e8b141d35aa61f2e96a2e83de212a7
> 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/ddf6ee7c-dfa8-4383-b004-10140edc081c/config
> syz repro:
> https://ci.syzbot.org/findings/f42da242-e16e-4f10-bf25-0bd7e192d989/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-use-after-free in ext4_dirent_get_data_len
> fs/ext4/ext4.h:4069 [inline]
> BUG: KASAN: slab-use-after-free in ext4_dir_entry_len fs/ext4/ext4.h:4096
> [inline]
> BUG: KASAN: slab-use-after-free in ext4_inlinedir_to_tree+0x94c/0x10d0
> fs/ext4/inline.c:1335
> Read of size 1 at addr ffff88816fee8825 by task syz.0.20/5867
>
> CPU: 1 UID: 0 PID: 5867 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+0x55/0x1e0 mm/kasan/report.c:378
> print_report+0x58/0x70 mm/kasan/report.c:482
> kasan_report+0x117/0x150 mm/kasan/report.c:595
> ext4_dirent_get_data_len fs/ext4/ext4.h:4069 [inline]
> ext4_dir_entry_len fs/ext4/ext4.h:4096 [inline]
> ext4_inlinedir_to_tree+0x94c/0x10d0 fs/ext4/inline.c:1335
> ext4_htree_fill_tree+0x517/0x1230 fs/ext4/namei.c:1182
> ext4_dx_readdir fs/ext4/dir.c:600 [inline]
> ext4_readdir+0x2db4/0x3640 fs/ext4/dir.c:146
> iterate_dir+0x399/0x570 fs/readdir.c:110
> __do_sys_getdents fs/readdir.c:319 [inline]
> __se_sys_getdents+0xf1/0x270 fs/readdir.c:304
> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
> do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
> RIP: 0033:0x7f010ad9ce59
> 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:00007f010bc0f028 EFLAGS: 00000246 ORIG_RAX: 000000000000004e
> RAX: ffffffffffffffda RBX: 00007f010b015fa0 RCX: 00007f010ad9ce59
> RDX: 0000000000000054 RSI: 0000000000000000 RDI: 0000000000000004
> RBP: 00007f010ae32d6f R08: 0000000000000000 R09: 0000000000000000
> R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
> R13: 00007f010b016038 R14: 00007f010b015fa0 R15: 00007ffd93577348
> </TASK>
>
> Allocated by task 5064:
> 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:5296 [inline]
> __kmalloc_noprof+0x35c/0x760 mm/slub.c:5308
> 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_path_perm+0x283/0x560 security/tomoyo/file.c:827
> security_inode_getattr+0x12b/0x310 security/security.c:1895
> vfs_getattr fs/stat.c:259 [inline]
> vfs_fstat fs/stat.c:281 [inline]
> vfs_fstatat+0xb4/0x170 fs/stat.c:371
> __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+0x174/0x580 arch/x86/entry/syscall_64.c:94
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>
> Freed by task 5064:
> 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:2689 [inline]
> slab_free mm/slub.c:6251 [inline]
> kfree+0x1c5/0x640 mm/slub.c:6566
> tomoyo_path_perm+0x403/0x560 security/tomoyo/file.c:847
> security_inode_getattr+0x12b/0x310 security/security.c:1895
> vfs_getattr fs/stat.c:259 [inline]
> vfs_fstat fs/stat.c:281 [inline]
> vfs_fstatat+0xb4/0x170 fs/stat.c:371
> __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+0x174/0x580 arch/x86/entry/syscall_64.c:94
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>
> The buggy address belongs to the object at ffff88816fee8800
> which belongs to the cache kmalloc-64 of size 64
> The buggy address is located 37 bytes inside of
> freed 64-byte region [ffff88816fee8800, ffff88816fee8840)
>
> The buggy address belongs to the physical page:
> page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x16fee8
> flags: 0x57ff00000000000(node=1|zone=2|lastcpupid=0x7ff)
> page_type: f5(slab)
> raw: 057ff00000000000 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 21294026082, free_ts 0
> set_page_owner include/linux/page_owner.h:32 [inline]
> post_alloc_hook+0x22d/0x280 mm/page_alloc.c:1853
> prep_new_page mm/page_alloc.c:1861 [inline]
> get_page_from_freelist+0x2593/0x2610 mm/page_alloc.c:3941
> __alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5221
> alloc_slab_page mm/slub.c:3278 [inline]
> allocate_slab+0x77/0x660 mm/slub.c:3467
> new_slab mm/slub.c:3525 [inline]
> refill_objects+0x339/0x3d0 mm/slub.c:7272
> refill_sheaf mm/slub.c:2816 [inline]
> __pcs_replace_empty_main+0x321/0x720 mm/slub.c:4652
> alloc_from_pcs mm/slub.c:4750 [inline]
> slab_alloc_node mm/slub.c:4884 [inline]
> __do_kmalloc_node mm/slub.c:5295 [inline]
> __kmalloc_noprof+0x474/0x760 mm/slub.c:5308
> kmalloc_noprof include/linux/slab.h:954 [inline]
> kzalloc_noprof include/linux/slab.h:1188 [inline]
> handler_new_ref+0x261/0x9c0 drivers/media/v4l2-core/v4l2-ctrls-core.c:1882
> v4l2_ctrl_add_handler+0x19f/0x290
> drivers/media/v4l2-core/v4l2-ctrls-core.c:2443
> vivid_create_controls+0x332d/0x3bd0
> drivers/media/test-drivers/vivid/vivid-ctrls.c:2072
> vivid_create_instance drivers/media/test-drivers/vivid/vivid-core.c:1933
> [inline]
> vivid_probe+0x4261/0x72b0
> drivers/media/test-drivers/vivid/vivid-core.c:2095
> platform_probe+0xf9/0x190 drivers/base/platform.c:1432
> call_driver_probe drivers/base/dd.c:-1 [inline]
> really_probe+0x267/0xaf0 drivers/base/dd.c:709
> __driver_probe_device+0x1ef/0x380 drivers/base/dd.c:871
> driver_probe_device+0x4f/0x240 drivers/base/dd.c:901
> __driver_attach+0x34c/0x640 drivers/base/dd.c:1295
> page_owner free stack trace missing
>
> Memory state around the buggy address:
> ffff88816fee8700: 00 00 00 00 00 00 00 fc fc fc fc fc fc fc fc fc
> ffff88816fee8780: 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc fc
> >ffff88816fee8800: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
> ^
> ffff88816fee8880: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
> ffff88816fee8900: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
> ==================================================================
>
>
> ***
>
> 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: 9716c086c8e8b141d35aa61f2e96a2e83de212a7
> 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/ddf6ee7c-dfa8-4383-b004-10140edc081c/config
> syz repro:
> https://ci.syzbot.org/findings/57c0b75a-8922-4dc1-9a20-ca947564792b/syz_repro
>
> ==================================================================
> BUG: KASAN: use-after-free in ext4_dirent_get_data_len fs/ext4/ext4.h:4069
> [inline]
> BUG: KASAN: use-after-free in ext4_dir_entry_len fs/ext4/ext4.h:4096
> [inline]
> BUG: KASAN: use-after-free in __ext4_check_dir_entry+0x65a/0xc40
> fs/ext4/dir.c:96
> Read of size 1 at addr ffff88816be85045 by task syz.2.21/5880
>
> CPU: 1 UID: 0 PID: 5880 Comm: syz.2.21 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+0x55/0x1e0 mm/kasan/report.c:378
> print_report+0x58/0x70 mm/kasan/report.c:482
> kasan_report+0x117/0x150 mm/kasan/report.c:595
> ext4_dirent_get_data_len fs/ext4/ext4.h:4069 [inline]
> ext4_dir_entry_len fs/ext4/ext4.h:4096 [inline]
> __ext4_check_dir_entry+0x65a/0xc40 fs/ext4/dir.c:96
> ext4_find_dest_de+0x136/0x770 fs/ext4/namei.c:2203
> ext4_add_dirent_to_inline+0xcf/0x430 fs/ext4/inline.c:984
> ext4_try_add_inline_entry+0x235/0x8e0 fs/ext4/inline.c:1213
> __ext4_add_entry+0x390/0x1f40 fs/ext4/namei.c:2529
> ext4_add_entry fs/ext4/namei.c:2613 [inline]
> ext4_add_nondir+0x111/0x310 fs/ext4/namei.c:2936
> ext4_create+0x2e9/0x470 fs/ext4/namei.c:2982
> lookup_open fs/namei.c:4511 [inline]
> open_last_lookups fs/namei.c:4611 [inline]
> path_openat+0x1395/0x3860 fs/namei.c:4855
> do_file_open+0x23e/0x4a0 fs/namei.c:4887
> do_sys_openat2+0x113/0x200 fs/open.c:1364
> do_sys_open fs/open.c:1370 [inline]
> __do_sys_openat fs/open.c:1386 [inline]
> __se_sys_openat fs/open.c:1381 [inline]
> __x64_sys_openat+0x138/0x170 fs/open.c:1381
> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
> do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
> RIP: 0033:0x7f5713b9ce59
> 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:00007fff672b25f8 EFLAGS: 00000246 ORIG_RAX: 0000000000000101
> RAX: ffffffffffffffda RBX: 00007f5713e15fa0 RCX: 00007f5713b9ce59
> RDX: 0000000000042042 RSI: 0000200000000080 RDI: 0000000000000004
> RBP: 00007f5713c32d6f R08: 0000000000000000 R09: 0000000000000000
> R10: 000000000000014a R11: 0000000000000246 R12: 0000000000000000
> R13: 00007f5713e15fac R14: 00007f5713e15fa0 R15: 00007f5713e15fa0
> </TASK>
>
> The buggy address belongs to the physical page:
> page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x16be85
> flags: 0x57ff00000000000(node=1|zone=2|lastcpupid=0x7ff)
> page_type: f0(buddy)
> raw: 057ff00000000000 ffffea0005afa0c8 ffffea0005afa1c8 0000000000000000
> raw: 0000000000000000 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
> 0xcc0(GFP_KERNEL), pid 5630, tgid 5630 (syz-executor), ts 67290853657,
> free_ts 69321168948
> set_page_owner include/linux/page_owner.h:32 [inline]
> post_alloc_hook+0x22d/0x280 mm/page_alloc.c:1853
> prep_new_page mm/page_alloc.c:1861 [inline]
> get_page_from_freelist+0x2593/0x2610 mm/page_alloc.c:3941
> __alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5221
> __alloc_pages_noprof+0x10/0x100 mm/page_alloc.c:5255
> alloc_pages_bulk_noprof+0x5ff/0x7c0 mm/page_alloc.c:5175
> ___alloc_pages_bulk mm/kasan/shadow.c:345 [inline]
> __kasan_populate_vmalloc_do mm/kasan/shadow.c:370 [inline]
> __kasan_populate_vmalloc+0xc1/0x1d0 mm/kasan/shadow.c:424
> kasan_populate_vmalloc include/linux/kasan.h:580 [inline]
> alloc_vmap_area+0xd47/0x1480 mm/vmalloc.c:2123
> __get_vm_area_node+0x1f8/0x300 mm/vmalloc.c:3226
> __vmalloc_node_range_noprof+0x36a/0x1750 mm/vmalloc.c:4024
> vmalloc_user_noprof+0xad/0xe0 mm/vmalloc.c:4218
> kcov_ioctl+0x55/0x620 kernel/kcov.c:726
> vfs_ioctl fs/ioctl.c:51 [inline]
> __do_sys_ioctl fs/ioctl.c:597 [inline]
> __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
> do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
> page last free pid 5693 tgid 5693 stack trace:
> reset_page_owner include/linux/page_owner.h:25 [inline]
> __free_pages_prepare mm/page_alloc.c:1397 [inline]
> __free_frozen_pages+0xc1c/0xd30 mm/page_alloc.c:2938
> kasan_depopulate_vmalloc_pte+0x6d/0x90 mm/kasan/shadow.c:484
> apply_to_pte_range mm/memory.c:3338 [inline]
> apply_to_pmd_range mm/memory.c:3382 [inline]
> apply_to_pud_range mm/memory.c:3418 [inline]
> apply_to_p4d_range mm/memory.c:3454 [inline]
> __apply_to_page_range+0xbdc/0x1420 mm/memory.c:3490
> __kasan_release_vmalloc+0xa2/0xd0 mm/kasan/shadow.c:602
> kasan_release_vmalloc include/linux/kasan.h:593 [inline]
> kasan_release_vmalloc_node mm/vmalloc.c:2284 [inline]
> purge_vmap_node+0x220/0x960 mm/vmalloc.c:2306
> __purge_vmap_area_lazy+0x779/0xb40 mm/vmalloc.c:2396
> drain_vmap_area_work+0x27/0x40 mm/vmalloc.c:2430
> process_one_work kernel/workqueue.c:3314 [inline]
> process_scheduled_works+0xb5d/0x1860 kernel/workqueue.c:3397
> worker_thread+0xa53/0xfc0 kernel/workqueue.c:3478
> kthread+0x389/0x470 kernel/kthread.c:436
> ret_from_fork+0x514/0xb70 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:
> ffff88816be84f00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
> ffff88816be84f80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
> >ffff88816be85000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
> ^
> ffff88816be85080: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
> ffff88816be85100: 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.
>
>
[-- Attachment #1.2: Type: text/html, Size: 49607 bytes --]
[-- Attachment #2: dirdata-syzbot-fix.patch --]
[-- Type: application/octet-stream, Size: 11438 bytes --]
From e3d5c74f1ec0fbefb9a4b9193a474614b98d640a Mon Sep 17 00:00:00 2001
From: Artem Blagodarenko <artem.blagodarenko@gmail.com>
Date: Fri, 19 Jun 2026 09:48:12 -0400
Subject: [PATCH] ext4: fix issues reported by syzbot/CI on the dirdata series
Address the following issues found by automated review of the v2
dirdata patch series:
- dx_get_dx_info() called ext4_dir_entry_len() with dir hardcoded to
NULL, forcing its blocksize fallback to 4096 regardless of the real
filesystem blocksize, and never validated that the computed offset
stayed within the block. Thread the real inode through and reject
out-of-bounds results.
- get_dx_countlimit() had the same NULL-dir blocksize-fallback bug at
a separate call site; pass the real inode through there too.
- ext4_dirdata_get() declared a local "dfid" inside the
EXT4_DIRENT_LUFID branch that shadowed the function's own "dfid"
output parameter, so the LUFID copy never reached the caller's
buffer. Rename the local and copy into the real parameter. Also,
both ext4_dirdata_get() and ext4_dirdata_set() compared offsets
against the raw on-disk de->rec_len instead of decoding it via
ext4_rec_len_from_disk(), which is wrong on big-endian hosts and
mishandles the "0/65535 means full block" sentinel.
- ext4_dirdata_set_lufid() (EXT4_IOC_SET_LUFID) deleted the existing
directory entry and then tried to re-add it with the new LUFID
data; if ext4_add_entry() failed, the inode was left with no
directory entry pointing at it. On failure, attempt to restore the
original entry, and loudly flag inode corruption if that also
fails.
Signed-off-by: Artem Blagodarenko <artem.blagodarenko@gmail.com>
---
fs/ext4/namei.c | 105 +++++++++++++++++++++++++++++++++++-------------
1 file changed, 78 insertions(+), 27 deletions(-)
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index 65c53c08213a..e6f54dba735e 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -412,7 +412,7 @@ static struct dx_countlimit *get_dx_countlimit(struct inode *inode,
if (le16_to_cpu(de->rec_len) != (blocksize - rlen))
return NULL;
/* de->rec_len covers whole dx_root block, calculate actual length */
- dotdot_rec_len = ext4_dir_entry_len(de, NULL);
+ dotdot_rec_len = ext4_dir_entry_len(de, inode);
root = (struct dx_root_info *)(((char *)de + dotdot_rec_len));
if (root->reserved_zero ||
root->info_length != sizeof(struct dx_root_info))
@@ -520,13 +520,20 @@ 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)
+static struct dx_root_info *dx_get_dx_info(struct inode *dir, void *de_buf)
{
+ unsigned int blocksize = dir->i_sb->s_blocksize;
+ void *base = de_buf;
+
/* get dotdot first */
- de_buf += ext4_dir_entry_len(de_buf, NULL);
+ de_buf += ext4_dir_entry_len(de_buf, dir);
/* dx root info is after dotdot entry */
- de_buf += ext4_dir_entry_len(de_buf, NULL);
+ de_buf += ext4_dir_entry_len(de_buf, dir);
+
+ if (de_buf < base || (char *)de_buf - (char *)base +
+ sizeof(struct dx_root_info) > blocksize)
+ return ERR_PTR(-EFSCORRUPTED);
return (struct dx_root_info *)de_buf;
}
@@ -577,7 +584,9 @@ static inline unsigned dx_root_limit(struct inode *dir,
struct dx_root_info *info;
unsigned int entry_space;
- info = dx_get_dx_info(dot_de);
+ info = dx_get_dx_info(dir, dot_de);
+ if (IS_ERR(info))
+ return 0;
entry_space = dir->i_sb->s_blocksize - ((char *)info - (char *)dot_de) -
info->info_length;
@@ -793,7 +802,9 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,
if (IS_ERR(frame->bh))
return (struct dx_frame *) frame->bh;
- info = dx_get_dx_info((struct ext4_dir_entry_2 *)frame->bh->b_data);
+ info = dx_get_dx_info(dir, (struct ext4_dir_entry_2 *)frame->bh->b_data);
+ if (IS_ERR(info))
+ goto fail;
if (info->hash_version != DX_HASH_TEA &&
info->hash_version != DX_HASH_HALF_MD4 &&
info->hash_version != DX_HASH_LEGACY &&
@@ -938,7 +949,7 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,
return ret_err;
}
-static void dx_release(struct dx_frame *frames)
+static void dx_release(struct inode *dir, struct dx_frame *frames)
{
struct dx_root_info *info;
int i;
@@ -947,7 +958,9 @@ static void dx_release(struct dx_frame *frames)
if (frames[0].bh == NULL)
return;
- info = dx_get_dx_info((struct ext4_dir_entry_2 *)frames[0].bh->b_data);
+ info = dx_get_dx_info(dir, (struct ext4_dir_entry_2 *)frames[0].bh->b_data);
+ if (IS_ERR(info))
+ return;
/* save local copy, "info" may be freed after brelse() */
indirect_levels = info->indirect_levels;
for (i = 0; i <= indirect_levels; i++) {
@@ -1253,12 +1266,12 @@ int ext4_htree_fill_tree(struct file *dir_file, __u32 start_hash,
(count && ((hashval & 1) == 0)))
break;
}
- dx_release(frames);
+ dx_release(dir, frames);
dxtrace(printk(KERN_DEBUG "Fill tree: returned %d entries, "
"next hash: %x\n", count, *next_hash));
return count;
errout:
- dx_release(frames);
+ dx_release(dir, frames);
return (err);
}
@@ -1296,8 +1309,10 @@ unsigned char ext4_dirdata_get(struct ext4_dir_entry_2 *de, struct inode *dir,
{
unsigned char ret = 0;
unsigned int data_offset = de->name_len + 1;
+ unsigned int rec_len = ext4_rec_len_from_disk(de->rec_len,
+ dir->i_sb->s_blocksize);
- if (data_offset > de->rec_len)
+ if (data_offset > rec_len)
return ret;
/* compatibility: hash stored inline after filename (no dirdata) */
@@ -1312,19 +1327,20 @@ unsigned char ext4_dirdata_get(struct ext4_dir_entry_2 *de, struct inode *dir,
/* EXT4_DIRENT_* are not expected without flag in i_sb */
if (de->file_type & EXT4_DIRENT_LUFID) {
- struct ext4_dirent_fid *dfid =
+ struct ext4_dirent_fid *disk_fid =
(struct ext4_dirent_fid *)(de->name + data_offset);
unsigned int dlen;
- if (data_offset + sizeof(dfid->df_header) > de->rec_len)
+ if (data_offset + sizeof(disk_fid->df_header) > rec_len)
return ret;
- dlen = dfid->df_header.ddh_length;
- if (dlen < sizeof(*dfid) || data_offset + dlen > de->rec_len)
+ dlen = disk_fid->df_header.ddh_length;
+ if (dlen < sizeof(*disk_fid) || data_offset + dlen > rec_len)
return ret;
if (dfid) {
- memcpy(dfid, dfid->df_fid, dfid->df_header.ddh_length);
+ memcpy(dfid, disk_fid->df_fid,
+ disk_fid->df_header.ddh_length);
ret |= EXT4_DIRENT_LUFID;
}
data_offset += dlen;
@@ -1336,11 +1352,11 @@ unsigned char ext4_dirdata_get(struct ext4_dir_entry_2 *de, struct inode *dir,
(struct ext4_dirent_data_header *)(de->name + data_offset);
unsigned int dlen;
- if (data_offset + sizeof(*ddh) > de->rec_len)
+ if (data_offset + sizeof(*ddh) > rec_len)
return ret;
dlen = ddh->ddh_length;
- if (dlen < sizeof(*ddh) || data_offset + dlen > de->rec_len)
+ if (dlen < sizeof(*ddh) || data_offset + dlen > rec_len)
return ret;
data_offset += dlen;
@@ -1355,7 +1371,7 @@ unsigned char ext4_dirdata_get(struct ext4_dir_entry_2 *de, struct inode *dir,
unsigned int dlen;
dlen = dh->dh_header.ddh_length;
- if (dlen < sizeof(*dh) || data_offset + dlen > de->rec_len)
+ if (dlen < sizeof(*dh) || data_offset + dlen > rec_len)
return ret;
hinfo->hash = le32_to_cpu(dh->dh_hash.hash);
@@ -1383,12 +1399,14 @@ static void ext4_dirdata_set(struct ext4_dir_entry_2 *de, struct inode *dir,
{
struct dx_hash_info *hinfo = &fname->hinfo;
unsigned int data_offset = de->name_len + 1;
+ unsigned int rec_len = ext4_rec_len_from_disk(de->rec_len,
+ dir->i_sb->s_blocksize);
if (dfid) {
unsigned int dlen = dfid->df_header.ddh_length;
- if (data_offset + dlen > de->rec_len) {
+ if (data_offset + dlen > rec_len) {
EXT4_ERROR_INODE(dir, "Can not insert FID");
return;
}
@@ -1406,7 +1424,7 @@ static void ext4_dirdata_set(struct ext4_dir_entry_2 *de, struct inode *dir,
struct ext4_dirent_hash *dh =
(struct ext4_dirent_hash *)(de->name + data_offset);
- if (data_offset + sizeof(*dh) > de->rec_len) {
+ if (data_offset + sizeof(*dh) > rec_len) {
EXT4_ERROR_INODE(dir, "Can not insert dhash dirdata");
return;
}
@@ -1418,7 +1436,7 @@ static void ext4_dirdata_set(struct ext4_dir_entry_2 *de, struct inode *dir,
} else {
/* Compatibility: store hash inline after filename */
if (data_offset + sizeof(struct ext4_dir_entry_hash) >
- de-> rec_len) {
+ rec_len) {
EXT4_ERROR_INODE(dir, "Can not insert dhash");
return;
}
@@ -1906,7 +1924,7 @@ static struct buffer_head * ext4_dx_find_entry(struct inode *dir,
errout:
dxtrace(printk(KERN_DEBUG "%s not found\n", fname->usr_fname->name));
success:
- dx_release(frames);
+ dx_release(dir, frames);
return bh;
}
@@ -2425,7 +2443,12 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,
blocksize);
/* initialize hashing info */
- dx_info = dx_get_dx_info(dot_de);
+ dx_info = dx_get_dx_info(dir, dot_de);
+ if (IS_ERR(dx_info)) {
+ brelse(bh2);
+ brelse(bh);
+ return PTR_ERR(dx_info);
+ }
memset(dx_info, 0, sizeof(*dx_info));
dx_info->info_length = sizeof(*dx_info);
if (ext4_hash_in_dirent(dir))
@@ -2483,7 +2506,7 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,
*/
if (retval)
ext4_mark_inode_dirty(handle, dir);
- dx_release(frames);
+ dx_release(dir, frames);
brelse(bh2);
return retval;
}
@@ -2759,8 +2782,13 @@ 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);
- info = dx_get_dx_info((struct ext4_dir_entry_2 *)
+ info = dx_get_dx_info(dir, (struct ext4_dir_entry_2 *)
frames[0].bh->b_data);
+ if (IS_ERR(info)) {
+ err = PTR_ERR(info);
+ brelse(bh2);
+ goto journal_error;
+ }
info->indirect_levels += 1;
dxtrace(printk(KERN_DEBUG
"Creating %d level index...\n",
@@ -2788,7 +2816,7 @@ static int ext4_dx_add_entry(handle_t *handle, struct ext4_filename *fname,
ext4_std_error(dir->i_sb, err); /* this is a no-op if err == 0 */
cleanup:
brelse(bh);
- dx_release(frames);
+ dx_release(dir, frames);
/* @restart is true means htree-path has been changed, we need to
* repeat dx_probe() to find out valid htree-path
*/
@@ -4463,6 +4491,29 @@ int ext4_dirdata_set_lufid(struct inode *dir, const char *filename,
}
EXT4_I(inode)->i_dirdata = old_dirdata;
+ if (err) {
+ /*
+ * The original entry was already removed above and the
+ * re-add with the new LUFID failed; try to restore the
+ * original entry so the inode isn't left without any
+ * directory entry pointing at it.
+ */
+ struct dentry parent_dentry = { .d_inode = dir };
+ struct dentry orig_dentry = {
+ .d_name = d_name,
+ .d_parent = &parent_dentry,
+ .d_inode = inode,
+ };
+ int rollback_err = ext4_add_entry(handle, &orig_dentry, inode);
+
+ if (rollback_err)
+ EXT4_ERROR_INODE(dir,
+ "Failed to set LUFID on '%.*s' (err=%d) and failed to restore the original directory entry (err=%d); inode %llu may be orphaned",
+ namelen, filename, err, rollback_err,
+ inode->i_ino);
+ goto out_unlock;
+ }
+
/* Update inode times */
inode_set_ctime_current(dir);
inode_inc_iversion(dir);
--
2.43.7
^ permalink raw reply related
* Re: [PATCH 0/2] fs: refactor code to use clear_and_wake_up_bit()
From: Christian Brauner @ 2026-06-19 13:34 UTC (permalink / raw)
To: linux-fsdevel, linux-ext4, linux-kernel, Jan Kara, shuo chen,
Theodore Ts'o, linux-kernel-mentees, shuah, patch-reply,
Agatha Isabelle Moreira
In-Reply-To: <ag4PEP52c8rxrYPc@guidai>
On Wed, 20 May 2026 16:45:35 -0300, Agatha Isabelle Moreira wrote:
> fs: refactor code to use clear_and_wake_up_bit()
>
> Refactor code to use `clear_and_wake_up_bit()` instead of manual calls
> to:
> clear_bit_unlock();
> smp_mb__after_atomic();
> wake_up_bit();
>
> [...]
Applied to the vfs-7.3.misc branch of the vfs/vfs.git tree.
Patches in the vfs-7.3.misc branch should appear in linux-next soon.
Please report any outstanding bugs that were missed during review in a
new review to the original patch series allowing us to drop it.
It's encouraged to provide Acked-bys and Reviewed-bys even though the
patch has now been applied. If possible patch trailers will be updated.
Note that commit hashes shown below are subject to change due to rebase,
trailer updates or similar. If in doubt, please check the listed branch.
tree: https://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs.git
branch: vfs-7.3.misc
[1/2] fs: buffer: use clear_and_wake_up_bit() in unlock_buffer()
https://git.kernel.org/vfs/vfs/c/1a6e4692deca
[2/2] fs: jbd2: use clear_and_wake_up_bit() in journal_end_buffer_io_sync()
https://git.kernel.org/vfs/vfs/c/8efd38683c81
^ permalink raw reply
* Re: [PATCH v10 03/22] ovl: use core fsverity ensure info interface
From: Amir Goldstein @ 2026-06-19 7:28 UTC (permalink / raw)
To: Eric Biggers
Cc: Andrey Albershteyn, linux-xfs, fsverity, linux-fsdevel, hch,
linux-ext4, linux-f2fs-devel, linux-btrfs, linux-unionfs, djwong
In-Reply-To: <20260520190719.GB3424023@google.com>
On Wed, May 20, 2026 at 9:07 PM Eric Biggers <ebiggers@kernel.org> wrote:
>
> On Wed, May 20, 2026 at 02:37:01PM +0200, Andrey Albershteyn wrote:
> > fsverity now exposes fsverity_ensure_verity_info() which could be used
> > instead of opening file to ensure that fsverity info is loaded and
> > attached to inode.
> >
> > Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
> > Acked-by: Amir Goldstein <amir73il@gmail.com>
> > ---
> > fs/overlayfs/util.c | 14 +++-----------
> > 1 file changed, 3 insertions(+), 11 deletions(-)
>
> Reviewed-by: Eric Biggers <ebiggers@kernel.org>
>
> I'm still confused by the new implementation of fsverity_active() that
> got introduced by "fsverity: use a hashtable to find the fsverity_info",
> though. I should have caught this during review of that commit. For
> one its comment is outdated, but also the memory barrier seems to be
> specific to the fsverity_get_info() caller and probably should be moved
> to there. Anyway, that's not directly related to this patch.
Eric, Andrey,
Did you see the Sashiko review for this patch and others in this series?
https://sashiko.dev/#/patchset/20260520123722.405752-1-aalbersh%40kernel.org
It annotated some review comments as high and critical.
For this patch it is about interaction with fscrypt.
Please take a look and say if this is concerning or false positive.
Thanks,
Amir.
^ permalink raw reply
* Re: [PATCH v7 3/4] ext4: introduce ext4_put_ea_inode() for safe deferred iput
From: Zhou, Yun @ 2026-06-19 6:24 UTC (permalink / raw)
To: Jan Kara
Cc: tytso, adilger.kernel, libaokun, ojaswin, ritesh.list, yi.zhang,
linux-ext4, linux-kernel
In-Reply-To: <jxcbsd2ot63wy3dcoximemkuitwoqn2a7jgxcsfdwaf5q3ecdu@sahahqqopo6y>
On 6/18/2026 2:42 AM, Jan Kara wrote:
> On Tue 16-06-26 23:15:57, Yun Zhou wrote:
>> +
>> + /* Deferred iput for EA inodes to avoid lock ordering issues */
>> + struct llist_head s_ea_inode_to_free;
>> + struct work_struct s_ea_inode_work;
>> +
>
> I'd probably use delayed work and schedule it with a delay of one jiffie so
> that some inodes can accumulate before we process them which should reduce
> the amount of task switching to workqueues.
>
Good idea, I will use delayed_work in next version.
>> diff --git a/fs/ext4/super.c b/fs/ext4/super.c
>> index 6a77db4d3124..b777bb0a81ea 100644
>> --- a/fs/ext4/super.c
>> +++ b/fs/ext4/super.c
>> @@ -1308,6 +1308,9 @@ static void ext4_put_super(struct super_block *sb)
>> destroy_workqueue(sbi->rsv_conversion_wq);
>> ext4_release_orphan_info(sb);
>>
>> + /* Flush deferred EA inode iputs before destroying journal */
>> + flush_work(&sbi->s_ea_inode_work);
>> +
>
> This should happen earlier in ext4_put_super(). At this place quotas were
> already turned off and so quota accounting would go wrong.
That makes sense. I'll move it up to right before ext4_quotas_off().
>> +static void ext4_xattr_inode_array_free_deferred(struct super_block *sb,
>> + struct ext4_xattr_inode_array *array)
>
> The array of EA inodes used in xattr handling is just another mechanism
> used for delaying iput() of EA inodes. It doesn't make sense to stack these
> to one on top of another. Just completely replace the array mechanism with
> always deferring iput of EA inode into the workqueue.
>
I'm thinking that a complete replacement might be too large a change.
Should we consider postponing this work, or perhaps appending a new
patch to this series to handle it?
>
> Allocating ext4_ea_iput_entry for dropping each inode is somewhat wasteful.
> I want to suggest another scheme (somewhat more involved but more efficient
> scheme):
>
> 1) Create a VFS helper bool iput_if_not_last(struct inode *inode) which
> drops inode reference if it is not the last one (and returns true in that
> case). Basically:
>
> bool iput_if_not_last(struct inode *inode)
> {
> return atomic_add_unless(&inode->i_count, -1, 1);
> }
>
> This needs to be a separate patch as it should get vetting from VFS
> maintainers.
>
> 2) Use iput_if_not_last() in ext4_put_ea_inode(). If it returns true, we
> are done. Otherwise we know we were at least for a moment holders of the
> last inode reference, so we link the inode to the list of inodes to drop
> through llist_node embedded in ext4_inode_info. We cannot race with anybody
> else trying to link the same inode into the list because we hold one inode
> ref and so nobody else can hit this "I was holding the last ref" path.
> I'd union this llist_node say with xattr_sem which is unused for EA inodes
> to avoid growing ext4_inode_info.
>
> This way we avoid offloading unless really necessary and we don't have to
> do allocations just to drop EA inode ref.
>
Your idea makes a lot of sense. It greatly simplifies the current deferred
iput logic and eliminates the risk of failing to allocate an entry during
an OOM. However, as you mentioned, getting the VFS maintainers to agree
might be quite challenging.
BR,
Yun
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox