From: Izaac Chong <izaacchong934@gmail.com>
To: Breno Leitao <leitao@debian.org>,
Andreas Hindborg <a.hindborg@kernel.org>,
Al Viro <viro@zeniv.linux.org.uk>
Cc: linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org,
Izaac Chong <izaacchong934@gmail.com>
Subject: [PATCH v2 2/2] configfs: pin symlink target against rmdir before ->allow_link()
Date: Sat, 14 Nov 2026 13:29:47 +0800 [thread overview]
Message-ID: <20261114-configfs-symlink-uaf-v2-2-48c9cf068a91@gmail.com> (raw)
In-Reply-To: <20261114-configfs-symlink-uaf-v2-0-48c9cf068a91@gmail.com>
configfs_symlink() looks up the link target with get_target(), which
takes references on the target item and dirent but does not stop the
target from being removed. The target is only pinned (target_sd->s_links
incremented, refusing CONFIGFS_USET_DROPPING) later, in create_link(),
after ->allow_link() has run.
A concurrent rmdir of the target can therefore complete in between.
unlink_obj() then clears target->ci_parent and ->ci_group, and
->allow_link() implementations that walk the target's parents
dereference NULL. With the USB gadget UVC function:
BUG: KASAN: null-ptr-deref in uvcg_streaming_header_allow_link+0xc3/0x270
Read of size 8 at addr 0000000000000030
Call Trace:
uvcg_streaming_header_allow_link
configfs_symlink
vfs_symlink
filename_symlinkat
__x64_sys_symlink
target_fabric_mappedlun_link() also walks the target's ->ci_parent.
Take the s_links pin before calling ->allow_link() instead, under
configfs_dirent_lock and only if the target is not being dropped, as
create_link() did. configfs_detach_prep() sets CONFIGFS_USET_DROPPING
before checking s_links under the same lock, so the target either fails
to pin (-ENOENT) or rmdir sees the pin and returns -EBUSY. On success the
link keeps the count and configfs_unlink() drops it as before; on failure
it is dropped again.
Fixes: 4768e9b18dc6 ("[PATCH] configfs: Fix symlink() to a removing item")
Link: https://sashiko.dev/#/bug/linux-df6ae898-b1a6-4091-be3b-fbe4c177763f
Signed-off-by: Izaac Chong <izaacchong934@gmail.com>
---
fs/configfs/symlink.c | 42 ++++++++++++++++++++++++++++--------------
1 file changed, 28 insertions(+), 14 deletions(-)
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index 1b2c9048a920..52f0f3a6cd7e 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -90,23 +90,11 @@ static int create_link(struct config_item *parent_item,
return -ENOMEM;
configfs_get(target_sd);
- spin_lock(&configfs_dirent_lock);
- if (target_sd->s_type & CONFIGFS_USET_DROPPING) {
- spin_unlock(&configfs_dirent_lock);
- configfs_put(target_sd);
- kfree(body);
- return -ENOENT;
- }
- target_sd->s_links++;
- spin_unlock(&configfs_dirent_lock);
ret = configfs_get_target_path(parent_item, item, body);
if (!ret)
ret = configfs_create_link(target_sd, parent_item->ci_dentry,
dentry, body);
if (ret) {
- spin_lock(&configfs_dirent_lock);
- target_sd->s_links--;
- spin_unlock(&configfs_dirent_lock);
configfs_put(target_sd);
kfree(body);
}
@@ -220,8 +208,28 @@ int configfs_symlink(const 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)
+ goto out_put_parent;
+
+ /*
+ * Pin the target against rmdir before calling ->allow_link().
+ * configfs_detach_prep() refuses to remove a dirent with s_links
+ * set, and sets CONFIGFS_USET_DROPPING (under configfs_dirent_lock)
+ * before it checks s_links. Without the pin, a concurrent rmdir can
+ * unlink the target (clearing ->ci_parent and ->ci_group) while
+ * ->allow_link() looks at it. On success the link owns the count,
+ * which configfs_unlink() drops.
+ */
+ spin_lock(&configfs_dirent_lock);
+ if (target_sd->s_type & CONFIGFS_USET_DROPPING)
+ ret = -ENOENT;
+ else
+ target_sd->s_links++;
+ spin_unlock(&configfs_dirent_lock);
+ if (ret)
+ goto out_put_parent;
+
+ 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, target_sd, dentry);
@@ -230,7 +238,13 @@ int configfs_symlink(const struct mnt_idmap *idmap, struct inode *dir,
type->ct_item_ops->drop_link(parent_item,
target_item);
}
+ if (ret) {
+ spin_lock(&configfs_dirent_lock);
+ target_sd->s_links--;
+ spin_unlock(&configfs_dirent_lock);
+ }
+out_put_parent:
config_item_put(parent_item);
out_put_target:
configfs_put(target_sd);
--
Git-143)
prev parent reply other threads:[~2026-11-14 5:30 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-14 5:29 [PATCH v2 0/2] configfs: fix use-after-free races in configfs_symlink() Izaac Chong
2026-11-14 5:29 ` [PATCH v2 1/2] configfs: fix use-after-free of default group " Izaac Chong
2026-11-14 5:29 ` Izaac Chong [this message]
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=20261114-configfs-symlink-uaf-v2-2-48c9cf068a91@gmail.com \
--to=izaacchong934@gmail.com \
--cc=a.hindborg@kernel.org \
--cc=leitao@debian.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-usb@vger.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®