| From 46c116b920ebec58031f0a78c5ea9599b0d2a371 Mon Sep 17 00:00:00 2001 |
| From: Jan Kara <jack@suse.cz> |
| Date: Wed, 18 May 2022 11:33:28 +0200 |
| Subject: ext4: verify dir block before splitting it |
| |
| From: Jan Kara <jack@suse.cz> |
| |
| commit 46c116b920ebec58031f0a78c5ea9599b0d2a371 upstream. |
| |
| Before splitting a directory block verify its directory entries are sane |
| so that the splitting code does not access memory it should not. |
| |
| Cc: stable@vger.kernel.org |
| Signed-off-by: Jan Kara <jack@suse.cz> |
| Link: https://lore.kernel.org/r/20220518093332.13986-1-jack@suse.cz |
| Signed-off-by: Theodore Ts'o <tytso@mit.edu> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| fs/ext4/namei.c | 32 +++++++++++++++++++++----------- |
| 1 file changed, 21 insertions(+), 11 deletions(-) |
| |
| --- a/fs/ext4/namei.c |
| +++ b/fs/ext4/namei.c |
| @@ -270,9 +270,9 @@ static struct dx_frame *dx_probe(struct |
| struct dx_hash_info *hinfo, |
| struct dx_frame *frame); |
| static void dx_release(struct dx_frame *frames); |
| -static int dx_make_map(struct inode *dir, struct ext4_dir_entry_2 *de, |
| - unsigned blocksize, struct dx_hash_info *hinfo, |
| - struct dx_map_entry map[]); |
| +static int dx_make_map(struct inode *dir, struct buffer_head *bh, |
| + struct dx_hash_info *hinfo, |
| + struct dx_map_entry *map_tail); |
| static void dx_sort_map(struct dx_map_entry *map, unsigned count); |
| static struct ext4_dir_entry_2 *dx_move_dirents(char *from, char *to, |
| struct dx_map_entry *offsets, int count, unsigned blocksize); |
| @@ -1185,15 +1185,23 @@ static inline int search_dirblock(struct |
| * Create map of hash values, offsets, and sizes, stored at end of block. |
| * Returns number of entries mapped. |
| */ |
| -static int dx_make_map(struct inode *dir, struct ext4_dir_entry_2 *de, |
| - unsigned blocksize, struct dx_hash_info *hinfo, |
| +static int dx_make_map(struct inode *dir, struct buffer_head *bh, |
| + struct dx_hash_info *hinfo, |
| struct dx_map_entry *map_tail) |
| { |
| int count = 0; |
| - char *base = (char *) de; |
| + struct ext4_dir_entry_2 *de = (struct ext4_dir_entry_2 *)bh->b_data; |
| + unsigned int buflen = bh->b_size; |
| + char *base = bh->b_data; |
| struct dx_hash_info h = *hinfo; |
| |
| - while ((char *) de < base + blocksize) { |
| + if (ext4_has_metadata_csum(dir->i_sb)) |
| + buflen -= sizeof(struct ext4_dir_entry_tail); |
| + |
| + while ((char *) de < base + buflen) { |
| + if (ext4_check_dir_entry(dir, NULL, de, bh, base, buflen, |
| + ((char *)de) - base)) |
| + return -EFSCORRUPTED; |
| if (de->name_len && de->inode) { |
| ext4fs_dirhash(de->name, de->name_len, &h); |
| map_tail--; |
| @@ -1203,8 +1211,7 @@ static int dx_make_map(struct inode *dir |
| count++; |
| cond_resched(); |
| } |
| - /* XXX: do we need to check rec_len == 0 case? -Chris */ |
| - de = ext4_next_entry(de, blocksize); |
| + de = ext4_next_entry(de, dir->i_sb->s_blocksize); |
| } |
| return count; |
| } |
| @@ -1755,8 +1762,11 @@ static struct ext4_dir_entry_2 *do_split |
| |
| /* create map in the end of data2 block */ |
| map = (struct dx_map_entry *) (data2 + blocksize); |
| - count = dx_make_map(dir, (struct ext4_dir_entry_2 *) data1, |
| - blocksize, hinfo, map); |
| + count = dx_make_map(dir, *bh, hinfo, map); |
| + if (count < 0) { |
| + err = count; |
| + goto journal_error; |
| + } |
| map -= count; |
| dx_sort_map(map, count); |
| /* Ensure that neither split block is over half full */ |