[PATCH] jffs: initialize inocache to NULL when initializing a jffs2_inode_info

Lucas Jeffrey luquijeffrey at gmail.com
Wed Sep 9 19:53:45 PDT 2026


Hi richard
I've sort of manually confirmed the crash pointed out by Sashiko,
while not exactly the failure path mentioned in the review,
I found that if immediately after returning a new inode from
jffs2_alloc_inode, if allocation fails in 'security_inode_alloc',
then an ENOMEM is propagated to inode_init_always_gfp and then to
alloc_inode, and enters this failure path:

struct inode *alloc_inode(...) {
...
if (unlikely(inode_init_always(sb, inode))) {
if (ops->destroy_inode) {
ops->destroy_inode(inode);
if (!ops->free_inode)
return NULL;
}

inode->free_inode = ops->free_inode;
i_callback(&inode->i_rcu); //Here it ends up calling jffs2_free_inode
which frees f->target, double free/UAF if f->target != NULL
return NULL;
}
...
}


Here's the report
[  104.928161] ==================================================================
[  104.931812] BUG: KASAN: double-free in jffs2_free_inode+0x31/0x50
[  104.935737] Free of addr ffff88800ad5eb80 by task repro/76
[  104.936013]
[  104.937388] CPU: 0 UID: 0 PID: 76 Comm: repro Not tainted
7.3.0-rc2-00028-g692c6ca486c3-dirty #16 PREEMPT(lazy)
[  104.937872] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX,
arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[  104.938350] Call Trace:
[  104.939517]  <TASK>
[  104.939999]  dump_stack_lvl+0x53/0x70
[  104.940358]  print_report+0xd0/0x630
[  104.940415]  ? __pfx__raw_spin_lock_irqsave+0x10/0x10
[  104.940459]  ? jffs2_free_inode+0x31/0x50
[  104.940486]  ? jffs2_free_inode+0x31/0x50
[  104.940509]  kasan_report_invalid_free+0xa2/0xd0
[  104.940542]  ? jffs2_free_inode+0x31/0x50
[  104.940570]  ? jffs2_free_inode+0x31/0x50
[  104.940595]  check_slab_allocation+0xd9/0x100
[  104.940625]  kfree+0xfc/0x390
[  104.940657]  jffs2_free_inode+0x31/0x50
[  104.940684]  ? __pfx_jffs2_free_inode+0x10/0x10
[  104.940715]  alloc_inode+0x135/0x1f0
[  104.940755]  new_inode+0x13/0x140
[  104.940790]  jffs2_new_inode+0x77/0xa00
[  104.940828]  jffs2_create+0x8a/0x340
[  104.940860]  lookup_open+0xa90/0x18d0
[  104.940906]  path_openat+0x1257/0x2280
[  104.940949]  ? __pfx_path_openat+0x10/0x10
[  104.940984]  do_file_open+0x1cd/0x400
[  104.941013]  ? __pfx_do_file_open+0x10/0x10
[  104.941061]  ? alloc_fd+0x331/0x5b0
[  104.941103]  ? do_getname+0x66/0x2c0
[  104.941138]  do_sys_openat2+0xd9/0x170
[  104.941168]  ? __pfx_do_sys_openat2+0x10/0x10
[  104.941203]  __x64_sys_openat+0x11d/0x1d0
[  104.941229]  ? kasan_save_track+0x14/0x30
[  104.941256]  ? __pfx___x64_sys_openat+0x10/0x10
[  104.941283]  ? kmem_cache_free+0xba/0x3a0
[  104.941310]  ? fpregs_restore_userregs+0xe0/0x230
[  104.941362]  do_syscall_64+0xdd/0x4a0
[  104.941400]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[  104.941872] RIP: 0033:0x420f77
[  104.942862] Code: 55 9c 48 89 75 a0 89 7d a8 44 89 55 ac e8 71 f0
02 00 44 8b 55 ac 8b 55 9c 41 89 c0 48 8b 75 a0 8b 7d a8 b8 01 01 00
00 0f 05 <48> 3d 00 f0 ff ff 77 31 44 89 c7 89 45 ac e8 c6 f0 02 00 8b
45 ac
[  104.943373] RSP: 002b:00007f1ef5c4e570 EFLAGS: 00000293 ORIG_RAX:
0000000000000101
[  104.944349] RAX: ffffffffffffffda RBX: 000000000a413220 RCX: 0000000000420f77
[  104.944401] RDX: 00000000000000c2 RSI: 00007f1ef5c4e600 RDI: 00000000ffffff9c
[  104.944447] RBP: 00007f1ef5c4e5e0 R08: 0000000000000000 R09: 0000000000000000
[  104.944491] R10: 00000000000001a4 R11: 0000000000000293 R12: 0000000000000483
[  104.944533] R13: 0000000000000007 R14: 00007f1ef5c4ea00 R15: 000000000a413224
[  104.944619]  </TASK>
[  104.944813]
[  104.944856] Allocated by task 78:
[  104.945202]  kasan_save_stack+0x33/0x60
[  104.945489]  kasan_save_track+0x14/0x30
[  104.945515]  __kasan_kmalloc+0x8f/0xa0
[  104.945538]  __kmalloc_node_track_caller_noprof+0x1bc/0x480
[  104.945564]  kmemdup_noprof+0x20/0x50
[  104.945600]  jffs2_symlink+0x520/0x1080
[  104.945624]  vfs_symlink+0x132/0x420
[  104.945645]  filename_symlinkat+0x139/0x3d0
[  104.945670]  __x64_sys_symlink+0x74/0xa0
[  104.945694]  do_syscall_64+0xdd/0x4a0
[  104.945720]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[  104.945778]
[  104.945804] Freed by task 80:
[  104.945932]  kasan_save_stack+0x33/0x60
[  104.945984]  kasan_save_track+0x14/0x30
[  104.946074]  kasan_save_free_info+0x3b/0x60
[  104.946101]  __kasan_slab_free+0x43/0x70
[  104.946126]  kfree+0x121/0x390
[  104.946146]  jffs2_free_inode+0x31/0x50
[  104.946167]  rcu_core+0x55a/0x1870
[  104.946192]  handle_softirqs+0x182/0x5a0
[  104.946226]  __irq_exit_rcu+0x63/0x140
[  104.946248]  sysvec_apic_timer_interrupt+0x6b/0x80
[  104.946273]  asm_sysvec_apic_timer_interrupt+0x1a/0x20
[  104.946314]
[  104.946386] The buggy address belongs to the object at ffff88800ad5eb80
[  104.946386]  which belongs to the cache kmalloc-64 of size 64
[  104.946519] The buggy address is located 0 bytes inside of
[  104.946519]  64-byte region [ffff88800ad5eb80, ffff88800ad5ebc0)
[  104.946573]
[  104.946800] The buggy address belongs to the physical page:
[  104.947880] page: refcount:0 mapcount:0 mapping:0000000000000000
index:0x0 pfn:0xad5e
[  104.948383] flags: 0x100000000000000(node=0|zone=1)
[  104.949052] page_type: f5(slab)
[  104.949572] raw: 0100000000000000 ffff8880070418c0 dead000000000100
dead000000000122
[  104.949623] raw: 0000000000000000 0000000000200020 00000000f5000000
0000000000000000
[  104.949710] page dumped because: kasan: bad access detected
[  104.949727]
[  104.949743] Memory state around the buggy address:
[  104.950194]  ffff88800ad5ea80: 00 00 00 00 00 03 fc fc fc fc fc fc
fc fc fc fc
[  104.950242]  ffff88800ad5eb00: 00 00 00 00 00 00 03 fc fc fc fc fc
fc fc fc fc
[  104.950282] >ffff88800ad5eb80: fa fb fb fb fb fb fb fb fc fc fc fc
fc fc fc fc
[  104.950315]                    ^
[  104.950381]  ffff88800ad5ec00: 00 00 00 00 00 00 03 fc fc fc fc fc
fc fc fc fc
[  104.950397]  ffff88800ad5ec80: 00 00 00 00 00 00 03 fc fc fc fc fc
fc fc fc fc
[  104.950439] ==================================================================
[  104.968624] Disabling lock debugging due to kernel taint



