All of lore.kernel.org
 help / color / mirror / Atom feed
From: "syzbot" <syzbot@kernel.org>
To: syzkaller-upstream-moderation@googlegroups.com
Cc: syzbot@lists.linux.dev, uladzislau.zhauniarovich@gmail.com
Subject: [PATCH RFC v3] exfat: avoid setting SB_RDONLY directly on error
Date: Thu,  6 Aug 2026 15:00:37 +0000 (UTC)	[thread overview]
Message-ID: <9c0678ed-ecc6-455f-bb8a-2f605899c1fc@mail.kernel.org> (raw)

In exfat, an I/O error during sync_blockdev() triggers the error handler
(__exfat_fs_error()), which directly sets SB_RDONLY without holding the
s_umount semaphore. If thaw_super() is called after this, it sees the
filesystem as read-only and skips releasing the freeze semaphores. When the
filesystem is later unmounted and destroyed, the s_writers.rw_sem per-CPU
rw-semaphores are freed while still held for write, triggering a warning in
rcu_sync_dtor():

WARNING: kernel/rcu/sync.c:177 at rcu_sync_dtor+0xcd/0x180
Call Trace:
 <TASK>
 percpu_free_rwsem+0x43/0x80 kernel/locking/percpu-rwsem.c:42
 destroy_super_work+0x217/0x310 fs/super.c:284
 process_one_work kernel/workqueue.c:3322 [inline]
 process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405
 worker_thread+0xa47/0xfb0 kernel/workqueue.c:3486
 kthread+0x388/0x470 kernel/kthread.c:436
 ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
 </TASK>

Fix this by introducing an internal EXFAT_FLAGS_ERROR_RO flag instead of
directly setting SB_RDONLY on error. This prevents confusing the VFS freeze
logic and allows thaw_super() to correctly release the semaphores. We also
add the exfat_check_writable() helper to check both EXFAT_FLAGS_SHUTDOWN
and EXFAT_FLAGS_ERROR_RO, and use it to reject write operations when the
filesystem is in an error-induced read-only state.

A separate EXFAT_FLAGS_ERROR_RO flag is used instead of reusing
EXFAT_FLAGS_SHUTDOWN because reads, fsync, and writeback of already-dirty
data must keep working when the filesystem is in an error-induced read-only
state, so only modifying operations are rejected with -EROFS.

Fixes: f761fcdd289d ("exfat: Implement sops->shutdown and ioctl")
Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot
Reported-by: syzbot+823cd0d24881f21ab9f1@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=823cd0d24881f21ab9f1
Link: https://syzkaller.appspot.com/ai_job?id=11376f36-01ae-409d-9d3b-0b3206678981
To: "Namjae Jeon" <linkinjeon@kernel.org>
To: <linux-fsdevel@vger.kernel.org>
To: "Sungjong Seo" <sj1557.seo@samsung.com>
To: "Dongliang Cui" <dongliang.cui@unisoc.com>
Cc: <linux-kernel@vger.kernel.org>
Cc: "Yuezhang Mo" <yuezhang.mo@sony.com>

---
v3:
- Wrapped long lines in exfat_check_writable() and __exfat_fs_error() to comply with coding style guidelines.
- Updated the commit description to explain why EXFAT_FLAGS_ERROR_RO is used instead of EXFAT_FLAGS_SHUTDOWN.

v2:
- Dropped the fs/super.c changes to handle rollback of freeze on sync_blockdev failure.
- Fixed the issue entirely inside exfat by introducing an internal EXFAT_FLAGS_ERROR_RO flag instead of directly setting SB_RDONLY.
- Added exfat_check_writable() helper to check both EXFAT_FLAGS_SHUTDOWN and EXFAT_FLAGS_ERROR_RO.
- Updated write paths in exfat to use exfat_check_writable().
- Cleared EXFAT_FLAGS_ERROR_RO in exfat_reconfigure() when remounting read-write.
https://lore.kernel.org/all/8a80967c-5201-4c6a-a2b2-521c49cd5e93@mail.kernel.org/T/

