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: [PATCH] loop: drop caches if offset or block_size are changed
Git-commit: 5db470e229e22b7eda6e23b5566e532c96fb5bc3
Patch-mainline: v5.0-rc2
References: bsc#1124975
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>
Acked-by: Jan Kara <jack@suse.cz>
---
drivers/block/loop.c | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
--- a/drivers/block/loop.c
+++ b/drivers/block/loop.c
@@ -1144,6 +1144,15 @@ 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 ||
+ lo->lo_flags != lo_flags ||
+ ((lo->lo_flags & LO_FLAGS_BLOCKSIZE) &&
+ lo->lo_logical_blocksize != LO_INFO_BLOCKSIZE(info))) {
+ 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);
@@ -1188,6 +1197,14 @@ loop_set_status(struct loop_device *lo,
lo->lo_flags != lo_flags ||
((lo->lo_flags & LO_FLAGS_BLOCKSIZE) &&
lo->lo_logical_blocksize != LO_INFO_BLOCKSIZE(info))) {
+ /* 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,
LO_INFO_BLOCKSIZE(info))) {
err = -EFBIG;