[PATCH v3 1/4] gfs2: protect quota refresh from superblock teardown
From: Jiacheng Xu
Date: Sun Sep 20 2026 - 04:31:44 EST
The quota_refresh sysfs attributes become visible before fill_super()
has completed, so the quota inode can still be uninitialized when a
userspace write reaches gfs2_quota_refresh(). The same callback can
also race with superblock teardown.
Serialize the callback with the superblock lifetime using a read-side
s_umount lock. Use down_read_trylock() so the mount failure path can
remove the sysfs files while holding the write side of the lock without
deadlocking, and reject accesses before SB_ACTIVE is set.
Keep the locking in the user and group sysfs callbacks and introduce
shared helpers for the other sysfs operations.
Return -EAGAIN when the filesystem is not available for the callback.
Signed-off-by: Jiacheng Xu <stitch@xxxxxxxxxx>
---
fs/gfs2/sys.c | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/fs/gfs2/sys.c b/fs/gfs2/sys.c
index ea2c7b9e4a77..0d247299625c 100644
--- a/fs/gfs2/sys.c
+++ b/fs/gfs2/sys.c
@@ -63,6 +63,25 @@ static ssize_t id_show(struct gfs2_sbd *sdp, char *buf)
MAJOR(sdp->sd_vfs->s_dev), MINOR(sdp->sd_vfs->s_dev));
}
+/*
+ * Sysfs callbacks must not block on s_umount: mount failure and unmount
+ * remove the sysfs files while holding it for writing.
+ */
+static bool super_trylock_shared_active(struct super_block *sb)
+{
+ if (!down_read_trylock(&sb->s_umount))
+ return false;
+ if (sb->s_flags & SB_ACTIVE)
+ return true;
+ up_read(&sb->s_umount);
+ return false;
+}
+
+static void super_unlock_active(struct super_block *sb)
+{
+ up_read(&sb->s_umount);
+}
+
static ssize_t status_show(struct gfs2_sbd *sdp, char *buf)
{
unsigned long f = sdp->sd_flags;
@@ -248,6 +267,7 @@ static ssize_t quota_sync_store(struct gfs2_sbd *sdp, const char *buf,
static ssize_t quota_refresh_user_store(struct gfs2_sbd *sdp, const char *buf,
size_t len)
{
+ struct super_block *sb = sdp->sd_vfs;
struct kqid qid;
int error;
u32 id;
@@ -263,13 +283,19 @@ static ssize_t quota_refresh_user_store(struct gfs2_sbd *sdp, const char *buf,
if (!qid_valid(qid))
return -EINVAL;
+ if (!super_trylock_shared_active(sb))
+ return -EAGAIN;
+
error = gfs2_quota_refresh(sdp, qid);
+
+ super_unlock_active(sb);
return error ? error : len;
}
static ssize_t quota_refresh_group_store(struct gfs2_sbd *sdp, const char *buf,
size_t len)
{
+ struct super_block *sb = sdp->sd_vfs;
struct kqid qid;
int error;
u32 id;
@@ -285,7 +311,12 @@ static ssize_t quota_refresh_group_store(struct gfs2_sbd *sdp, const char *buf,
if (!qid_valid(qid))
return -EINVAL;
+ if (!super_trylock_shared_active(sb))
+ return -EAGAIN;
+
error = gfs2_quota_refresh(sdp, qid);
+
+ super_unlock_active(sb);
return error ? error : len;
}
--
2.51.0