[PATCH v3] f2fs: protect published gc_thread during teardown

Zhang Cen posted 1 patch 1 week, 4 days ago
There is a newer version of this series
fs/f2fs/f2fs.h    |  30 +++++++++-
fs/f2fs/gc.c      |  78 ++++++++++++++++--------
fs/f2fs/gc.h      |   8 ++-
fs/f2fs/segment.c |  34 +++++++----
fs/f2fs/super.c   |  11 ++++
fs/f2fs/sysfs.c   | 148 ++++++++++++++++++++++++++++++++++------------
6 files changed, 233 insertions(+), 76 deletions(-)
[PATCH v3] f2fs: protect published gc_thread during teardown
Posted by Zhang Cen 1 week, 4 days ago
f2fs stores the background GC worker in a heap-allocated sbi->gc_thread.
Foreground GC merge waiters, GC-thread sysfs callbacks, and victim-
selection readers can still touch waitqueues, policy knobs, or
f2fs_gc_task through that published object, but the dead-device shutdown
path can reach f2fs_stop_gc_thread() under shared s_umount coverage and
free it.

The buggy scenario involves two paths, with each column showing the order
within that path:

f2fs_balance_fs() reader:          dead-device shutdown:
1. sees low free space and         1. enters f2fs_shutdown()
   enters GC_MERGE                 2. calls f2fs_do_shutdown(..., false)
2. borrows sbi->gc_thread          3. calls f2fs_stop_gc_thread()
3. queues on gc_th->fggc_wq        4. stops gc_th->f2fs_gc_task
4. wakes gc_th->gc_wait_queue_head 5. frees gc_th
5. sleeps in io_schedule()         6. clears sbi->gc_thread
6. resumes in finish_wait()

Fix this by making gc_thread a properly published object. Initialize it
fully before publishing it, remove it from sbi->gc_thread with xchg()
before teardown, keep f2fs_gc_task alive until an SRCU grace period has
drained the borrowed readers, and route the waitqueue, scalar, and sysfs
users through shared get/put helpers. Recheck f2fs_gc_task after
prepare_to_wait() in f2fs_balance_fs() so teardown cannot miss an
already-queued foreground waiter.

Validation reproduced this kernel report:
KASAN slab-use-after-free in _raw_spin_lock_irqsave()
Read of size 1
Call trace:
  dump_stack_lvl() (?:?)
  print_address_description() (mm/kasan/report.c:373)
  _raw_spin_lock_irqsave() (?:?)
  print_report() (?:?)
  __virt_addr_valid() (?:?)
  srso_alias_return_thunk() (arch/x86/include/asm/nospec-branch.h:375)
  kasan_addr_to_slab() (mm/kasan/common.c:45)
  kasan_report() (?:?)
  __kasan_check_byte() (mm/kasan/common.c:571)
  lock_acquire() (?:?)
  rcu_is_watching() (?:?)
  prepare_to_wait() (kernel/sched/wait.c:248)
  f2fs_balance_fs() (fs/f2fs/segment.c:448)
  f2fs_write_single_data_page() (fs/f2fs/f2fs.h:4184)
  do_raw_spin_lock() (?:?)
  find_held_lock() (kernel/locking/lockdep.c:5340)
  folio_mkclean() (?:?)
  f2fs_write_cache_pages() (fs/f2fs/data.c:3238)
  __lock_acquire() (kernel/locking/lockdep.c:5077)
  unwind_next_frame() (?:?)
  arch_stack_walk() (?:?)
  __f2fs_write_data_pages() (fs/f2fs/data.c:3565)
  validate_chain() (kernel/locking/lockdep.c:3861)
  insert_work() (kernel/workqueue.c:2220)
  do_writepages() (mm/page-writeback.c:2560)
  filemap_writeback() (mm/filemap.c:371)
  __queue_work() (kernel/workqueue.c:2275)
  file_write_and_wait_range() (?:?)
  f2fs_do_sync_file() (fs/f2fs/file.c:285)
  __lock_release() (kernel/locking/lockdep.c:5511)
  f2fs_sync_file() (fs/f2fs/f2fs.h:3781)
  do_fsync() (fs/sync.c:204)
  __x64_sys_fsync() (?:?)
  do_syscall_64() (arch/x86/entry/syscall_64.c:87)
  entry_SYSCALL_64_after_hwframe() (?:?)
  kasan_save_stack() (mm/kasan/common.c:52)
  kasan_save_track() (mm/kasan/common.c:74)
  __kasan_kmalloc() (?:?)
  f2fs_start_gc_thread() (fs/f2fs/f2fs.h:4203)
  f2fs_fill_super() (fs/f2fs/super.c:4993)

Fixes: 5911d2d1d1a3 ("f2fs: introduce gc_merge mount option")
Signed-off-by: Zhang Cen <rollkingzzc@gmail.com>
---
v3:
- Add the Fixes tag for the GC_MERGE foreground wait path.
- Add comments for the gc_thread release/acquire publication pair.
- Fix checkpatch alignment issues and avoid misleading indentation in
  the critical_task_priority sysfs update path.

v2:
- Sashiko.dev pointed out that sysfs critical_task_priority could use
  a stale f2fs_gc_task.
