Hi Jan,
On 2018/2/7 0:05, Jan Kara wrote:
> 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 <[email protected]>
> Signed-off-by: Jan Kara <[email protected]>
> ---
> fs/block_dev.c | 7 +++++--
> 1 file changed, 5 insertions(+), 2 deletions(-)
>
After applying the patch, the use-after-free problem is gone, so
Tested-by: Hou Tao <[email protected]>
> diff --git a/fs/block_dev.c b/fs/block_dev.c
> index 1dbbf847911a..fe41a76769fa 100644
> --- a/fs/block_dev.c
> +++ b/fs/block_dev.c
> @@ -1409,6 +1409,7 @@ static int __blkdev_get(struct block_device *bdev,
> fmode_t mode, int for_part)
> int ret;
> int partno;
> int perm = 0;
> + bool first_open = false;
>
> if (mode & FMODE_READ)
> perm |= MAY_READ;
> @@ -1435,6 +1436,7 @@ static int __blkdev_get(struct block_device *bdev,
> fmode_t mode, int for_part)
> disk_block_events(disk);
> mutex_lock_nested(&bdev->bd_mutex, for_part);
> if (!bdev->bd_openers) {
> + first_open = true;
> bdev->bd_disk = disk;
> bdev->bd_queue = disk->queue;
> bdev->bd_contains = bdev;
> @@ -1520,14 +1522,15 @@ static int __blkdev_get(struct block_device *bdev,
> fmode_t mode, int for_part)
> if (ret)
> goto out_unlock_bdev;
> }
> - /* only one opener holds refs to the module and disk */
> - put_disk_and_module(disk);
> }
> bdev->bd_openers++;
> if (for_part)
> bdev->bd_part_count++;
> mutex_unlock(&bdev->bd_mutex);
> disk_unblock_events(disk);
> + /* only one opener holds refs to the module and disk */
> + if (!first_open)
> + put_disk_and_module(disk);
> return 0;
>
> out_clear:
>