f2fs: reduce the scope of setting fsck tag when de->name_len is zero
authorYangtao Li <frank.li@vivo.com>
Wed, 4 Aug 2021 03:29:46 +0000 (11:29 +0800)
committerJaegeuk Kim <jaegeuk@kernel.org>
Wed, 4 Aug 2021 22:51:21 +0000 (15:51 -0700)
I recently found a case where de->name_len is 0 in f2fs_fill_dentries()
easily reproduced, and finally set the fsck flag.

Thread A Thread B
- f2fs_readdir
 - f2fs_read_inline_dir
  - ctx->pos = d.max
- f2fs_add_dentry
 - f2fs_add_inline_entry
  - do_convert_inline_dir
 - f2fs_add_regular_entry
- f2fs_readdir
 - f2fs_fill_dentries
  - set_sbi_flag(sbi, SBI_NEED_FSCK)

Process A opens the folder, and has been reading without closing it.
During this period, Process B created a file under the folder (occupying
multiple f2fs_dir_entry, exceeding the d.max of the inline dir). After
creation, process A uses the d.max of inline dir to read it again, and
it will read that de->name_len is 0.

And Chao pointed out that w/o inline conversion, the race condition still
can happen as below:

dir_entry1: A
dir_entry2: B
dir_entry3: C
free slot: _
ctx->pos: ^

Thread A is traversing directory,
ctx-pos moves to below position after readdir() by thread A:
AAAABBBB___
        ^

Then thread B delete dir_entry2, and create dir_entry3.

Thread A calls readdir() to lookup dirents starting from middle
of new dirent slots as below:
AAAACCCCCC_
        ^
In these scenarios, the file system is not damaged, and it's hard to
avoid it. But we can bypass tagging FSCK flag if:
a) bit_pos (:= ctx->pos % d->max) is non-zero and
b) before bit_pos moves to first valid dir_entry.

Fixes: ddf06b753a85 ("f2fs: fix to trigger fsck if dirent.name_len is zero")
Signed-off-by: Yangtao Li <frank.li@vivo.com>
[Chao: clean up description]
Reviewed-by: Chao Yu <chao@kernel.org>
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
fs/f2fs/dir.c

index 4566516..c250bf4 100644 (file)
@@ -1000,6 +1000,7 @@ int f2fs_fill_dentries(struct dir_context *ctx, struct f2fs_dentry_ptr *d,
        struct f2fs_sb_info *sbi = F2FS_I_SB(d->inode);
        struct blk_plug plug;
        bool readdir_ra = sbi->readdir_ra == 1;
+       bool found_valid_dirent = false;
        int err = 0;
 
        bit_pos = ((unsigned long)ctx->pos % d->max);
@@ -1014,13 +1015,15 @@ int f2fs_fill_dentries(struct dir_context *ctx, struct f2fs_dentry_ptr *d,
 
                de = &d->dentry[bit_pos];
                if (de->name_len == 0) {
+                       if (found_valid_dirent || !bit_pos) {
+                               printk_ratelimited(
+                                       "%sF2FS-fs (%s): invalid namelen(0), ino:%u, run fsck to fix.",
+                                       KERN_WARNING, sbi->sb->s_id,
+                                       le32_to_cpu(de->ino));
+                               set_sbi_flag(sbi, SBI_NEED_FSCK);
+                       }
                        bit_pos++;
                        ctx->pos = start_pos + bit_pos;
-                       printk_ratelimited(
-                               "%sF2FS-fs (%s): invalid namelen(0), ino:%u, run fsck to fix.",
-                               KERN_WARNING, sbi->sb->s_id,
-                               le32_to_cpu(de->ino));
-                       set_sbi_flag(sbi, SBI_NEED_FSCK);
                        continue;
                }
 
@@ -1063,6 +1066,7 @@ int f2fs_fill_dentries(struct dir_context *ctx, struct f2fs_dentry_ptr *d,
                        f2fs_ra_node_page(sbi, le32_to_cpu(de->ino));
 
                ctx->pos = start_pos + bit_pos;
+               found_valid_dirent = true;
        }
 out:
        if (readdir_ra)