| From 5db470e229e22b7eda6e23b5566e532c96fb5bc3 Mon Sep 17 00:00:00 2001 |
| From: Jaegeuk Kim <jaegeuk@kernel.org> |
| Date: Wed, 9 Jan 2019 19:17:14 -0800 |
| Subject: loop: drop caches if offset or block_size are changed |
| |
| From: Jaegeuk Kim <jaegeuk@kernel.org> |
| |
| commit 5db470e229e22b7eda6e23b5566e532c96fb5bc3 upstream. |
| |
| If we don't drop caches used in old offset or block_size, we can get old data |
| from new offset/block_size, which gives unexpected data to user. |
| |
| For example, Martijn found a loopback bug in the below scenario. |
| 1) LOOP_SET_FD loads first two pages on loop file |
| 2) LOOP_SET_STATUS64 changes the offset on the loop file |
| 3) mount is failed due to the cached pages having wrong superblock |
| |
| Cc: Jens Axboe <axboe@kernel.dk> |
| Cc: linux-block@vger.kernel.org |
| Reported-by: Martijn Coenen <maco@google.com> |
| Reviewed-by: Bart Van Assche <bvanassche@acm.org> |
| Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org> |
| Signed-off-by: Jens Axboe <axboe@kernel.dk> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| drivers/block/loop.c | 35 +++++++++++++++++++++++++++++++++-- |
| 1 file changed, 33 insertions(+), 2 deletions(-) |
| |
| --- a/drivers/block/loop.c |
| +++ b/drivers/block/loop.c |
| @@ -1097,6 +1097,12 @@ loop_set_status(struct loop_device *lo, |
| if ((unsigned int) info->lo_encrypt_key_size > LO_KEY_SIZE) |
| return -EINVAL; |
| |
| + if (lo->lo_offset != info->lo_offset || |
| + lo->lo_sizelimit != info->lo_sizelimit) { |
| + sync_blockdev(lo->lo_device); |
| + kill_bdev(lo->lo_device); |
| + } |
| + |
| /* I/O need to be drained during transfer transition */ |
| blk_mq_freeze_queue(lo->lo_queue); |
| |
| @@ -1125,6 +1131,14 @@ loop_set_status(struct loop_device *lo, |
| |
| if (lo->lo_offset != info->lo_offset || |
| lo->lo_sizelimit != info->lo_sizelimit) { |
| + /* kill_bdev should have truncated all the pages */ |
| + if (lo->lo_device->bd_inode->i_mapping->nrpages) { |
| + err = -EAGAIN; |
| + pr_warn("%s: loop%d (%s) has still dirty pages (nrpages=%lu)\n", |
| + __func__, lo->lo_number, lo->lo_file_name, |
| + lo->lo_device->bd_inode->i_mapping->nrpages); |
| + goto exit; |
| + } |
| if (figure_loop_size(lo, info->lo_offset, info->lo_sizelimit)) { |
| err = -EFBIG; |
| goto exit; |
| @@ -1346,22 +1360,39 @@ static int loop_set_dio(struct loop_devi |
| |
| static int loop_set_block_size(struct loop_device *lo, unsigned long arg) |
| { |
| + int err = 0; |
| + |
| if (lo->lo_state != Lo_bound) |
| return -ENXIO; |
| |
| if (arg < 512 || arg > PAGE_SIZE || !is_power_of_2(arg)) |
| return -EINVAL; |
| |
| + if (lo->lo_queue->limits.logical_block_size != arg) { |
| + sync_blockdev(lo->lo_device); |
| + kill_bdev(lo->lo_device); |
| + } |
| + |
| blk_mq_freeze_queue(lo->lo_queue); |
| |
| + /* kill_bdev should have truncated all the pages */ |
| + if (lo->lo_queue->limits.logical_block_size != arg && |
| + lo->lo_device->bd_inode->i_mapping->nrpages) { |
| + err = -EAGAIN; |
| + pr_warn("%s: loop%d (%s) has still dirty pages (nrpages=%lu)\n", |
| + __func__, lo->lo_number, lo->lo_file_name, |
| + lo->lo_device->bd_inode->i_mapping->nrpages); |
| + goto out_unfreeze; |
| + } |
| + |
| blk_queue_logical_block_size(lo->lo_queue, arg); |
| blk_queue_physical_block_size(lo->lo_queue, arg); |
| blk_queue_io_min(lo->lo_queue, arg); |
| loop_update_dio(lo); |
| - |
| +out_unfreeze: |
| blk_mq_unfreeze_queue(lo->lo_queue); |
| |
| - return 0; |
| + return err; |
| } |
| |
| static int lo_ioctl(struct block_device *bdev, fmode_t mode, |