diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index 28271d42bfaf..c5d4aaed8e6e 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -4007,6 +4007,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 @@ -4015,21 +4016,39 @@ 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(struct ext4_dirent_data_header) > + rec_len) + return dlen; + + /* validate ddh_length is reasonable */ + if (ddh->ddh_length == 0 || ddh->ddh_length > + rec_len - offset) + return dlen; + /* * The first dirdata field is preceded by a NUL * terminator byte that is already included in ddh's @@ -4038,7 +4057,9 @@ static inline int ext4_dirent_get_data_len(struct ext4_dir_entry_2 *de) if (dlen == 0) dlen = 1; /* NUL terminator */ dlen += ddh->ddh_length; - ddh = ext4_dirdata_next(ddh); + offset += ddh->ddh_length; + ddh = (struct ext4_dirent_data_header *) + ((char *)ddh + ddh->ddh_length); } extra_data_flags >>= 1; } @@ -4057,7 +4078,9 @@ static inline int ext4_dirent_get_data_len(struct ext4_dir_entry_2 *de) static inline unsigned int ext4_dir_entry_len(struct ext4_dir_entry_2 *de, const struct inode *dir) { - unsigned int dirdata = ext4_dirent_get_data_len(de); + 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_dirent_rec_len(de->name_len + dirdata, dir); } diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c index 071a637c8869..709acbf5e198 100644 --- a/fs/ext4/inline.c +++ b/fs/ext4/inline.c @@ -1318,7 +1318,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)) {