[f2fs-dev] [PATCH] f2fs: wait for decompress contexts before teardown

Cen Zhang <[email protected]>
Newsgroups net.sourceforge.lists.linux-f2fs-devel,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
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 <[email protected]>
---
 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


_______________________________________________
Linux-f2fs-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.