summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKeith Busch <keith.busch@intel.com>2014-10-03 21:15:47 +0400
committerJens Axboe <axboe@fb.com>2014-11-04 23:18:32 +0300
commit9e60352cf83faaba57f99f6960b545687b8bbb20 (patch)
treeda92850b088a1d975b2f489a67f5583f5f1d273e
parent5940c8578fe720afbf4ef041bad0d72a101f1d88 (diff)
downloadlinux-9e60352cf83faaba57f99f6960b545687b8bbb20.tar.xz
NVMe: Do not open disks that are being deleted
It is possible the block layer will request to open a block device after the driver deleted it. Subsequent releases will cause a double free, or the disk's private_data is pointing to freed memory. This patch protects the driver's freed disks from being opened and accessed: the nvme namespaces are freed only when the device's refcount is 0, so at that moment there were no active openers and no more should be allowed, and it is safe to clear the disk's private_data that is about to be freed. Signed-off-by: Keith Busch <keith.busch@intel.com> Reported-by: Henry Chow <henry.chow@oracle.com> Signed-off-by: Matthew Wilcox <matthew.r.wilcox@intel.com> Signed-off-by: Jens Axboe <axboe@fb.com>
-rw-r--r--drivers/block/nvme-core.c20
1 files changed, 16 insertions, 4 deletions
diff --git a/drivers/block/nvme-core.c b/drivers/block/nvme-core.c
index 8fffc68c74eb..fb21d365efb5 100644
--- a/drivers/block/nvme-core.c
+++ b/drivers/block/nvme-core.c
@@ -1832,11 +1832,18 @@ static int nvme_compat_ioctl(struct block_device *bdev, fmode_t mode,
static int nvme_open(struct block_device *bdev, fmode_t mode)
{
- struct nvme_ns *ns = bdev->bd_disk->private_data;
- struct nvme_dev *dev = ns->dev;
+ int ret = 0;
+ struct nvme_ns *ns;
- kref_get(&dev->kref);
- return 0;
+ spin_lock(&dev_list_lock);
+ ns = bdev->bd_disk->private_data;
+ if (!ns)
+ ret = -ENXIO;
+ else if (!kref_get_unless_zero(&ns->dev->kref))
+ ret = -ENXIO;
+ spin_unlock(&dev_list_lock);
+
+ return ret;
}
static void nvme_free_dev(struct kref *kref);
@@ -2711,6 +2718,11 @@ static void nvme_free_namespaces(struct nvme_dev *dev)
list_for_each_entry_safe(ns, next, &dev->namespaces, list) {
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);
}