Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

genhd: Fix use after free in __blkdev_get()

When two blkdev_open() calls race with device removal and recreation,
__blkdev_get() can use looked up gendisk after it is freed:

CPU0 CPU1 CPU2
del_gendisk(disk);
bdev_unhash_inode(inode);
blkdev_open() blkdev_open()
bdev = bd_acquire(inode);
- creates and returns new inode
bdev = bd_acquire(inode);
- returns the same inode
__blkdev_get(devt) __blkdev_get(devt)
disk = get_gendisk(devt);
- got structure of device going away
<finish device removal>
<new device gets
created under the same
device number>
disk = get_gendisk(devt);
- got new device structure
if (!bdev->bd_openers) {
does the first open
}
if (!bdev->bd_openers)
- false
} else {
put_disk_and_module(disk)
- remember this was old device - this was last ref and disk is
now freed
}
disk_unblock_events(disk); -> oops

Fix the problem by making sure we drop reference to disk in
__blkdev_get() only after we are really done with it.

Reported-by: Hou Tao <houtao1@huawei.com>
Tested-by: Hou Tao <houtao1@huawei.com>
Signed-off-by: Jan Kara <jack@suse.cz>
Signed-off-by: Jens Axboe <axboe@kernel.dk>

authored by

Jan Kara and committed by
Jens Axboe
89736653 9df6c299

+5 -2
+5 -2
fs/block_dev.c
··· 1409 1409 int ret; 1410 1410 int partno; 1411 1411 int perm = 0; 1412 + bool first_open = false; 1412 1413 1413 1414 if (mode & FMODE_READ) 1414 1415 perm |= MAY_READ; ··· 1436 1435 disk_block_events(disk); 1437 1436 mutex_lock_nested(&bdev->bd_mutex, for_part); 1438 1437 if (!bdev->bd_openers) { 1438 + first_open = true; 1439 1439 bdev->bd_disk = disk; 1440 1440 bdev->bd_queue = disk->queue; 1441 1441 bdev->bd_contains = bdev; ··· 1522 1520 if (ret) 1523 1521 goto out_unlock_bdev; 1524 1522 } 1525 - /* only one opener holds refs to the module and disk */ 1526 - put_disk_and_module(disk); 1527 1523 } 1528 1524 bdev->bd_openers++; 1529 1525 if (for_part) 1530 1526 bdev->bd_part_count++; 1531 1527 mutex_unlock(&bdev->bd_mutex); 1532 1528 disk_unblock_events(disk); 1529 + /* only one opener holds refs to the module and disk */ 1530 + if (!first_open) 1531 + put_disk_and_module(disk); 1533 1532 return 0; 1534 1533 1535 1534 out_clear: