From nobody Mon Jun 8 09:48:07 2026 Received: from mail-pl1-f195.google.com (mail-pl1-f195.google.com [209.85.214.195]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3C717C15C for ; Sat, 30 May 2026 14:33:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.195 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780151595; cv=none; b=Sz6OePtgbNR0ZZeDiDCgkBlCadngSh63na710iDd0rwF1f4GAtO1wuCGGFOPsPF8HHQqMdWTGzXsUUlojJh8dry5AOMm6qjwKVPYbwgqi+KrDp12pvr5OqrDF1ZVV20o3Sj8VLH10hQC+rURIKbgt01W/HVDd6v7fnd36hOxGbk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780151595; c=relaxed/simple; bh=xQI3DXPoEMJINroFEAF7wrNtgxSm7vE/Xarx2nv25CY=; h=From:To:Cc:Subject:Date:Message-Id:MIME-Version; b=Hsi8VmCJdOkrb48981CBHur0scGoBuVXena9l6Mk11mDltzdRoyvn3pkARH62mKrWLicgrz1lNUnaBuxfbrh32DudXk+mbTS4BUbzIzD1GYNhPYkMd8mjSZjyWpLmOHoF6T+Yj6Jww69T3AVNUgnEeeICkHzlzaLs4YSVOyythQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=MXK0TxGT; arc=none smtp.client-ip=209.85.214.195 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="MXK0TxGT" Received: by mail-pl1-f195.google.com with SMTP id d9443c01a7336-2bf77d4a4e2so3019915ad.1 for ; Sat, 30 May 2026 07:33:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1780151593; x=1780756393; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=9VAOLCzoyEGpuvHPIu3W860b+yu0vzmb8ayENUJBlHM=; b=MXK0TxGTwsT4WSeM7GrKmZt0gqnwss51lk/FuGeA6FRmQUrjPiW5SzbrKLbSIfkjPp mcjsMGGWK47Ux6RF4acjhpzyDBgewNBxlr8mrof0VZ3j1orRN4wvGl2yRoduimXpqIHn GtTETSNbmR9G9YzlACruA8wJav8UcuLCRaK9J9ABDCRJSo/8Lm8Zyx1h33V9YLiGpjlb 3E5nfYSIr3KsAy3ZJclOfvucc5Gd8YCPeXdisZ4FwgVBRQvrM0tBHdIAivL4V5ey7CeW tomPPSmchF9ud0RqkVvXqowHURTQqKfdTX1NV881FADWMlngQyf57ag7kMqD0KSV8FQA np5A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780151593; x=1780756393; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=9VAOLCzoyEGpuvHPIu3W860b+yu0vzmb8ayENUJBlHM=; b=ghNYRjib2T6NvWs88Wzvg5RbwDfV/p+sL74/7pmdrPldvOrdU6yxtiI0lXAZr9wqT/ xPo+u0+Fi7TAkRPNyfwvVl3tktu7nGoY+IFpryLsBMA3/gfcxtbMJ1Ecvd1rJEZQx0Qv us11EVDaNsNGPYtcCwjbcl1nMDLbJUJhOnfH4obxj0yT4ZGDIZ2tlX6BU1kj01CbSmLR FbsLsNZkw8wdRUVSRwztYqV6tVivPukKy9AS4CVZiDvtWI9TAaQjJCEzR4JpBGZ5vxiP A0mDsvo4ONm15SuoXCLyg6Pt/Br3Tz41ig1KxeynOYV37235H/qhTiHHGu1FKn9vp3aX 3yKw== X-Forwarded-Encrypted: i=1; AFNElJ9YjoqPoTBGJXw2qBq+zWd2pWZHDqrb7690Q51M5GyB5wKVAQ4D68/vsKosXiaMdhLRWrOrqkMjpXCb3IQ=@vger.kernel.org X-Gm-Message-State: AOJu0YxxBSiCsFWTAM0YuS66dsSq00NbcoPN2UzTpHilUQcGwGjnvtNh v9VjQfk+14YHbk59gOV8D/boONuTPHa5HPg6pzXPqVv5Ff7u5s7UD1Cx X-Gm-Gg: Acq92OGBrVan8OWOyy2aT5ftZ00YDUQFEGOWwv4i/TEMyaPNgPDzE+k2rc7BWEcgmXx Tu/eip5rrGRspucHUB4gmayoJ64f7k+yzTUgC+Slj3uR5jVcEIVehh8QNKWRpUhWpzAMjnBu4eH nmKrgZODvW4Y+E551Y5sSpK7A/+E68tDbwnrYUUwVfGescTJ439hSJeN8ojwKZ8q/dso0kMId/i oXtV/Uluey14tSl5QOdM7cy1hJrMg/rnQK4Gvh1m44QvONbXmfpR39vbxggbahgn2wDFsW9PUVs dFcVjPOWRN17cQ5e9C9XFl2TY1bf1pD9tcy7DH9eFuBrgsEkD1PhEbW9yD8J9suA4NBQyyRTQZX 9Ioy93dcg3CLlo7YVLbOCvbE1tVf0mXDhrWMUs9IbHUbfXy0NakqCk5fvpMEsSGNKOsZ2rAH/jU TX5NkwuAzHKhCDNCLud5droe/mWAZqPDo= X-Received: by 2002:a17:903:22c3:b0:2c0:b31b:b0b with SMTP id d9443c01a7336-2c0b31b0cafmr12670605ad.22.1780151593234; Sat, 30 May 2026 07:33:13 -0700 (PDT) Received: from localhost ([111.228.63.84]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2bf23c2381bsm52939645ad.62.2026.05.30.07.33.10 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 30 May 2026 07:33:12 -0700 (PDT) From: Zhang Cen To: Jaegeuk Kim , Chao Yu Cc: Gao Xiang , linux-f2fs-devel@lists.sourceforge.net, linux-kernel@vger.kernel.org, zerocling0077@gmail.com, 2045gemini@gmail.com, Zhang Cen Subject: [PATCH v4] f2fs: protect published gc_thread during teardown Date: Sat, 30 May 2026 22:33:07 +0800 Message-Id: <20260530143307.3596771-1-rollkingzzc@gmail.com> X-Mailer: git-send-email 2.34.1 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" f2fs_stop_gc_thread() stops the background GC task, wakes foreground GC_MERGE waiters, frees sbi->gc_thread, and then clears the published pointer. A foreground f2fs_balance_fs() caller can already have copied that pointer and queued itself on gc_th->fggc_wq, so freeing gc_th at stop time can leave finish_wait() operating on a freed waitqueue. Keep the allocated GC-thread state until the superblock is destroyed and use gc_th->f2fs_gc_task as the running-state marker. The stop path now withdraws the task pointer with xchg(), stops the task, and wakes any foreground waiters, but leaves the waitqueue storage valid. The start path reuses a stopped gc_thread object instead of reinitializing its waitqueues, and remount restart decisions check the task pointer rather than only the object pointer. f2fs_balance_fs() also snapshots sbi->gc_thread once and rechecks f2fs_gc_task after prepare_to_wait(). If teardown wins the race after the first check, the foreground caller removes its wait entry without sleeping on a worker that has already been withdrawn. Validation reproduced this kernel report: BUG: KASAN: slab-use-after-free in finish_wait+0x276/0x290 Write of size 8 at addr ffff8881150819b8 by task dd/802 The buggy address belongs to the object at ffff888115081900 which belongs to the cache kmalloc-256 of size 256 The buggy address is located 184 bytes inside of freed 256-byte region Call trace: finish_wait() f2fs_balance_fs() f2fs_write_single_data_page() f2fs_write_cache_pages() __f2fs_write_data_pages() do_writepages() filemap_fdatawrite_wbc() __filemap_fdatawrite_range() file_write_and_wait_range() f2fs_do_sync_file() f2fs_sync_file() do_fsync() Freed by task stack: kfree() f2fs_stop_gc_thread() f2fs_do_shutdown() f2fs_shutdown() fs_bdev_mark_dead() Fixes: 5911d2d1d1a3 ("f2fs: introduce gc_merge mount option") Signed-off-by: Zhang Cen --- v4: - Replace the v3 SRCU/refcounted lifetime model with a smaller fix that keeps the existing heap-allocated gc_thread object alive until superblock teardown. - Use f2fs_gc_task as the running-state marker and withdraw it with xchg() before waking GC_MERGE waiters. - Reuse a stopped gc_thread object across remount restarts so the waitqueues are not reinitialized while old waiters can still finish. - Recheck f2fs_gc_task after prepare_to_wait() so a waiter that races with teardown does not sleep after the worker has been withdrawn. v3: - Add the Fixes tag for the GC_MERGE foreground wait path. - Fix checkpatch style issues in the broader lifetime variant. v2: - Sashiko.dev pointed out that GC_MERGE foreground waiters and GC-thread users needed lifetime-safe access after teardown. fs/f2fs/gc.c | 48 +++++++++++++++++++++++++++++------------------ fs/f2fs/segment.c | 19 ++++++++++++------- fs/f2fs/super.c | 7 +++++-- 3 files changed, 47 insertions(+), 27 deletions(-) diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index ba93010924c06..20f8394482a09 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -193,12 +193,23 @@ static int gc_thread_func(void *data) =20 int f2fs_start_gc_thread(struct f2fs_sb_info *sbi) { - struct f2fs_gc_kthread *gc_th; + struct f2fs_gc_kthread *gc_th =3D sbi->gc_thread; + struct task_struct *task; + bool allocated =3D false; dev_t dev =3D sbi->sb->s_bdev->bd_dev; =20 - gc_th =3D f2fs_kmalloc(sbi, sizeof(struct f2fs_gc_kthread), GFP_KERNEL); - if (!gc_th) - return -ENOMEM; + if (gc_th && READ_ONCE(gc_th->f2fs_gc_task)) + return 0; + + if (!gc_th) { + gc_th =3D f2fs_kmalloc(sbi, sizeof(*gc_th), GFP_KERNEL); + if (!gc_th) + return -ENOMEM; + init_waitqueue_head(&gc_th->gc_wait_queue_head); + init_waitqueue_head(&gc_th->fggc_wq); + sbi->gc_thread =3D gc_th; + allocated =3D true; + } =20 gc_th->urgent_sleep_time =3D DEF_GC_THREAD_URGENT_SLEEP_TIME; gc_th->valid_thresh_ratio =3D DEF_GC_THREAD_VALID_THRESH_RATIO; @@ -221,34 +232,35 @@ int f2fs_start_gc_thread(struct f2fs_sb_info *sbi) =20 gc_th->gc_wake =3D false; =20 - sbi->gc_thread =3D 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 =3D kthread_run(gc_thread_func, sbi, - "f2fs_gc-%u:%u", MAJOR(dev), MINOR(dev)); - if (IS_ERR(gc_th->f2fs_gc_task)) { - int err =3D PTR_ERR(gc_th->f2fs_gc_task); + task =3D kthread_run(gc_thread_func, sbi, "f2fs_gc-%u:%u", + MAJOR(dev), MINOR(dev)); + if (IS_ERR(task)) { + int err =3D PTR_ERR(task); =20 - kfree(gc_th); - sbi->gc_thread =3D NULL; + if (allocated) { + kfree(gc_th); + sbi->gc_thread =3D NULL; + } return err; } =20 - set_user_nice(gc_th->f2fs_gc_task, - PRIO_TO_NICE(sbi->critical_task_priority)); + WRITE_ONCE(gc_th->f2fs_gc_task, task); + set_user_nice(task, PRIO_TO_NICE(sbi->critical_task_priority)); return 0; } =20 void f2fs_stop_gc_thread(struct f2fs_sb_info *sbi) { struct f2fs_gc_kthread *gc_th =3D sbi->gc_thread; + struct task_struct *task; =20 if (!gc_th) return; - kthread_stop(gc_th->f2fs_gc_task); + task =3D xchg(&gc_th->f2fs_gc_task, NULL); + if (!task) + return; + kthread_stop(task); wake_up_all(&gc_th->fggc_wq); - kfree(gc_th); - sbi->gc_thread =3D NULL; } =20 static int select_gc_type(struct f2fs_sb_info *sbi, int gc_type) diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index 788f8b0502492..84307525edd27 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -424,6 +424,8 @@ int f2fs_commit_atomic_write(struct inode *inode) */ void f2fs_balance_fs(struct f2fs_sb_info *sbi, bool need) { + struct f2fs_gc_kthread *gc_th; + if (f2fs_cp_error(sbi)) return; =20 @@ -444,15 +446,18 @@ void f2fs_balance_fs(struct f2fs_sb_info *sbi, bool n= eed) if (has_enough_free_secs(sbi, 0, 0)) return; =20 - if (test_opt(sbi, GC_MERGE) && sbi->gc_thread && - sbi->gc_thread->f2fs_gc_task) { + gc_th =3D sbi->gc_thread; + if (test_opt(sbi, GC_MERGE) && gc_th && + READ_ONCE(gc_th->f2fs_gc_task)) { DEFINE_WAIT(wait); =20 - 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); + 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); } else { struct f2fs_gc_control gc_control =3D { .victim_segno =3D NULL_SEGNO, diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index ccf806b676f53..d6863da05a7c2 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -2925,11 +2925,12 @@ static int __f2fs_remount(struct fs_context *fc, st= ruct super_block *sb) if ((flags & SB_RDONLY) || (F2FS_OPTION(sbi).bggc_mode =3D=3D BGGC_MODE_OFF && !test_opt(sbi, GC_MERGE))) { - if (sbi->gc_thread) { + if (sbi->gc_thread && READ_ONCE(sbi->gc_thread->f2fs_gc_task)) { f2fs_stop_gc_thread(sbi); need_restart_gc =3D true; } - } else if (!sbi->gc_thread) { + } else if (!sbi->gc_thread || + !READ_ONCE(sbi->gc_thread->f2fs_gc_task)) { err =3D f2fs_start_gc_thread(sbi); if (err) goto restore_opts; @@ -5451,6 +5452,7 @@ static int f2fs_fill_super(struct super_block *sb, st= ruct fs_context *fc) free_sb_buf: kfree(raw_super); free_sbi: + kfree(sbi->gc_thread); #ifdef CONFIG_DEBUG_LOCK_ALLOC lockdep_unregister_key(&sbi->cp_global_sem_key); #endif @@ -5535,6 +5537,7 @@ static void kill_f2fs_super(struct super_block *sb) /* Release block devices last, after fscrypt_destroy_keyring(). */ if (sbi) { destroy_device_list(sbi); + kfree(sbi->gc_thread); #ifdef CONFIG_DEBUG_LOCK_ALLOC lockdep_unregister_key(&sbi->cp_global_sem_key); #endif --=20 2.43.0