[PATCH] configfs: fix use-after-free in configfs_symlink() racing with rmdir()
From: Ran Hongyun
Date: Fri Oct 23 2026 - 05:20:50 EST
configfs_symlink() drops the parent directory's inode lock before
resolving the link target, because resolving may need to lock other
directories in the same tree and holding the parent lock would deadlock.
While the lock is dropped, rmdir() on the parent can proceed all the way
to freeing it:
configfs_symlink configfs_rmdir
---------------------------------- ------------------------------
inode_unlock(dir)
get_target()
detach_groups() -> kfree()
inode_lock(dir)
allow_link(parent_item, target)
<---- use-after-free
configfs_symlink() takes a reference on parent_item, but that only pins
the config_item itself. When parent_item is a default group embedded in
a larger object, the reference cannot keep the containing object alive.
For example:
symlink("subsys1", "port1/subsystems/subsys1")
rmdir("port1")
Here parent_item is port->subsys_group.cg_item, embedded in nvmet_port.
Pinning it does not prevent rmdir() from freeing the whole nvmet_port,
leaving parent_item dangling when allow_link() runs.
configfs_rmdir() marks the parent as CONFIGFS_USET_DROPPING in
detach_prep(), before any teardown starts. Re-check this flag after
re-acquiring the inode lock. If it is set, the parent is already being
removed and allow_link() must not run; return -ENOENT. If it is clear,
rmdir() has not reached detach_prep() yet and is waiting on the inode
lock, so allow_link() can proceed safely.
Fixes: 351e5d869e5a ("configfs: fix a deadlock in configfs_symlink()")
Signed-off-by: Ran Hongyun <ranhongyun1@xxxxxxxxxx>
---
fs/configfs/symlink.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index 89178f5d371a..56064bb42909 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -201,6 +201,16 @@ int configfs_symlink(const struct mnt_idmap *idmap, struct inode *dir,
inode_lock(dir);
if (ret)
goto out_put;
+ /*
+ * A racing rmdir() may have freed parent_item while we had
+ * the inode unlocked. detach_prep() sets CONFIGFS_USET_DROPPING
+ * before any teardown, so check it to avoid dereferencing freed
+ * memory in allow_link().
+ */
+ if (sd->s_type & CONFIGFS_USET_DROPPING) {
+ ret = -ENOENT;
+ goto out_put;
+ }
if (dentry->d_inode || d_unhashed(dentry))
ret = -EEXIST;
--
2.52.0