Skip to content

Commit 56c0908

Browse files
jankaraaxboe
authored andcommitted
genhd: Fix BUG in blkdev_open()
When two blkdev_open() calls for a partition race with device removal and recreation, we can hit BUG_ON(!bd_may_claim(bdev, whole, holder)) in blkdev_open(). The race can happen as follows: CPU0 CPU1 CPU2 del_gendisk() bdev_unhash_inode(part1); blkdev_open(part1, O_EXCL) blkdev_open(part1, O_EXCL) bdev = bd_acquire() bdev = bd_acquire() blkdev_get(bdev) bd_start_claiming(bdev) - finds old inode 'whole' bd_prepare_to_claim() -> 0 bdev_unhash_inode(whole); <device removed> <new device under same number created> blkdev_get(bdev); bd_start_claiming(bdev) - finds new inode 'whole' bd_prepare_to_claim() - this also succeeds as we have different 'whole' here... - bad things happen now as we have two exclusive openers of the same bdev The problem here is that block device opens can see various intermediate states while gendisk is shutting down and then being recreated. We fix the problem by introducing new lookup_sem in gendisk that synchronizes gendisk deletion with get_gendisk() and furthermore by making sure that get_gendisk() does not return gendisk that is being (or has been) deleted. This makes sure that once we ever manage to look up newly created bdev inode, we are also guaranteed that following get_gendisk() will either return failure (and we fail open) or it returns gendisk for the new device and following bdget_disk() will return new bdev inode (i.e., blkdev_open() follows the path as if it is completely run after new device is created). Reported-and-analyzed-by: Hou Tao <[email protected]> Tested-by: Hou Tao <[email protected]> Signed-off-by: Jan Kara <[email protected]> Signed-off-by: Jens Axboe <[email protected]>
1 parent 8973665 commit 56c0908

File tree

2 files changed

+21
-1
lines changed

2 files changed

+21
-1
lines changed

block/genhd.c

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,11 @@ void del_gendisk(struct gendisk *disk)
717717
blk_integrity_del(disk);
718718
disk_del_events(disk);
719719

720+
/*
721+
* Block lookups of the disk until all bdevs are unhashed and the
722+
* disk is marked as dead (GENHD_FL_UP cleared).
723+
*/
724+
down_write(&disk->lookup_sem);
720725
/* invalidate stuff */
721726
disk_part_iter_init(&piter, disk,
722727
DISK_PITER_INCL_EMPTY | DISK_PITER_REVERSE);
@@ -731,6 +736,7 @@ void del_gendisk(struct gendisk *disk)
731736
bdev_unhash_inode(disk_devt(disk));
732737
set_capacity(disk, 0);
733738
disk->flags &= ~GENHD_FL_UP;
739+
up_write(&disk->lookup_sem);
734740

735741
if (!(disk->flags & GENHD_FL_HIDDEN))
736742
sysfs_remove_link(&disk_to_dev(disk)->kobj, "bdi");
@@ -816,9 +822,21 @@ struct gendisk *get_gendisk(dev_t devt, int *partno)
816822
spin_unlock_bh(&ext_devt_lock);
817823
}
818824

819-
if (disk && unlikely(disk->flags & GENHD_FL_HIDDEN)) {
825+
if (!disk)
826+
return NULL;
827+
828+
/*
829+
* Synchronize with del_gendisk() to not return disk that is being
830+
* destroyed.
831+
*/
832+
down_read(&disk->lookup_sem);
833+
if (unlikely((disk->flags & GENHD_FL_HIDDEN) ||
834+
!(disk->flags & GENHD_FL_UP))) {
835+
up_read(&disk->lookup_sem);
820836
put_disk_and_module(disk);
821837
disk = NULL;
838+
} else {
839+
up_read(&disk->lookup_sem);
822840
}
823841
return disk;
824842
}
@@ -1418,6 +1436,7 @@ struct gendisk *__alloc_disk_node(int minors, int node_id)
14181436
kfree(disk);
14191437
return NULL;
14201438
}
1439+
init_rwsem(&disk->lookup_sem);
14211440
disk->node_id = node_id;
14221441
if (disk_expand_part_tbl(disk, 0)) {
14231442
free_part_stats(&disk->part0);

include/linux/genhd.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ struct gendisk {
198198
void *private_data;
199199

200200
int flags;
201+
struct rw_semaphore lookup_sem;
201202
struct kobject *slave_dir;
202203

203204
struct timer_rand_state *random;

0 commit comments

Comments
 (0)