Linux-mtd Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] jffs2: fix use-after-free in jffs2_garbage_collect_thread
@ 2026-11-03  9:00 Yiqing Wu
  2026-11-03  9:16 ` sashiko-bot
  0 siblings, 1 reply; 2+ messages in thread
From: Yiqing Wu @ 2026-11-03  9:00 UTC (permalink / raw)
  To: David Woodhouse, Richard Weinberger
  Cc: Al Viro, David Howells, linux-mtd, linux-kernel,
	syzbot+ae2d1f6d474e1725bdc0

jffs2_do_remount_fs() decides whether to (re)start the garbage collect
thread from the requested flags:

	if (!(fc->sb_flags & SB_RDONLY))
		jffs2_start_garbage_collect_thread(c);

The flags which actually take effect, however, are computed by
reconfigure_super() as

	sb->s_flags = (sb->s_flags & ~fc->sb_flags_mask) |
		      (fc->sb_flags & fc->sb_flags_mask);

and fspick(2) creates its reconfiguration context with both sb_flags
and sb_flags_mask set to zero, i.e. "no change requested".  So after

	fd = fspick(AT_FDCWD, mnt, 0);
	fsconfig(fd, FSCONFIG_CMD_RECONFIGURE, NULL, NULL, 0);

on a filesystem which was mounted read-only, jffs2 starts the GC
thread (because fc->sb_flags carries no SB_RDONLY) while the superblock
keeps its SB_RDONLY (because fc->sb_flags_mask is zero).  The classic
mount(2) remount path is not affected: it always passes a full
MS_RMT_MASK, so the flags it hands to the filesystem do take effect.

jffs2_kill_sb() then refuses to stop the GC thread for a read-only
superblock:

	if (c && !sb_rdonly(sb))
		jffs2_stop_garbage_collect_thread(c);
	kill_mtd_super(sb);
	kfree(c);

so at unmount the thread is neither signalled nor waited for, and
kfree(c) frees the jffs2_sb_info out from under the still-running
jffs2_gcd_mtd%d kthread.  It dereferences the freed object when it
wakes up from its 50 ms idle sleep:

  BUG: KASAN: slab-use-after-free in rt_spin_lock
  Read of size 1 at addr ffff88804f2b02c0 by task jffs2_gcd_mtd0/12364
  Call Trace:
  rt_spin_lock kernel/locking/spinlock_rt.c:56
  spin_lock include/linux/spinlock_rt.h:45 [inline]
  jffs2_garbage_collect_thread fs/jffs2/background.c:94

Fix both layers of the desynchronisation:

  - jffs2_do_remount_fs() now derives the flags which will be in
    effect after the reconfiguration the same way reconfigure_super()
    does, and only starts the GC thread if the filesystem will
    actually be writable.

  - jffs2_kill_sb() unconditionally stops and waits for the GC thread
    before freeing c.  Without a running GC thread this is a no-op,
    and the thread can no longer outlive the jffs2_sb_info, no matter
    which future path manages to start it on a superblock that stays
    read-only.

Fixes: ec10a24f10c8 ("vfs: Convert jffs2 to use the new mount API")
Reported-by: syzbot+ae2d1f6d474e1725bdc0@syzkaller.appspotmail.com
Closes: https://syzbot.org/bug?extid=ae2d1f6d474e1725bdc0
Signed-off-by: Yiqing Wu <wuyiqing@cmss.chinamobile.com>
---
 fs/jffs2/fs.c    | 13 ++++++++++++-
 fs/jffs2/super.c |  2 +-
 2 files changed, 13 insertions(+), 2 deletions(-)

diff --git a/fs/jffs2/fs.c b/fs/jffs2/fs.c
index 05cf860307c7..a124f5309c2b 100644
--- a/fs/jffs2/fs.c
+++ b/fs/jffs2/fs.c
@@ -396,6 +396,7 @@ void jffs2_dirty_inode(struct inode *inode, int flags)
 int jffs2_do_remount_fs(struct super_block *sb, struct fs_context *fc)
 {
 	struct jffs2_sb_info *c = JFFS2_SB_INFO(sb);
+	unsigned int sb_flags;
 
 	if (c->flags & JFFS2_SB_FLAG_RO && !sb_rdonly(sb))
 		return -EROFS;
@@ -411,7 +412,17 @@ int jffs2_do_remount_fs(struct super_block *sb, struct fs_context *fc)
 		mutex_unlock(&c->alloc_sem);
 	}
 
-	if (!(fc->sb_flags & SB_RDONLY))
+	/*
+	 * Start the GC thread only if the filesystem will actually be
+	 * writable after the reconfiguration.  Note that reconfigure_super()
+	 * applies the flags as
+	 * (sb->s_flags & ~fc->sb_flags_mask) | (fc->sb_flags & fc->sb_flags_mask),
+	 * so a zero sb_flags_mask (as e.g. used by fspick()) leaves the
+	 * superblock read-only.
+	 */
+	sb_flags = (sb->s_flags & ~fc->sb_flags_mask) |
+		   (fc->sb_flags & fc->sb_flags_mask);
+	if (!(sb_flags & SB_RDONLY))
 		jffs2_start_garbage_collect_thread(c);
 
 	fc->sb_flags |= SB_NOATIME;
diff --git a/fs/jffs2/super.c b/fs/jffs2/super.c
index 81396a092ba8..c846b435a38b 100644
--- a/fs/jffs2/super.c
+++ b/fs/jffs2/super.c
@@ -345,7 +345,7 @@ static void jffs2_put_super (struct super_block *sb)
 static void jffs2_kill_sb(struct super_block *sb)
 {
 	struct jffs2_sb_info *c = JFFS2_SB_INFO(sb);
-	if (c && !sb_rdonly(sb))
+	if (c)
 		jffs2_stop_garbage_collect_thread(c);
 	kill_mtd_super(sb);
 	kfree(c);
-- 
2.56.0




______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH] jffs2: fix use-after-free in jffs2_garbage_collect_thread
  2026-11-03  9:00 [PATCH] jffs2: fix use-after-free in jffs2_garbage_collect_thread Yiqing Wu
@ 2026-11-03  9:16 ` sashiko-bot
  0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-11-03  9:16 UTC (permalink / raw)
  To: Yiqing Wu, David Woodhouse, Richard Weinberger
  Cc: Al Viro, David Howells, linux-mtd, syzbot+ae2d1f6d474e1725bdc0

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] jffs2: uninitialized spinlock in jffs2_kill_sb() on rp_size mount error
--