- Sashiko.dev pointed out that GC_MERGE foreground waiters could miss
  teardown wakeups.
- Sashiko.dev pointed out that holding gc_thread_lock across
  kthread_run() could deadlock with reclaim.
- Sashiko.dev pointed out that gc_urgent and gc_idle still touched GC
  state without safe lifetime protection.
- Reset gc_thread_srcu_inited per f2fs_fill_super() attempt so cleanup
  only covers the current SRCU state.

 fs/f2fs/f2fs.h    |  30 +++++++++-
 fs/f2fs/gc.c      |  78 ++++++++++++++++--------
 fs/f2fs/gc.h      |   8 ++-
 fs/f2fs/segment.c |  34 +++++++----
 fs/f2fs/super.c   |  11 ++++
 fs/f2fs/sysfs.c   | 148 ++++++++++++++++++++++++++++++++++------------
 6 files changed, 233 insertions(+), 76 deletions(-)

diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index 91f506e7c9cfb..719c94119b71c 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -25,6 +25,7 @@
 #include <linux/quotaops.h>
 #include <linux/part_stat.h>
 #include <linux/rw_hint.h>
+#include <linux/srcu.h>
 
 #include <linux/fscrypt.h>
 #include <linux/fsverity.h>
@@ -156,6 +157,8 @@ typedef u32 block_t;	/*
 			 */
 typedef u32 nid_t;
 
+struct f2fs_gc_kthread;
+
 #define COMPRESS_EXT_NUM		16
 
 enum blkzone_allocation_policy {
@@ -1873,7 +1876,8 @@ struct f2fs_sb_info {
 						 * semaphore for GC, avoid
 						 * race between GC and GC or CP
 						 */
-	struct f2fs_gc_kthread	*gc_thread;	/* GC thread */
+	struct f2fs_gc_kthread	*gc_thread;	/* published GC thread */
+	struct srcu_struct gc_thread_srcu;	/* protects gc_thread readers */
 	struct atgc_management am;		/* atgc management */
 	unsigned int cur_victim_sec;		/* current victim section num */
 	unsigned int gc_mode;			/* current GC state */
@@ -4200,6 +4204,30 @@ extern const struct iomap_ops f2fs_iomap_ops;
 /*
  * gc.c
  */
+static inline struct f2fs_gc_kthread *
+f2fs_get_gc_thread(struct f2fs_sb_info *sbi, int *srcu_idx)
+{
+	struct f2fs_gc_kthread *gc_th;
+
+	*srcu_idx = srcu_read_lock(&sbi->gc_thread_srcu);
+	/*
+	 * Pairs with the release store in f2fs_start_gc_thread() so readers
+	 * see a fully initialized worker after observing sbi->gc_thread.
+	 */
+	gc_th = smp_load_acquire(&sbi->gc_thread);
+	if (!gc_th) {
+		srcu_read_unlock(&sbi->gc_thread_srcu, *srcu_idx);
+		*srcu_idx = -1;
+	}
+
+	return gc_th;
+}
+
+static inline void f2fs_put_gc_thread(struct f2fs_sb_info *sbi, int srcu_idx)
+{
+	srcu_read_unlock(&sbi->gc_thread_srcu, srcu_idx);
+}
+
 int f2fs_start_gc_thread(struct f2fs_sb_info *sbi);
 void f2fs_stop_gc_thread(struct f2fs_sb_info *sbi);
 block_t f2fs_start_bidx_of_node(unsigned int node_ofs, struct inode *inode);
diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c
index ba93010924c06..c38dc5e4f66de 100644
--- a/fs/f2fs/gc.c
+++ b/fs/f2fs/gc.c
@@ -30,10 +30,10 @@ static unsigned int count_bits(const unsigned long *addr,
 
 static int gc_thread_func(void *data)
 {
-	struct f2fs_sb_info *sbi = data;
-	struct f2fs_gc_kthread *gc_th = sbi->gc_thread;
-	wait_queue_head_t *wq = &sbi->gc_thread->gc_wait_queue_head;
-	wait_queue_head_t *fggc_wq = &sbi->gc_thread->fggc_wq;
+	struct f2fs_gc_kthread *gc_th = data;
+	struct f2fs_sb_info *sbi = gc_th->sbi;
+	wait_queue_head_t *wq = &gc_th->gc_wait_queue_head;
+	wait_queue_head_t *fggc_wq = &gc_th->fggc_wq;
 	unsigned int wait_ms;
 	struct f2fs_gc_control gc_control = {
 		.victim_segno = NULL_SEGNO,
@@ -134,7 +134,7 @@ static int gc_thread_func(void *data)
 				wait_ms = gc_th->max_sleep_time;
 		}
 
-		if (need_to_boost_gc(sbi)) {
+		if (need_to_boost_gc(sbi, gc_th)) {
 			decrease_sleep_time(gc_th, &wait_ms);
 			if (f2fs_sb_has_blkzoned(sbi))
 				gc_boost = true;
@@ -194,6 +194,7 @@ static int gc_thread_func(void *data)
 int f2fs_start_gc_thread(struct f2fs_sb_info *sbi)
 {
 	struct f2fs_gc_kthread *gc_th;
+	struct task_struct *task;
 	dev_t dev = sbi->sb->s_bdev->bd_dev;
 
 	gc_th = f2fs_kmalloc(sbi, sizeof(struct f2fs_gc_kthread), GFP_KERNEL);
@@ -219,36 +220,45 @@ int f2fs_start_gc_thread(struct f2fs_sb_info *sbi)
 		gc_th->boost_zoned_gc_percent = 0;
 	}
 
+	gc_th->sbi = sbi;
 	gc_th->gc_wake = false;
+	init_waitqueue_head(&gc_th->gc_wait_queue_head);
+	init_waitqueue_head(&gc_th->fggc_wq);
 
-	sbi->gc_thread = gc_th;
-	init_waitqueue_head(&sbi->gc_thread->gc_wait_queue_head);
-	init_waitqueue_head(&sbi->gc_thread->fggc_wq);
-	sbi->gc_thread->f2fs_gc_task = kthread_run(gc_thread_func, sbi,
-			"f2fs_gc-%u:%u", MAJOR(dev), MINOR(dev));
-	if (IS_ERR(gc_th->f2fs_gc_task)) {
-		int err = PTR_ERR(gc_th->f2fs_gc_task);
+	task = kthread_run(gc_thread_func, gc_th, "f2fs_gc-%u:%u",
+			   MAJOR(dev), MINOR(dev));
+	if (IS_ERR(task)) {
+		int err = PTR_ERR(task);
 
 		kfree(gc_th);
-		sbi->gc_thread = NULL;
 		return err;
 	}
 
-	set_user_nice(gc_th->f2fs_gc_task,
-			PRIO_TO_NICE(sbi->critical_task_priority));
+	get_task_struct(task);
+	WRITE_ONCE(gc_th->f2fs_gc_task, task);
+	set_user_nice(task, PRIO_TO_NICE(sbi->critical_task_priority));
+	/* Publish only after gc_th and its task pointer are fully initialized. */
+	smp_store_release(&sbi->gc_thread, gc_th);
 	return 0;
 }
 
 void f2fs_stop_gc_thread(struct f2fs_sb_info *sbi)
 {
-	struct f2fs_gc_kthread *gc_th = sbi->gc_thread;
+	struct f2fs_gc_kthread *gc_th;
+	struct task_struct *task;
 
+	gc_th = xchg(&sbi->gc_thread, NULL);
 	if (!gc_th)
 		return;
-	kthread_stop(gc_th->f2fs_gc_task);
+
+	task = xchg(&gc_th->f2fs_gc_task, NULL);
+	if (task)
+		kthread_stop(task);
 	wake_up_all(&gc_th->fggc_wq);
+	synchronize_srcu(&sbi->gc_thread_srcu);
+	if (task)
+		put_task_struct(task);
 	kfree(gc_th);
-	sbi->gc_thread = NULL;
 }
 
 static int select_gc_type(struct f2fs_sb_info *sbi, int gc_type)
@@ -795,8 +805,16 @@ int f2fs_get_victim(struct f2fs_sb_info *sbi, unsigned int *result,
 	p.age_threshold = sbi->am.age_threshold;
 	if (one_time) {
 		p.one_time_gc = one_time;
-		if (has_enough_free_secs(sbi, 0, NR_PERSISTENT_LOG))
-			valid_thresh_ratio = sbi->gc_thread->valid_thresh_ratio;
+		if (has_enough_free_secs(sbi, 0, NR_PERSISTENT_LOG)) {
+			struct f2fs_gc_kthread *gc_th;
+			int srcu_idx;
+
+			gc_th = f2fs_get_gc_thread(sbi, &srcu_idx);
+			if (gc_th) {
+				valid_thresh_ratio = gc_th->valid_thresh_ratio;
+				f2fs_put_gc_thread(sbi, srcu_idx);
+			}
+		}
 	}
 
 retry:
@@ -1776,11 +1794,21 @@ static int do_garbage_collect(struct f2fs_sb_info *sbi,
 			unsigned int window_granularity =
 				sbi->migration_window_granularity;
 
-			if (f2fs_sb_has_blkzoned(sbi) &&
-					!has_enough_free_blocks(sbi,
-					sbi->gc_thread->boost_zoned_gc_percent))
-				window_granularity *=
-					sbi->gc_thread->boost_gc_multiple;
+			if (f2fs_sb_has_blkzoned(sbi)) {
+				struct f2fs_gc_kthread *gc_th;
+				int srcu_idx;
+
+				gc_th = f2fs_get_gc_thread(sbi, &srcu_idx);
+				if (gc_th) {
+					unsigned int boost_percent =
+						gc_th->boost_zoned_gc_percent;
+
+					if (!has_enough_free_blocks(sbi, boost_percent))
+						window_granularity *=
+							gc_th->boost_gc_multiple;
+					f2fs_put_gc_thread(sbi, srcu_idx);
+				}
+			}
 
 			end_segno = start_segno + window_granularity;
 		}
diff --git a/fs/f2fs/gc.h b/fs/f2fs/gc.h
index 6c4d4567571eb..b32d57a70f760 100644
--- a/fs/f2fs/gc.h
+++ b/fs/f2fs/gc.h
@@ -45,7 +45,10 @@
 
 #define NR_GC_CHECKPOINT_SECS (3)	/* data/node/dentry sections */
 
+struct f2fs_sb_info;
+
 struct f2fs_gc_kthread {
+	struct f2fs_sb_info *sbi;
 	struct task_struct *f2fs_gc_task;
 	wait_queue_head_t gc_wait_queue_head;
 
@@ -193,10 +196,11 @@ static inline bool has_enough_invalid_blocks(struct f2fs_sb_info *sbi)
 			limit_free_user_blocks(invalid_user_blocks));
 }
 
-static inline bool need_to_boost_gc(struct f2fs_sb_info *sbi)
+static inline bool need_to_boost_gc(struct f2fs_sb_info *sbi,
+				    struct f2fs_gc_kthread *gc_th)
 {
 	if (f2fs_sb_has_blkzoned(sbi))
 		return !has_enough_free_blocks(sbi,
-				sbi->gc_thread->boost_zoned_gc_percent);
+				gc_th->boost_zoned_gc_percent);
 	return has_enough_invalid_blocks(sbi);
 }
diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c
index 788f8b0502492..cd8d5084c3f31 100644
--- a/fs/f2fs/segment.c
+++ b/fs/f2fs/segment.c
@@ -444,16 +444,30 @@ void f2fs_balance_fs(struct f2fs_sb_info *sbi, bool need)
 	if (has_enough_free_secs(sbi, 0, 0))
 		return;
 
-	if (test_opt(sbi, GC_MERGE) && sbi->gc_thread &&
-				sbi->gc_thread->f2fs_gc_task) {
-		DEFINE_WAIT(wait);
-
-		prepare_to_wait(&sbi->gc_thread->fggc_wq, &wait,
-					TASK_UNINTERRUPTIBLE);
-		wake_up(&sbi->gc_thread->gc_wait_queue_head);
-		io_schedule();
-		finish_wait(&sbi->gc_thread->fggc_wq, &wait);
-	} else {
+	if (test_opt(sbi, GC_MERGE)) {
+		struct f2fs_gc_kthread *gc_th;
+		int srcu_idx;
+
+		gc_th = f2fs_get_gc_thread(sbi, &srcu_idx);
+		if (gc_th) {
+			if (READ_ONCE(gc_th->f2fs_gc_task)) {
+				DEFINE_WAIT(wait);
+
+				prepare_to_wait(&gc_th->fggc_wq, &wait,
+						TASK_UNINTERRUPTIBLE);
+				if (READ_ONCE(gc_th->f2fs_gc_task)) {
+					wake_up(&gc_th->gc_wait_queue_head);
+					io_schedule();
+				}
+				finish_wait(&gc_th->fggc_wq, &wait);
+				f2fs_put_gc_thread(sbi, srcu_idx);
+				return;
+			}
+			f2fs_put_gc_thread(sbi, srcu_idx);
+		}
+	}
+
+	{
 		struct f2fs_gc_control gc_control = {
 			.victim_segno = NULL_SEGNO,
 			.init_gc_type = f2fs_sb_has_blkzoned(sbi) ?
diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
index ccf806b676f53..a1c3784656fb2 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -4954,11 +4954,13 @@ static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc)
 	int recovery, i, valid_super_block;
 	struct curseg_info *seg_i;
 	int retry_cnt = 1;
+	bool gc_thread_srcu_inited = false;
 #ifdef CONFIG_QUOTA
 	bool quota_enabled = false;
 #endif
 
 try_onemore:
+	gc_thread_srcu_inited = false;
 	err = -EINVAL;
 	raw_super = NULL;
 	valid_super_block = -1;
@@ -4993,6 +4995,10 @@ static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc)
 		spin_lock_init(&sbi->inode_lock[i]);
 	}
 	mutex_init(&sbi->flush_lock);
+	err = init_srcu_struct(&sbi->gc_thread_srcu);
+	if (err)
+		goto free_sbi;
+	gc_thread_srcu_inited = true;
 
 	/* set a block size */
 	if (unlikely(!sb_set_blocksize(sb, F2FS_BLKSIZE))) {
@@ -5454,6 +5460,10 @@ static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc)
 #ifdef CONFIG_DEBUG_LOCK_ALLOC
 	lockdep_unregister_key(&sbi->cp_global_sem_key);
 #endif
+	if (gc_thread_srcu_inited) {
+		cleanup_srcu_struct(&sbi->gc_thread_srcu);
+		gc_thread_srcu_inited = false;
+	}
 	kfree(sbi);
 	sb->s_fs_info = NULL;
 
@@ -5538,6 +5548,7 @@ static void kill_f2fs_super(struct super_block *sb)
 #ifdef CONFIG_DEBUG_LOCK_ALLOC
 		lockdep_unregister_key(&sbi->cp_global_sem_key);
 #endif
+		cleanup_srcu_struct(&sbi->gc_thread_srcu);
 		kfree(sbi);
 		sb->s_fs_info = NULL;
 	}
diff --git a/fs/f2fs/sysfs.c b/fs/f2fs/sysfs.c
index 352e96ad5c3a5..14f0d3c179789 100644
--- a/fs/f2fs/sysfs.c
+++ b/fs/f2fs/sysfs.c
@@ -375,12 +375,10 @@ static ssize_t __sbi_show_value(struct f2fs_attr *a,
 	}
 }
 
-static ssize_t f2fs_sbi_show(struct f2fs_attr *a,
-			struct f2fs_sb_info *sbi, char *buf)
+static ssize_t __f2fs_sbi_show(struct f2fs_attr *a,
+			       struct f2fs_sb_info *sbi, char *buf,
+			       unsigned char *ptr)
 {
-	unsigned char *ptr = NULL;
-
-	ptr = __struct_ptr(sbi, a->struct_type);
 	if (!ptr)
 		return -EINVAL;
 
@@ -464,6 +462,29 @@ static ssize_t f2fs_sbi_show(struct f2fs_attr *a,
 	return __sbi_show_value(a, sbi, buf, ptr + a->offset);
 }
 
+static ssize_t f2fs_sbi_show(struct f2fs_attr *a,
+			     struct f2fs_sb_info *sbi, char *buf)
+{
+	return __f2fs_sbi_show(a, sbi, buf,
+			       __struct_ptr(sbi, a->struct_type));
+}
+
+static ssize_t f2fs_gc_thread_show(struct f2fs_attr *a,
+				   struct f2fs_sb_info *sbi, char *buf)
+{
+	struct f2fs_gc_kthread *gc_th;
+	int srcu_idx;
+	ssize_t ret;
+
+	gc_th = f2fs_get_gc_thread(sbi, &srcu_idx);
+	if (!gc_th)
+		return -EINVAL;
+
+	ret = __f2fs_sbi_show(a, sbi, buf, (unsigned char *)gc_th);
+	f2fs_put_gc_thread(sbi, srcu_idx);
+	return ret;
+}
+
 static void __sbi_store_value(struct f2fs_attr *a,
 			struct f2fs_sb_info *sbi,
 			unsigned char *ui, unsigned long value)
@@ -487,16 +508,47 @@ static void __sbi_store_value(struct f2fs_attr *a,
 	}
 }
 
-static ssize_t __sbi_store(struct f2fs_attr *a,
-			struct f2fs_sb_info *sbi,
-			const char *buf, size_t count)
+static bool f2fs_wake_gc_thread(struct f2fs_sb_info *sbi)
+{
+	struct f2fs_gc_kthread *gc_th;
+	int srcu_idx;
+
+	gc_th = f2fs_get_gc_thread(sbi, &srcu_idx);
+	if (!gc_th)
+		return false;
+
+	gc_th->gc_wake = true;
+	wake_up_interruptible_all(&gc_th->gc_wait_queue_head);
+	f2fs_put_gc_thread(sbi, srcu_idx);
+	return true;
+}
+
+static void f2fs_set_gc_thread_nice(struct f2fs_sb_info *sbi, long priority)
+{
+	struct f2fs_gc_kthread *gc_th;
+	struct task_struct *gc_task;
+	int srcu_idx;
+
+	gc_th = f2fs_get_gc_thread(sbi, &srcu_idx);
+	if (!gc_th)
+		return;
+
+	gc_task = READ_ONCE(gc_th->f2fs_gc_task);
+	if (gc_task)
+		set_user_nice(gc_task, PRIO_TO_NICE(priority));
+
+	f2fs_put_gc_thread(sbi, srcu_idx);
+}
+
+static ssize_t __f2fs_sbi_store(struct f2fs_attr *a,
+				struct f2fs_sb_info *sbi,
+				unsigned char *ptr,
+				const char *buf, size_t count)
 {
-	unsigned char *ptr;
 	unsigned long t;
 	unsigned int *ui;
 	ssize_t ret;
 
-	ptr = __struct_ptr(sbi, a->struct_type);
 	if (!ptr)
 		return -EINVAL;
 
@@ -664,21 +716,13 @@ static ssize_t __sbi_store(struct f2fs_attr *a,
 			sbi->gc_mode = GC_NORMAL;
 		} else if (t == 1) {
 			sbi->gc_mode = GC_URGENT_HIGH;
-			if (sbi->gc_thread) {
-				sbi->gc_thread->gc_wake = true;
-				wake_up_interruptible_all(
-					&sbi->gc_thread->gc_wait_queue_head);
+			if (f2fs_wake_gc_thread(sbi))
 				wake_up_discard_thread(sbi, true);
-			}
 		} else if (t == 2) {
 			sbi->gc_mode = GC_URGENT_LOW;
 		} else if (t == 3) {
 			sbi->gc_mode = GC_URGENT_MID;
-			if (sbi->gc_thread) {
-				sbi->gc_thread->gc_wake = true;
-				wake_up_interruptible_all(
-					&sbi->gc_thread->gc_wait_queue_head);
-			}
+			f2fs_wake_gc_thread(sbi);
 		} else {
 			return -EINVAL;
 		}
@@ -934,14 +978,14 @@ static ssize_t __sbi_store(struct f2fs_attr *a,
 	if (!strcmp(a->attr.name, "gc_boost_gc_multiple")) {
 		if (t < 1 || t > SEGS_PER_SEC(sbi))
 			return -EINVAL;
-		sbi->gc_thread->boost_gc_multiple = (unsigned int)t;
+		*ui = (unsigned int)t;
 		return count;
 	}
 
 	if (!strcmp(a->attr.name, "gc_boost_gc_greedy")) {
 		if (t > GC_GREEDY)
 			return -EINVAL;
-		sbi->gc_thread->boost_gc_greedy = (unsigned int)t;
+		*ui = (unsigned int)t;
 		return count;
 	}
 
@@ -980,39 +1024,64 @@ static ssize_t __sbi_store(struct f2fs_attr *a,
 		return count;
 	}
 
-	if (!strcmp(a->attr.name, "critical_task_priority")) {
-		if (t < NICE_TO_PRIO(MIN_NICE) || t > NICE_TO_PRIO(MAX_NICE))
-			return -EINVAL;
-		if (!capable(CAP_SYS_NICE))
-			return -EPERM;
-		sbi->critical_task_priority = t;
-		if (sbi->cprc_info.f2fs_issue_ckpt)
-			set_user_nice(sbi->cprc_info.f2fs_issue_ckpt,
-					PRIO_TO_NICE(sbi->critical_task_priority));
-		if (sbi->gc_thread && sbi->gc_thread->f2fs_gc_task)
-			set_user_nice(sbi->gc_thread->f2fs_gc_task,
-					PRIO_TO_NICE(sbi->critical_task_priority));
-		return count;
-	}
+		if (!strcmp(a->attr.name, "critical_task_priority")) {
+			int nice;
+
+			if (t < NICE_TO_PRIO(MIN_NICE) || t > NICE_TO_PRIO(MAX_NICE))
+				return -EINVAL;
+			if (!capable(CAP_SYS_NICE))
+				return -EPERM;
+			sbi->critical_task_priority = t;
+			nice = PRIO_TO_NICE(sbi->critical_task_priority);
+			if (sbi->cprc_info.f2fs_issue_ckpt)
+				set_user_nice(sbi->cprc_info.f2fs_issue_ckpt, nice);
+			f2fs_set_gc_thread_nice(sbi, sbi->critical_task_priority);
+			return count;
+		}
 
 	__sbi_store_value(a, sbi, ptr + a->offset, t);
 
 	return count;
 }
 
+static ssize_t f2fs_gc_thread_store(struct f2fs_attr *a,
+				    struct f2fs_sb_info *sbi,
+				    const char *buf, size_t count)
+{
+	struct f2fs_gc_kthread *gc_th;
+	int srcu_idx;
+	ssize_t ret;
+
+	if (!down_read_trylock(&sbi->sb->s_umount))
+		return -EAGAIN;
+
+	gc_th = f2fs_get_gc_thread(sbi, &srcu_idx);
+	if (!gc_th) {
+		up_read(&sbi->sb->s_umount);
+		return -EINVAL;
+	}
+
+	ret = __f2fs_sbi_store(a, sbi, (unsigned char *)gc_th, buf, count);
+	f2fs_put_gc_thread(sbi, srcu_idx);
+	up_read(&sbi->sb->s_umount);
+	return ret;
+}
+
 static ssize_t f2fs_sbi_store(struct f2fs_attr *a,
 			struct f2fs_sb_info *sbi,
 			const char *buf, size_t count)
 {
 	ssize_t ret;
 	bool gc_entry = (!strcmp(a->attr.name, "gc_urgent") ||
+				 !strcmp(a->attr.name, "gc_idle") ||
 					a->struct_type == GC_THREAD);
 
 	if (gc_entry) {
 		if (!down_read_trylock(&sbi->sb->s_umount))
 			return -EAGAIN;
 	}
-	ret = __sbi_store(a, sbi, buf, count);
+	ret = __f2fs_sbi_store(a, sbi,
+			       __struct_ptr(sbi, a->struct_type), buf, count);
 	if (gc_entry)
 		up_read(&sbi->sb->s_umount);
 
@@ -1173,7 +1242,10 @@ static struct f2fs_attr f2fs_attr_##name = __ATTR(name, 0444, name##_show, NULL)
 #endif
 
 #define GC_THREAD_RW_ATTR(name, elname)				\
-	F2FS_RW_ATTR(GC_THREAD, f2fs_gc_kthread, name, elname)
+	F2FS_ATTR_OFFSET(GC_THREAD, name, 0644,			\
+		f2fs_gc_thread_show, f2fs_gc_thread_store,	\
+		offsetof(struct f2fs_gc_kthread, elname),	\
+		sizeof_field(struct f2fs_gc_kthread, elname))
 
 #define SM_INFO_RW_ATTR(name, elname)				\
 	F2FS_RW_ATTR(SM_INFO, f2fs_sm_info, name, elname)
-- 
2.43.0
Re: [PATCH v3] f2fs: protect published gc_thread during teardown
Posted by Dan Carpenter 1 week, 2 days ago
Hi Zhang,

kernel test robot noticed the following build warnings:

https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Zhang-Cen/f2fs-protect-published-gc_thread-during-teardown/20260528-145912
base:   linus/master
patch link:    https://lore.kernel.org/r/20260528065601.3257303-1-rollkingzzc%40gmail.com
patch subject: [PATCH v3] f2fs: protect published gc_thread during teardown
config: i386-randconfig-r071-20260529 (https://download.01.org/0day-ci/archive/20260530/202605300627.kUlwXIge-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
smatch: v0.5.0-9185-gbcc58b9c

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Reported-by: Dan Carpenter <error27@gmail.com>
| Closes: https://lore.kernel.org/r/202605300627.kUlwXIge-lkp@intel.com/

smatch warnings:
fs/f2fs/super.c:5006 f2fs_fill_super() warn: missing error code 'err'
fs/f2fs/sysfs.c:1027 __f2fs_sbi_store() warn: inconsistent indenting

vim +/err +5006 fs/f2fs/super.c

aff063e266cbf4 Jaegeuk Kim      2012-11-02  4961  
ed2e621a95d704 Jaegeuk Kim      2014-08-08  4962  try_onemore:
d1267b5f0b476d Zhang Cen        2026-05-28  4963  	gc_thread_srcu_inited = false;
da554e48caab95 hujianyang       2015-05-21  4964  	err = -EINVAL;
da554e48caab95 hujianyang       2015-05-21  4965  	raw_super = NULL;
e8240f656d4d5d Chao Yu          2015-12-15  4966  	valid_super_block = -1;
da554e48caab95 hujianyang       2015-05-21  4967  	recovery = 0;
da554e48caab95 hujianyang       2015-05-21  4968  
aff063e266cbf4 Jaegeuk Kim      2012-11-02  4969  	/* allocate memory for f2fs-specific super block info */
bf4afc53b77aea Linus Torvalds   2026-02-21  4970  	sbi = kzalloc_obj(struct f2fs_sb_info);
aff063e266cbf4 Jaegeuk Kim      2012-11-02  4971  	if (!sbi)
aff063e266cbf4 Jaegeuk Kim      2012-11-02  4972  		return -ENOMEM;
aff063e266cbf4 Jaegeuk Kim      2012-11-02  4973  
df728b0f6954c3 Jaegeuk Kim      2016-03-23  4974  	sbi->sb = sb;
df728b0f6954c3 Jaegeuk Kim      2016-03-23  4975  
92b4cf5b48955a Tetsuo Handa     2022-11-09  4976  	/* initialize locks within allocated memory */
e605302c14ffda Chao Yu          2026-01-04  4977  	init_f2fs_rwsem_trace(&sbi->gc_lock, sbi, LOCK_NAME_GC_LOCK);
92b4cf5b48955a Tetsuo Handa     2022-11-09  4978  	mutex_init(&sbi->writepages);
ce9fe67c9cdb21 Chao Yu          2026-01-04  4979  	init_f2fs_rwsem_trace(&sbi->cp_global_sem, sbi, LOCK_NAME_CP_GLOBAL);
6a5e3de9c2bb0b Chao Yu          2026-03-06  4980  #ifdef CONFIG_DEBUG_LOCK_ALLOC
6a5e3de9c2bb0b Chao Yu          2026-03-06  4981  	lockdep_register_key(&sbi->cp_global_sem_key);
6a5e3de9c2bb0b Chao Yu          2026-03-06  4982  	lockdep_set_class(&sbi->cp_global_sem.internal_rwsem,
6a5e3de9c2bb0b Chao Yu          2026-03-06  4983  					&sbi->cp_global_sem_key);
6a5e3de9c2bb0b Chao Yu          2026-03-06  4984  #endif
bb28b66875cca7 Chao Yu          2026-01-04  4985  	init_f2fs_rwsem_trace(&sbi->node_write, sbi, LOCK_NAME_NODE_WRITE);
f9f93602512bce Chao Yu          2026-01-04  4986  	init_f2fs_rwsem_trace(&sbi->node_change, sbi, LOCK_NAME_NODE_CHANGE);
92b4cf5b48955a Tetsuo Handa     2022-11-09  4987  	spin_lock_init(&sbi->stat_lock);
66e9e0d55d117a Chao Yu          2026-01-04  4988  	init_f2fs_rwsem_trace(&sbi->cp_rwsem, sbi, LOCK_NAME_CP_RWSEM);
92b4cf5b48955a Tetsuo Handa     2022-11-09  4989  	init_f2fs_rwsem(&sbi->quota_sem);
92b4cf5b48955a Tetsuo Handa     2022-11-09  4990  	init_waitqueue_head(&sbi->cp_wait);
92b4cf5b48955a Tetsuo Handa     2022-11-09  4991  	spin_lock_init(&sbi->error_lock);
92b4cf5b48955a Tetsuo Handa     2022-11-09  4992  
92b4cf5b48955a Tetsuo Handa     2022-11-09  4993  	for (i = 0; i < NR_INODE_TYPE; i++) {
92b4cf5b48955a Tetsuo Handa     2022-11-09  4994  		INIT_LIST_HEAD(&sbi->inode_list[i]);
92b4cf5b48955a Tetsuo Handa     2022-11-09  4995  		spin_lock_init(&sbi->inode_lock[i]);
92b4cf5b48955a Tetsuo Handa     2022-11-09  4996  	}
92b4cf5b48955a Tetsuo Handa     2022-11-09  4997  	mutex_init(&sbi->flush_lock);
d1267b5f0b476d Zhang Cen        2026-05-28  4998  	err = init_srcu_struct(&sbi->gc_thread_srcu);
d1267b5f0b476d Zhang Cen        2026-05-28  4999  	if (err)
d1267b5f0b476d Zhang Cen        2026-05-28  5000  		goto free_sbi;
d1267b5f0b476d Zhang Cen        2026-05-28  5001  	gc_thread_srcu_inited = true;
92b4cf5b48955a Tetsuo Handa     2022-11-09  5002  
ff9234ad4e9747 Namjae Jeon      2013-01-12  5003  	/* set a block size */
6bacf52fb58aeb Jaegeuk Kim      2013-12-06  5004  	if (unlikely(!sb_set_blocksize(sb, F2FS_BLKSIZE))) {
dcbb4c10e6d969 Joe Perches      2019-06-18  5005  		f2fs_err(sbi, "unable to set blocksize");
aff063e266cbf4 Jaegeuk Kim      2012-11-02 @5006  		goto free_sbi;

error code?

a07ef784356cf9 Namjae Jeon      2012-12-30  5007  	}
aff063e266cbf4 Jaegeuk Kim      2012-11-02  5008  
df728b0f6954c3 Jaegeuk Kim      2016-03-23  5009  	err = read_raw_super_block(sbi, &raw_super, &valid_super_block,
e8240f656d4d5d Chao Yu          2015-12-15  5010  								&recovery);
c0d39e65ba3243 Namjae Jeon      2013-03-17  5011  	if (err)
9076a75f8e0f23 Gu Zheng         2013-10-14  5012  		goto free_sbi;
9076a75f8e0f23 Gu Zheng         2013-10-14  5013  
5fb08372a68936 Gu Zheng         2013-06-07  5014  	sb->s_fs_info = sbi;
52763a4b7a2112 Jaegeuk Kim      2016-06-13  5015  	sbi->raw_super = raw_super;
52763a4b7a2112 Jaegeuk Kim      2016-06-13  5016  
b62e71be2110d8 Chao Yu          2023-04-23  5017  	INIT_WORK(&sbi->s_error_work, f2fs_record_error_work);
92b4cf5b48955a Tetsuo Handa     2022-11-09  5018  	memcpy(sbi->errors, raw_super->s_errors, MAX_F2FS_ERRORS);
b62e71be2110d8 Chao Yu          2023-04-23  5019  	memcpy(sbi->stop_reason, raw_super->s_stop_reason, MAX_STOP_REASON);
92b4cf5b48955a Tetsuo Handa     2022-11-09  5020  
704956ecf5bcdc Chao Yu          2017-07-31  5021  	/* precompute checksum seed for metadata */
7beb01f74415c5 Chao Yu          2018-10-24  5022  	if (f2fs_sb_has_inode_chksum(sbi))
d005af3b6756e5 Eric Biggers     2025-05-12  5023  		sbi->s_chksum_seed = f2fs_chksum(~0, raw_super->uuid,
704956ecf5bcdc Chao Yu          2017-07-31  5024  						 sizeof(raw_super->uuid));
704956ecf5bcdc Chao Yu          2017-07-31  5025  
458c15dfbce62c Chao Yu          2023-05-23  5026  	default_options(sbi, false);
dabc4a5c60f796 Jaegeuk Kim      2015-01-23  5027  
94b3ce7f1509d9 Hongbo Li        2025-07-10  5028  	err = f2fs_check_opt_consistency(fc, sb);
dabc4a5c60f796 Jaegeuk Kim      2015-01-23  5029  	if (err)
94b3ce7f1509d9 Hongbo Li        2025-07-10  5030  		goto free_sb_buf;
d185351325237d Hongbo Li        2025-07-10  5031  
94b3ce7f1509d9 Hongbo Li        2025-07-10  5032  	f2fs_apply_options(fc, sb);
d185351325237d Hongbo Li        2025-07-10  5033  
d185351325237d Hongbo Li        2025-07-10  5034  	err = f2fs_sanity_check_options(sbi, false);
abd0e040e9a516 Eric Sandeen     2025-03-03  5035  	if (err)
abd0e040e9a516 Eric Sandeen     2025-03-03  5036  		goto free_options;
abd0e040e9a516 Eric Sandeen     2025-03-03  5037  
6d1451bf7f84ea Chengguang Xu    2021-01-13  5038  	sb->s_maxbytes = max_file_blocks(NULL) <<
e0afc4d6d0d3e7 Chao Yu          2015-12-31  5039  				le32_to_cpu(raw_super->log_blocksize);
aff063e266cbf4 Jaegeuk Kim      2012-11-02  5040  	sb->s_max_links = F2FS_LINK_MAX;
aff063e266cbf4 Jaegeuk Kim      2012-11-02  5041  
5aba54302a46fd Daniel Rosenberg 2019-07-23  5042  	err = f2fs_setup_casefold(sbi);
5aba54302a46fd Daniel Rosenberg 2019-07-23  5043  	if (err)
5aba54302a46fd Daniel Rosenberg 2019-07-23  5044  		goto free_options;
5aba54302a46fd Daniel Rosenberg 2019-07-23  5045  

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki