mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Cen Zhang <zzzccc427@gmail.com>
To: Jaegeuk Kim <jaegeuk@kernel.org>, Chao Yu <chao@kernel.org>
Cc: linux-f2fs-devel@lists.sourceforge.net,
	linux-kernel@vger.kernel.org, baijiaju1990@gmail.com,
	zzzccc427@gmail.com
Subject: [PATCH] f2fs: wait for decompress contexts before teardown
Date: Mon, 29 Jun 2026 13:28:11 +0800	[thread overview]
Message-ID: <20260629052811.2167181-1-zzzccc427@gmail.com> (raw)

A compressed read owns a decompress_io_ctx (DIC) until cluster
completion drops its final reference.  The final cleanup still reads
dic->sbi and releases DIC page arrays through sbi->page_array_slab.  If
the final put is not in task context, cleanup is queued on sbi->wq.

The rpage unlocks in f2fs_verify_cluster() and f2fs_decompress_end_io()
can make the inode evictable before f2fs_put_dic() has finished.  Then
unmount can pass inode eviction, destroy the per-mount workqueue and
page-array slab, and free the f2fs_sb_info while the DIC cleanup path
still has to run.

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

fsverity_read_queue worker:        unmount:
1. verify decompressed rpages      1. observe the inode evictable
2. unlock the last rpage           2. evict and truncate pagecache
3. call f2fs_put_dic(dic, true)    3. destroy sbi->wq/page_array_slab
4. f2fs_free_dic() reads dic->sbi  4. free the f2fs_sb_info

Validation reproduced this kernel report:
KASAN: slab-use-after-free in f2fs_release_decomp_mem+0x1da/0x240
Workqueue: fsverity_read_queue f2fs_verify_cluster
The buggy address belongs to the object at ffff88810267c000 which belongs
to the cache kmalloc-8k of size 8192
The buggy address is located 4868 bytes inside of freed 8192-byte region
[ffff88810267c000, ffff88810267e000)
Read of size 4
Call trace:
  dump_stack_lvl+0x66/0xa0
  print_report+0xce/0x630
  f2fs_release_decomp_mem+0x1da/0x240 (fs/f2fs/compress.c:1685)
  srso_alias_return_thunk+0x5/0xfbef5
  __virt_addr_valid+0x224/0x430
  kasan_report+0xe0/0x110
  f2fs_free_dic+0x5b/0x630 (fs/f2fs/compress.c:1766)
  process_one_work+0x897/0x1820 (kernel/workqueue.c:3220)
  lock_is_held_type+0x8f/0x100
  worker_thread+0x575/0xf80
  kthread+0x2e7/0x3c0
  ret_from_fork+0x576/0x810
  __switch_to+0x57e/0xe10
  __switch_to_asm+0x33/0x70
  ret_from_fork_asm+0x1a/0x30
Allocated by task stack:
  kasan_save_stack+0x33/0x60
  kasan_save_track+0x14/0x30
  __kasan_kmalloc+0x8f/0xa0
  __kmalloc_cache_noprof+0x235/0x5c0
  f2fs_fill_super+0xd6/0x8840 (fs/f2fs/super.c:5004)
  get_tree_bdev_flags+0x312/0x590
  vfs_get_tree+0x8d/0x320
  fc_mount+0x15/0x1d0
  path_mount+0x59b/0x2020
  __x64_sys_mount+0x210/0x270
  do_syscall_64+0x115/0x6a0 (arch/x86/entry/syscall_64.c:87)
  entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task stack:
  kasan_save_stack+0x33/0x60
  kasan_save_track+0x14/0x30
  kasan_save_free_info+0x3b/0x60
  __kasan_slab_free+0x43/0x70
  kfree+0x2ed/0x520
  kill_f2fs_super+0x3bc/0x590 (fs/f2fs/super.c:5560)
  deactivate_locked_super+0xa8/0x160
  cleanup_mnt+0x1e2/0x3e0
  task_work_run+0x11d/0x1f0
  exit_to_user_mode_loop+0x17d/0x570
  do_syscall_64+0x4f0/0x6a0 (arch/x86/entry/syscall_64.c:87)
  entry_SYSCALL_64_after_hwframe+0x77/0x7f

Track live DICs in the per-mount compression state.  Increment the count
once a DIC is tied to the mounted sbi, decrement it only after
f2fs_free_dic() has released all sbi-owned DIC resources, and make
f2fs_put_super() wait for the count to reach zero before destroying
sbi->wq, sbi->page_array_slab, or the sbi itself.  This preserves the
existing read-completion and page-unlock behavior while extending
teardown to cover the cleanup lifetime that DIC already requires.

Assisted-by: Codex:gpt-5.5
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
---
 fs/f2fs/compress.c | 14 ++++++++++++++
 fs/f2fs/f2fs.h     |  4 ++++
 fs/f2fs/super.c    |  2 ++
 3 files changed, 20 insertions(+)

