NVMe: Reference count open namespaces
authorKeith Busch <keith.busch@intel.com>
Thu, 1 Oct 2015 23:14:10 +0000 (17:14 -0600)
committerJens Axboe <axboe@fb.com>
Fri, 9 Oct 2015 16:40:36 +0000 (10:40 -0600)
Dynamic namespace attachment means the namespace may be removed at any
time, so the namespace reference count can not be tied to the device
reference count. This fixes a NULL dereference if an opened namespace
is detached from a controller.

Signed-off-by: Keith Busch <keith.busch@intel.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Jens Axboe <axboe@fb.com>
drivers/block/nvme-core.c
include/linux/nvme.h

index 6f04771f1019798cc2feabf73eff2ddbadc84b81..b02ae3d759d7098e44b545b1e7c4cac4494bb326 100644 (file)
@@ -1943,6 +1943,18 @@ static int nvme_compat_ioctl(struct block_device *bdev, fmode_t mode,
 #define nvme_compat_ioctl      NULL
 #endif
 
+static void nvme_free_ns(struct kref *kref)
+{
+       struct nvme_ns *ns = container_of(kref, struct nvme_ns, kref);
+
+       spin_lock(&dev_list_lock);
+       ns->disk->private_data = NULL;
+       spin_unlock(&dev_list_lock);
+
+       put_disk(ns->disk);
+       kfree(ns);
+}
+
 static int nvme_open(struct block_device *bdev, fmode_t mode)
 {
        int ret = 0;
@@ -1952,21 +1964,25 @@ static int nvme_open(struct block_device *bdev, fmode_t mode)
        ns = bdev->bd_disk->private_data;
        if (!ns)
                ret = -ENXIO;
-       else if (!kref_get_unless_zero(&ns->dev->kref))
+       else if (!kref_get_unless_zero(&ns->kref))
                ret = -ENXIO;
+       else if (!kref_get_unless_zero(&ns->dev->kref)) {
+               kref_put(&ns->kref, nvme_free_ns);
+               ret = -ENXIO;
+       }
        spin_unlock(&dev_list_lock);
 
        return ret;
 }
 
 static void nvme_free_dev(struct kref *kref);
-
 static void nvme_release(struct gendisk *disk, fmode_t mode)
 {
        struct nvme_ns *ns = disk->private_data;
        struct nvme_dev *dev = ns->dev;
 
        kref_put(&dev->kref, nvme_free_dev);
+       kref_put(&ns->kref, nvme_free_ns);
 }
 
 static int nvme_getgeo(struct block_device *bd, struct hd_geometry *geo)
@@ -2126,6 +2142,7 @@ static void nvme_alloc_ns(struct nvme_dev *dev, unsigned nsid)
        if (!disk)
                goto out_free_queue;
 
+       kref_init(&ns->kref);
        ns->ns_id = nsid;
        ns->disk = disk;
        ns->lba_shift = 9; /* set to a default value for 512 until disk is validated */
@@ -2360,13 +2377,7 @@ static int nvme_setup_io_queues(struct nvme_dev *dev)
 static void nvme_free_namespace(struct nvme_ns *ns)
 {
        list_del(&ns->list);
-
-       spin_lock(&dev_list_lock);
-       ns->disk->private_data = NULL;
-       spin_unlock(&dev_list_lock);
-
-       put_disk(ns->disk);
-       kfree(ns);
+       kref_put(&ns->kref, nvme_free_ns);
 }
 
 static int ns_cmp(void *priv, struct list_head *a, struct list_head *b)
index b5812c395351a54d4eff5bacb3ed94528b9d25e1..992b9c11867882fe9aa2e54a239939f237cef9fb 100644 (file)
@@ -135,6 +135,7 @@ struct nvme_ns {
        struct nvme_dev *dev;
        struct request_queue *queue;
        struct gendisk *disk;
+       struct kref kref;
 
        unsigned ns_id;
        int lba_shift;