From: Artem Blagodarenko 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 Signed-off-by: Artem Blagodarenko Reviewed-by: Andreas Dilger --- 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 066c49fe3266..ef99e4fa99d7 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -2248,6 +2248,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 | \ @@ -2949,10 +2950,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) && @@ -3196,8 +3205,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 cd20b1094134..40a3394f7eac 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, NULL); + root = (struct dx_root_info *)(((char *)de + dotdot_rec_len)); if (root->reserved_zero || root->info_length != sizeof(struct dx_root_info)) return NULL; - count_offset = 32; - } else - return NULL; + count_offset = root->info_length + rlen + dotdot_rec_len; + } if (offset) *offset = count_offset; @@ -698,7 +699,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); @@ -2068,13 +2069,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); @@ -2116,15 +2114,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) @@ -2139,7 +2137,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 @@ -2968,8 +2966,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