v1:
https://lore.kernel.org/all/fcc77fd9-e38f-49f9-8268-e86683444f29@mail.kernel.org/T/
---
diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h
index 9be50949c..cffac6590 100644
--- a/fs/exfat/exfat_fs.h
+++ b/fs/exfat/exfat_fs.h
@@ -122,6 +122,7 @@ enum {
 
 /* Superblock flags */
 #define EXFAT_FLAGS_SHUTDOWN	1
+#define EXFAT_FLAGS_ERROR_RO	2
 
 struct exfat_dentry_namebuf {
 	char *lfn;
@@ -320,6 +321,16 @@ static inline int exfat_forced_shutdown(struct super_block *sb)
 	return test_bit(EXFAT_FLAGS_SHUTDOWN, &EXFAT_SB(sb)->s_exfat_flags);
 }
 
+static inline int exfat_check_writable(struct super_block *sb)
+{
+	if (unlikely(exfat_forced_shutdown(sb)))
+		return -EIO;
+	if (unlikely(test_bit(EXFAT_FLAGS_ERROR_RO,
+			&EXFAT_SB(sb)->s_exfat_flags)))
+		return -EROFS;
+	return 0;
+}
+
 /*
  * If ->i_mode can't hold 0222 (i.e. ATTR_RO), we use ->i_attrs to
  * save ATTR_RO instead of ->i_mode.
diff --git a/fs/exfat/file.c b/fs/exfat/file.c
index 5fc13378d..b6afeede2 100644
--- a/fs/exfat/file.c
+++ b/fs/exfat/file.c
@@ -125,8 +125,9 @@ static long exfat_fallocate(struct file *file, int mode,
 	if (!S_ISREG(inode->i_mode))
 		return -EOPNOTSUPP;
 
-	if (unlikely(exfat_forced_shutdown(inode->i_sb)))
-		return -EIO;
+	err = exfat_check_writable(inode->i_sb);
+	if (err)
+		return err;
 
 	inode_lock(inode);
 
@@ -354,8 +355,9 @@ int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
 	unsigned int ia_valid;
 	int error;
 
-	if (unlikely(exfat_forced_shutdown(inode->i_sb)))
-		return -EIO;
+	error = exfat_check_writable(inode->i_sb);
+	if (error)
+		return error;
 
 	if ((attr->ia_valid & ATTR_SIZE) &&
 	    attr->ia_size > i_size_read(inode)) {
@@ -738,8 +740,9 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
 	loff_t valid_size;
 	int err;
 
-	if (unlikely(exfat_forced_shutdown(inode->i_sb)))
-		return -EIO;
+	ret = exfat_check_writable(inode->i_sb);
+	if (ret < 0)
+		return ret;
 
 	inode_lock(inode);
 
@@ -826,6 +829,11 @@ static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf)
 	struct exfat_inode_info *ei = EXFAT_I(inode);
 	vm_fault_t ret;
 	loff_t new_valid_size, mmap_valid_size;
+	int err;
+
+	err = exfat_check_writable(inode->i_sb);
+	if (err)
+		return vmf_fs_error(err);
 
 	if (!inode_trylock(inode))
 		return VM_FAULT_RETRY;
@@ -835,8 +843,6 @@ static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf)
 
 	if (ei->valid_size < new_valid_size) {
 		if (ei->zeroed_size < mmap_valid_size) {
-			int err;
-
 			/*
 			 * Only zero the range that hasn't been zeroed yet for
 			 * this mmap write path. zeroed_size tracks the largest
diff --git a/fs/exfat/misc.c b/fs/exfat/misc.c
index 6f11a96a4..d5cd5b9a6 100644
--- a/fs/exfat/misc.c
+++ b/fs/exfat/misc.c
@@ -41,8 +41,9 @@ void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...)
 		panic("exFAT-fs (%s): fs panic from previous error\n",
 			sb->s_id);
 	} else if (opts->errors == EXFAT_ERRORS_RO && !sb_rdonly(sb)) {
-		sb->s_flags |= SB_RDONLY;
-		exfat_err(sb, "Filesystem has been set read-only");
+		if (!test_and_set_bit(EXFAT_FLAGS_ERROR_RO,
+				&EXFAT_SB(sb)->s_exfat_flags))
+			exfat_err(sb, "Filesystem has been set read-only");
 	}
 }
 
diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c
index b7d5e44ad..f08170de6 100644
--- a/fs/exfat/namei.c
+++ b/fs/exfat/namei.c
@@ -547,8 +547,9 @@ static int exfat_create(struct mnt_idmap *idmap, struct inode *dir,
 	int err;
 	loff_t size = i_size_read(dir);
 
-	if (unlikely(exfat_forced_shutdown(sb)))
-		return -EIO;
+	err = exfat_check_writable(sb);
+	if (err)
+		return err;
 
 	mutex_lock(&EXFAT_SB(sb)->s_lock);
 	exfat_set_volume_dirty(sb);
@@ -765,10 +766,11 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry)
 	struct inode *inode = dentry->d_inode;
 	struct exfat_inode_info *ei = EXFAT_I(inode);
 	struct exfat_entry_set_cache es;
-	int err = 0;
+	int err;
 
-	if (unlikely(exfat_forced_shutdown(sb)))
-		return -EIO;
+	err = exfat_check_writable(sb);
+	if (err)
+		return err;
 
 	mutex_lock(&EXFAT_SB(sb)->s_lock);
 	if (ei->dir.dir == DIR_DELETED) {
@@ -820,8 +822,9 @@ static struct dentry *exfat_mkdir(struct mnt_idmap *idmap, struct inode *dir,
 	int err;
 	loff_t size = i_size_read(dir);
 
-	if (unlikely(exfat_forced_shutdown(sb)))
-		return ERR_PTR(-EIO);
+	err = exfat_check_writable(sb);
+	if (err)
+		return ERR_PTR(err);
 
 	mutex_lock(&EXFAT_SB(sb)->s_lock);
 	exfat_set_volume_dirty(sb);
@@ -911,8 +914,9 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
 	struct exfat_entry_set_cache es;
 	int err;
 
-	if (unlikely(exfat_forced_shutdown(sb)))
-		return -EIO;
+	err = exfat_check_writable(sb);
+	if (err)
+		return err;
 
 	mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock);
 
@@ -995,9 +999,6 @@ static int exfat_rename_file(struct inode *parent_inode,
 	int sync = IS_DIRSYNC(parent_inode);
 	unsigned int num_extra_entries, num_total_entries;
 
-	if (unlikely(exfat_forced_shutdown(sb)))
-		return -EIO;
-
 	num_new_entries = exfat_calc_num_entries(p_uniname);
 	if (num_new_entries < 0)
 		return num_new_entries;
@@ -1267,6 +1268,10 @@ static int exfat_rename(struct mnt_idmap *idmap,
 	if (flags & ~RENAME_NOREPLACE)
 		return -EINVAL;
 
+	err = exfat_check_writable(sb);
+	if (err)
+		return err;
+
 	mutex_lock(&EXFAT_SB(sb)->s_lock);
 	old_inode = old_dentry->d_inode;
 	new_inode = new_dentry->d_inode;
diff --git a/fs/exfat/super.c b/fs/exfat/super.c
index 388db271c..dcb6e15e4 100644
--- a/fs/exfat/super.c
+++ b/fs/exfat/super.c
@@ -807,6 +807,9 @@ static int exfat_reconfigure(struct fs_context *fc)
 
 	swap(*cur_opts, *new_opts);
 
+	if (!(fc->sb_flags & SB_RDONLY))
+		clear_bit(EXFAT_FLAGS_ERROR_RO, &sbi->s_exfat_flags);
+
 	return 0;
 }
 


base-commit: 1590cf0329716306e948a8fc29f1d3ee87d3989f
-- 
This is an AI-generated patch subject to moderation.
Reply with '#syz upstream' to Sign-off the patch as a human author
and send it to the upstream kernel mailing lists.
Reply with '#syz reject' to reject it ('#syz unreject' to undo).

See https://goo.gle/syzbot-ai-patches for information about AI-generated patches.
The person who has signed off on the patch is responsible for
addressing comments.
syzbot engineers can be reached at syzkaller@googlegroups.com.

                 reply	other threads:[~2026-08-06 15:00 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=9c0678ed-ecc6-455f-bb8a-2f605899c1fc@mail.kernel.org \
    --to=syzbot@kernel.org \
    --cc=syzbot@lists.linux.dev \
    --cc=syzkaller-upstream-moderation@googlegroups.com \
    --cc=uladzislau.zhauniarovich@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.