diff --git a/fs/f2fs/compress.c b/fs/f2fs/compress.c
index 91855d91bbdd..bbdc009cfed3 100644
--- a/fs/f2fs/compress.c
+++ b/fs/f2fs/compress.c
@@ -1732,6 +1732,7 @@ struct decompress_io_ctx *f2fs_alloc_dic(struct compress_ctx *cc)
 	refcount_set(&dic->refcnt, 1);
 	dic->failed = false;
 	dic->vi = cc->vi;
+	atomic_inc(&sbi->nr_decompress_ctx);
 
 	for (i = 0; i < dic->cluster_size; i++)
 		dic->rpages[i] = cc->rpages[i];
@@ -1794,6 +1795,8 @@ static void f2fs_free_dic(struct decompress_io_ctx *dic,
 
 	page_array_free(sbi, dic->rpages, dic->nr_rpages);
 	kmem_cache_free(dic_entry_slab, dic);
+	if (atomic_dec_and_test(&sbi->nr_decompress_ctx))
+		wake_up_all(&sbi->decompress_io_wait);
 }
 
 static void f2fs_late_free_dic(struct work_struct *work)
@@ -2077,6 +2080,9 @@ int f2fs_init_page_array_cache(struct f2fs_sb_info *sbi)
 	if (!f2fs_sb_has_compression(sbi))
 		return 0;
 
+	atomic_set(&sbi->nr_decompress_ctx, 0);
+	init_waitqueue_head(&sbi->decompress_io_wait);
+
 	sprintf(slab_name, "f2fs_page_array_entry-%u:%u", MAJOR(dev), MINOR(dev));
 
 	sbi->page_array_slab_size = sizeof(struct page *) <<
@@ -2092,6 +2098,14 @@ void f2fs_destroy_page_array_cache(struct f2fs_sb_info *sbi)
 	kmem_cache_destroy(sbi->page_array_slab);
 }
 
+void f2fs_wait_on_decompress_io(struct f2fs_sb_info *sbi)
+{
+	if (!f2fs_sb_has_compression(sbi))
+		return;
+
+	wait_event(sbi->decompress_io_wait, !atomic_read(&sbi->nr_decompress_ctx));
+}
+
 int __init f2fs_init_compress_cache(void)
 {
 	cic_entry_slab = f2fs_kmem_cache_create("f2fs_cic_entry",
diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index 8f3e632f315c..63ae46a2d400 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -2025,6 +2025,8 @@ struct f2fs_sb_info {
 #ifdef CONFIG_F2FS_FS_COMPRESSION
 	struct kmem_cache *page_array_slab;	/* page array entry */
 	unsigned int page_array_slab_size;	/* default page array slab size */
+	atomic_t nr_decompress_ctx;		/* in-flight decompress contexts */
+	wait_queue_head_t decompress_io_wait;	/* wait for decompress contexts */
 
 	/* For runtime compression statistics */
 	u64 compr_written_block;
@@ -4696,6 +4698,7 @@ int f2fs_init_compress_inode(struct f2fs_sb_info *sbi);
 void f2fs_destroy_compress_inode(struct f2fs_sb_info *sbi);
 int f2fs_init_page_array_cache(struct f2fs_sb_info *sbi);
 void f2fs_destroy_page_array_cache(struct f2fs_sb_info *sbi);
+void f2fs_wait_on_decompress_io(struct f2fs_sb_info *sbi);
 int __init f2fs_init_compress_cache(void);
 void f2fs_destroy_compress_cache(void);
 struct address_space *COMPRESS_MAPPING(struct f2fs_sb_info *sbi);
@@ -4751,6 +4754,7 @@ static inline int f2fs_init_compress_inode(struct f2fs_sb_info *sbi) { return 0;
 static inline void f2fs_destroy_compress_inode(struct f2fs_sb_info *sbi) { }
 static inline int f2fs_init_page_array_cache(struct f2fs_sb_info *sbi) { return 0; }
 static inline void f2fs_destroy_page_array_cache(struct f2fs_sb_info *sbi) { }
+static inline void f2fs_wait_on_decompress_io(struct f2fs_sb_info *sbi) { }
 static inline int __init f2fs_init_compress_cache(void) { return 0; }
 static inline void f2fs_destroy_compress_cache(void) { }
 static inline void f2fs_invalidate_compress_pages_range(struct f2fs_sb_info *sbi,
diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
index 2b8d96411156..b279d3eaae53 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -2075,6 +2075,8 @@ static void f2fs_put_super(struct super_block *sb)
 	/* flush s_error_work before sbi destroy */
 	flush_work(&sbi->s_error_work);
 
+	f2fs_wait_on_decompress_io(sbi);
+
 	f2fs_destroy_wq(sbi);
 
 	kvfree(sbi->ckpt);
-- 
2.43.0

                 reply	other threads:[~2026-06-29  5:28 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=20260629052811.2167181-1-zzzccc427@gmail.com \
    --to=zzzccc427@gmail.com \
    --cc=baijiaju1990@gmail.com \
    --cc=chao@kernel.org \
    --cc=jaegeuk@kernel.org \
    --cc=linux-f2fs-devel@lists.sourceforge.net \
    --cc=linux-kernel@vger.kernel.org \
    /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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®