[PATCH] minix: keep the pinned bitmap buffers uptodate when dirtying them
From: Nguyen Ngoc Thang
Date: Tue Oct 13 2026 - 16:46:11 EST
minix reads the inode and zone bitmap buffer heads at mount time, keeps
them pinned and modifies them in place. If a write to the backing device
fails, bh_end_write() clears BH_Uptodate on the buffer. The next
allocation or free then calls mark_buffer_dirty() on it and trips:
WARNING: fs/buffer.c:991 at mark_buffer_dirty+0x299/0x410
!buffer_uptodate(bh)
Call Trace:
evict+0x624/0xb50 fs/inode.c:822
add_nondir fs/minix/namei.c:18 [inline]
minix_mknod fs/minix/namei.c:50 [inline]
minix_create+0x88/0xb0 fs/minix/namei.c:69
lookup_open+0xc0d/0x1a40 fs/namei.c:4596
Reproduced by mounting a minix image on a loop device, making all writes
to it fail with fail_make_request, syncing and creating a file; the
warning then comes from minix_new_inode(), and from minix_free_inode()
when the creation fails and the inode is evicted.
The in-memory bitmap is the only up to date copy, so it is still valid
after a failed write. Mark it uptodate again before dirtying it, so that
the bit is written out once the device recovers.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Reported-by: syzbot+d35de3666410c93fd821@xxxxxxxxxxxxxxxxxxxxxxxxx
Closes: https://syzkaller.appspot.com/bug?extid=d35de3666410c93fd821
Signed-off-by: Nguyen Ngoc Thang <ngocthang2710.1999@xxxxxxxxx>
---
fs/minix/bitmap.c | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/fs/minix/bitmap.c b/fs/minix/bitmap.c
index abec438330a7..011063a8999b 100644
--- a/fs/minix/bitmap.c
+++ b/fs/minix/bitmap.c
@@ -39,6 +39,16 @@ static __u32 count_free(struct buffer_head *map[], unsigned blocksize, __u32 num
return sum;
}
+/*
+ * The bitmap buffers are pinned and modified in place, so they stay
+ * authoritative even if an earlier write failed and cleared BH_Uptodate.
+ */
+static void minix_dirty_bitmap(struct buffer_head *bh)
+{
+ set_buffer_uptodate(bh);
+ mark_buffer_dirty(bh);
+}
+
void minix_free_block(struct inode *inode, unsigned long block)
{
struct super_block *sb = inode->i_sb;
@@ -64,7 +74,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_dirty_bitmap(bh);
return;
}
@@ -83,7 +93,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_dirty_bitmap(bh);
j += i * bits_per_zone + sbi->s_firstdatazone-1;
if (j < sbi->s_firstdatazone || j >= sbi->s_nzones)
break;
@@ -207,7 +217,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_dirty_bitmap(bh);
}
struct inode *minix_new_inode(const struct inode *dir, umode_t mode)
@@ -243,7 +253,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_dirty_bitmap(bh);
j += i * bits_per_zone;
if (!j || j > sbi->s_ninodes) {
iput(inode);
--
2.43.0