summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoryangerkun <yangerkun@huawei.com>2021-09-14 19:14:15 +0800
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2021-10-06 15:56:02 +0200
commit3770e21f60fc29326a7f3febfa8adc9a3c2dde62 (patch)
tree579318468f1add0f154d3ad5a5a15a3f83df8478
parenta63474dbf692dd09b50fed592bc41f6de5f102fc (diff)
downloadlinux-3770e21f60fc29326a7f3febfa8adc9a3c2dde62.tar.gz
linux-3770e21f60fc29326a7f3febfa8adc9a3c2dde62.tar.bz2
linux-3770e21f60fc29326a7f3febfa8adc9a3c2dde62.zip
ext4: fix potential infinite loop in ext4_dx_readdir()
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>
-rw-r--r--fs/ext4/dir.c6
1 files changed, 3 insertions, 3 deletions
diff --git a/fs/ext4/dir.c b/fs/ext4/dir.c
index ca50c90adc4c..70a0f5e56f4d 100644
--- a/fs/ext4/dir.c
+++ b/fs/ext4/dir.c
@@ -534,7 +534,7 @@ static int ext4_dx_readdir(struct file *file, struct dir_context *ctx)
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 *file, struct dir_context *ctx)
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 *file, struct dir_context *ctx)
}
finished:
info->last_pos = ctx->pos;
- return 0;
+ return ret < 0 ? ret : 0;
}
static int ext4_dir_open(struct inode * inode, struct file * filp)