[PATCH] nvmet: Fix possible infinite loop triggered on hot namespace removal

Sagi Grimberg sagi at grimberg.me
Sun Oct 30 02:22:17 PDT 2016


From: Solganik Alexander <sashas at lightbitslabs.com>

When removing a namespace we delete it from the subsystem namespaces
list with list_del_init which allows us to know if it is enabled or
not.

The problem is that list_del_init initialize the list next and does
not respect the RCU list-traversal we do on the IO path for locating
a namespace. Instead we need to use list_del_rcu which is allowed to
run concurrently with the _rcu list-traversal primitives (keeps list
next intact) and guarantees concurrent nvmet_find_naespace forward
progress.

By changing that, we cannot rely on ns->dev_link for knowing if the
namspace is enabled, so add a flags entry to nvmet_ns and use atomic
updates on NVMET_NS_ENABLED.

Signed-off-by: Sagi Grimberg <sagi at grimberg.me>
Signed-off-by: Solganik Alexander <sashas at lightbitslabs.com>
Cc: <stable at vger.kernel.org> # v4.8+
---
 drivers/nvme/target/core.c  | 29 +++++++++++++----------------
 drivers/nvme/target/nvmet.h |  5 +++++
 2 files changed, 18 insertions(+), 16 deletions(-)

diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c
index bbb6bba45e3c..efb78f23b0e7 100644
--- a/drivers/nvme/target/core.c
+++ b/drivers/nvme/target/core.c
@@ -261,11 +261,10 @@ int nvmet_ns_enable(struct nvmet_ns *ns)
 {
 	struct nvmet_subsys *subsys = ns->subsys;
 	struct nvmet_ctrl *ctrl;
-	int ret = 0;
+	int ret;
 
-	mutex_lock(&subsys->lock);
-	if (!list_empty(&ns->dev_link))
-		goto out_unlock;
+	if (test_and_set_bit(NVMET_NS_ENABLED, &ns->flags))
+		return 0;
 
 	ns->bdev = blkdev_get_by_path(ns->device_path, FMODE_READ | FMODE_WRITE,
 			NULL);
@@ -274,7 +273,7 @@ int nvmet_ns_enable(struct nvmet_ns *ns)
 			ns->device_path, PTR_ERR(ns->bdev));
 		ret = PTR_ERR(ns->bdev);
 		ns->bdev = NULL;
-		goto out_unlock;
+		goto out;
 	}
 
 	ns->size = i_size_read(ns->bdev->bd_inode);
@@ -292,6 +291,7 @@ int nvmet_ns_enable(struct nvmet_ns *ns)
 	 * The namespaces list needs to be sorted to simplify the implementation
 	 * of the Identify Namepace List subcommand.
 	 */
+	mutex_lock(&subsys->lock);
 	if (list_empty(&subsys->namespaces)) {
 		list_add_tail_rcu(&ns->dev_link, &subsys->namespaces);
 	} else {
@@ -308,15 +308,15 @@ int nvmet_ns_enable(struct nvmet_ns *ns)
 
 	list_for_each_entry(ctrl, &subsys->ctrls, subsys_entry)
 		nvmet_add_async_event(ctrl, NVME_AER_TYPE_NOTICE, 0, 0);
-
-	ret = 0;
-out_unlock:
 	mutex_unlock(&subsys->lock);
-	return ret;
+
+	return 0;
+
 out_blkdev_put:
 	blkdev_put(ns->bdev, FMODE_WRITE|FMODE_READ);
 	ns->bdev = NULL;
-	goto out_unlock;
+out:
+	return ret;
 }
 
 void nvmet_ns_disable(struct nvmet_ns *ns)
@@ -324,13 +324,10 @@ void nvmet_ns_disable(struct nvmet_ns *ns)
 	struct nvmet_subsys *subsys = ns->subsys;
 	struct nvmet_ctrl *ctrl;
 
-	mutex_lock(&subsys->lock);
-	if (list_empty(&ns->dev_link)) {
-		mutex_unlock(&subsys->lock);
+	if (!test_and_clear_bit(NVMET_NS_ENABLED, &ns->flags))
 		return;
-	}
-	list_del_init(&ns->dev_link);
-	mutex_unlock(&subsys->lock);
+
+	list_del_rcu(&ns->dev_link);
 
 	/*
 	 * Now that we removed the namespaces from the lookup list, we
diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h
index 76b6eedccaf9..f5db4b6a85c3 100644
--- a/drivers/nvme/target/nvmet.h
+++ b/drivers/nvme/target/nvmet.h
@@ -38,6 +38,10 @@
 #define IPO_IATTR_CONNECT_SQE(x)	\
 	(cpu_to_le32(offsetof(struct nvmf_connect_command, x)))
 
+enum nvmet_ns_flags {
+	NVMET_NS_ENABLED	= (1 << 0),
+};
+
 struct nvmet_ns {
 	struct list_head	dev_link;
 	struct percpu_ref	ref;
@@ -47,6 +51,7 @@ struct nvmet_ns {
 	loff_t			size;
 	u8			nguid[16];
 
+	unsigned long		flags;
 	struct nvmet_subsys	*subsys;
 	const char		*device_path;
 
-- 
2.7.4




More information about the Linux-nvme mailing list