Message ID | 20200616121655.3516305-1-yanaijie@huawei.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | [v7] block: Fix use-after-free in blkdev_get() | expand |
On 6/16/20 6:16 AM, Jason Yan wrote: > In blkdev_get() we call __blkdev_get() to do some internal jobs and if > there is some errors in __blkdev_get(), the bdput() is called which > means we have released the refcount of the bdev (actually the refcount of > the bdev inode). This means we cannot access bdev after that point. But > acctually bdev is still accessed in blkdev_get() after calling > __blkdev_get(). This results in use-after-free if the refcount is the > last one we released in __blkdev_get(). Let's take a look at the > following scenerio: > > CPU0 CPU1 CPU2 > blkdev_open blkdev_open Remove disk > bd_acquire > blkdev_get > __blkdev_get del_gendisk > bdev_unhash_inode > bd_acquire bdev_get_gendisk > bd_forget failed because of unhashed > bdput > bdput (the last one) > bdev_evict_inode > > access bdev => use after free I've queued this up for 5.8, thanks.
diff --git a/fs/block_dev.c b/fs/block_dev.c index 47860e589388..08c87db3a92b 100644 --- a/fs/block_dev.c +++ b/fs/block_dev.c @@ -1565,10 +1565,8 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part) */ if (!for_part) { ret = devcgroup_inode_permission(bdev->bd_inode, perm); - if (ret != 0) { - bdput(bdev); + if (ret != 0) return ret; - } } restart: @@ -1637,8 +1635,10 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part) goto out_clear; BUG_ON(for_part); ret = __blkdev_get(whole, mode, 1); - if (ret) + if (ret) { + bdput(whole); goto out_clear; + } bdev->bd_contains = whole; bdev->bd_part = disk_get_part(disk, partno); if (!(disk->flags & GENHD_FL_UP) || @@ -1688,7 +1688,6 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part) disk_unblock_events(disk); put_disk_and_module(disk); out: - bdput(bdev); return ret; } @@ -1755,6 +1754,9 @@ int blkdev_get(struct block_device *bdev, fmode_t mode, void *holder) bdput(whole); } + if (res) + bdput(bdev); + return res; } EXPORT_SYMBOL(blkdev_get);