All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jeffin Philip <jeffinphilip14@gmail.com>
To: a.hindborg@kernel.org
Cc: leitao@debian.org, viro@zeniv.linux.org.uk, sage@newdream.net,
	linux-kernel@vger.kernel.org, syzbot <syzbot@kernel.org>,
	syzbot+8358d1f3d9c15bdf1c9a@syzkaller.appspotmail.com,
	Jeffin Philip <jeffinphilip14@gmail.com>
Subject: [PATCH] configfs: fix race between symlink and rmdir
Date: Thu, 30 Jul 2026 08:50:40 +0530	[thread overview]
Message-ID: <20260730032040.95378-1-jeffinphilip14@gmail.com> (raw)

From: syzbot <syzbot@kernel.org>

A race condition between configfs_rmdir() and configfs_symlink() can lead
to a use-after-free of a config_item.

When a directory is removed, configfs_rmdir() frees the associated
config_item but leaves the dentry hashed until vfs_rmdir() calls
d_delete(). This creates a window where a concurrent symlinkat() can find
the hashed dentry, see that !d_unhashed(dentry) is true, and attempt to
increment the refcount of the already freed config_item, triggering a
warning:

  refcount_t: addition on 0; use-after-free.
  WARNING: lib/refcount.c:25 at refcount_warn_saturate+0x9f/0x110
  ...
  Call Trace:
   __refcount_add include/linux/refcount.h:-1 [inline]
   __refcount_inc include/linux/refcount.h:366 [inline]
   refcount_inc include/linux/refcount.h:383 [inline]
   kref_get include/linux/kref.h:45 [inline]
   config_item_get+0x88/0x90 fs/configfs/item.c:104
   configfs_get_config_item fs/configfs/configfs_internal.h:127 [inline]
   get_target fs/configfs/symlink.c:128 [inline]
   configfs_symlink+0x4ab/0x1030 fs/configfs/symlink.c:185
   vfs_symlink+0x18b/0x330 fs/namei.c:5660
   filename_symlinkat+0x1cd/0x420 fs/namei.c:5685
   __do_sys_symlinkat fs/namei.c:5705 [inline]
   __se_sys_symlinkat+0x4e/0x2b0 fs/namei.c:5700

To fix this, add d_drop(dentry) in configfs_rmdir() immediately after
detaching the group/item and before unlinking the object. This ensures that
any concurrent kern_path() will either not find the dentry, or
configfs_get_config_item() will see d_unhashed(dentry) == true and safely
return NULL.

However, adding d_drop(dentry) exposes a secondary use-after-free bug in
create_link(). configfs_symlink() calls get_target(), which resolves the
path, gets the config_item, and then immediately drops the path reference.
If configfs_rmdir() runs concurrently, it drops its config_item reference,
and vfs_rmdir() calls d_delete_notify(). Since the dentry has only 1
reference left, dentry_unlink_inode() is called, freeing the
configfs_dirent. Later, create_link() accesses the freed configfs_dirent.

To fix this secondary issue, modify get_target() to return the path, and
hold the path reference in configfs_symlink() until after create_link()
finishes. By holding the path reference, the dentry's refcount remains >=
2, preventing d_delete() from calling dentry_unlink_inode(), which keeps
the configfs_dirent alive. The path is safely cleaned up using the
__free(path_put) attribute in configfs_symlink().

Fixes: 98702467f829 ("configfs: remove unnecessary dentry_unhash on rmdir, dir rename")
Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot
Reported-by: syzbot+8358d1f3d9c15bdf1c9a@syzkaller.appspotmail.com
Tested-by: Jeffin Philip <jeffinphilip14@gmail.com>
Reviewed-by: Jeffin Philip <jeffinphilip14@gmail.com>
Signed-off-by: Jeffin Philip <jeffinphilip14@gmail.com>
Closes: https://syzkaller.appspot.com/bug?extid=8358d1f3d9c15bdf1c9a
Link: https://syzkaller.appspot.com/ai_job?id=13fec684-23f7-4f06-99db-ed37ae92a405
Cc: Breno Leitao <leitao@debian.org>
---
 fs/configfs/dir.c     |  2 ++
 fs/configfs/symlink.c | 14 +++++++-------
 2 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/fs/configfs/dir.c b/fs/configfs/dir.c
index 3c88f13f1ca2..b0812432f6c5 100644
--- a/fs/configfs/dir.c
+++ b/fs/configfs/dir.c
@@ -1552,12 +1552,14 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry)
 
 	if (sd->s_type & CONFIGFS_USET_DIR) {
 		configfs_detach_group(dentry);
+		d_drop(dentry);
 
 		mutex_lock(&subsys->su_mutex);
 		client_disconnect_notify(parent_item, item);
 		unlink_group(to_config_group(item));
 	} else {
 		configfs_detach_item(dentry);
+		d_drop(dentry);
 
 		mutex_lock(&subsys->su_mutex);
 		client_disconnect_notify(parent_item, item);
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index 31eb28b27309..c8a2d9ad6cac 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -114,18 +114,17 @@ static int create_link(struct config_item *parent_item,
 }
 
 
-static int get_target(const char *symname, struct config_item **target,
-		      struct super_block *sb)
+static int get_target(const char *symname, struct path *path,
+		      struct config_item **target, struct super_block *sb)
 {
-	struct path path __free(path_put) = {};
 	int ret;
 
-	ret = kern_path(symname, LOOKUP_FOLLOW|LOOKUP_DIRECTORY, &path);
+	ret = kern_path(symname, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, path);
 	if (ret)
 		return ret;
-	if (path.dentry->d_sb != sb)
+	if (path->dentry->d_sb != sb)
 		return -EPERM;
-	*target = configfs_get_config_item(path.dentry);
+	*target = configfs_get_config_item(path->dentry);
 	if (!*target)
 		return -ENOENT;
 	return 0;
@@ -136,6 +135,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
 		     struct dentry *dentry, const char *symname)
 {
 	int ret;
+	struct path path __free(path_put) = {};
 	struct configfs_dirent *sd;
 	struct config_item *parent_item;
 	struct config_item *target_item = NULL;
@@ -182,7 +182,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir,
 	 *  AV, a thoroughly annoyed bastard.
 	 */
 	inode_unlock(dir);
-	ret = get_target(symname, &target_item, dentry->d_sb);
+	ret = get_target(symname, &path, &target_item, dentry->d_sb);
 	inode_lock(dir);
 	if (ret)
 		goto out_put;
-- 
2.55.0


             reply	other threads:[~2026-07-30  3:21 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-30  3:20 Jeffin Philip [this message]
2026-07-30  8:30 ` [PATCH] configfs: fix race between symlink and rmdir Breno Leitao
2026-07-30  9:05   ` Jeffin Philip

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260730032040.95378-1-jeffinphilip14@gmail.com \
    --to=jeffinphilip14@gmail.com \
    --cc=a.hindborg@kernel.org \
    --cc=leitao@debian.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=sage@newdream.net \
    --cc=syzbot+8358d1f3d9c15bdf1c9a@syzkaller.appspotmail.com \
    --cc=syzbot@kernel.org \
    --cc=viro@zeniv.linux.org.uk \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.