With errors=remount-ro ext2_error() sets SB_RDONLY from whatever context hit the error and without holding s_umount. That races with everyone else changing sb->s_flags and it breaks freezing. If it happens while the filesystem is frozen thaw_super() sees a read-only superblock and leaves the s_writers rwsems locked so every writer hangs. A lookup in a corrupted directory is enough. Karl proposed teaching thaw_super() about it but Jan preferred fixing the filesystems (cf. [1]) like ext4 and f2fs already did. So call sb_set_emergency_ro() instead and let the VFS refuse modifications. ext2 still refuses allocations and ext2_xattr_set() itself, ecryptfs calls the latter without write access on the lower mount. Unlinked inodes are left to e2fsck. ext2_write_inode() keeps working because writeback still writes data, bitmaps, indirect and directory blocks after the error and they'd be lost without the inode. A thaw after an error while frozen still writes the superblock but EXT2_ERROR_FS stays set in it. After a remount to read-only a remount to read-write fails until umount. A plain remount to read-write right after the error succeeds but the filesystem stays read-only. Writes through file descriptors opened before the error fail with EROFS. [1]: https://lore.kernel.org/r/hyhuckyohsabx2suvq3z7uy3pbgmihva6wgzxr3op7pzcrlu33@p7zzlgp2ehjf Reported-by: Karl Mehltretter Link: https://lore.kernel.org/r/20260903060855.4610-1-kmehltretter@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/ext2/balloc.c | 5 +++++ fs/ext2/ialloc.c | 2 ++ fs/ext2/inode.c | 4 +++- fs/ext2/super.c | 13 ++++++++----- fs/ext2/xattr.c | 2 ++ 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/fs/ext2/balloc.c b/fs/ext2/balloc.c index 80acc1e193871..77f2ed5a76a28 100644 --- a/fs/ext2/balloc.c +++ b/fs/ext2/balloc.c @@ -1236,6 +1236,11 @@ ext2_fsblk_t ext2_new_blocks(struct inode *inode, ext2_fsblk_t goal, *errp = -ENOSPC; sb = inode->i_sb; + if (sb_emergency_ro(sb)) { + *errp = -EROFS; + return 0; + } + /* * Check quota for allocation of this block. */ diff --git a/fs/ext2/ialloc.c b/fs/ext2/ialloc.c index bf21b57cf98cd..2741ecd2b94fc 100644 --- a/fs/ext2/ialloc.c +++ b/fs/ext2/ialloc.c @@ -433,6 +433,8 @@ struct inode *ext2_new_inode(struct inode *dir, umode_t mode, int err; sb = dir->i_sb; + if (sb_emergency_ro(sb)) + return ERR_PTR(-EROFS); inode = new_inode(sb); if (!inode) return ERR_PTR(-ENOMEM); diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c index 12ac4cfe15003..dd28637c60737 100644 --- a/fs/ext2/inode.c +++ b/fs/ext2/inode.c @@ -71,7 +71,9 @@ void ext2_evict_inode(struct inode * inode) struct ext2_block_alloc_info *rsv; int want_delete = 0; - if (!inode->i_nlink && !is_bad_inode(inode)) { + /* After an error made us read-only the inode is left to e2fsck. */ + if (!inode->i_nlink && !is_bad_inode(inode) && + !sb_emergency_ro(inode->i_sb)) { want_delete = 1; dquot_initialize(inode); } else { diff --git a/fs/ext2/super.c b/fs/ext2/super.c index fb217ef2d69f0..10d5165459749 100644 --- a/fs/ext2/super.c +++ b/fs/ext2/super.c @@ -53,7 +53,7 @@ void ext2_error(struct super_block *sb, const char *function, struct ext2_sb_info *sbi = EXT2_SB(sb); struct ext2_super_block *es = sbi->s_es; - if (!sb_rdonly(sb)) { + if (!sb_rdonly_any(sb)) { spin_lock(&sbi->s_lock); sbi->s_mount_state |= EXT2_ERROR_FS; es->s_state |= cpu_to_le16(EXT2_ERROR_FS); @@ -73,11 +73,11 @@ void ext2_error(struct super_block *sb, const char *function, if (test_opt(sb, ERRORS_PANIC)) panic("EXT2-fs: panic from previous error\n"); - if (!sb_rdonly(sb) && test_opt(sb, ERRORS_RO)) { + /* SB_RDONLY needs s_umount, see sb_set_emergency_ro(). */ + if (!sb_rdonly(sb) && test_opt(sb, ERRORS_RO) && + sb_set_emergency_ro(sb)) ext2_msg(sb, KERN_CRIT, "error: remounting filesystem read-only"); - sb->s_flags |= SB_RDONLY; - } } static void ext2_msg_fc(struct fs_context *fc, const char *prefix, @@ -179,7 +179,7 @@ static void ext2_put_super (struct super_block * sb) ext2_xattr_destroy_cache(sbi->s_ea_block_cache); sbi->s_ea_block_cache = NULL; - if (!sb_rdonly(sb)) { + if (!sb_rdonly_any(sb)) { struct ext2_super_block *es = sbi->s_es; spin_lock(&sbi->s_lock); @@ -1360,6 +1360,9 @@ static int ext2_reconfigure(struct fs_context *fc) if ((bool)(flags & SB_RDONLY) == sb_rdonly(sb)) goto out_set; if (flags & SB_RDONLY) { + /* After an error nothing may be written, just go read-only. */ + if (sb_emergency_ro(sb)) + goto out_set; if (le16_to_cpu(es->s_state) & EXT2_VALID_FS || !(sbi->s_mount_state & EXT2_VALID_FS)) goto out_set; diff --git a/fs/ext2/xattr.c b/fs/ext2/xattr.c index 8f608930a48c6..12dfffff189cb 100644 --- a/fs/ext2/xattr.c +++ b/fs/ext2/xattr.c @@ -429,6 +429,8 @@ ext2_xattr_set(struct inode *inode, int name_index, const char *name, ea_idebug(inode, "name=%d.%s, value=%p, value_len=%ld", name_index, name, value, (long)value_len); + if (sb_emergency_ro(sb)) + return -EROFS; if (value == NULL) value_len = 0; if (name == NULL) -- 2.53.0