[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