summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCarlos Maiolino <cmaiolino@redhat.com>2019-02-26 05:51:50 -0500
committerJens Axboe <axboe@kernel.dk>2019-02-28 15:59:41 -0500
commitdce30ca9e3b676fb288c33c1f4725a0621361185 (patch)
tree9a9c1cfdda2cef84f3e9a2d90d1cd171bf8cc36a
parent7d76f8562f4c42a5515741375790843fe4b8df83 (diff)
fs: fix guard_bio_eod to check for real EOD errors
guard_bio_eod() can truncate a segment in bio to allow it to do IO on odd last sectors of a device. It already checks if the IO starts past EOD, but it does not consider the possibility of an IO request starting within device boundaries can contain more than one segment past EOD. In such cases, truncated_bytes can be bigger than PAGE_SIZE, and will underflow bvec->bv_len. Fix this by checking if truncated_bytes is lower than PAGE_SIZE. This situation has been found on filesystems such as isofs and vfat, which doesn't check the device size before mount, if the device is smaller than the filesystem itself, a readahead on such filesystem, which spans EOD, can trigger this situation, leading a call to zero_user() with a wrong size possibly corrupting memory. I didn't see any crash, or didn't let the system run long enough to check if memory corruption will be hit somewhere, but adding instrumentation to guard_bio_end() to check truncated_bytes size, was enough to see the error. The following script can trigger the error. MNT=/mnt IMG=./DISK.img DEV=/dev/loop0 mkfs.vfat $IMG mount $IMG $MNT cp -R /etc $MNT &> /dev/null umount $MNT losetup -D losetup --find --show --sizelimit 16247280 $IMG mount $DEV $MNT find $MNT -type f -exec cat {} + >/dev/null Kudos to Eric Sandeen for coming up with the reproducer above Reviewed-by: Ming Lei <ming.lei@redhat.com> Signed-off-by: Carlos Maiolino <cmaiolino@redhat.com> Signed-off-by: Jens Axboe <axboe@kernel.dk>
-rw-r--r--fs/buffer.c7
1 files changed, 7 insertions, 0 deletions
diff --git a/fs/buffer.c b/fs/buffer.c
index 89a4e42b9aad..ce357602f471 100644
--- a/fs/buffer.c
+++ b/fs/buffer.c
@@ -3027,6 +3027,13 @@ void guard_bio_eod(int op, struct bio *bio)
3027 /* Uhhuh. We've got a bio that straddles the device size! */ 3027 /* Uhhuh. We've got a bio that straddles the device size! */
3028 truncated_bytes = bio->bi_iter.bi_size - (maxsector << 9); 3028 truncated_bytes = bio->bi_iter.bi_size - (maxsector << 9);
3029 3029
3030 /*
3031 * The bio contains more than one segment which spans EOD, just return
3032 * and let IO layer turn it into an EIO
3033 */
3034 if (truncated_bytes > bvec->bv_len)
3035 return;
3036
3030 /* Truncate the bio.. */ 3037 /* Truncate the bio.. */
3031 bio->bi_iter.bi_size -= truncated_bytes; 3038 bio->bi_iter.bi_size -= truncated_bytes;
3032 bvec->bv_len -= truncated_bytes; 3039 bvec->bv_len -= truncated_bytes;