commit 8866980c81747d68f6037e251702023d6140fd9d
Author: Yiqing Wu <wuyiqing@cmss.chinamobile.com>

jffs2: fix use-after-free in jffs2_garbage_collect_thread

This commit fixes a use-after-free of struct jffs2_sb_info by computing the
effective superblock flags in jffs2_do_remount_fs() before starting the
garbage collection thread, and by unconditionally calling
jffs2_stop_garbage_collect_thread() in jffs2_kill_sb().

[ ... ]

> diff --git a/fs/jffs2/super.c b/fs/jffs2/super.c
> index 81396a092ba88..c846b435a38b6 100644
> --- a/fs/jffs2/super.c
> +++ b/fs/jffs2/super.c
> @@ -345,7 +345,7 @@ static void jffs2_put_super (struct super_block *sb)
>  static void jffs2_kill_sb(struct super_block *sb)
>  {
>  	struct jffs2_sb_info *c = JFFS2_SB_INFO(sb);
> -	if (c && !sb_rdonly(sb))
> +	if (c)
>  		jffs2_stop_garbage_collect_thread(c);

[Severity: High]
Can calling jffs2_stop_garbage_collect_thread(c) unconditionally here lock an
uninitialized c->erase_completion_lock when a mount fails early in
jffs2_fill_super()?

During mount context initialization, jffs2_init_fs_context() in
fs/jffs2/super.c allocates struct jffs2_sb_info and assigns it to
fc->s_fs_info without initializing c->erase_completion_lock. When
get_tree_mtd()->mtd_get_sb()->sget_dev()->sget_fc() creates the superblock,
fc->s_fs_info is transferred to sb->s_fs_info before mtd_get_sb() calls
jffs2_fill_super(sb, fc).

In fs/jffs2/super.c:jffs2_fill_super():

	if (c->mount_opts.rp_size > c->mtd->size)
		return invalf(fc, "jffs2: Too large reserve pool specified, max is %llu KB",
			      c->mtd->size / 1024);

	/* Initialize JFFS2 superblock locks, the further initialization will
	 * be done later */
	mutex_init(&c->alloc_sem);
	mutex_init(&c->erase_free_sem);
	init_waitqueue_head(&c->erase_wait);
	init_waitqueue_head(&c->inocache_wq);
	spin_lock_init(&c->erase_completion_lock);

If a mount specifies an rp_size larger than c->mtd->size, jffs2_fill_super()
returns -EINVAL before spin_lock_init(&c->erase_completion_lock) is called.
mtd_get_sb() then calls deactivate_locked_super(sb)->jffs2_kill_sb(sb), where
c is non-NULL.

Previously, failed read-only mounts skipped jffs2_stop_garbage_collect_thread()
because sb_rdonly(sb) was true (though failed read-write mounts already hit
this path). With the !sb_rdonly(sb) check removed, any mount failing at the
rp_size check now calls jffs2_stop_garbage_collect_thread(c) in
fs/jffs2/background.c:

void jffs2_stop_garbage_collect_thread(struct jffs2_sb_info *c)
{
	int wait = 0;
	spin_lock(&c->erase_completion_lock);

Should c->erase_completion_lock be initialized earlier (for example, in
jffs2_init_fs_context() or before the rp_size check in jffs2_fill_super()) so
jffs2_kill_sb() can safely call jffs2_stop_garbage_collect_thread(c) on early
mount failure?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261103090018.157034-1-wuyiqing@cmss.chinamobile.com?part=1

______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-11-03  9:16 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-03  9:00 [PATCH] jffs2: fix use-after-free in jffs2_garbage_collect_thread Yiqing Wu
2026-11-03  9:16 ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox