mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: "Maurizio Lombardi" <mlombard@arkamax.eu>
To: "syzbot"
	<syzbot+list003bd56f9e8b27f9d049@syzkaller.appspotmail.com>,
	<hch@lst.de>, <linux-kernel@vger.kernel.org>,
	<linux-nvme@lists.infradead.org>, <sagi@grimberg.me>,
	<syzkaller-bugs@googlegroups.com>
Subject: Re: [syzbot] Monthly nvme report (Sep 2026)
Date: Fri, 25 Sep 2026 16:22:06 +0200	[thread overview]
Message-ID: <DLOGQG9GZC3X.1UZ2MY7S3T5N2@arkamax.eu> (raw)
In-Reply-To: <6ab3569c.514bccd6.255447.0023.GAE@google.com>

On Wed Sep 23, 2026 at 6:33 AM CEST, syzbot wrote:
> Hello nvme maintainers/developers,
>
> Ref Crashes Repro Title
> <2> 2       Yes   KASAN: slab-use-after-free Read in nvmet_port_subsys_allow_link
>                   https://syzkaller.appspot.com/bug?extid=b0996ac2197dd7420c3e

I have a patch that fixes this one, but I still have to carefully test
it with blktests before submitting it:


----------

nvmet: fix null-ptr-deref and use-after-free in port configfs
 lifecycle

When concurrent threads execute rmdir on an NVMe-oF port directory
and symlink or unlink on the port's subsystems directory, a race
condition leads to kernel crashes because of NULL pointer dereferences
and UAF bugs.

There are two flaws in how the port's configfs default groups are handled
during the teardown.

The first one is a NULL pointer dereference via ci_parent: When rmdir is
called on the port, configfs sets parent->ci_parent to NULL.
If a concurrent symlink operation executes nvmet_port_subsys_allow_link()
or drop_link(), the to_nvmet_port(parent->ci_parent) calculation
subtracts the structure offset from NULL, resulting in a bogus negative
memory address and a kernel panic.

The second one is due to the configfs groups (subsystems, referrals,
ana_groups and ana_default directories) being embedded within the
nvmet_port memory allocation.
During rmdir, nvmet_port_release() executes kfree(port) immediately.
However, concurrent operations like symlinkat might still hold
active configfs references to the embedded groups. When the system
subsequently drops these references, configfs writes to the already
freed nvmet_port memory block, triggering a UAF.

Fix those bugs by decoupling the port's memory lifecycle from its
primary configfs item lifecycle and by replacing the ci_parent pointer
dereferencing:

Introduces a kref to struct nvmet_port. The kref is initialized to 5
in nvmet_ports_make() (1 for the port itself, plus 4 for its embedded
default groups). release() callbacks are added for the embedded groups
to drop their references via kref_put(). The nvmet_port memory block is
now only freed via nvmet_port_free() when all activity across all nested
directories has completely concluded.

Replaces volatile ci_parent dereferencing in nvmet_port_subsys_allow_link()
and drop_link() by using container_of() on the embedded subsys_group.

Serializes visibility of the port by moving list_add() to the very end of
port creation and list_del() to the very beginning of port teardown and
protecting it with the semaphore.

Signed-off-by: Maurizio Lombardi <mlombard@redhat.com>
---
 drivers/nvme/target/configfs.c | 64 ++++++++++++++++++++++++++++++----
 drivers/nvme/target/nvmet.h    |  1 +
 2 files changed, 58 insertions(+), 7 deletions(-)

