Skip to content

Commit 523e1d3

Browse files
htejunaxboe
authored andcommitted
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+ torvalds#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: stable@kernel.org Signed-off-by: Jens Axboe <axboe@kernel.dk>
1 parent 5c04b42 commit 523e1d3

File tree

2 files changed

+16
-5
lines changed

2 files changed

+16
-5
lines changed

block/genhd.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,12 @@ void add_disk(struct gendisk *disk)
611611
register_disk(disk);
612612
blk_register_queue(disk);
613613

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+
614620
retval = sysfs_create_link(&disk_to_dev(disk)->kobj, &bdi->dev->kobj,
615621
"bdi");
616622
WARN_ON(retval);
@@ -1095,6 +1101,8 @@ static void disk_release(struct device *dev)
10951101
disk_replace_part_tbl(disk, NULL);
10961102
free_part_stats(&disk->part0);
10971103
free_part_info(&disk->part0);
1104+
if (disk->queue)
1105+
blk_put_queue(disk->queue);
10981106
kfree(disk);
10991107
}
11001108
struct class block_class = {

fs/block_dev.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,6 +1085,7 @@ static int __blkdev_put(struct block_device *bdev, fmode_t mode, int for_part);
10851085
static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part)
10861086
{
10871087
struct gendisk *disk;
1088+
struct module *owner;
10881089
int ret;
10891090
int partno;
10901091
int perm = 0;
@@ -1110,6 +1111,7 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part)
11101111
disk = get_gendisk(bdev->bd_dev, &partno);
11111112
if (!disk)
11121113
goto out;
1114+
owner = disk->fops->owner;
11131115

11141116
disk_block_events(disk);
11151117
mutex_lock_nested(&bdev->bd_mutex, for_part);
@@ -1137,8 +1139,8 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part)
11371139
bdev->bd_disk = NULL;
11381140
mutex_unlock(&bdev->bd_mutex);
11391141
disk_unblock_events(disk);
1140-
module_put(disk->fops->owner);
11411142
put_disk(disk);
1143+
module_put(owner);
11421144
goto restart;
11431145
}
11441146
}
@@ -1194,8 +1196,8 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part)
11941196
goto out_unlock_bdev;
11951197
}
11961198
/* only one opener holds refs to the module and disk */
1197-
module_put(disk->fops->owner);
11981199
put_disk(disk);
1200+
module_put(owner);
11991201
}
12001202
bdev->bd_openers++;
12011203
if (for_part)
@@ -1215,8 +1217,8 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part)
12151217
out_unlock_bdev:
12161218
mutex_unlock(&bdev->bd_mutex);
12171219
disk_unblock_events(disk);
1218-
module_put(disk->fops->owner);
12191220
put_disk(disk);
1221+
module_put(owner);
12201222
out:
12211223
bdput(bdev);
12221224

@@ -1442,14 +1444,15 @@ static int __blkdev_put(struct block_device *bdev, fmode_t mode, int for_part)
14421444
if (!bdev->bd_openers) {
14431445
struct module *owner = disk->fops->owner;
14441446

1445-
put_disk(disk);
1446-
module_put(owner);
14471447
disk_put_part(bdev->bd_part);
14481448
bdev->bd_part = NULL;
14491449
bdev->bd_disk = NULL;
14501450
if (bdev != bdev->bd_contains)
14511451
victim = bdev->bd_contains;
14521452
bdev->bd_contains = NULL;
1453+
1454+
put_disk(disk);
1455+
module_put(owner);
14531456
}
14541457
mutex_unlock(&bdev->bd_mutex);
14551458
bdput(bdev);

0 commit comments

Comments
 (0)