| From 42cb447410d024e9d54139ae9c21ea132a8c384c Mon Sep 17 00:00:00 2001 |
| From: yangerkun <yangerkun@huawei.com> |
| Date: Tue, 14 Sep 2021 19:14:15 +0800 |
| Subject: ext4: fix potential infinite loop in ext4_dx_readdir() |
| |
| From: yangerkun <yangerkun@huawei.com> |
| |
| commit 42cb447410d024e9d54139ae9c21ea132a8c384c upstream. |
| |
| When ext4_htree_fill_tree() fails, ext4_dx_readdir() can run into an |
| infinite loop since if info->last_pos != ctx->pos this will reset the |
| directory scan and reread the failing entry. For example: |
| |
| 1. a dx_dir which has 3 block, block 0 as dx_root block, block 1/2 as |
| leaf block which own the ext4_dir_entry_2 |
| 2. block 1 read ok and call_filldir which will fill the dirent and update |
| the ctx->pos |
| 3. block 2 read fail, but we has already fill some dirent, so we will |
| return back to userspace will a positive return val(see ksys_getdents64) |
| 4. the second ext4_dx_readdir will reset the world since info->last_pos |
| != ctx->pos, and will also init the curr_hash which pos to block 1 |
| 5. So we will read block1 too, and once block2 still read fail, we can |
| only fill one dirent because the hash of the entry in block1(besides |
| the last one) won't greater than curr_hash |
| 6. this time, we forget update last_pos too since the read for block2 |
| will fail, and since we has got the one entry, ksys_getdents64 can |
| return success |
| 7. Latter we will trapped in a loop with step 4~6 |
| |
| Cc: stable@kernel.org |
| Signed-off-by: yangerkun <yangerkun@huawei.com> |
| Reviewed-by: Jan Kara <jack@suse.cz> |
| Signed-off-by: Theodore Ts'o <tytso@mit.edu> |
| Link: https://lore.kernel.org/r/20210914111415.3921954-1-yangerkun@huawei.com |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| fs/ext4/dir.c | 6 +++--- |
| 1 file changed, 3 insertions(+), 3 deletions(-) |
| |
| --- a/fs/ext4/dir.c |
| +++ b/fs/ext4/dir.c |
| @@ -534,7 +534,7 @@ static int ext4_dx_readdir(struct file * |
| struct dir_private_info *info = file->private_data; |
| struct inode *inode = file_inode(file); |
| struct fname *fname; |
| - int ret; |
| + int ret = 0; |
| |
| if (!info) { |
| info = ext4_htree_create_dir_info(file, ctx->pos); |
| @@ -582,7 +582,7 @@ static int ext4_dx_readdir(struct file * |
| info->curr_minor_hash, |
| &info->next_hash); |
| if (ret < 0) |
| - return ret; |
| + goto finished; |
| if (ret == 0) { |
| ctx->pos = ext4_get_htree_eof(file); |
| break; |
| @@ -613,7 +613,7 @@ static int ext4_dx_readdir(struct file * |
| } |
| finished: |
| info->last_pos = ctx->pos; |
| - return 0; |
| + return ret < 0 ? ret : 0; |
| } |
| |
| static int ext4_dir_open(struct inode * inode, struct file * filp) |