diff --git a/drivers/nvme/target/configfs.c b/drivers/nvme/target/configfs.c
index 6286e38436dd..363cb800b8cd 100644
--- a/drivers/nvme/target/configfs.c
+++ b/drivers/nvme/target/configfs.c
@@ -1056,7 +1056,8 @@ static void nvmet_add_passthru_group(struct nvmet_subsys *subsys)
 static int nvmet_port_subsys_allow_link(struct config_item *parent,
 		struct config_item *target)
 {
-	struct nvmet_port *port = to_nvmet_port(parent->ci_parent);
+	struct nvmet_port *port = container_of(to_config_group(parent),
+					struct nvmet_port, subsys_group);
 	struct nvmet_subsys *subsys;
 	struct nvmet_subsys_link *link, *p;
 	int ret;
@@ -1099,7 +1100,8 @@ static int nvmet_port_subsys_allow_link(struct config_item *parent,
 static void nvmet_port_subsys_drop_link(struct config_item *parent,
 		struct config_item *target)
 {
-	struct nvmet_port *port = to_nvmet_port(parent->ci_parent);
+	struct nvmet_port *port = container_of(to_config_group(parent),
+					struct nvmet_port, subsys_group);
 	struct nvmet_subsys *subsys = to_subsys(target);
 	struct nvmet_subsys_link *p;
 
@@ -1122,9 +1124,23 @@ static void nvmet_port_subsys_drop_link(struct config_item *parent,
 	kfree(p);
 }
 
+static void nvmet_port_free(struct kref *ref)
+{
+	struct nvmet_port *port = container_of(ref, struct nvmet_port, ref);
+	key_put(port->keyring);
+	kfree(port);
+}
+
+static void nvmet_port_subsys_release(struct config_item *item)
+{
+	struct nvmet_port *port = container_of(to_config_group(item), struct nvmet_port, subsys_group);
+	kref_put(&port->ref, nvmet_port_free);
+}
+
 static struct configfs_item_operations nvmet_port_subsys_item_ops = {
 	.allow_link		= nvmet_port_subsys_allow_link,
 	.drop_link		= nvmet_port_subsys_drop_link,
+	.release		= nvmet_port_subsys_release,
 };
 
 static const struct config_item_type nvmet_port_subsys_type = {
@@ -1862,6 +1878,16 @@ static struct config_group *nvmet_referral_make(
 	return &port->group;
 }
 
+static void nvmet_referrals_group_release(struct config_item *item)
+{
+	struct nvmet_port *port = container_of(to_config_group(item), struct nvmet_port, referrals_group);
+	kref_put(&port->ref, nvmet_port_free);
+}
+
+static struct configfs_item_operations nvmet_referrals_item_ops = {
+	.release	= nvmet_referrals_group_release,
+};
+
 static struct configfs_group_operations nvmet_referral_group_ops = {
 	.make_group		= nvmet_referral_make,
 	.disconnect_notify	= nvmet_referral_notify,
@@ -1870,6 +1896,7 @@ static struct configfs_group_operations nvmet_referral_group_ops = {
 static const struct config_item_type nvmet_referrals_type = {
 	.ct_owner	= THIS_MODULE,
 	.ct_group_ops	= &nvmet_referral_group_ops,
+	.ct_item_ops	= &nvmet_referrals_item_ops,
 };
 
 static struct nvmet_type_name_map nvmet_ana_state[] = {
@@ -1930,8 +1957,10 @@ static void nvmet_ana_group_release(struct config_item *item)
 {
 	struct nvmet_ana_group *grp = to_ana_group(item);
 
-	if (grp == &grp->port->ana_default_group)
+	if (grp == &grp->port->ana_default_group) {
+		kref_put(&grp->port->ref, nvmet_port_free);
 		return;
+	}
 
 	down_write(&nvmet_ana_sem);
 	grp->port->ana_state[grp->grpid] = NVME_ANA_INACCESSIBLE;
@@ -1992,8 +2021,19 @@ static struct configfs_group_operations nvmet_ana_groups_group_ops = {
 	.make_group		= nvmet_ana_groups_make_group,
 };
 
+static void nvmet_ana_groups_release(struct config_item *item)
+{
+	struct nvmet_port *port = container_of(to_config_group(item), struct nvmet_port, ana_groups_group);
+	kref_put(&port->ref, nvmet_port_free);
+}
+
+static struct configfs_item_operations nvmet_ana_groups_item_ops = {
+	.release	= nvmet_ana_groups_release,
+};
+
 static const struct config_item_type nvmet_ana_groups_type = {
 	.ct_group_ops		= &nvmet_ana_groups_group_ops,
+	.ct_item_ops		= &nvmet_ana_groups_item_ops,
 	.ct_owner		= THIS_MODULE,
 };
 
@@ -2004,12 +2044,13 @@ static void nvmet_port_release(struct config_item *item)
 {
 	struct nvmet_port *port = to_nvmet_port(item);
 
+	down_write(&nvmet_config_sem);
+	list_del(&port->global_entry);
+	up_write(&nvmet_config_sem);
 	/* Let inflight controllers teardown complete */
 	flush_workqueue(nvmet_wq);
-	list_del(&port->global_entry);
 
-	key_put(port->keyring);
-	kfree(port);
+	kref_put(&port->ref, nvmet_port_free);
 }
 
 static struct configfs_attribute *nvmet_port_attrs[] = {
@@ -2067,7 +2108,12 @@ static struct config_group *nvmet_ports_make(struct config_group *group,
 			port->ana_state[i] = NVME_ANA_INACCESSIBLE;
 	}
 
-	list_add(&port->global_entry, &nvmet_ports_list);
+	/* 1 for the port itself, plus 4 for embedded default groups */
+	kref_init(&port->ref);
+	kref_get(&port->ref); /* Referrals */
+	kref_get(&port->ref); /* Subsystems */
+	kref_get(&port->ref); /* ana_grous */
+	kref_get(&port->ref); /* ana_default */
 
 	INIT_LIST_HEAD(&port->entry);
 	INIT_LIST_HEAD(&port->subsystems);
@@ -2102,6 +2148,10 @@ static struct config_group *nvmet_ports_make(struct config_group *group,
 	configfs_add_default_group(&port->ana_default_group.group,
 			&port->ana_groups_group);
 
+	down_write(&nvmet_config_sem);
+	list_add(&port->global_entry, &nvmet_ports_list);
+	up_write(&nvmet_config_sem);
+
 	return &port->group;
 }
 
diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h
index dbda55895f4f..1c8a5e2275af 100644
--- a/drivers/nvme/target/nvmet.h
+++ b/drivers/nvme/target/nvmet.h
@@ -219,6 +219,7 @@ struct nvmet_port {
 	int				mdts;
 	const struct nvmet_fabrics_ops	*tr_ops;
 	bool				pi_enable;
+	struct kref			ref;
 	enum nvme_ana_state		ana_state[];
 };
 
-- 
2.55.0

      reply	other threads:[~2026-09-25 14:22 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-23  4:33 syzbot
2026-09-25 14:22 ` Maurizio Lombardi [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=DLOGQG9GZC3X.1UZ2MY7S3T5N2@arkamax.eu \
    --to=mlombard@arkamax.eu \
    --cc=hch@lst.de \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-nvme@lists.infradead.org \
    --cc=sagi@grimberg.me \
    --cc=syzbot+list003bd56f9e8b27f9d049@syzkaller.appspotmail.com \
    --cc=syzkaller-bugs@googlegroups.com \
    /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®