Skip to content

Commit 679bd7e

Browse files
konisakpm00
authored andcommitted
nilfs2: fix buffer corruption due to concurrent device reads
As a result of analysis of a syzbot report, it turned out that in three cases where nilfs2 allocates block device buffers directly via sb_getblk, concurrent reads to the device can corrupt the allocated buffers. Nilfs2 uses sb_getblk for segment summary blocks, that make up a log header, and the super root block, that is the trailer, and when moving and writing the second super block after fs resize. In any of these, since the uptodate flag is not set when storing metadata to be written in the allocated buffers, the stored metadata will be overwritten if a device read of the same block occurs concurrently before the write. This causes metadata corruption and misbehavior in the log write itself, causing warnings in nilfs_btree_assign() as reported. Fix these issues by setting an uptodate flag on the buffer head on the first or before modifying each buffer obtained with sb_getblk, and clearing the flag on failure. When setting the uptodate flag, the lock_buffer/unlock_buffer pair is used to perform necessary exclusive control, and the buffer is filled to ensure that uninitialized bytes are not mixed into the data read from others. As for buffers for segment summary blocks, they are filled incrementally, so if the uptodate flag was unset on their allocation, set the flag and zero fill the buffer once at that point. Also, regarding the superblock move routine, the starting point of the memset call to zerofill the block is incorrectly specified, which can cause a buffer overflow on file systems with block sizes greater than 4KiB. In addition, if the superblock is moved within a large block, it is necessary to assume the possibility that the data in the superblock will be destroyed by zero-filling before copying. So fix these potential issues as well. Link: https://lkml.kernel.org/r/[email protected] Signed-off-by: Ryusuke Konishi <[email protected]> Reported-by: [email protected] Closes: https://lkml.kernel.org/r/[email protected] Tested-by: Ryusuke Konishi <[email protected]> Cc: <[email protected]> Signed-off-by: Andrew Morton <[email protected]>
1 parent 6a59cb5 commit 679bd7e

File tree

3 files changed

+35
-1
lines changed

3 files changed

+35
-1
lines changed

fs/nilfs2/segbuf.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ int nilfs_segbuf_extend_segsum(struct nilfs_segment_buffer *segbuf)
101101
if (unlikely(!bh))
102102
return -ENOMEM;
103103

104+
lock_buffer(bh);
105+
if (!buffer_uptodate(bh)) {
106+
memset(bh->b_data, 0, bh->b_size);
107+
set_buffer_uptodate(bh);
108+
}
109+
unlock_buffer(bh);
104110
nilfs_segbuf_add_segsum_buffer(segbuf, bh);
105111
return 0;
106112
}

fs/nilfs2/segment.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,10 +981,13 @@ static void nilfs_segctor_fill_in_super_root(struct nilfs_sc_info *sci,
981981
unsigned int isz, srsz;
982982

983983
bh_sr = NILFS_LAST_SEGBUF(&sci->sc_segbufs)->sb_super_root;
984+
985+
lock_buffer(bh_sr);
984986
raw_sr = (struct nilfs_super_root *)bh_sr->b_data;
985987
isz = nilfs->ns_inode_size;
986988
srsz = NILFS_SR_BYTES(isz);
987989

990+
raw_sr->sr_sum = 0; /* Ensure initialization within this update */
988991
raw_sr->sr_bytes = cpu_to_le16(srsz);
989992
raw_sr->sr_nongc_ctime
990993
= cpu_to_le64(nilfs_doing_gc() ?
@@ -998,6 +1001,8 @@ static void nilfs_segctor_fill_in_super_root(struct nilfs_sc_info *sci,
9981001
nilfs_write_inode_common(nilfs->ns_sufile, (void *)raw_sr +
9991002
NILFS_SR_SUFILE_OFFSET(isz), 1);
10001003
memset((void *)raw_sr + srsz, 0, nilfs->ns_blocksize - srsz);
1004+
set_buffer_uptodate(bh_sr);
1005+
unlock_buffer(bh_sr);
10011006
}
10021007

10031008
static void nilfs_redirty_inodes(struct list_head *head)
@@ -1780,6 +1785,7 @@ static void nilfs_abort_logs(struct list_head *logs, int err)
17801785
list_for_each_entry(segbuf, logs, sb_list) {
17811786
list_for_each_entry(bh, &segbuf->sb_segsum_buffers,
17821787
b_assoc_buffers) {
1788+
clear_buffer_uptodate(bh);
17831789
if (bh->b_page != bd_page) {
17841790
if (bd_page)
17851791
end_page_writeback(bd_page);
@@ -1791,6 +1797,7 @@ static void nilfs_abort_logs(struct list_head *logs, int err)
17911797
b_assoc_buffers) {
17921798
clear_buffer_async_write(bh);
17931799
if (bh == segbuf->sb_super_root) {
1800+
clear_buffer_uptodate(bh);
17941801
if (bh->b_page != bd_page) {
17951802
end_page_writeback(bd_page);
17961803
bd_page = bh->b_page;

fs/nilfs2/super.c

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,10 +372,31 @@ static int nilfs_move_2nd_super(struct super_block *sb, loff_t sb2off)
372372
goto out;
373373
}
374374
nsbp = (void *)nsbh->b_data + offset;
375-
memset(nsbp, 0, nilfs->ns_blocksize);
376375

376+
lock_buffer(nsbh);
377377
if (sb2i >= 0) {
378+
/*
379+
* The position of the second superblock only changes by 4KiB,
380+
* which is larger than the maximum superblock data size
381+
* (= 1KiB), so there is no need to use memmove() to allow
382+
* overlap between source and destination.
383+
*/
378384
memcpy(nsbp, nilfs->ns_sbp[sb2i], nilfs->ns_sbsize);
385+
386+
/*
387+
* Zero fill after copy to avoid overwriting in case of move
388+
* within the same block.
389+
*/
390+
memset(nsbh->b_data, 0, offset);
391+
memset((void *)nsbp + nilfs->ns_sbsize, 0,
392+
nsbh->b_size - offset - nilfs->ns_sbsize);
393+
} else {
394+
memset(nsbh->b_data, 0, nsbh->b_size);
395+
}
396+
set_buffer_uptodate(nsbh);
397+
unlock_buffer(nsbh);
398+
399+
if (sb2i >= 0) {
379400
brelse(nilfs->ns_sbh[sb2i]);
380401
nilfs->ns_sbh[sb2i] = nsbh;
381402
nilfs->ns_sbp[sb2i] = nsbp;

0 commit comments

Comments
 (0)