i'll send a V2 patch that also initializes f->target = NULL when
allocating the inode
Thanks,
Lucas J.


On Wed, Sep 9, 2026 at 2:57 AM Richard Weinberger <richard at nod.at> wrote:
>
> Lucas,
>
> Am Wed, Sep 9, 2026, 04:54 schrieb sashiko-bot at kernel.org:
> > Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> >
> > Pre-existing issues:
> > - [Critical] Double free of `f->target` on early VFS failure paths due to uninitialized slab memory.
> > --
> >
> > commit 9fd2012cfec1b8d15df193fea97deabda99aa5e5
> > Author: Lucas Jeffrey <luquijeffrey at gmail.com>
> >
> > jffs: initialize inocache to NULL when initializing a jffs2_inode_info
> >
> > When a jffs2_inode_info is allocated via new_inode(), due to memory reuse it
> > can retain stale values from its previous use.
> >
> > If the new jffs2_inode_info object allocated retains an old reference to a
> > freed inocache, the function jffs2_new_inode may crash if either
> > jffs2_init_acl_pre or jffs2_do_new_inode returns an error value because
> > when calling iput() with the inode it will eventually attempt to free again
> > the inocache.
> >
> > Reported-by: syzbot+3a8099322b09d8d073d1 at syzkaller.appspotmail.com
> > Closes: https://syzkaller.appspot.com/bug?extid=3a8099322b09d8d073d1
> > Signed-off-by: Lucas Jeffrey <luquijeffrey at gmail.com>
> >
> > > diff --git a/fs/jffs2/os-linux.h b/fs/jffs2/os-linux.h
> > > index 86ab014a349c5..40f17218a276b 100644
> > > --- a/fs/jffs2/os-linux.h
> > > +++ b/fs/jffs2/os-linux.h
> > > @@ -52,6 +52,7 @@ static inline void jffs2_init_inode_info(struct jffs2_inode_info *f)
> > >  {
> > >     f->highest_version = 0;
> > >     f->fragtree = RB_ROOT;
> > > +   f->inocache = NULL;
> > >     f->metadata = NULL;
> > >     f->dents = NULL;
> > >     f->target = NULL;
> >
> > [Severity: Critical]
> > This isn't a bug introduced by this patch, but doesn't f->target suffer from
> > a similar issue due to uninitialized slab memory on early VFS failure paths?
> >
> > When the VFS allocates a new inode via jffs2_alloc_inode() in
> > fs/jffs2/super.c, the object is returned from the SLAB cache without
> > __GFP_ZERO. It retains stale values from its previous lifecycle, including
> > a dangling pointer in f->target, because jffs2_free_inode() frees f->target
> > but does not nullify it.
> >
> > JFFS2 defers initializing f->target = NULL to jffs2_init_inode_info(), which
> > is called after VFS initialization completes. If the VFS fails early (e.g.,
> > inode_init_always() fails due to an LSM memory allocation, or iget_locked()
> > in fs/inode.c encounters a hash collision race and drops the newly allocated
> > inode), the VFS destroys the inode via jffs2_free_inode() before JFFS2 ever
> > initializes it.
> >
> > This causes a double free when kfree(f->target) executes again on the
> > dangling pointer:
> >
> > fs/jffs2/super.c:jffs2_free_inode() {
> >       ...
> >       kfree(f->target);
> >       kmem_cache_free(jffs2_inode_cachep, f);
> > }
> >
> > Userspace can reliably trigger the iget_locked() hash collision race by
> > performing concurrent lookups (e.g., stat() or open()) on the same
> > non-resident inode:
> >
> > fs/inode.c:iget_locked() {
> >       ...
> >       if (unlikely(test_bit(__I_NEW, &inode->i_state))) {
> >               /* Uhhuh, somebody else created the same inode under us... */
> >               spin_unlock(&inode_hash_lock);
> >               destroy_inode(inode);
> >       ...
> > }
> >
> > Would it be safer to zero these fields in jffs2_alloc_inode() or ensure
> > jffs2_free_inode() nullifies them before returning the object to the slab
> > cache?
>
> I think this is a legit bug pointed out by Sashiko.
> Do you want to double check and (if needed) fix it?
>
> Thanks,
> //richard



More information about the linux-mtd mailing list