Re: [BUG] configfs: slab-use-after-free in configfs_drop_dentry() on rmdir
From: Farhad Alemi
Date: Sat May 30 2026 - 13:20:52 EST
Hi Breno,
Thanks for sending the patch. I applied your patch on top of v7.1-rc5
e7ae89a0c97ce2b68b0983cd01eda67cf373517d and rebuilt with the same
config that originally caught this. The attached reproducer now runs
without causing a panic. Without the patch, the same reproducer still
trips it.
Please feel free to add:
Tested-by: Farhad Alemi <farhad.alemi@xxxxxxxxxxxx>
Thanks again,
On Wed, May 27, 2026 at 3:27 AM Breno Leitao <leitao@xxxxxxxxxx> wrote:
>
> On Tue, May 26, 2026 at 08:42:00PM +0000, Farhad Alemi wrote:
> > Hello Andreas and the configfs team,
> >
> > I am reporting a configfs use-after-free found by syzkaller.
> >
> > Summary:
> > A configfs config-item rmdir(2) can land in configfs_drop_dentry() with
> > sd->s_dentry pointing at a dentry whose RCU grace period has not yet
> > elapsed. The spin_lock(&dentry->d_lock) at fs/configfs/inode.c:209 then
> > reads freed slab memory and KASAN reports a slab-use-after-free.
>
> Thanks for the report. I was able to reproduce it on linus' upstream
> tree.
>
> I've came up with the following fix, would you mind trying it, please?
>
>
> commit d78c04736ef0ede76093c780a188560b6baed3c3
> Author: Breno Leitao <leitao@xxxxxxxxxx>
> Date: Wed May 27 06:25:49 2026 -0400
>
> configfs: fix UAF in configfs_drop_dentry() after failed attribute lookup
>
> When configfs_lookup() matches an attribute it publishes the new dentry
> into sd->s_dentry (and dentry->d_fsdata) before calling
> configfs_create() to allocate the inode:
>
> dentry->d_fsdata = configfs_get(sd);
> sd->s_dentry = dentry;
> spin_unlock(&configfs_dirent_lock);
>
> inode = configfs_create(dentry, mode);
> if (IS_ERR(inode)) {
> configfs_put(sd);
> return ERR_CAST(inode);
> }
>
> If configfs_create() fails (e.g. new_inode() returns NULL under memory
> pressure or fault injection), the lookup returns an error and the
> caller dputs the now-negative dentry, which goes through
> __dentry_kill() and is freed via call_rcu().
>
> Because the dentry never gained an inode it does not go through the
> .d_iput op (configfs_d_iput), which is the only place that clears
> sd->s_dentry. sd therefore keeps a stale pointer to the freed dentry.
> A subsequent rmdir of the parent item walks the parent's s_children
> list in detach_attrs() and configfs_drop_dentry() does
> spin_lock(&sd->s_dentry->d_lock) on freed memory:
>
> BUG: KASAN: slab-use-after-free in _raw_spin_lock+0xac/0x110
> Read of size 1 at addr ffff00012bacd028 by task repro/2440
> _raw_spin_lock+0xac/0x110
> configfs_drop_dentry+0x48/0x158 [configfs]
> detach_attrs.isra.0+0x18c/0x494 [configfs]
> configfs_rmdir+0x450/0x71c [configfs]
> vfs_rmdir+0x170/0x620
> ...
> Freed by task 0:
> __d_free+0x28/0x34
> rcu_do_batch+0x37c/0x1bd0
> ...
> Last potentially related work creation:
> call_rcu+0x34/0x68
> dentry_free+0xe8/0x3e0
> __dentry_kill+0x404/0x604
> dput+0x14/0x30
> lookup_open.isra.0+0x6ac/0xc00
> path_openat+0xd18/0x2588
>
> Fix this by tearing down the sd<->dentry linkage in the
> configfs_create() error path, under configfs_dirent_lock, mirroring
> what configfs_d_iput() would have done for a positive dentry.
>
> Reported-by: Farhad Alemi <farhad.alemi@xxxxxxxxxxxx>
> Signed-off-by: Breno Leitao <leitao@xxxxxxxxxx>
>
> diff --git a/fs/configfs/dir.c b/fs/configfs/dir.c
> index 362b6ff9b908..68d857dcb2d9 100644
> --- a/fs/configfs/dir.c
> +++ b/fs/configfs/dir.c
> @@ -486,6 +486,22 @@ static struct dentry * configfs_lookup(struct inode *dir,
>
> inode = configfs_create(dentry, mode);
> if (IS_ERR(inode)) {
> + /*
> + * configfs_create() failed (e.g. -ENOMEM
> + * from new_inode()). The dentry will be
> + * dput()ed by the caller and freed via RCU;
> + * because it never gained an inode,
> + * configfs_d_iput() will not run to clear
> + * sd->s_dentry. Drop the linkage here so a
> + * later detach_attrs() walking the parent's
> + * s_children list does not dereference a
> + * freed dentry in configfs_drop_dentry().
> + */
> + spin_lock(&configfs_dirent_lock);
> + if (sd->s_dentry == dentry)
> + sd->s_dentry = NULL;
> + dentry->d_fsdata = NULL;
> + spin_unlock(&configfs_dirent_lock);
> configfs_put(sd);
> return ERR_CAST(inode);
> }
Attachment:
reproducer.c
Description: Binary data