In configfs_symlink(), inode_unlock(dir) is called before resolving the
symlink target via get_target() to avoid deadlocks during pathname lookup.
When configfs_symlink() operates within a default group (such as
ports/1/subsystems/ in NVMe-oF target), parent_item is an embedded default
group inside the dynamically allocated parent object (struct nvmet_port).
Because default groups are embedded within their containing structure,
holding a reference to parent_item alone does not prevent the containing
parent structure from being freed when a concurrent configfs_rmdir()
removes the root directory.
When configfs_rmdir() runs concurrently while inode_lock(dir) is dropped in
configfs_symlink(), it detaches all default groups, unlinks the root group,
sets frag_dead to true, and drops the subsystem's reference to the root
group. Once the root object's refcount drops to zero, the entire containing
structure is freed via kfree(). When configfs_symlink() re-acquires
inode_lock(dir), accessing parent_item in allow_link() accesses freed
memory, causing a slab-use-after-free:
BUG: KASAN: slab-use-after-free in nvmet_port_subsys_allow_link+0x41/0x2e0
drivers/nvme/target/configfs.c:1059
Read of size 8 at addr ffff88811c6d44c8
Call Trace:
kasan_report+0x117/0x150 mm/kasan/report.c:595
nvmet_port_subsys_allow_link+0x41/0x2e0
drivers/nvme/target/configfs.c:1059
configfs_symlink+0x59a/0x1030 fs/configfs/symlink.c:196
vfs_symlink+0x18b/0x330 fs/namei.c:5794
filename_symlinkat+0x1cd/0x410 fs/namei.c:5819
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Allocated by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
__kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
__kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563
nvmet_ports_make+0xe6/0xf00 drivers/nvme/target/configfs.c:2051
configfs_mkdir+0x4f6/0x9e0 fs/configfs/dir.c:1360
vfs_mkdir+0x40c/0x620 fs/namei.c:5410
filename_mkdirat+0x285/0x510 fs/namei.c:5443
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 5847:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
__kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
kfree+0x1c5/0x650 mm/slub.c:6792
config_item_release+0x13a/0x2d0 fs/configfs/item.c:137
configfs_rmdir+0x885/0x950 fs/configfs/dir.c:1571
vfs_rmdir+0x3e6/0x6a0 fs/namei.c:5515
filename_rmdir+0x292/0x520 fs/namei.c:5572
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Fix this by:
1. Adding configfs_get_root_item() to walk up the dentry tree past any
default groups (CONFIGFS_USET_DEFAULT) while holding inode_lock(dir) to
acquire a reference to the root object (root_item), preventing the
containing structure from being freed while inode_lock(dir) is dropped.
2. Checking sd->s_frag->frag_dead after re-acquiring inode_lock(dir) to
detect if the directory was removed during the unlocked window, returning
-ENOENT instead of calling allow_link().
3. Releasing parent_item before root_item in the out_put cleanup path so
that the embedded item reference is dropped while the memory of the
containing root object remains pinned.
Fixes: 351e5d869e5a ("configfs: fix a deadlock in configfs_symlink()")
Assisted-by: Gemini:gemini-3.7-flash Gemini:gemini-3.1-pro-preview syzbot
Reported-by: syzbot+b0996ac2197dd7420c3e@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=b0996ac2197dd7420c3e
Link: https://syzkaller.appspot.com/ai_job?id=e3aa1920-4324-4492-b79e-dffb87dfa600
To: "Breno Leitao"
To:
To: "Al Viro"
Cc: "Andreas Hindborg"
---
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index 31eb28b27..5fe3722bd 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -131,6 +131,19 @@ static int get_target(const char *symname, struct config_item **target,
return 0;
}
+static struct config_item *configfs_get_root_item(struct dentry *dentry)
+{
+ struct dentry *root_dentry = dentry;
+ struct configfs_dirent *sd;
+
+ while (root_dentry) {
+ sd = root_dentry->d_fsdata;
+ if (!sd || !(sd->s_type & CONFIGFS_USET_DEFAULT))
+ break;
+ root_dentry = root_dentry->d_parent;
+ }
+ return root_dentry ? configfs_get_config_item(root_dentry) : NULL;
+}
int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *dentry, const char *symname)
@@ -139,6 +152,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
struct configfs_dirent *sd;
struct config_item *parent_item;
struct config_item *target_item = NULL;
+ struct config_item *root_item = NULL;
const struct config_item_type *type;
sd = dentry->d_parent->d_fsdata;
@@ -157,6 +171,12 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
!type->ct_item_ops->allow_link)
goto out_put;
+ /*
+ * Pin the root object so that the parent_item (which may be an
+ * embedded default group) is not freed while we drop the lock.
+ */
+ root_item = configfs_get_root_item(dentry->d_parent);
+
/*
* This is really sick. What they wanted was a hybrid of
* link(2) and symlink(2) - they wanted the target resolved
@@ -192,8 +212,12 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
else
ret = inode_permission(&nop_mnt_idmap, dir,
MAY_WRITE | MAY_EXEC);
- if (!ret)
- ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ if (!ret) {
+ if (sd->s_frag->frag_dead)
+ ret = -ENOENT;
+ else
+ ret = type->ct_item_ops->allow_link(parent_item, target_item);
+ }
if (!ret) {
mutex_lock(&configfs_symlink_mutex);
ret = create_link(parent_item, target_item, dentry);
@@ -207,6 +231,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
out_put:
config_item_put(parent_item);
+ config_item_put(root_item);
return ret;
}
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
--
This is an AI-generated patch subject to moderation.
Reply with '#syz upstream' to Sign-off the patch as a human author
and send it to the upstream kernel mailing lists.
Reply with '#syz reject' to reject it ('#syz unreject' to undo).
See https://goo.gle/syzbot-ai-patches for information about AI-generated patches.
You can comment on the patch as usual, syzbot will try to address
the comments and send a new version of the patch if necessary.
syzbot engineers can be reached at syzkaller@googlegroups.com.