fat: work around race with userspace's read via blockdev while mounting
authorOGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
Mon, 23 Sep 2019 22:32:53 +0000 (15:32 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Tue, 24 Sep 2019 22:54:06 +0000 (15:54 -0700)
If userspace reads the buffer via blockdev while mounting,
sb_getblk()+modify can race with buffer read via blockdev.

For example,

            FS                               userspace
    bh = sb_getblk()
    modify bh->b_data
                                  read
    ll_rw_block(bh)
      fill bh->b_data by on-disk data
      /* lost modified data by FS */
      set_buffer_uptodate(bh)
    set_buffer_uptodate(bh)

Userspace should not use the blockdev while mounting though, the udev
seems to be already doing this.  Although I think the udev should try to
avoid this, workaround the race by small overhead.

Link: http://lkml.kernel.org/r/87pnk7l3sw.fsf_-_@mail.parknet.co.jp
Signed-off-by: OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
Reported-by: Jan Stancek <jstancek@redhat.com>
Tested-by: Jan Stancek <jstancek@redhat.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
fs/fat/dir.c
fs/fat/fatent.c

index 1bda2ab..814ad2c 100644 (file)
@@ -1100,8 +1100,11 @@ static int fat_zeroed_cluster(struct inode *dir, sector_t blknr, int nr_used,
                        err = -ENOMEM;
                        goto error;
                }
+               /* Avoid race with userspace read via bdev */
+               lock_buffer(bhs[n]);
                memset(bhs[n]->b_data, 0, sb->s_blocksize);
                set_buffer_uptodate(bhs[n]);
+               unlock_buffer(bhs[n]);
                mark_buffer_dirty_inode(bhs[n], dir);
 
                n++;
@@ -1158,6 +1161,8 @@ int fat_alloc_new_dir(struct inode *dir, struct timespec64 *ts)
        fat_time_unix2fat(sbi, ts, &time, &date, &time_cs);
 
        de = (struct msdos_dir_entry *)bhs[0]->b_data;
+       /* Avoid race with userspace read via bdev */
+       lock_buffer(bhs[0]);
        /* filling the new directory slots ("." and ".." entries) */
        memcpy(de[0].name, MSDOS_DOT, MSDOS_NAME);
        memcpy(de[1].name, MSDOS_DOTDOT, MSDOS_NAME);
@@ -1180,6 +1185,7 @@ int fat_alloc_new_dir(struct inode *dir, struct timespec64 *ts)
        de[0].size = de[1].size = 0;
        memset(de + 2, 0, sb->s_blocksize - 2 * sizeof(*de));
        set_buffer_uptodate(bhs[0]);
+       unlock_buffer(bhs[0]);
        mark_buffer_dirty_inode(bhs[0], dir);
 
        err = fat_zeroed_cluster(dir, blknr, 1, bhs, MAX_BUF_PER_PAGE);
@@ -1237,11 +1243,14 @@ static int fat_add_new_entries(struct inode *dir, void *slots, int nr_slots,
 
                        /* fill the directory entry */
                        copy = min(size, sb->s_blocksize);
+                       /* Avoid race with userspace read via bdev */
+                       lock_buffer(bhs[n]);
                        memcpy(bhs[n]->b_data, slots, copy);
-                       slots += copy;
-                       size -= copy;
                        set_buffer_uptodate(bhs[n]);
+                       unlock_buffer(bhs[n]);
                        mark_buffer_dirty_inode(bhs[n], dir);
+                       slots += copy;
+                       size -= copy;
                        if (!size)
                                break;
                        n++;
index 2659836..3647c65 100644 (file)
@@ -388,8 +388,11 @@ static int fat_mirror_bhs(struct super_block *sb, struct buffer_head **bhs,
                                err = -ENOMEM;
                                goto error;
                        }
+                       /* Avoid race with userspace read via bdev */
+                       lock_buffer(c_bh);
                        memcpy(c_bh->b_data, bhs[n]->b_data, sb->s_blocksize);
                        set_buffer_uptodate(c_bh);
+                       unlock_buffer(c_bh);
                        mark_buffer_dirty_inode(c_bh, sbi->fat_inode);
                        if (sb->s_flags & SB_SYNCHRONOUS)
                                err = sync_dirty_buffer(c_bh);