From 60e23b13980bbc950655ba3f0e819180aa82a8aa Mon Sep 17 00:00:00 2001 From: Sanan Hasanov Date: Sun, 18 Oct 2026 21:36:45 -0400 Subject: [PATCH] minix: restore uptodate on bitmap buffers before redirtying them The inode and zone bitmap buffers are read once at mount time and stay pinned in sbi->s_imap[] and sbi->s_zmap[] until unmount. Their in-memory contents are the authoritative copy of the allocation state. If writeback of one of these buffers fails, the buffer write completion handler marks a write I/O error and clears BH_Uptodate. The next bitmap update then calls mark_buffer_dirty() on a buffer that is no longer uptodate and trips: WARNING: fs/buffer.c:991 at mark_buffer_dirty+0x299/0x410 ... minix_free_block fs/minix/bitmap.c free_data fs/minix/itree_common.c:272 [inline] truncate fs/minix/itree_common.c:321 [inline] V1_minix_truncate+0x310/0x10c0 fs/minix/itree_v1.c:61 minix_evict_inode+0x1c9/0x260 fs/minix/inode.c:52 evict+0x624/0xb50 fs/inode.c:822 minix_mkdir+0xdc/0x120 fs/minix/namei.c:136 syzbot hits this by shrinking the loop device under a mounted minix filesystem so that bitmap writeback fails, then letting a failed mkdir evict the new inode and free its blocks. Since the buffer data is still valid, do what ext2 and ext4 do for their long-lived superblock buffers: clear the write I/O error and set the buffer uptodate again before dirtying it, so the bitmap is written out again on the next writeback. Do this under the buffer lock so it cannot interleave with a write that is still in flight and about to fail. The original failure has already been logged and recorded in the block device mapping by mark_buffer_write_io_error(), so clearing the per-buffer flag does not hide it. Reported-by: syzbot+e353ad4408c4f9fac965@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=e353ad4408c4f9fac965 Signed-off-by: Sanan Hasanov Link: https://patch.msgid.link/20261019013645.40834-1-sanan.hasanou@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/minix/bitmap.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/fs/minix/bitmap.c b/fs/minix/bitmap.c index abec438330a7..0ac96c88e74d 100644 --- a/fs/minix/bitmap.c +++ b/fs/minix/bitmap.c @@ -39,6 +39,24 @@ static __u32 count_free(struct buffer_head *map[], unsigned blocksize, __u32 num return sum; } +/* + * The bitmap buffers stay pinned for the lifetime of the mount and their + * in-memory contents are authoritative. A failed writeback clears + * BH_Uptodate on them, so restore it before redirtying the buffer instead + * of tripping the warning in mark_buffer_dirty(). The buffer lock keeps + * this from racing with a write that is still in flight. + */ +static void minix_mark_bitmap_dirty(struct buffer_head *bh) +{ + lock_buffer(bh); + if (!buffer_uptodate(bh)) { + clear_buffer_write_io_error(bh); + set_buffer_uptodate(bh); + } + mark_buffer_dirty(bh); + unlock_buffer(bh); +} + void minix_free_block(struct inode *inode, unsigned long block) { struct super_block *sb = inode->i_sb; @@ -64,7 +82,7 @@ void minix_free_block(struct inode *inode, unsigned long block) printk("minix_free_block (%s:%lu): bit already cleared\n", sb->s_id, block); spin_unlock(&bitmap_lock); - mark_buffer_dirty(bh); + minix_mark_bitmap_dirty(bh); return; } @@ -83,7 +101,7 @@ int minix_new_block(struct inode * inode) if (j < bits_per_zone) { minix_set_bit(j, bh->b_data); spin_unlock(&bitmap_lock); - mark_buffer_dirty(bh); + minix_mark_bitmap_dirty(bh); j += i * bits_per_zone + sbi->s_firstdatazone-1; if (j < sbi->s_firstdatazone || j >= sbi->s_nzones) break; @@ -207,7 +225,7 @@ void minix_free_inode(struct inode * inode) if (!minix_test_and_clear_bit(bit, bh->b_data)) printk("minix_free_inode: bit %lu already cleared\n", bit); spin_unlock(&bitmap_lock); - mark_buffer_dirty(bh); + minix_mark_bitmap_dirty(bh); } struct inode *minix_new_inode(const struct inode *dir, umode_t mode) @@ -243,7 +261,7 @@ struct inode *minix_new_inode(const struct inode *dir, umode_t mode) return ERR_PTR(-ENOSPC); } spin_unlock(&bitmap_lock); - mark_buffer_dirty(bh); + minix_mark_bitmap_dirty(bh); j += i * bits_per_zone; if (!j || j > sbi->s_ninodes) { iput(inode);