From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pl1-f181.google.com (mail-pl1-f181.google.com [209.85.214.181]) (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 E61A3358378 for ; Mon, 29 Jun 2026 05:28:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.181 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782710902; cv=none; b=XgyhFvv08AGo4GcZ+2UXXAZWplQNmW1wVLh+mmLXHLaLM/Ra7RcGifffDgMMh3UV67LehoyZf8b1cee5yuO4UbxrnDl2huNFB2x7lMXsJARu1Vw4e+madsKLyZmMfRMC1oiCUXowkYGLiDvyiMeXTK0biQcfEMIiMdvfF7Azv/M= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782710902; c=relaxed/simple; bh=hXxvxDj483LwSE03ib13aNRVqe8rbnf620TFrAuGDTM=; h=From:To:Cc:Subject:Date:Message-Id:MIME-Version; b=eEoIhzGIn4MtzQxrCutEdvGPHZ1CYVCwGvYYJSlGNOdNwTfvUbx4zes3FEfwG0DqPSr84vUuYoa+StXotFSs37e5TmJERIzLdOtGxIwSLy0o0I1ZSSEN9P3rWSLdg7tF0ckKzt+9Q0cp22rkc3JkzGS3lB8qFovCjl3Yn/Sy3WQ= 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=qUQQrOIy; arc=none smtp.client-ip=209.85.214.181 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="qUQQrOIy" Received: by mail-pl1-f181.google.com with SMTP id d9443c01a7336-2c825c88744so17194245ad.1 for ; Sun, 28 Jun 2026 22:28:20 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1782710900; x=1783315700; 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=4rlXTL1AbUKSOvBNRJvUhw7f25JhxheO5eY/uajbqco=; b=qUQQrOIyZe+GFVkVBmby6jnBFulHLhe5vIf4Ic+aTRS7wTac+Xxj70R8GFcfFfIqeo rzVoT4VIC0B8DWEl7+wEV1sFwsfLw1JwGPpkzi5wwT9bXVWZmXHQbnPFhEvASi5CSLhk vlTodz4ND0zVYSvjvx4Xhi91b1M4DwRg6Ck+yMvkjr+CTk/HUXAlGgIm2KH8+eEUzR0z uBZQhZ5sMnfK0j+GCG5aTkpl5r6rItl9gU0GeVqI033/XyqreGgXFRyiIQFJJweyYsC4 +T6Esyqz2mtnGbKsf5B0VURCZGJueOOitf6nqHVWHcDfltYYUlZNWDgMdGy8+4iS9Niq eYBQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782710900; x=1783315700; 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=4rlXTL1AbUKSOvBNRJvUhw7f25JhxheO5eY/uajbqco=; b=OWa1vOvFYlNQ+D2PBFnmxkdA+CFyFATsb9elNBdD7v6qXc45E0qF523RvtGEIUVddV Y15dUb10XkDeCUi9zFkmNJVSFSZviAVmPzWR2rBm+SnjLcJE+I1ghUBPHBY6/xrZevZl JEkel7R00F4VHcmlsL85GU495sGbFD6KRmvv9NeEbyKdMeZjc0U35koW0JAUONEStSEm OWtWEGLoVkUqixCXgL1i44wFUehQ+izdVpbnL2OXlwcXI2q1dm24/vyAI18/QfsWC/9e tpTjalLHPAu0U5BZr7iT4ZMB3W+A7ejjpDup/koQvgObUOMNTJVZZq7txhv8cFU2vnbd 1Kgw== X-Forwarded-Encrypted: i=1; AHgh+Rqzn+3bCieDDq2puM61P6RTtRXzKJOUgk9rTTloj9oFWvHJF1Nh5rVvo/ncbKMVku+lZWeAw9iVqNViLto=@vger.kernel.org X-Gm-Message-State: AOJu0YzNkTDL0f4O5wHvSHwGILSak2v1W6NABMvRqaavjcAxIYLZC197 jSA/MzWFk/saA2w01OYYvXhQSMJClz9EAcRh+Vu0QeStoeV/25ZVVLoT X-Gm-Gg: AfdE7ckMvDEHeKNaK2X1yQJ4EqndQcSWcc/dt4orMVi/wLeZhsHXw/U65pGEaYfk523 qwzLtCr55amsIIkHA74XM/FSiLD5E9WCfB72/SMPU51AhUYcUac9jPDXwwjqYYkMepDGT+eHKs6 S8ug/QQ13KJ/aHG60V5k7uVNBbUtIXiaMkgQ1u21gKY9+QNmm1GB9yI7N5AZQjWoD8JdgmXP5L2 vZtb/Zt0d5GYw/AnjpmUzmGud5Iga8hbHyeWmrgD+e7G90kVgxRZgdRnZOX0T4TuhESyXVPwIOx 8KrCPV9osXMTpL0XjdZxCANOJ00pET3uFlzJ9x8eiu+zpt99MUC+CYGX7vsQ2VIPgAAZWYCBAnJ IlT1rJWHl/oh8VRSAx/sddOXl7AdwY5y3TAPKV1+9TMyzzkaDaxNuwhZX4jYVbaAbUTBgz4KC4i k50k6jkUTAWwD6wmpu+Q== X-Received: by 2002:a17:903:2f4c:b0:2c9:9a19:10c with SMTP id d9443c01a7336-2c99a19037fmr73344515ad.40.1782710900031; Sun, 28 Jun 2026 22:28:20 -0700 (PDT) Received: from localhost ([111.228.63.84]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2c7f5afdd40sm84838635ad.33.2026.06.28.22.28.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 28 Jun 2026 22:28:19 -0700 (PDT) From: Cen Zhang To: Jaegeuk Kim , Chao Yu 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 Message-Id: <20260629052811.2167181-1-zzzccc427@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: 8bit 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 --- 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