diff options
| author | OGAWA Hirofumi <hirofumi@mail.parknet.co.jp> | 2019-09-23 15:32:53 -0700 | 
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2019-09-24 15:54:06 -0700 | 
| commit | 07bfa4415ab607e459b69bd86aa7e7602ce10b4f (patch) | |
| tree | 62a7bc60deca51a1a795c8a22293e658d88efc07 /fs/fat | |
| parent | 619e17cf75dd58905aa67ccd494a6ba5f19d6cc6 (diff) | |
| download | linux-07bfa4415ab607e459b69bd86aa7e7602ce10b4f.tar.bz2 | |
fat: work around race with userspace's read via blockdev while mounting
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>
Diffstat (limited to 'fs/fat')
| -rw-r--r-- | fs/fat/dir.c | 13 | ||||
| -rw-r--r-- | fs/fat/fatent.c | 3 | 
2 files changed, 14 insertions, 2 deletions
diff --git a/fs/fat/dir.c b/fs/fat/dir.c index 1bda2ab6745b..814ad2c2ba80 100644 --- a/fs/fat/dir.c +++ b/fs/fat/dir.c @@ -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++; diff --git a/fs/fat/fatent.c b/fs/fat/fatent.c index 265983635f2b..3647c65a0f48 100644 --- a/fs/fat/fatent.c +++ b/fs/fat/fatent.c @@ -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);  |