diff options
author | OGAWA Hirofumi <hirofumi@mail.parknet.co.jp> | 2019-09-23 18:32:53 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2019-09-24 18:54:06 -0400 |
commit | 07bfa4415ab607e459b69bd86aa7e7602ce10b4f (patch) | |
tree | 62a7bc60deca51a1a795c8a22293e658d88efc07 | |
parent | 619e17cf75dd58905aa67ccd494a6ba5f19d6cc6 (diff) |
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>
-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, | |||
1100 | err = -ENOMEM; | 1100 | err = -ENOMEM; |
1101 | goto error; | 1101 | goto error; |
1102 | } | 1102 | } |
1103 | /* Avoid race with userspace read via bdev */ | ||
1104 | lock_buffer(bhs[n]); | ||
1103 | memset(bhs[n]->b_data, 0, sb->s_blocksize); | 1105 | memset(bhs[n]->b_data, 0, sb->s_blocksize); |
1104 | set_buffer_uptodate(bhs[n]); | 1106 | set_buffer_uptodate(bhs[n]); |
1107 | unlock_buffer(bhs[n]); | ||
1105 | mark_buffer_dirty_inode(bhs[n], dir); | 1108 | mark_buffer_dirty_inode(bhs[n], dir); |
1106 | 1109 | ||
1107 | n++; | 1110 | n++; |
@@ -1158,6 +1161,8 @@ int fat_alloc_new_dir(struct inode *dir, struct timespec64 *ts) | |||
1158 | fat_time_unix2fat(sbi, ts, &time, &date, &time_cs); | 1161 | fat_time_unix2fat(sbi, ts, &time, &date, &time_cs); |
1159 | 1162 | ||
1160 | de = (struct msdos_dir_entry *)bhs[0]->b_data; | 1163 | de = (struct msdos_dir_entry *)bhs[0]->b_data; |
1164 | /* Avoid race with userspace read via bdev */ | ||
1165 | lock_buffer(bhs[0]); | ||
1161 | /* filling the new directory slots ("." and ".." entries) */ | 1166 | /* filling the new directory slots ("." and ".." entries) */ |
1162 | memcpy(de[0].name, MSDOS_DOT, MSDOS_NAME); | 1167 | memcpy(de[0].name, MSDOS_DOT, MSDOS_NAME); |
1163 | memcpy(de[1].name, MSDOS_DOTDOT, MSDOS_NAME); | 1168 | memcpy(de[1].name, MSDOS_DOTDOT, MSDOS_NAME); |
@@ -1180,6 +1185,7 @@ int fat_alloc_new_dir(struct inode *dir, struct timespec64 *ts) | |||
1180 | de[0].size = de[1].size = 0; | 1185 | de[0].size = de[1].size = 0; |
1181 | memset(de + 2, 0, sb->s_blocksize - 2 * sizeof(*de)); | 1186 | memset(de + 2, 0, sb->s_blocksize - 2 * sizeof(*de)); |
1182 | set_buffer_uptodate(bhs[0]); | 1187 | set_buffer_uptodate(bhs[0]); |
1188 | unlock_buffer(bhs[0]); | ||
1183 | mark_buffer_dirty_inode(bhs[0], dir); | 1189 | mark_buffer_dirty_inode(bhs[0], dir); |
1184 | 1190 | ||
1185 | err = fat_zeroed_cluster(dir, blknr, 1, bhs, MAX_BUF_PER_PAGE); | 1191 | 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, | |||
1237 | 1243 | ||
1238 | /* fill the directory entry */ | 1244 | /* fill the directory entry */ |
1239 | copy = min(size, sb->s_blocksize); | 1245 | copy = min(size, sb->s_blocksize); |
1246 | /* Avoid race with userspace read via bdev */ | ||
1247 | lock_buffer(bhs[n]); | ||
1240 | memcpy(bhs[n]->b_data, slots, copy); | 1248 | memcpy(bhs[n]->b_data, slots, copy); |
1241 | slots += copy; | ||
1242 | size -= copy; | ||
1243 | set_buffer_uptodate(bhs[n]); | 1249 | set_buffer_uptodate(bhs[n]); |
1250 | unlock_buffer(bhs[n]); | ||
1244 | mark_buffer_dirty_inode(bhs[n], dir); | 1251 | mark_buffer_dirty_inode(bhs[n], dir); |
1252 | slots += copy; | ||
1253 | size -= copy; | ||
1245 | if (!size) | 1254 | if (!size) |
1246 | break; | 1255 | break; |
1247 | n++; | 1256 | 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, | |||
388 | err = -ENOMEM; | 388 | err = -ENOMEM; |
389 | goto error; | 389 | goto error; |
390 | } | 390 | } |
391 | /* Avoid race with userspace read via bdev */ | ||
392 | lock_buffer(c_bh); | ||
391 | memcpy(c_bh->b_data, bhs[n]->b_data, sb->s_blocksize); | 393 | memcpy(c_bh->b_data, bhs[n]->b_data, sb->s_blocksize); |
392 | set_buffer_uptodate(c_bh); | 394 | set_buffer_uptodate(c_bh); |
395 | unlock_buffer(c_bh); | ||
393 | mark_buffer_dirty_inode(c_bh, sbi->fat_inode); | 396 | mark_buffer_dirty_inode(c_bh, sbi->fat_inode); |
394 | if (sb->s_flags & SB_SYNCHRONOUS) | 397 | if (sb->s_flags & SB_SYNCHRONOUS) |
395 | err = sync_dirty_buffer(c_bh); | 398 | err = sync_dirty_buffer(c_bh); |