WARNING in nilfs_btree_assign
syzbot
syzbotHello,
syzbot found the following issue on:
HEAD commit: bf682942cd26 Merge tag 'scsi-fixes' of git://git.kernel.or.. git tree: upstream console output: https://syzkaller.appspot.com/x/log.txt?x=15f32754880000 kernel config: https://syzkaller.appspot.com/x/.config?x=c221af36f6d1d811 dashboard link: https://syzkaller.appspot.com/bug?extid=31837fe952932efc8fb9 compiler: Debian clang version 13.0.1-++20220126092033+75e33f71c2da-1~exp1~20220126212112.63, GNU ld (GNU Binutils for Debian) 2.35.2 syz repro: https://syzkaller.appspot.com/x/repro.syz?x=178d9754880000 C reproducer: https://syzkaller.appspot.com/x/repro.c?x=17a0e86c880000
Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/a27f1315833f/disk-bf682942.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/10067330020a/vmlinux-bf682942.xz
IMPORTANT: if you fix the issue, please add the following tag to the commit:
------------[ cut here ]------------ WARNING: CPU: 1 PID: 5419 at fs/nilfs2/btree.c:2275 nilfs_btree_assign+0xa75/0xd00 Modules linked in: CPU: 1 PID: 5419 Comm: segctord Not tainted 6.0.0-rc6-syzkaller-00210-gbf682942cd26 #0 Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 08/26/2022 RIP: 0010:nilfs_btree_assign+0xa75/0xd00 fs/nilfs2/btree.c:2275 Code: 00 0f 85 a4 02 00 00 44 89 f8 48 8d 65 d8 5b 41 5c 41 5d 41 5e 41 5f 5d c3 e8 17 03 41 fe 4c 8b 74 24 38 eb a5 e8 0b 03 41 fe <0f> 0b 41 bf fe ff ff ff 4c 8b 74 24 38 eb 91 44 89 f9 80 e1 07 80 RSP: 0018:ffffc900038675a0 EFLAGS: 00010293 RAX: ffffffff83468745 RBX: ffff888062904028 RCX: ffff88801c9ad880 RDX: 0000000000000000 RSI: 00000000fffffffe RDI: 00000000fffffffe RBP: ffffc900038676d0 R08: ffffffff834680af R09: ffffed100cc669b4 R10: ffffed100cc669b4 R11: 1ffff1100cc669b3 R12: ffff88807be97800 R13: dffffc0000000000 R14: 0000000000000001 R15: 00000000fffffffe FS: 0000000000000000(0000) GS:ffff8880b9b00000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00000000200d9000 CR3: 00000000715c0000 CR4: 00000000003506e0 DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400 Call Trace: <TASK> nilfs_bmap_assign+0x87/0x150 fs/nilfs2/bmap.c:382 nilfs_segctor_update_payload_blocknr fs/nilfs2/segment.c:1589 [inline] nilfs_segctor_assign fs/nilfs2/segment.c:1623 [inline] nilfs_segctor_do_construct+0x3a49/0x6fe0 fs/nilfs2/segment.c:2050 nilfs_segctor_construct+0x143/0x8d0 fs/nilfs2/segment.c:2375 nilfs_segctor_thread_construct fs/nilfs2/segment.c:2483 [inline] nilfs_segctor_thread+0x534/0x1180 fs/nilfs2/segment.c:2566 kthread+0x266/0x300 kernel/kthread.c:376 ret_from_fork+0x1f/0x30 </TASK>
---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller-/JYPxA39Uh5TLH3MbocFF+G/[email protected]
syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot. syzbot can test patches for this issue, for details see:
https://goo.gl/tpsmEJ#testing-patches
Ryusuke Konishi
maintainerPATCH 1/1 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.
--- fs/nilfs2/segbuf.c | 6 ++++++ fs/nilfs2/segment.c | 7 +++++++ fs/nilfs2/super.c | 23 ++++++++++++++++++++++- 3 files changed, 35 insertions(+), 1 deletion(-)
fs/nilfs2/segbuf.c +6 -0
@@ -101,6 +101,12 @@ int nilfs_segbuf_extend_segsum(struct nilfs_segment_buffer *segbuf)if (unlikely (!bh))return -ENOMEM ; +lock_buffer (bh); +if (!buffer_uptodate (bh)) { +memset (bh->b_data,0 , bh->b_size); +set_buffer_uptodate (bh); + } +unlock_buffer (bh);nilfs_segbuf_add_segsum_buffer (segbuf, bh);return 0 ; }
fs/nilfs2/segment.c +7 -0
@@ -981,10 +981,13 @@ static void nilfs_segctor_fill_in_super_root(struct nilfs_sc_info *sci,unsigned int isz, srsz; bh_sr =NILFS_LAST_SEGBUF (&sci->sc_segbufs)->sb_super_root; + +lock_buffer (bh_sr); raw_sr = (struct nilfs_super_root *)bh_sr->b_data; isz = nilfs->ns_inode_size; srsz =NILFS_SR_BYTES (isz); + raw_sr->sr_sum =0 ;/* Ensure initialization within this update */ raw_sr->sr_bytes =cpu_to_le16 (srsz); raw_sr->sr_nongc_ctime = cpu_to_le64(nilfs_doing_gc() ?
@@ -998,6 +1001,8 @@ static void nilfs_segctor_fill_in_super_root(struct nilfs_sc_info *sci,nilfs_write_inode_common (nilfs->ns_sufile, (void *)raw_sr +NILFS_SR_SUFILE_OFFSET (isz),1 );memset ((void *)raw_sr + srsz,0 , nilfs->ns_blocksize - srsz); +set_buffer_uptodate (bh_sr); +unlock_buffer (bh_sr); }static void nilfs_redirty_inodes (struct list_head *head)
@@ -1780,6 +1785,7 @@ static void nilfs_abort_logs(struct list_head *logs, int err)list_for_each_entry (segbuf, logs, sb_list) {list_for_each_entry (bh, &segbuf->sb_segsum_buffers, b_assoc_buffers) { +clear_buffer_uptodate (bh);if (bh->b_page != bd_page) {if (bd_page)end_page_writeback (bd_page);
@@ -1791,6 +1797,7 @@ static void nilfs_abort_logs(struct list_head *logs, int err) b_assoc_buffers) {clear_buffer_async_write (bh);if (bh == segbuf->sb_super_root) { +clear_buffer_uptodate (bh);if (bh->b_page != bd_page) {end_page_writeback (bd_page); bd_page = bh->b_page;
fs/nilfs2/super.c +22 -1
@@ -372,10 +372,31 @@ static int nilfs_move_2nd_super(struct super_block *sb, loff_t sb2off) goto out; } nsbp = (void *)nsbh->b_data + offset; -memset (nsbp,0 , nilfs->ns_blocksize); +lock_buffer (nsbh);if (sb2i >=0 ) { +/* +* The position of the second superblock only changes by 4KiB, +* which is larger than the maximum superblock data size +* (= 1KiB), so there is no need to use memmove() to allow +* overlap between source and destination. +*/ memcpy (nsbp, nilfs->ns_sbp[sb2i], nilfs->ns_sbsize); + +/* +* Zero fill after copy to avoid overwriting in case of move +* within the same block. +*/ +memset (nsbh->b_data,0 , offset); +memset ((void *)nsbp + nilfs->ns_sbsize,0 , + nsbh->b_size - offset - nilfs->ns_sbsize); + }else { +memset (nsbh->b_data,0 , nsbh->b_size); + } +set_buffer_uptodate (nsbh); +unlock_buffer (nsbh); + +if (sb2i >=0 ) {brelse (nilfs->ns_sbh[sb2i]); nilfs->ns_sbh[sb2i] = nsbh; nilfs->ns_sbp[sb2i] = nsbp;
Signature · 1 line
2.34.1