aboutsummaryrefslogtreecommitdiffstats
path: root/block
diff options
context:
space:
mode:
authorTejun Heo <tj@kernel.org>2011-10-17 07:42:43 -0400
committerJens Axboe <axboe@kernel.dk>2011-10-24 10:24:31 -0400
commitf992ae801a7dec34a4ed99a6598bbbbfb82af4fb (patch)
tree5c8892f67867cb09e9c3c046f881e56200926ff8 /block
parente67b77c791ca2778198c9e7088f3266ed2da7a55 (diff)
block: make gendisk hold a reference to its queue
The following command sequence triggers an oops. # mount /dev/sdb1 /mnt # echo 1 > /sys/class/scsi_device/0\:0\:1\:0/device/delete # umount /mnt general protection fault: 0000 [#1] PREEMPT SMP CPU 2 Modules linked in: Pid: 791, comm: umount Not tainted 3.1.0-rc3-work+ #8 Bochs Bochs RIP: 0010:[<ffffffff810d0879>] [<ffffffff810d0879>] __lock_acquire+0x389/0x1d60 ... Call Trace: [<ffffffff810d2845>] lock_acquire+0x95/0x140 [<ffffffff81aed87b>] _raw_spin_lock+0x3b/0x50 [<ffffffff811573bc>] bdi_lock_two+0x5c/0x70 [<ffffffff811c2f6c>] bdev_inode_switch_bdi+0x4c/0xf0 [<ffffffff811c3fcb>] __blkdev_put+0x11b/0x1d0 [<ffffffff811c4010>] __blkdev_put+0x160/0x1d0 [<ffffffff811c40df>] blkdev_put+0x5f/0x190 [<ffffffff8118f18d>] kill_block_super+0x4d/0x80 [<ffffffff8118f4a5>] deactivate_locked_super+0x45/0x70 [<ffffffff8119003a>] deactivate_super+0x4a/0x70 [<ffffffff811ac4ad>] mntput_no_expire+0xed/0x130 [<ffffffff811acf2e>] sys_umount+0x7e/0x3a0 [<ffffffff81aeeeab>] system_call_fastpath+0x16/0x1b This is because bdev holds on to disk but disk doesn't pin the associated queue. If a SCSI device is removed while the device is still open, the sdev puts the base reference to the queue on release. When the bdev is finally released, the associated queue is already gone along with the bdi and bdev_inode_switch_bdi() ends up dereferencing already freed bdi. Even if it were not for this bug, disk not holding onto the associated queue is very unusual and error-prone. Fix it by making add_disk() take an extra reference to its queue and put it on disk_release() and ensuring that disk and its fops owner are put in that order after all accesses to the disk and queue are complete. Signed-off-by: Tejun Heo <tj@kernel.org> Cc: Jens Axboe <axboe@kernel.dk> Cc: stable@kernel.org Signed-off-by: Jens Axboe <axboe@kernel.dk>
Diffstat (limited to 'block')
-rw-r--r--block/genhd.c8
1 files changed, 8 insertions, 0 deletions
diff --git a/block/genhd.c b/block/genhd.c
index e2f67902dd02..d261b73b9744 100644
--- a/block/genhd.c
+++ b/block/genhd.c
@@ -611,6 +611,12 @@ void add_disk(struct gendisk *disk)
611 register_disk(disk); 611 register_disk(disk);
612 blk_register_queue(disk); 612 blk_register_queue(disk);
613 613
614 /*
615 * Take an extra ref on queue which will be put on disk_release()
616 * so that it sticks around as long as @disk is there.
617 */
618 WARN_ON_ONCE(blk_get_queue(disk->queue));
619
614 retval = sysfs_create_link(&disk_to_dev(disk)->kobj, &bdi->dev->kobj, 620 retval = sysfs_create_link(&disk_to_dev(disk)->kobj, &bdi->dev->kobj,
615 "bdi"); 621 "bdi");
616 WARN_ON(retval); 622 WARN_ON(retval);
@@ -1095,6 +1101,8 @@ static void disk_release(struct device *dev)
1095 disk_replace_part_tbl(disk, NULL); 1101 disk_replace_part_tbl(disk, NULL);
1096 free_part_stats(&disk->part0); 1102 free_part_stats(&disk->part0);
1097 free_part_info(&disk->part0); 1103 free_part_info(&disk->part0);
1104 if (disk->queue)
1105 blk_put_queue(disk->queue);
1098 kfree(disk); 1106 kfree(disk);
1099} 1107}
1100struct class block_class = { 1108struct class block_class = {