From nobody Fri Jun 12 12:46:29 2026 Received: from mx2.cyberprotect.ru (mx2.cyberprotect.ru [176.10.93.31]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 75FD22E8B8A for ; Wed, 8 Apr 2026 11:02:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=176.10.93.31 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775646173; cv=none; b=VDMrpkYQf/aesJsNjeAfIK9xqoivFeCKXMpU7rKTvnsOLKG+9F1HShWG4g8hjfK1b98AcxszVNyOkXrPheWvw0STgRX7TCu10X9/Wr+6/FOzzPixfMOZjb0pdSylb3BrZ+/xXY+MRJKTnAg9/j3jKBPCHbHNWBB4WMdj7DNl5EI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775646173; c=relaxed/simple; bh=pdKI7JUMYrMe9aUtpW4avLY4q8xySsM8w24OfRsfXtE=; h=From:To:CC:Subject:Date:Message-ID:MIME-Version:Content-Type; b=JmV8KE1pANGXl51108boVLIZrpIx1tkwAQKexAGAPviigLbNKfpuo7H9g1FmuMlh4f9JB+9etBJwB02KowJGX8QPwRNibEnokpyZBm1chV+5sQj4v5u0a9w337BbDbZ0u+G5YlzJ5DVg4REBOmiJpmxWxo5OGHAmglaFY3eIpQw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=cyberprotect.ru; spf=pass smtp.mailfrom=cyberprotect.ru; dkim=pass (2048-bit key) header.d=cyberprotect.ru header.i=@cyberprotect.ru header.b=lTkRcwFf; dkim=permerror (0-bit key) header.d=cyberprotect.ru header.i=@cyberprotect.ru header.b=TthtbO/f; arc=none smtp.client-ip=176.10.93.31 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=cyberprotect.ru Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=cyberprotect.ru Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=cyberprotect.ru header.i=@cyberprotect.ru header.b="lTkRcwFf"; dkim=permerror (0-bit key) header.d=cyberprotect.ru header.i=@cyberprotect.ru header.b="TthtbO/f" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=cyberprotect.ru; s=dkim-r; h=MIME-Version:Date:From:Sender:Reply-To; bh=68jUZvXWunVeoQkkWi7hSN7won0FQkNVFRmyntCsBik=; b=lTkRcwFf+sCVdHbpJtec8fE06e oa9g4pnFmuucwBGGsRyLryAYssspJltQ+xrwHyf00GPx7uyx+PUOHDX8qyl6gmBI+WarmS7SK3xMp UAm14+pgbHbEEblBfyHTsQEmSoUq7POufCVWDEcUHoQbeZ0CIi6aILk5+f3ilrQKEWDJSxw2lnFsl VkK868zJi7rJ+O0RA3sehSkkBSF+tOImDXNjuIp4s1tR3q1S8EYdSdprXOQWlG5ifqQIKkVwBJiMX yTFtLb8YyAEmSgelTISFvsUMoINVLjaZoE6XXno0CMEZcyecivxfMte/29QC4gc3eUm7BZNsVkUf9 cYu1p4qQ==; DKIM-Signature: v=1; a=ed25519-sha256; q=dns/txt; c=relaxed/relaxed; d=cyberprotect.ru; s=dkim; h=MIME-Version:Date:From:Sender:Reply-To; bh=68jUZvXWunVeoQkkWi7hSN7won0FQkNVFRmyntCsBik=; b=TthtbO/fR9kjj40hq1L5bVe2rA bY2BuvhSTRPjlz8k+xhTj0TAfuCvxvP3xAfEXCmWVIjjDb8iyfJWMkERIYDw==; From: Dmitriy Chumachenko To: David Woodhouse CC: Richard Weinberger , Thomas Gleixner , , , Subject: [PATCH v2] jffs2: fix use-after-free in jffs2_garbage_collect_thread() Date: Wed, 8 Apr 2026 13:31:27 +0300 Message-ID: <20260408103127.22218-1-Dmitry.Chumachenko@cyberprotect.ru> X-Mailer: git-send-email 2.49.0 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 X-ClientProxiedBy: AIP-EXCH-2.aip.ooo (10.77.28.102) To AIP-EXCH-2.aip.ooo (10.77.28.102) Content-Type: text/plain; charset="utf-8" During fuzz testing, the following issue was discovered. BUG: KASAN: use-after-free in __lock_acquire+0x3f22/0x53c0 kernel/locking/l= ockdep.c:4825 Read of size 8 at addr ffff888053cfa098 by task jffs2_gcd_mtd0/11093 CPU: 1 PID: 11093 Comm: jffs2_gcd_mtd0 Not tainted 5.10.232-syzkaller #0 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.12.0-1 04/01/= 2014 Call Trace: __dump_stack lib/dump_stack.c:77 [inline] dump_stack+0x107/0x167 lib/dump_stack.c:118 print_address_description.constprop.0+0x1c/0x220 mm/kasan/report.c:377 __kasan_report mm/kasan/report.c:537 [inline] kasan_report.cold+0x1f/0x37 mm/kasan/report.c:554 __lock_acquire+0x3f22/0x53c0 kernel/locking/lockdep.c:4825 lock_acquire kernel/locking/lockdep.c:5566 [inline] lock_acquire+0x197/0x480 kernel/locking/lockdep.c:5531 __raw_spin_lock_irqsave include/linux/spinlock_api_smp.h:110 [inline] _raw_spin_lock_irqsave+0x36/0x60 kernel/locking/spinlock.c:159 complete+0x13/0x60 kernel/sched/completion.c:32 complete_and_exit+0x20/0x40 kernel/exit.c:943 jffs2_garbage_collect_thread+0x554/0x750 fs/jffs2/background.c:164 kthread+0x3a9/0x490 kernel/kthread.c:328 ret_from_fork+0x1f/0x30 arch/x86/entry/entry_64.S:298 Allocated by task 11091: kasan_save_stack+0x1b/0x40 mm/kasan/common.c:48 kasan_set_track mm/kasan/common.c:56 [inline] __kasan_kmalloc.constprop.0+0xc9/0xd0 mm/kasan/common.c:461 kmalloc include/linux/slab.h:552 [inline] kzalloc include/linux/slab.h:664 [inline] jffs2_init_fs_context+0x41/0xd0 fs/jffs2/super.c:314 alloc_fs_context+0x4f9/0x840 fs/fs_context.c:267 do_new_mount fs/namespace.c:2896 [inline] path_mount+0xb99/0x2140 fs/namespace.c:3247 do_mount fs/namespace.c:3260 [inline] __do_sys_mount fs/namespace.c:3468 [inline] __se_sys_mount fs/namespace.c:3445 [inline] __x64_sys_mount+0x283/0x300 fs/namespace.c:3445 do_syscall_64+0x30/0x40 arch/x86/entry/common.c:46 entry_SYSCALL_64_after_hwframe+0x67/0xd1 Freed by task 28546: kasan_save_stack+0x1b/0x40 mm/kasan/common.c:48 kasan_set_track+0x1c/0x30 mm/kasan/common.c:56 kasan_set_free_info+0x1b/0x30 mm/kasan/generic.c:355 __kasan_slab_free+0x112/0x170 mm/kasan/common.c:422 slab_free_hook mm/slub.c:1542 [inline] slab_free_freelist_hook+0xb8/0x1b0 mm/slub.c:1576 slab_free mm/slub.c:3149 [inline] kfree+0xd9/0x360 mm/slub.c:4125 deactivate_locked_super+0x96/0x170 fs/super.c:335 deactivate_super+0xb2/0xd0 fs/super.c:366 cleanup_mnt+0x3a3/0x530 fs/namespace.c:1118 task_work_run+0xdf/0x1a0 kernel/task_work.c:185 tracehook_notify_resume include/linux/tracehook.h:188 [inline] exit_to_user_mode_loop kernel/entry/common.c:172 [inline] exit_to_user_mode_prepare+0x1de/0x1f0 kernel/entry/common.c:199 syscall_exit_to_user_mode+0x38/0x1e0 kernel/entry/common.c:274 In jffs2_garbage_collect_thread() gc_task is set to NULL and then kthread_complete_and_exit() calls complete() on gc_thread_exit. These operations are not atomic: stop path can see gc_task =3D=3D NULL, skip wait_for_completion(), and the caller frees jffs2_sb_info while the GC thread still accesses gc_thread_exit in complete(). Moreover, spin_unlock() itself accesses c after complete() has woken the stop path: jffs2_kill_sb jffs2_garbage_collect_thread jffs2_stop_garbage_collect_thread spin_lock send_sig(SIGKILL) wait =3D 1 spin_unlock goto die spin_lock c->gc_task =3D NULL spin_unlock kthread_complete_and_exit() complete(&c->gc_thread_exit) wait_for_completion() kfree(c) Fix by adding a gc_thread_started flag that is set when the GC thread is=20 successfully started. Use this flag instead of gc_task to decide whether=20 to wait. The flag is never cleared by the GC thread, so=20 wait_for_completion() is always called when start() succeeded, regardless=20 of the current value of gc_task. =20 Found by Linux Verification Center (linuxtesting.org) with Syzkaller. Fixes: e2d48b1a98bb ("[JFFS2] Fix cleanup in case of GC-Task not started") Signed-off-by: Dmitriy Chumachenko Reviewed-by: Zhihao Cheng --- v1->v2: Use gc_thread_started flag as a reliable indicator instead of gc_task. Drop complete() under erase_completion_lock=20 (thanks, Zhihao Cheng). fs/jffs2/background.c | 7 ++++--- fs/jffs2/jffs2_fs_sb.h | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/fs/jffs2/background.c b/fs/jffs2/background.c index bb0ee1a59e71..5e5ed9053326 100644 --- a/fs/jffs2/background.c +++ b/fs/jffs2/background.c @@ -52,6 +52,7 @@ int jffs2_start_garbage_collect_thread(struct jffs2_sb_in= fo *c) /* Wait for it... */ jffs2_dbg(1, "Garbage collect thread is pid %d\n", tsk->pid); wait_for_completion(&c->gc_thread_start); + c->gc_thread_started =3D true; ret =3D tsk->pid; } =20 @@ -60,16 +61,16 @@ int jffs2_start_garbage_collect_thread(struct jffs2_sb_= info *c) =20 void jffs2_stop_garbage_collect_thread(struct jffs2_sb_info *c) { - int wait =3D 0; spin_lock(&c->erase_completion_lock); if (c->gc_task) { jffs2_dbg(1, "Killing GC task %d\n", c->gc_task->pid); send_sig(SIGKILL, c->gc_task, 1); - wait =3D 1; } spin_unlock(&c->erase_completion_lock); - if (wait) + if (c->gc_thread_started) { wait_for_completion(&c->gc_thread_exit); + c->gc_thread_started =3D false; + } } =20 static int jffs2_garbage_collect_thread(void *_c) diff --git a/fs/jffs2/jffs2_fs_sb.h b/fs/jffs2/jffs2_fs_sb.h index 5a7091746f68..4c833e0ff03c 100644 --- a/fs/jffs2/jffs2_fs_sb.h +++ b/fs/jffs2/jffs2_fs_sb.h @@ -55,6 +55,7 @@ struct jffs2_sb_info { unsigned int flags; =20 struct task_struct *gc_task; /* GC task struct */ + bool gc_thread_started; /* GC thread was successfully started */ struct completion gc_thread_start; /* GC thread start completion */ struct completion gc_thread_exit; /* GC thread exit completion port */ =20 --=20 2.49.0