disk_release drops the reference on the request_queue if disk->queue is set. But many drivers set the pointer early, which means they have to clear it before calling put_disk during a probe failure, which is often forgotten. Even worse for drivers that refcount their per-disk structure like nvme the path for a real teardown vs probe failure can be shared. Fix all this by setting a gendisk flag only when add_disk has acquired a request_queue reference, and only drop the reference when the flag was set. Also make sure add_disk always grabs a reference, even if the queue is marked dying, as we expect the reference. This patch fixes a potential use after free during nvme probe failure. The fact that nvme can call add_disk on a dying queue is another bug in the nvme code that we'll fix separatel. Reported-by: Tong Zhang <ztong0001@xxxxxxxxx> Signed-off-by: Christoph Hellwig <hch@xxxxxx> --- block/genhd.c | 6 ++++-- include/linux/genhd.h | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/block/genhd.c b/block/genhd.c index 9d060e79eb31d8..ef2784c69d59ee 100644 --- a/block/genhd.c +++ b/block/genhd.c @@ -832,7 +832,9 @@ static void __device_add_disk(struct device *parent, struct gendisk *disk, * Take an extra ref on queue which will be put on disk_release() * so that it sticks around as long as @disk is there. */ - WARN_ON_ONCE(!blk_get_queue(disk->queue)); + WARN_ON_ONCE(blk_queue_dying(disk->queue)); + __blk_get_queue(disk->queue); + disk->flags |= GENHD_FL_QUEUE_REF; disk_add_events(disk); blk_integrity_add(disk); @@ -1564,7 +1566,7 @@ static void disk_release(struct device *dev) kfree(disk->random); disk_replace_part_tbl(disk, NULL); hd_free_part(&disk->part0); - if (disk->queue) + if (disk->flags & GENHD_FL_QUEUE_REF) blk_put_queue(disk->queue); kfree(disk); } diff --git a/include/linux/genhd.h b/include/linux/genhd.h index 1c97cf84f011a7..822a619924e3b5 100644 --- a/include/linux/genhd.h +++ b/include/linux/genhd.h @@ -133,6 +133,7 @@ struct hd_struct { #define GENHD_FL_BLOCK_EVENTS_ON_EXCL_WRITE 0x0100 #define GENHD_FL_NO_PART_SCAN 0x0200 #define GENHD_FL_HIDDEN 0x0400 +#define GENHD_FL_QUEUE_REF 0x0800 enum { DISK_EVENT_MEDIA_CHANGE = 1 << 0, /* media changed */ -- 2.28.0